Skip to content

Releases: Nizoka/zipnative-cli

v1.0.0 — the agent-grade ZIP CLI on zipnative 1.0.0

Choose a tag to compare

@Nizoka Nizoka released this 05 Sep 11:49
dd43f98

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, through realpath), 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--json puts { ok: false, command, error: { code, message, zipCode?, entryName?, detail? } } on stderr: code is the class (13 E_* values),
    zipCode is the exact cause (zipnative's 39 frozen codes, verbatim), detail carries
    the engine's structured fields. Branch on codes, never on text.
  • stream for what you cannot seek — list, extract or --cat from stdin, a pipe or an
    upload body, with the engine's trust caveat made explicit (trust: "local-headers-only").
  • modify without recompression, and without laundering — add, replace, remove, rename;
    append-only by default, --compact for true deletion; --in-place; every entry it
    re-emits is verified first.
  • Agent ergonomics--dry-run on seven commands, --summary / --fields / compact
    JSON, --strict, eight --max-* bounds plus --max-input-size, 22 schema subjects (JSON
    Schemas, an error registry and a capability manifest), doctor as a preflight,
    batch --manifest pipelines whose --json output is one document, and llms.txt +
    AGENTS.md + docs/data/errors.json in 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 archives extract must 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)extract and stream --output-dir share one sink
    (src/utils/sink.ts). Beyond the lexical safeJoin plan, the nearest existing ancestor of
    every target is realpath'd under the root before mkdir -p and 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 policycreate / modify / cat / inflate --output, extract,
    stream --output-dir and batch --task create refuse an existing file (E_IO) unless
    --overwrite; modify --in-place writes 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
    with detail { limit: "maxInputSize", configured, observed }; streaming commands stay
    constant-memory.
  • modify verifies what it re-emits — eager open, then verifyEntry() 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--codec stays 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 for create /
    modify (even under --deterministic; a warning: line says so), a deflateImpl shows as
    tier: "injected" unless --deterministic, and create --parallel refuses such a module
    because its workers cannot see it.
  • Signal cleanupSIGINT / SIGTERM remove 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.zip are 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 through sanitizeEntryPath().
  • 50 MB JSON / 1 MB config / 1 000-task caps, a mandatory inflate output 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) through createZip; --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
    (createParallelZip from zipnative/worker, loaded lazily with an explicit worker URL),
    --method, --level, --order canonical|insertion (insertion = argv order: an EPUB
    mimetype listed 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; manifest extraFields and
    commentBase64. Every entry name is pre-checked with the engine's sanitizeEntryPath().
  • modify--remove, --rename, --replace, --add, --add-dir, --comment /
    --comment-file or --from-manifest (mode, extraFields, commentBase64), applied in a
    fixed order through createZipModifier; append-only save() by default (an info: line
    documents data remanence and the 7-Zip caveat), --compact for saveCompact(),
    --in-place, --overwrite, --dry-run; envelope verified / verifySkipped / tier /
    layout.

Read & extract

  • list — text (unzip -l style) | json | ndjson, --long (with rawNameHex and
    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 --check assertions 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
    --codec method falls back to readEntry().
  • extract — the two-phase sink (plan with safeJoin containment 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.
  • streamiterateZipEntries over unseekable input: --list (default),
    --output-dir, --cat; --long, --skip-unsafe, --skip-unsupported; attribute-dependent
    flags refused; trust: "local-headers-only" in every JSON output; --summary carries
    descriptorEntries / bytesKnown.

Integrity & codecs

  • verifyverifyZip's report plus failed / skipped / strict; --entry
    (repeatable) verifies named entries through verifyEntry() and reports selected;
    encrypted entries honestly skipped; exit 1 / E_VERIFY_FAILED with zipCode for
    structural refusals; --strict also fails on any diagnostic.
  • crc32 — files or stdin in 64 KiB chunks; --seed, --expect (exit 1 /
    E_CHECK_FAILED with both CRCs in detail).
  • inflate — the resumable inflater with a mandatory --max-output bound (default:
    the effective --max-entry-size); --sync, --method deflate|store|<id> (codecs via
    --codec), `...
Read more