Releases: Nizoka/zipnative-cli
Release list
v1.0.0 — the agent-grade ZIP CLI on zipnative 1.0.0
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: true output bytes are frozen under semver. This is the
first 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 stable E_* classes, secure-by-default extraction with the
CLI 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
- Reproducible archives from the shell:
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 --strict - Extraction that refuses instead of guessing — zip-slip and Windows device names,
symlink 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, throughrealpath), opens every file exclusively, never
overwrites without--overwrite, and never materialises a symlink. Opt-outs skip; they
never write anything unsafe. - One envelope, two codes —
--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),detailcarries
the engine's structured fields. Branch on codes, never on text. streamfor what you cannot seek — list, extract or--catfrom stdin, a pipe or an
upload 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 it
re-emits is verified first.- Agent ergonomics —
--dry-runon seven commands,--summary/--fields/ compact
JSON,--strict, eight--max-*bounds plus--max-input-size, 22schemasubjects (JSON
Schemas, 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. - Conformance is proven, not asserted — every archive the CLI writes is validated
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 archivesextractmust 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:
- Sink hardening (CWE-59 / CWE-367) —
extractandstream --output-dirshare one sink
(src/utils/sink.ts). Beyond the lexicalsafeJoinplan, the nearest existing ancestor of
every target isrealpath'd under the root beforemkdir -pand the created directory is
re-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 the
check-then-write window; partial files are removed on failure. The residual realpath → open
window is documented: extract into an empty or trusted destination. - Uniform overwrite policy —
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 file
and renames atomically. --max-input-size(CWE-400) — every buffered read (stdin and files, random-access
commands,create --stdin-name,inflate --sync) is bounded, 4 GiB by default,E_LIMIT
withdetail { limit: "maxInputSize", configured, observed }; streaming commands stay
constant-memory.modifyverifies what it re-emits — eager open, thenverifyEntry()on every entry
not 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 of
being 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.- Codec truth —
--codecstays the only dynamic import of user code (argv only, refused
from config files and from manifests without--allow-codec-load), and it is reported
honestly: a module registering method 0 / 8 replaces the writer's compressor forcreate/
modify(even under--deterministic; awarning:line says so), adeflateImplshows as
tier: "injected"unless--deterministic, andcreate --parallelrefuses such a module
because its workers cannot see it. - Signal cleanup —
SIGINT/SIGTERMremove only the file being written at that moment
(never a completed output, never the original of--in-place) and exit 130 / 143. - Argv paths are the user's —
../a.zip,-o ../out.zipare ordinary shell usage and are
accepted; the..refusal applies to path values that arrive as data (manifests), and every
entry name the CLI writes goes throughsanitizeEntryPath(). - 50 MB JSON / 1 MB config / 1 000-task caps, a mandatory
inflateoutput bound, no network
surface 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 as
layout),--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 EPUB
mimetypelisted 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; manifestextraFieldsand
commentBase64. 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 a
fixed order throughcreateZipModifier; append-onlysave()by default (aninfo:line
documents 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(withrawNameHexand
commentHex),--validate eager, globs,--summary/--fields. Nothing decompressed.inspect— eager open, archive facts, per-method statistics, a determinism verdict that
separates reproducibility (deterministic) from form (canonicalLayout), every diagnostic;
--entries/--entry/--extra; 19--checkassertions that print the report then exit
1 /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-dependent
flags refused;trust: "local-headers-only"in every JSON output;--summarycarries
descriptorEntries/bytesKnown.
Integrity & codecs
verify—verifyZip's report plusfailed/skipped/strict;--entry
(repeatable) verifies named entries throughverifyEntry()and reportsselected;
encrypted entries honestlyskipped; exit 1 /E_VERIFY_FAILEDwithzipCodefor
structural 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), `...