v1.0.0 — the agent-grade ZIP CLI on zipnative 1.0.0 #8
Nizoka
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
zipnative-cli v1.0.0
Released 2026-09-05
Built on zipnative 1.0.0 — the engine's first stable release, whose 77-export API, 39-code
error vocabulary and
deterministic: trueoutput bytes are frozen under semver. This is thefirst release of the CLI: 15 commands in four groups over that frozen surface, a thin
dispatch layer with no ZIP parsing of its own, an agent contract that carries the engine's
ZIP_*codes verbatim next to 13 stableE_*classes, secure-by-default extraction with theCLI as the proven filesystem trust boundary, and a blocking ISO/IEC 21320-1:2015 conformance
gate over every archive the CLI writes. Offline in every mode. Zero extra runtime dependencies.
Node.js >= 22. The release branch closed with two independent audits (74 accepted findings,
every one tagged in CHANGELOG.md).
Highlights
zipnative create dist/ --deterministic -o release.zip # identical SHA-256 on every runtime zipnative inspect --input release.zip --check deterministic,no-symlinks,no-encryption zipnative verify --input release.zip --strictsymlink entries, overlapping entries, central/local header disagreement, Zip64 spoofing,
duplicate output paths and decompression bombs are refused by default, each with its
ZIP_*code; the CLI then re-proves every destination stays under--output-dir(lexically and physically, through
realpath), opens every file exclusively, neveroverwrites without
--overwrite, and never materialises a symlink. Opt-outs skip; theynever write anything unsafe.
--jsonputs{ ok: false, command, error: { code, message, zipCode?, entryName?, detail? } }on stderr:codeis the class (13E_*values),zipCodeis the exact cause (zipnative's 39 frozen codes, verbatim),detailcarriesthe engine's structured fields. Branch on codes, never on text.
streamfor what you cannot seek — list, extract or--catfrom stdin, a pipe or anupload body, with the engine's trust caveat made explicit (
trust: "local-headers-only").modifywithout recompression, and without laundering — add, replace, remove, rename;append-only by default,
--compactfor true deletion;--in-place; every entry itre-emits is verified first.
--dry-runon seven commands,--summary/--fields/ compactJSON,
--strict, eight--max-*bounds plus--max-input-size, 22schemasubjects (JSONSchemas, an error registry and a capability manifest),
doctoras a preflight,batch --manifestpipelines whose--jsonoutput is one document, andllms.txt+AGENTS.md+docs/data/errors.jsonin the package.against ISO/IEC 21320-1:2015 by an engine-independent validator (veraZIP), blocking in CI
on Linux and Windows on every PR and before every publish, with negative canaries the
validator must reject and hostile-but-conformant archives
extractmust refuse.Security
No parser, writer or codec of its own — every ZIP decision is the engine's. What the CLI adds,
and what the audit pass hardened before this release:
extractandstream --output-dirshare one sink(
src/utils/sink.ts). Beyond the lexicalsafeJoinplan, the nearest existing ancestor ofevery target is
realpath'd under the root beforemkdir -pand the created directory isre-checked after, so a symlink or junction planted inside the destination cannot redirect a
write (
E_SECURITY). Files are opened exclusively (wx) unless--overwrite, closing thecheck-then-write window; partial files are removed on failure. The residual realpath → open
window is documented: extract into an empty or trusted destination.
create/modify/cat/inflate --output,extract,stream --output-dirandbatch --task createrefuse an existing file (E_IO) unless--overwrite;modify --in-placewrites an unpredictable, exclusively created temp fileand renames atomically.
--max-input-size(CWE-400) — every buffered read (stdin and files, random-accesscommands,
create --stdin-name,inflate --sync) is bounded, 4 GiB by default,E_LIMITwith
detail { limit: "maxInputSize", configured, observed }; streaming commands stayconstant-memory.
modifyverifies what it re-emits — eager open, thenverifyEntry()on every entrynot removed or replaced: a CRC lie, a size lie or a local header that contradicts the
central directory is refused with the entry named (
E_DATA/E_SECURITY), instead ofbeing copied verbatim into a canonical-looking archive. Encrypted and stream-only-codec
entries are copied as-is and counted (
verifySkipped). No opt-out; runs under--dry-run.--codecstays the only dynamic import of user code (argv only, refusedfrom config files and from manifests without
--allow-codec-load), and it is reportedhonestly: a module registering method 0 / 8 replaces the writer's compressor for
create/modify(even under--deterministic; awarning:line says so), adeflateImplshows astier: "injected"unless--deterministic, andcreate --parallelrefuses such a modulebecause its workers cannot see it.
SIGINT/SIGTERMremove only the file being written at that moment(never a completed output, never the original of
--in-place) and exit 130 / 143.../a.zip,-o ../out.zipare ordinary shell usage and areaccepted; the
..refusal applies to path values that arrive as data (manifests), and everyentry name the CLI writes goes through
sanitizeEntryPath().inflateoutput bound, no networksurface in any mode. Published via Trusted Publishing (OIDC) with npm provenance, an
attested CycloneDX SBOM (
actions/attest-build-provenance) and a verified bin-only tarball;CodeQL and OpenSSF Scorecard in CI.
What's new
Create & modify
create— files, directories, stdin (--stdin-name) or a JSON manifest(
--from-manifest) throughcreateZip;--deterministic(pinned pure-TS encoder),--stream(constant memory, streamed inputs in the data-descriptor layout, reported aslayout),--parallel/--workers/--min-job-size/--job-timeout(
createParallelZipfromzipnative/worker, loaded lazily with an explicit worker URL),--method,--level,--order canonical|insertion(insertion = argv order: an EPUBmimetypelisted first is written first),--date epoch|now|<ISO>(UTC wall-clock),--mtime,--comment/--comment-file(binary comments),--entry-comment,--preserve-mode,--store-ext,--base,--prefix,--dir-entries,--include/--exclude,--follow-symlinks,--overwrite,--dry-run; manifestextraFieldsandcommentBase64. Every entry name is pre-checked with the engine'ssanitizeEntryPath().modify—--remove,--rename,--replace,--add,--add-dir,--comment/--comment-fileor--from-manifest(mode,extraFields,commentBase64), applied in afixed order through
createZipModifier; append-onlysave()by default (aninfo:linedocuments data remanence and the 7-Zip caveat),
--compactforsaveCompact(),--in-place,--overwrite,--dry-run; envelopeverified/verifySkipped/tier/layout.Read & extract
list— text (unzip -lstyle) | json | ndjson,--long(withrawNameHexandcommentHex),--validate eager, globs,--summary/--fields. Nothing decompressed.inspect— eager open, archive facts, per-method statistics, a determinism verdict thatseparates reproducibility (
deterministic) from form (canonicalLayout), every diagnostic;--entries/--entry/--extra; 19--checkassertions that print the report then exit1 /
E_CHECK_FAILED.cat— random-access streaming of one or more entries,--raw,--no-verify-crc,--output(partial file removed on failure,--overwrite),--dry-run; a sync-only--codecmethod falls back toreadEntry().extract— the two-phase sink (plan withsafeJoincontainment and overwrite /case-fold checks, then write with realpath containment, exclusive open and backpressure);
--skip-unsafe,--skip-symlinks,--allow-symlinks(target text as a file),--skip-unsupported,--on-duplicate,--overwrite,--flat,--buffered,--preserve-mode,--preserve-mtime,--dry-run.stream—iterateZipEntriesover unseekable input:--list(default),--output-dir,--cat;--long,--skip-unsafe,--skip-unsupported; attribute-dependentflags refused;
trust: "local-headers-only"in every JSON output;--summarycarriesdescriptorEntries/bytesKnown.Integrity & codecs
verify—verifyZip's report plusfailed/skipped/strict;--entry(repeatable) verifies named entries through
verifyEntry()and reportsselected;encrypted entries honestly
skipped; exit 1 /E_VERIFY_FAILEDwithzipCodeforstructural refusals;
--strictalso fails on any diagnostic.crc32— files or stdin in 64 KiB chunks;--seed,--expect(exit 1 /E_CHECK_FAILEDwith both CRCs indetail).inflate— the resumable inflater with a mandatory--max-outputbound (default:the effective
--max-entry-size);--sync,--method deflate|store|<id>(codecs via--codec),--allow-trailing,--overwrite,--dry-run;bytesConsumedin the envelope.Automation & meta
batch— directory mode (--task createper subdirectory through the fullcreatecommand,
--task verifyper archive,--concurrency1–64,--fail-fast,--overwrite)and
--manifestpipelines (10 whitelisted manifest commands,"@<id>"output references,strict pre-validation,
--continue-on-error,--allow-codec-loadgate, 1 000-task and50 MB caps); under
--jsonevery task's output is captured into one batch document(
tasks[i].report);--summary/--fields/--dry-run.doctor— versions (package vs the engine'sVERSIONexport), deflate tier andpinned tier, web streams, workers, codecs, effective limits (as numbers under
--json,maxInputSizeincluded), command count; exit 0/1.schema— 22 subjects: the three manifests, every report and--summaryshape,doctor,govern-verify,crc32, thestatusanderrorenvelopes,errors(codesand the 39-entry mapping),
limits,diagnostics, and the capabilitymanifest.completion— bash, zsh, fish, powershell (path flags complete files).govern—rules, policy,
verify-issue(exit 1 /E_POLICY), pinned to the.githubfiles by a test.Global options and agent surface
--json,--pretty,--dry-run(create,extract,modify,stream,cat,inflate,batch),--strict,--quiet,--no-color(NO_COLOR/FORCE_COLOR/TERM=dumb),--config/--no-config(.zipnativerc.json,codecrefused),--version --json,--format, -fon every command that has a format. Global flags workbefore or after the command name; flags and positionals are order-independent.
--max-*bounds (--max-entries100000,--max-entry-size1 GiB,--max-total-size8 GiB,--max-ratio1024,--max-name-bytes4096,--max-extra-bytes65535,--max-comment-bytes65535,--max-cd-bytes256 MiB) overthe engine's CWE-tagged
ZipLimits, plus the CLI-owned--max-input-size(4 GiB);nonedisables a bound with a warning.
--pure-codecsand--codec <module>(argv only, gated in manifests, reported truthfully when a module shapes the writer).E_*classes; the 39ZIP_*causes carried verbatim through a typed mappingthat fails
tscwhen the engine adds a code; the 11 diagnostic codes bridged to stderrtext,
--jsonarrays or a--stricterror;docs/data/core-exports.jsonanddocs/data/errors.jsonshipped for agents;AGENTS.md,llms.txt.signals; a closed downstream pipe ends the run quietly with exit 0; a terminal with nothing
piped is refused instead of blocking;
ZIPNATIVE_JSON,ZIPNATIVE_DRY_RUN,ZIPNATIVE_QUIET,ZIPNATIVE_STRICT,ZIPNATIVE_PURE_CODECShonoured from the environment.Compatibility notes
The machine contract (envelope fields,
E_*classes, theZIP_*mapping, exit codes, schemasubjects) is the baseline every later 1.x release must keep additive. The audit pass settled
the following before that baseline froze — first release, so nothing to migrate, but scripts
written against pre-release builds should note:
--date <ISO>and manifestdatevalues without a zonedesignator are read as UTC; the stored DOS fields — and the archive bytes — are identical on
every host.
nowand--mtimeare local and not reproducible. DOS resolution is 2 seconds(odd seconds floored, warning), range 1980–2107 (warning outside).
--order insertionmeans the argv order (each directory still walks name-sorted;manifests keep their entries order). The default
canonicalorder is unchanged...is accepted.zipnative list ../a.zipand-o ../out.zipwork; only manifestpath values keep the
..refusal (E_INPUT).--overwrite— every writer, not only theextraction sink (
E_IO, the file is left intact). Re-run scripts pass--overwrite.batch --manifest --jsonwrites one stdout document; a task that would write itsartefact to stdout (
create/modify/cat/inflatewithoutoutput,stream --cat)is refused at validation (
E_USAGE). Text mode keeps the interleaved output.E_INPUT(exit 1) everywhere —modify --add/--rename/--add-dirandcreate --stdin-nameincluded; malformed flags stayE_USAGE(exit 2).Unknown commands are
E_USAGE(exit 2).unixModeis four octal digits ("0000","0644","4755").stream --summaryis{ entries, bytes, descriptorEntries, bytesKnown, trust }:data-descriptor rows carry the local header's zero sizes and
bytesexcludes them.inspect --check deterministicasserts reproducibility only; usecanonical-layout(alias of
no-data-descriptor) for the buffered layout. Acreate --streamarchive isdeterministic: true,canonicalLayout: false.dist/cli.cjs(the bin) and nothing elseexecutable — no ESM build, no type declarations, no source maps; use the CLI, not an import.
engines.node >= 22). CI: Ubuntu Node 22 and 24,Windows Node 22 and 24 (blocking), macOS Node 22.
zipnative ^1.0.0is the sole runtime dependency and stays external in the bundle(
zipnative/workerresolves its own worker script).Notes
modifyoutput and 7-Zip. The defaultsave()keeps the original bytesverbatim (removed / replaced content remains recoverable), and 7-Zip's CLI is known to
mis-read that layout — it extracts the stale payload and misses appended entries because
it does not honour the final central directory (unzip, bsdtar, Python, jar and
Expand-Archive all do). Pass
--compactwhen deletion or 7-Zip interoperability matters.streamtrusts local headers alone. There is no central directory to cross-checknames, sizes, methods or attributes; mode and symlink policy are unavailable and every
JSON output says
trust: "local-headers-only". Preferlist/extracton a completefile; store, encrypted or custom-codec entries with data descriptors are refused in
forward mode, and a custom-method entry can be listed and skipped but not decoded there
(
E_DATA— usecat/extract --codecon the complete file).listed, reported as
skippedbyverify, skippable withextract/stream --skip-unsupportedand copied unverified bymodify.ZIP_UNSUPPORTED_ZIP64_STREAMING) until theengine's per-entry Zip64 streaming lands; buffered entries are fully Zip64.
--max-input-size);stream,crc32,inflate(default) andcreate --streamareconstant-memory;
create --stream --deterministicbuffers per entry (the pinned encoder iswhole-buffer).
.sh+.ps1pair (41 demos, 73 jobs insamples/run-all.js) andruns offline;
samples/agent/walks the recommended agent loop.Install
Upgrade
First release — nothing to upgrade from. Pin
zipnative-cli@^1.0.0; the--jsoncontractand the exit / error codes are the 1.x baseline.
Verification
Ubuntu (Node 22, 24), Windows (Node 22, 24) and macOS (Node 22); coverage statements
96.32 % / branches 92.52 % / functions 97.93 % / lines 96.91 % against the enforced thresholds
93 / 88 / 94 / 93.
ISO/IEC 21320-1:2015 — 33 PASS + 4 XFAIL, 0 FAIL (negative canaries correctly rejected
with their declared check ids; the 3 hostile-but-conformant archives PASS the profile and
are refused by
extract), exit 0. The same gate runs blocking in CI (verazip.yml, Linux +Windows, every push and PR,
VERAZIP_REQUIRED=1) and pre-publish (publish.yml).npm auditclean;npm run lint(src and tests) 0 errors; built binary smoke-tested(
node dist/cli.cjs …) for every command, includingcreate --parallel(worker scriptresolved from the bundle) and
doctorreporting thenode-zlibtier;samples/run-all.js73/73.
dist/cli.cjs,AGENTS.md,LICENSE,README.md,llms.txt,docs/data/errors.json,package.json.can verify the published package's provenance and registry signatures with
npm audit signaturesafter installing, and the SBOM withgh attestation verify sbom.cdx.json -R Nizoka/zipnative-cli.This discussion was created from the release v1.0.0 — the agent-grade ZIP CLI on zipnative 1.0.0.
All reactions