chore(flare): drop mholt/archiver for stdlib archive/zip - #3363
Open
wdhif wants to merge 6 commits into
Open
Conversation
|
🎯 Code Coverage (details) 🔗 Commit SHA: a2b0b16 | Docs | View more details | Give us feedback! |
Member
Author
|
/dd-review codex |
wdhif
force-pushed
the
claude/archives-library-upgrade-38d5ab
branch
from
August 19, 2026 14:15
6e9ce53 to
4209b87
Compare
wdhif
force-pushed
the
claude/archives-library-upgrade-38d5ab
branch
from
August 24, 2026 11:51
4209b87 to
d05d4a8
Compare
wdhif
marked this pull request as ready for review
August 25, 2026 12:52
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the kubectl datadog flare implementation to stop using the deprecated github.com/mholt/archiver/v3 dependency and instead create flare zip archives using Go’s standard library (archive/zip). This reduces the dependency footprint and avoids known advisories associated with the removed module.
Changes:
- Replaced the
archiver.NewZip().Archive()call site with a newarchiveDir()helper built onarchive/zip. - Added unit tests that pin the zip layout (top-level folder prefix, nested directories, and skipping non-regular files like symlinks).
- Removed
mholt/archiver/v3and related transitive dependencies fromgo.mod/go.sum, and updated third-party license tracking.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| LICENSE-3rdparty.csv | Removes third-party license rows associated with the dropped archiver dependency tree. |
| go.work.sum | Updates workspace sum entries reflecting module graph changes after dependency removal. |
| go.sum | Removes checksums for archiver-related modules no longer in the main module graph. |
| go.mod | Drops github.com/mholt/archiver/v3 and related indirect dependencies. |
| cmd/kubectl-datadog/flare/flare.go | Introduces archiveDir() using archive/zip and removes the archiver.Zip field/call site. |
| cmd/kubectl-datadog/flare/archive_test.go | Adds unit tests verifying archive layout and behavior when the destination file already exists. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Member
Author
|
/dd-review codex |
github.com/mholt/archiver/v3 is deprecated and frozen at v3.5.1, which carries two unfixable path traversal advisories: CVE-2025-3445 (Zip Slip via a crafted zip) and CVE-2024-0406 (path traversal via a crafted tar), both reachable only through archiver.Unarchive. No fix was ever published -- v3.5.1 is the last version on the module proxy -- and upstream's only remediation is the rename to mholt/archives, so neither finding can be cleared by a version bump. The flare command only ever wrote a zip of a single directory, so the vulnerable extraction path was never reachable, but the dependency still surfaces in dependency scans and pulls 9 modules into the operator for one call site -- among them github.com/xi2/xz, which we ship with an unidentified license. Migrating to mholt/archives would instead grow that set to ~14 modules on a pre-1.0 API, so the single call is replaced with archive/zip. Zipping one real flare directory with both implementations yields identical entry names and order, directory entries, methods, CRCs, permissions, timestamps and extracted bytes. The only difference is compressed size: archiver deflated through github.com/klauspost/compress, so the same input now produces a ~5% smaller archive (9333 vs 9851 bytes). The plugin binary shrinks by ~1.9 MB. Co-Authored-By: Claude <noreply@anthropic.com>
The map literal in TestArchiveDir was hand-aligned; gofmt un-aligns the short key, which made check_formatting fail on git diff --exit-code. Co-Authored-By: Claude <noreply@anthropic.com>
archiveDir opened the destination with O_TRUNC, while archiver.NewZip() refused to write over an existing file. getArchivePath only has second precision, so two flares finishing collection in the same second picked the same name: instead of the second one failing, both wrote into one file, producing a corrupt archive or making a command upload the other's artifact. Open the destination with O_EXCL to restore the previous guarantee. The shared createFile keeps O_TRUNC, which is what the collected files in the flare directory need since it persists between runs. Co-Authored-By: Claude <noreply@anthropic.com>
Also delete the destination when archiving fails: the zip writer has already created it, so a failure left a truncated archive behind that both misleads whoever finds it and blocks the next flare of the same second through O_EXCL. The pre-existing-file case is untouched, since that open fails before anything is created. Raises patch coverage on the changed lines from 80.0% to 85.0%, above the 80% gate. Co-Authored-By: Claude <noreply@anthropic.com>
The os.Open branch is the last reachable uncovered path in archiveDir; the remaining ones are the call site in run(), which has no test harness, and errors from filepath.Rel, zip.FileInfoHeader and CreateHeader, which cannot be triggered on paths that filepath.Walk just produced. Skipped under root, which reads files whatever their permission bits say. Co-Authored-By: Claude <noreply@anthropic.com>
wdhif
force-pushed
the
claude/archives-library-upgrade-38d5ab
branch
from
August 26, 2026 09:33
3003ac6 to
e7e1f64
Compare
The symlink case lived in the layout test, so on a Windows host without Developer Mode that test failed in setup, reporting a layout problem that was really a missing privilege. It now has its own test that skips there. The unreadable-file case skips on Windows too: permission bits do not stop a read there, and Geteuid returns -1 so the root check never fired, leaving both assertions to fail rather than skip. Neither affects CI, which is Linux only, but the kubectl plugin ships a Windows binary so contributors may well run the suite there. Coverage on Linux is unchanged, the same branches still execute. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Replaces the single
github.com/mholt/archiver/v3call site inkubectl datadog flarewith the standard library's
archive/zip, and drops the dependency.cmd/kubectl-datadog/flare/flare.go: newarchiveDir()helper replacesarchiver.NewZip().Archive(); thezip *archiver.Zipfield onoptionsis gone.cmd/kubectl-datadog/flare/archive_test.go: new unit test pinning the archive layout(top-level folder, nested dirs, non-regular files skipped).
go.mod/go.sum/LICENSE-3rdparty.csv: removes 1 direct + 9 indirect modules.No change to the archive the flare command produces. The one observable difference is
the error text when two flares collide on the same filename — see Additional Notes.
Motivation
mholt/archiver/v3is deprecated and frozen atv3.5.1, which carries two path traversaladvisories:
Both are reachable only through
archiver.Unarchive, and neither can be cleared by aversion bump: no fix was ever published —
v3.5.1is the last version on the module proxy,so the
3.5.2that some scanners cite as the patched release does not exist — andupstream's only remediation is the rename to
mholt/archives.The flare command only ever wrote a zip of a single directory, so the vulnerable
extraction path was never reachable from the operator, but the dependency still shows up in
dependency scans with no remediation available in place.
Migrating to
mholt/archiveswas the other option and was rejected: it is pre-1.0(
v0.1.5, no API stability commitment) and it grows the footprint, requiring ~14 modules(adding
bodgit/sevenzip,STARRY-S/zip,minio/minlz,sorairolake/lzip-go,mikelolasagasti/xz,spf13/afero,hashicorp/golang-lru) — i.e. pulling 7z/rar/lzipdecoders into the operator in order to write one zip.
Going to the standard library instead removes 10 modules for ~60 lines of code:
LICENSE-3rdparty.csvrowskubectl-datadogbinaryThe removed set includes
github.com/xi2/xz, which we currently ship with its licenselisted as
Unknown.Additional Notes
Output equivalence. Zipping one real flare directory with both implementations produces
identical entry names and order, directory entries, compression methods, CRC32s,
uncompressed sizes, decompressed bytes, flag bits, creator/extract versions, permissions
(
0755dir /0644files), DOS timestamps and extended-timestamp extra fields.The one observable difference is compressed size —
archiverdeflated throughgithub.com/klauspost/compress/ziprather thanarchive/zip, so the same input now yieldsa slightly smaller archive:
datadog-custom-resources.yaml<pod>-metrics.txt<pod>-status.txt<pod>.jsonSame DEFLATE format, so no consumer is affected.
Same-second collisions behave as before, with different wording.
getArchivePath()onlyhas second precision, so two flares finishing collection in the same second pick the same
name.
archiver.NewZip()refused to write over an existing archive(
if !z.OverwriteExisting && fileExists(dest)), andarchiveDirkeeps that guarantee byopening the destination with
O_EXCL— which is additionally atomic, where archiver'scheck-then-
os.Createhad a TOCTOU window in which both runs could truncate. The commandstill fails in that case, only the message changed:
A failed archive is also removed rather than left truncated on disk, since with
O_EXCLaleftover would block the next flare of that second. Note this only serialises the archive
step —
baseDiris a fixed path, so concurrent flares already interleave their collectedfiles; making concurrent runs genuinely safe needs a per-run directory and is out of scope
here.
One latent behaviour difference:
archiverhadSelectiveCompression, which storedfiles whose extension is already compressed (
.gz,.png,.zip, …);archiveDiralwaysdeflates. The flare only ever writes
.yaml,.txtand.json, so this is unreachabletoday — replicating it would mean copying a 30-entry extension map for no gain.
go.work.sumpicks up 3/go.modhashes (brotli,snappy,pierrec/lz4) that movedout of
go.sumwhen the modules left the main module's graph. Stable acrossmake sync+rebuild.
Minimum Agent Versions
None — the change is confined to the kubectl plugin.
Describe your test plan
Automated:
make lint,go build ./...,go test ./...(106 packages ok, 0 failures),make verify-licenses(exit 0, no diff).Manual, on minikube (
minikube start, Kubernetes v1.35.1) with operator1.28.0installedfrom the
datadog-operatorHelm chart plus aDatadogAgentCR:go build -o /tmp/flare-before ./cmd/kubectl-datadog/main.goonmain, same for thisbranch.
go version -mon each confirmsgithub.com/mholt/archiver/v3is linked intothe first and absent from the second.
TMPDIRso the artifacts don't collide:TMPDIR=/tmp/before; echo n | /tmp/flare-before flare --email <you> --apiKey <key>.Answer
nat the upload prompt — the flare is written locally and nothing is sent toDatadog.
permissions. The collected
-metrics.txtand.jsonpayloads differ only because theycarry live counter and log data; the stable files (
datadog-custom-resources.yaml,-status.txt) come out with identical CRCs and content hashes.unzipboth anddiff -r— extracted trees are byte-identical, verified per file withshasum -a 256. Every zip header field matches exceptcompress_size.unzip -t <flare>.zipreports no errors on the new archive, and the collecteddatadog-custom-resources.yamlstill has the API key redacted.Checklist
bug,enhancement,refactoring,documentation,tooling, and/ordependenciesqa/skip-qalabel