Repository navigation
Release/v1.1.0 - #14
Merged
Merged
Conversation
…on to v2.4.4 All four github/codeql-action steps (init, autobuild, analyze in codeql.yml, upload-sarif in scorecard.yml) must run the same release: Dependabot opened one PR per step (#3, #4, #5, #7), and #7 failed because CodeQL refuses a mixed-version run. One commit moves them together; scorecard-action follows #1. SHAs verified against the peeled upstream tags (git ls-remote). Supersedes #1, #3, #4, #5 and #7.
…-encoding, --timeout, --progress Pin zipnative ^1.1.0 (the sole runtime dependency, bumped by hand: the Dependabot group #11 had moved it under a "dev" label and broke the build) and absorb every 1.1 change: - core bridge: the 29 new exports re-exported; docs/data/core-exports.json is the 106-row ledger (101 + 5 worker) with a third reach grade, "bridged", for exports no 1.1.0 command reaches yet (range reader, canonicalize, raw copy — each named in ROADMAP "1.2 candidates") - scripts/sync-engine-data.mjs: re-derives the engine half of docs/data/{errors,core-exports}.json from a zipnative checkout and the CLI half (cli mapping) from the built CLI; --check for CI - 12th diagnostic ZIP_TIMESTAMP_CLAMPED (typed satisfies Record<ZipDiagnosticCode, ...> so a new engine code fails tsc); the CLI's own DOS-range warning is gone (the engine reports it) - ZipLimits.maxEntryCompressedSize: reported by doctor / schema limits, deliberately without a flag (only openZipRange consults it) - removed compensations: ZLIB_TO_ZIP (every tier raises ZIP_DEFLATE_*), the hand-rolled skipped re-derivation in verify --entry and modify (verifyEntry().skipped + entryPasses(), the engine's archive rule), the Unix-mode encoder (externalAttributesFromUnixMode, still masked to 0o777) - inspect: determinism measured by analyzeDeterminism(); deterministic keeps its 1.0 meaning, plus canonicalExtras / canonicalVersionMadeBy / canonicalForm / offenders and --check canonical-form - create --stream|--stdin-name --zip64: streamed entries past 4 GiB - --name-encoding <label> (list, inspect, cat, extract, stream): a TextDecoder nameDecoder, applied by the engine before sanitising - --long rows: timestamps (UT / NTFS / DOS) and unixIds (0x7875) - --timeout <duration>: AbortSignal deadline -> E_LIMIT detail.limit "timeout", output removed; --progress: onProgress on stderr (NDJSON under --json) - stream: --codec methods decode through createDecompressor; codecs without it are refused before the first byte (--skip-unsupported skips) - modify: an encrypted survivor's local header is now cross-checked (mismatch -> ZIP_CD_LFH_MISMATCH); an unregistered method stays E_UNSUPPORTED dosTimeMode is deliberately not adopted: the CLI's date convention is per entry (explicit dates store UTC fields, --date now / --mtime the local wall-clock), which one archive-wide mode cannot express. The 37-archive veraZIP corpus is byte-identical to the 1.0.0 build. npm audit: the five vulnerable dev-only packages (brace-expansion, source-map-js, fast-uri, http-cache-semantics, ip-address) resolved by npm audit fix; 0 vulnerabilities.
…unds, symlink-safe writes, terminal-safe text
Runtime hardening to the level of pkinative-cli (ADRs 0001-0008 under
docs/adr/). Three items narrow what 1.0 accepted; each is a security fix
with its migration in the CHANGELOG:
- config (ADR 0001): .zipnativerc.json is presentation only (format,
fields, json, pretty, quiet, no-color, progress, strict, long, summary).
Every other key is E_USAGE naming the file (1.0 refused only codec, so
a planted file could disable the limits or turn on --overwrite /
--skip-unsafe / --allow-symlinks); prototype keys refused; regular file
read through one bounded descriptor; a key applies only where the
command declares it; the applying file is announced on stderr and
carried as config { path, applied } in the envelopes
- strict invocation (ADR 0003): prototype-free parser, aliases
canonicalised, repeated switches refused; assertInvocation() refuses
undeclared flags, repeated single-value flags and surplus operands
before any input is read — on argv and for every batch manifest task
before the first one runs (--constructor no longer crashes E_RUNTIME)
- finite bounds (ADR 0002): none / inf refused on every --max-*,
--max-input-size and inflate --max-output, with an explicit-value remedy
- reads (ADR 0005): one descriptor; regular files size-checked on it and
read to that size; pipes / FIFOs / devices byte-counted (/dev/zero is
E_LIMIT, process substitution still works); directories E_IO
- writes (ADR 0005): lstat refusal (dangling links count) + wx; under
--overwrite a temp sibling renamed into place, so a planted symlink is
replaced, never followed; the write layer removes only what it created
— fixes a 1.0 race where an engine error before the deferred exclusive
open made cat / inflate / create unlink a pre-existing output
- terminal-safe text (ADR 0004): escapeTerminal() on every text sink
(reports, progress, diagnostics, errors); JSON exact;
tests/docs/source-hygiene.test.ts forbids raw control / bidi characters
in tracked files (diagnostics.ts carried a literal NUL)
- text errors lead with the class and cause: error E_X (ZIP_Y): ...;
exits after a stdout / stderr write wait for the flush (async pipes on
macOS / Windows); --json is honoured for refusals raised by the parse
- doctor reads the supported Node range from package.json engines.node
(kept at >=22: pkinative-cli's patch floor answers a Web Crypto CVE that
does not apply here)
- samples/config rewritten for the presentation-only contract, with a
refused planted config demo; run-all.js 74/74
…esets as code Supply chain and CI to the level of pkinative-cli and zipnative 1.1 (docs/adr/0009-release-integrity.md): - scripts/gate.mjs: the single definition of green (--fast / --ci / --publish, --require-all, logs in test-output/.gate/), run by ci.yml, node-current.yml, sample-regression.yml and publish.yml instead of hand-copied steps; plain ESM with @ts-check (no tsx), typed by the new tsconfig.scripts.json - publish.yml: guard -> build -> publish -> attest. The tag must equal v<version>; build holds no id-token and no cache and runs the publish gate; publish runs behind the npm-publish environment with .nvmrc only, re-checks both digests and publishes the handed-on tarball with an npm client pinned by SHA-512; attest fetches it back from the registry, runs npm audit signatures, writes CycloneDX + SPDX + toolchain SBOMs (npm sbom — @cyclonedx/cyclonedx-npm dropped) and attests them - every job: harden-runner first (block with an allow-list, or audit with a dated reason), npm ci --ignore-scripts, read-only top-level permissions, persist-credentials: false, one SHA per action (verified against the peeled upstream tags); no path filter anywhere; verazip.yml no longer interpolates step outputs into run: scripts - codeql.yml: actions + javascript-typescript, build-mode none, security-extended, job-level permissions - new: audit.yml (weekly npm audit + OSV-Scanner), dependency-review.yml (high severity, licence allow-list), node-current.yml (Node 26, advisory), sample-regression.yml (run-all.js on Linux + Windows), and a workflow-lint job (zizmor, checksum-verified actionlint, reuse lint) - .github/rulesets/{main,tags}.json, CODEOWNERS widened to the trust boundary, SECURITY-INSIGHTS.yml (OpenSSF 2.0), docs/.well-known/ security.txt (RFC 9116), REUSE.toml + LICENSES/MIT.txt - dependabot: seven-day cooldown, actions grouped (the four codeql-action steps move together), zipnative excluded from the dev group, @types/node majors ignored (engines floor 22) - package: tracked .npmrc (ignore-scripts, engine-strict), .node-version, packageManager, exact overrides, @eslint/js declared, no main entry; the tarball is 10 files (+ CHANGELOG, SECURITY, THIRD-PARTY-NOTICES) pinned by docs/data/package-files.json; bundle-probe (engine external, no inlined engine code, no console.log, no attribution trailer) and an install smoke test of the packed tarball - tests/tools/workflows.test.ts pins the workflow invariants; tests/scripts/release-tools.test.ts the gate and its checks; opt-in git hooks (.githooks/, npm run hooks:install) - ai-governance.json 1.1.0: ai_attribution_trailers_allowed: false, configuration_relaxes_checks: false
…c, docs integrity
- tests/fuzz/parsers.test.ts: seeded campaigns (no dependency) over argv,
sizes / counts / durations, globs, --fields, batch manifests,
.zipnativerc.json, --name-encoding and the terminal escaper; invariant:
return or CliError, never E_RUNTIME (ADR 0010, ROADMAP A-25)
- fix(glob): a character class with a range out of order ('[h-%]') is a
usage error (exit 2) instead of a RegExp SyntaxError surfacing as
E_RUNTIME (fuzz finding)
- tsconfig: exactOptionalPropertyTypes, noImplicitOverride,
noPropertyAccessFromIndexSignature, lib ES2022 without dom
- eslint: no-non-null-assertion, no-console, no zipnative import outside
the core bridge
- veraZIP: re-port of zipnative 1.1.0 validate-zip.ts (blob 604321f,
commit 1e2ab42): WF/DESCRIPTOR-WIDTH keys the descriptor width on the
local header's Zip64 extra; 23-id vocabulary; the vendor test compares
the pins with the engine checkout at the installed tag when present
- corpus: create --stream --zip64 and a clamped pre-1980 --date
(39 archives: 35 PASS, 4 XFAIL); the 37 earlier archives stay
byte-identical to the 1.0.0 build
- tests/docs/links.test.ts: every relative link and anchor resolves, no
CRLF in the index; the fixture ledger records and checks SHA-256
- coverage thresholds ratcheted to 95 / 91 / 97 / 96
- version 1.1.0 (package.json, lockfile, CITATION.cff with the engine reference at 1.1.0) - CHANGELOG [1.1.0]: security and behaviour changes with their migration, added, changed, fixed, supply chain, documentation; the entries listed under [Unreleased] after 1.0.0 shipped in the 1.0.0 tarball (the published dist/cli.cjs is byte-identical to the v1.0.0 tag build) and now sit under [1.0.0] "Release-branch review" - ROADMAP: v1.1.0 released section; the v1.0.0 entry keeps its own 37-archive figures; the type-aware lint item is checked - release-notes/v1.1.0.md; release-notes/draft/PR-v1.1.0.md and triage-v1.1.0.md (every open PR and alert, read with read-only gh api calls, with a verdict and a ready-to-paste comment) - docs: README what's-new box and figures, AGENTS.md (zip64 in the create envelope, the timeout envelope, timestamps / unixIds / nameEncoding rows, canonicalForm and offenders, decoded names stay guarded), llms.txt, knowledge base, CONTRIBUTING, copilot-instructions and CLAUDE.md figures (106 exports, 12 diagnostics, 69 files, thresholds) - samples: create/08-zip64-stream, list/04-name-encoding (with a foreign Shift_JIS archive from .NET System.IO.Compression, SHA-256 recorded and tested), inspect/04-canonical-form, agent/04-timeout-and-progress; 45 demos, run-all.js 83 jobs; the samples README config rows follow the presentation-only contract - typescript-eslint 8.69.0 -> 8.71.1 (the dev half of Dependabot #11)
Each finding was reproduced on the built binary before the fix and is pinned by a test (tests/integration/review-fixes-1-1.test.ts, built-binary-smoke.test.ts): - stream --timeout no longer exits 0 with a truncated file: the engine's forward reader is not given the signal (its pending entry read did not settle on abort; upstream draft written), the CLI's input and write loops enforce the deadline; the dispatcher fails any run whose event loop drains with the command pending and removes the in-flight files - a timeout that reaches a decompressor is E_LIMIT, not ZIP_DECOMPRESSION_FAILED (mapZipError returns the deadline's reason) - false in .zipnativerc.json never turns a switch on - batch directory mode declares every create flag it forwards - one deadline per process: a manifest task cannot carry timeout - the file writer's backpressure wait no longer leaks close listeners (MaxListenersExceededWarning on stderr, a 1.0.0 bug) - --json after -- is an operand - pipe / FIFO / device reads fill 64 KiB blocks: memory is bounded with the bytes, not 64 KiB per read - terminal escaping per field: escapeLine() also escapes TAB and LF in names, messages, remedies and the config info line, and the escaped set covers the invisible look-alike characters (U+200B, U+2028/9, U+FEFF, U+00AD, Hangul fillers, tag characters) - publish.yml: the consumer install check runs in a fifth, read-only job, never next to the attestation identity - a manifest comment-file is anchored to the manifest directory
…t batch Second 1.1.0 review (reviewers A and B, verifier), group 1: - M-01: a switch takes only true|1|yes|on|false|0|no|off (case- insensitive); any other value, including an empty one, is E_USAGE before any input is read, on argv and in every batch manifest task. 1.1.0-dev read `--overwrite=flase` as ON (pkinative-cli parity: hasFlag delegates to the strict switch reader; the value is never echoed) - N-1: batch --task verify reads each archive through the bounded one-descriptor reader (--max-input-size), like verify - M-23: batch refuses the flags its mode ignores (create flags with --task verify, directory flags with --manifest); a manifest task cannot set a process-wide flag (json, quiet, no-color, config, no-config, progress, timeout, help, version); batch verify rows carry zipCode - M-24: --workers is bounded 0-64 - M-26: the alias table is prototype-free; an undeclared alias is named as typed (--output-dir (-d)); the combined-short-flag message lists the real short flags - M-27: extract refuses a file entry that is also a parent directory with E_IO naming the entry, before anything is written (dry-run agrees); sink mkdir failures map to E_IO, never E_RUNTIME - M-28: extract --entry naming no entry is E_NOT_FOUND (ZIP_ENTRY_NOT_FOUND), like cat and verify - the --json prescan also honours --json=true|1|yes|on
Second 1.1.0 review, M-03: the deadline was only checked when a chunk arrived, so a stalled pipe (list -, crc32, create --stdin-name, stream, extract -) waited forever. The input streams now carry the run signal (addAbortSignal), the buffered stdin reader rejects on abort, an AbortError past the deadline is reported as the deadline, and the dispatcher fails a run still pending 500 ms after the abort (a FIFO read the CLI cannot cancel), removing the files being written. Spawn test on the built binary; ADR 0006 updated.
…fety in the summary
Second 1.1.0 review, group 3:
- M-02: inspect-summary declares canonicalForm; the error and status
schemas declare config { path, applied } and the command-specific status
fields (zip64, method, level, order, stream, parallel, ...).
tests/commands/schema-conformance.test.ts validates the real output of
every JSON-emitting command against its schema subject (test-only
validator, no dependency)
- M-04: inspect --summary carries unsafeNames, symlinks and duplicateNames
(the "safe to extract?" probe)
- M-05: schema manifest adds per-command switches, repeatable, pathFlags
and operands, plus globalSwitches / globalRepeatable; exitCodes list
130 and 143. flags stays a string list
- M-06: a verify failure names the failing entries (detail.failedEntries,
failedCount; entryName when exactly one)
- M-08: verify --name-encoding decodes the --entry lookups (refused
without --entry: verifyZip takes no decoder)
- M-10: schema output is compact under --json (--pretty opts out)
- M-21: text-mode inspect --check, verify and batch failures print their
one-line reason; a parse-time envelope names the command
- M-07: --summary / --fields given to a text run print a warning (a
config-supplied default stays silent)
- KB: the mapZipError paragraph no longer describes the removed zlib
translation
…keywords Second 1.1.0 review, group 4: - M-29: engines.node is ^22.17.1 || >=24.4.1 (was >=22): the patch floor of the Windows path-normalisation fixes CVE-2025-23084 and CVE-2025-27210 (device names in path.join / normalize), which the sink's containment relies on. doctor reports error below it, and the new gate step node-floor (ci, publish) enforces it through the built CLI - M-30: the bundle probe refuses a network module (net, http, https, http2, tls, dgram, dns), with or without the node: prefix, so 'offline, always' is enforced by the gate, not only stated - M-22: the mcp keyword is gone (no MCP server ships with this package)
…I instructions - M-25: the migration notes are exact: 1.0.0 printed the bare message (an Error: prefix only for unexpected runtime errors); the switch rule is true|1|yes|on|false|0|no|off, an unrecognised value is E_USAGE - M-19: CHANGELOG [1.1.0] opens with a Keep a Changelog ### Security section (the migration lives there); links follow the new anchor - M-11: 45 demos (README, llms.txt), 12 diagnostic codes (README), 32 + 3 conformant archives (AGENTS), the 37 inherited archives (ADR 0008); the consistency test now pins the demo and diagnostic counts - M-12: AGENTS §7 step 5 reads as one instruction; step 2 gates on the new safety counts; §3 documents the inspect summary fields - M-13: CLAUDE.md names REPEATABLE_FLAGS / OPERANDS in the checklist and lists every utils module and script; copilot-instructions lists control, engines and sanitize and the --progress env mapping - M-09: AGENTS and ADR 0008 say JSON lastModified is read in the host's time zone (TZ=UTC or dosDate/dosTime across hosts) - ADR 0003 records the switch-value rule and the batch mode refusals - ROADMAP: the deferred findings (token budget, contract/guide split, family hygiene, modify --name-encoding, diagnostic remedies) are 1.2 targets
…-range, raw entries Second 1.1.0 review, M-17 (the user's decision: expose the bridged 1.1 capabilities in 1.1.0). The 11 exports graded "bridged" are now reached: - canonicalize (canonicalizeZip, CanonicalizeOptions, CanonicalOptions): rewrite any archive into the canonical deterministic form without recompression; --date (now refused), --keep-comments, --normalize-modes; the envelope carries deterministic / canonicalForm measured on the OUTPUT; idempotent. modify --compact --canonical (CompactOptions) - merge (addFromReader, AddFromReaderOptions, RawEntryReader): merge archives or split one with filters; sources opened eagerly, every entry verified (verify: true, no opt-out); duplicate names refused unless --on-duplicate first|last; encrypted or unregistered-method entries refused (E_UNSUPPORTED) unless --skip-unsupported - --range on list, cat, verify (openZipRange, ByteRangeSource, ZipRangeReader, rangeSourceFromBytes): fileRangeSource() reads one descriptor of a regular file by ranges, so an archive larger than --max-input-size is listed, read and verified; the ninth bound --max-entry-compressed-size bounds each whole-entry fetch (ADR 0011, superseding ADR 0007); inspect keeps reading whole (its determinism verdict needs the bytes) - create-manifest raw entries (addRaw, RawEntryMeta): the payload is decoded once and its size and CRC-32 checked before it is written (ADR 0012) 17 commands; batch manifests whitelist canonicalize and merge; schema manifest, completions, USAGE, README, AGENTS, llms.txt, the knowledge base (§2, §4, §8), SECURITY, CONTRIBUTING, CLAUDE.md, copilot-instructions, ROADMAP and the release notes follow; docs/data/core-exports.json grades no export "bridged". Samples canonicalize/01, merge/01, list/05-range (48 demos, 96 run-all jobs); veraZIP corpus + canonicalized.zip and merged.zip (41 archives, 37 PASS, 4 XFAIL; the 37 inherited archives byte-identical). The gate's smoke step reads the command count from --help.
- canonicalize counts hostile names (unsafeNames) and encrypted entries in its envelope and warns: reproducible is not safe to extract - manifest raw entries refuse bytes trailing a DEFLATE stream; a stored payload must be exactly the declared size - ZIPNATIVE_* environment switches accept the same eight spellings as the flags (ZIPNATIVE_DRY_RUN=true was silently off); anything else is E_USAGE - docs: raw, --range, --max-entry-compressed-size, the canonicalize envelope, nine --max-* bounds, Keep a Changelog headings, final figures - release-notes/draft/review-v1.1.0.md: findings, verdicts, commits, token table and the counter-verification outcome
…de settings - .claude/skills/release-audit/: the family's pre-release audit ported to zipnative-cli (two auditors, an adversarial verifier, an autonomy pass, a GO/NO-GO ledger under .audit/<version>/); the negative-space checks are the CLAUDE.md non-negotiables, each as a command on the built binary - .claude/settings.json tracked: attribution off, npm publish / git push / git tag / gh writes denied for both shell tools; per-machine allowances move to .claude/settings.local.json (git-ignored, with .audit/) - tests/tools/agent-settings.test.ts pins attribution, the deny list and the ignore rules - CONTRIBUTING §Release, AGENTS §6, CLAUDE.md, the knowledge base, the CHANGELOG and the release note name the audit step
release-notes/draft/ holds the PR bodies only, as in the sibling repositories: the second-review report now lives under .audit/1.1.0/ and the HITL triage under .github/drafts/ (both git-ignored). The PR draft carries their committed summary (§Independent audit with the rounds and the seven criteria, the triage table under §Human follow-ups) and the figures it still quoted from 1.0.0 (17 commands, 12 ADRs, 96 sample jobs).
verify: a bound is never a verdict. The engine's verifiers (verifyZip, verifyEntry on both readers) report a fetch their bound stopped as a CRC / size mismatch, which read as corruption; the declared sizes are checked against --max-entry-size / --max-entry-compressed-size first and the refusal is E_LIMIT with entryName, detail and the flag as remedy, on the buffered, --entry, --range and batch --task verify paths; a limit refusal at open keeps E_LIMIT and its detail; --strict names ZIP_STRICT_DIAGNOSTIC. Engine draft: .github/drafts/upstream-range-verify-swallows-limit.md. io: a path that cannot be opened is E_IO on every path (govern verify-issue and a manifest raw path were E_RUNTIME with the raw ENOENT). cli: a misspelt ZIPNATIVE_* value under --json is refused as an envelope; list / inspect --summary carry zip64Entries next to zip64 (a streamed --zip64 archive has entries and no Zip64 EOCD) and the schemas declare both; the usage refusals whose message named the lifting flag carry it as remedy; CRC details are numbers on every path; list --format ndjson warns when a projection flag is ignored; batch --task verify rows carry entryName, detail and remedy, declared in schema batch; the --timeout message no longer claims a written file on a read. docs: create --stream --zip64 is how a streamed entry passes 4 GiB (the README feature table, llms.txt, AGENTS.md and the knowledge base said it was refused); SECURITY.md describes the ninth bound and its flag; the createDecompressor() contract; batch --manifest --dry-run scope; the config announcement is text-mode only; verify --name-encoding; the doctor Node check; raisedBy of ZIP_TIMESTAMP_CLAMPED; the raw manifest keys in create --help; the sample note on --summary --fields; twelve ADRs; the measured figures (76 files, 1385 tests, 95.91 / 91.48 / 97.81 / 96.77, 186.1 kB); the audit's deferred items in the ROADMAP. tests: tests/integration/audit-1-1.test.ts, the range / zip64 / parity cases in core-surfaces-1-1, the env-switch envelope in the built-binary smoke test; the suites that pinned the old shapes follow the fix.
The release ships on 2026-10-20: CHANGELOG, the release note, CITATION.cff, the ROADMAP, SECURITY-INSIGHTS; the derived dates follow (1.0.x security fixes until 2027-04-20, security.txt Expires 2027-10-20).
batch --task verify parses every --max-* value once, before the pool and before any read: a malformed bound is one E_USAGE (exit 2) again, never a row per archive and never a plan that says ok (the b04f15f regression). The ZIPNATIVE_* switches are judged after the --json token decides the mode, and the raw ZIPNATIVE_JSON that token shadows is still judged; the refusal envelope names the command. The AI-instruction files say 17 commands and nine bounds, and the docs consistency test counts them; CONTRIBUTING states the inbound = outbound licensing rule; ADR 0009 names its five jobs; the batch --dry-run help and AGENTS.md say what the plan checks; the --timeout help and README match the message; the release-day items of the audit in the ROADMAP; the coverage figures dated 2026-10-20 (76 files, 1387 tests).
…gures in the PR draft Round 4 of /release-audit at 9eaecd9: closure 29 / 29 of the round-3 ledger, the fix diff re-read, release-day bookkeeping, a 96-row standards sweep, the triage re-read through gh api (PR #12, #13, Dependabot #4 — all resolved on the branch); 0 blockers, 2 majors + 7 minors fixed in 64089ac, 0 waived — GO on 2026-10-20. The ledger stays local under .audit/1.1.0/round-4/.
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
…mpdir root CodeQL js/insecure-temporary-file flagged src/utils/io.ts:122 on the release PR: the directory-refusal unit test passed os.tmpdir() itself to readFileOrStdin, so a path in the shared, world-writable temp root flowed into open(). The test now creates its own directory with mkdtempSync, removes it afterwards, and asserts the E_IO refusal its name promises. No src/ change: readFileOrStdin only reads.
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.
feat: zipnative-cli 1.1.0 — zipnative 1.1.0 parity and hardening
Description
zipnative-cli 1.1.0 on zipnative 1.1.0 (
^1.1.0, still the only runtime dependency). Therelease covers three things:
commands, and two new commands,
canonicalizeandmerge(17 commands).Inputs that 1.0.0 accepted and ignored or misread are now refused. They ship as security fixes
with a migration (CHANGELOG
### Security). Everything else is additive: the envelopes, theE_*codes, the exit codes and theZIP_*mapping are unchanged, and the 37 archives of the1.0.0 veraZIP corpus are byte-identical.
Release notes: release-notes/v1.1.0.md. Decisions:
docs/adr/ (ADRs 0001–0012). The independent reviews and the audit
are summarised below (§Independent audit); the triage of the open PRs and alerts is the table
under §Human follow-ups.
Related Issues
Supersedes #1, #3, #4, #5, #7 (one commit moves every
codeql-actionstep together) and #11(the engine is bumped by hand). Closes #10 (
@types/nodemajors ignored: the floor is Node 22).Resolves Dependabot alerts #1, #3 and Scorecard alerts #3, #8; prepares #1 (rulesets as code).
Upstream: closes the CLI's engine asks answered by zipnative 1.1.0
(zipnative#9–#12).
Commits
ci(deps)codeql-actionv4.37.9 on all four steps,scorecard-actionv2.4.4feat(core)--zip64,--name-encoding,--timeout,--progress,analyzeDeterminism,--longtimestamps / unix ids,stream --codecviacreateDecompressor, compensations removed,scripts/sync-engine-data.mjsfix(security)lstat+wx/ temp + rename writes, terminal-safe text, the early-error unlink fix, ADRs 0001–0008ci(supply-chain)scripts/gate.mjs, harden-runner /--ignore-scriptseverywhere, zizmor / actionlint / reuse, audit + OSV, dependency review, rulesets, SECURITY-INSIGHTS, security.txt, ADR 0009test(fuzz)tsconfig/ ESLint, veraZIP re-port (WF/DESCRIPTOR-WIDTH), corpus 39, docs link / anchor / EOL tests, ADR 0010chore(release)typescript-eslint8.71.1fix(review)stream --timeoutno longer exits 0 with a truncated file, a timeout in a decompressor isE_LIMIT, configfalsenever turns a switch on,batchaccepts the create flags it forwards, one deadline per process, FIFO reads bounded in memory, per-field terminal escaping (LF, invisible characters, remedies), the consumer check moved to a read-only fifth publish job, manifestcomment-fileanchoredfix(security)(2nd review)--overwrite=flasewas ON), boundedbatch --task verify, mode-strictbatch, process-wide flags refused in tasks,--workers0–64, prototype-free aliases,extractfile/dir conflict and missing--entryrefused up frontfix(control)--timeoutfires while an input is stalled (stdin, pipes, FIFOs)fix(contract)schema manifest, safety facts ininspect --summary, failing names inverifyerrors,verify --name-encoding, compactschemaunder--json, text-mode failure lineschore(supply-chain)docs### Security, AI-instruction lists, count pinsfeat(core)canonicalize,merge,--range(+--max-entry-compressed-size), manifestrawentries — 17 commandsfix(review)(counter-verification)canonicalizecounts hostile names (unsafeNames,encrypted) and warns, arawDEFLATE payload refuses trailing bytes,ZIPNATIVE_*values strict, docschore(agents)release-auditClaude Code skill tracked under.claude/skills/, a tracked.claude/settings.json(attribution off, the GitHub and npm writes denied),.audit/ignored,tests/tools/agent-settings.test.tschore(release).audit/1.1.0/,.github/drafts/), summarised herefix(audit)/release-audit(§Independent audit): a bound is never a verdict inverify, a missing file isE_IO, the env-switch envelope,zip64Entries, remedies, CRC shapes, batch rows, the documentation findingsdocs(release)chore(release)security.txtExpires2027-10-20)fix(audit)(release day)batch --task verifyparses the bounds once before the pool, the rawZIPNATIVE_JSONis still judged under--jsonand the refusal names the command, the instruction files counted by the docs test, the licensing sentence, ADR 0009's five jobs, thebatch --dry-runwordingdocs(release)(release day)test(io)js/insecure-temporary-fileon the PR: a unit test handed the shared temp root itself toreadFileOrStdin; it now uses a privatemkdtempdirectory and assertsE_IO(nosrc/change)Behaviour changes (reviewer attention)
.zipnativerc.json: onlyformat,fields,json,pretty,quiet,no-color,progress,strict,longandsummaryare allowed; any other key isE_USAGEnaming thefile (ADR 0001).
E_USAGE(exit 2) before any input isread, including for every
batchtask (ADR 0003).none/infon a bound isE_USAGE(ADR 0002).error E_X (ZIP_Y): message(1.0.0 printed the bare message).comment-fileresolves against the manifest directory (was the currentdirectory) and is refused on
..; a manifest task cannot carrytimeout.No security default was loosened.
dosTimeModeis deliberately not adopted (ADR 0008).maxEntryCompressedSizeis behind--max-entry-compressed-size(ADR 0011, superseding 0007).Independent audit
Agent-run, on the local branch, before the maintainer's own pass. Round 1: two independent
pre-PR reviews (correctness, security) of the branch diff. Round 2: two independent reviewers
over seven criteria — A (conformance and product: standards against pkinative-cli and
pdfnative-cli, coverage of the core, the AI instructions, every documented figure, measured
token cost) and B (adversarial: the surface diff from the published 1.0.0, a hardening matrix
against pkinative-cli, attacks on the binary and the workflows) — and a third, separate
verifier that reproduced every finding and ruled it CONFIRMED, REJECTED or DEFER. Round 2b: a
fourth agent re-ran the original scenario of every confirmed finding on the built binary.
Round 3:
/release-audit(the family's audit skill, ported in this release: two auditors, anadversarial verifier, an autonomy pass, a second verifier, a GO/NO-GO ledger) at HEAD.
stream --timeouthang, a decompressor timeout, configfalse, batch create flags, one deadline per process, FIFO reads, per-field escaping, the consumer job, manifestcomment-file)e2e820e, one regression test per finding (tests/integration/review-fixes-1-1.test.ts,built-binary-smoke.test.ts)--overwrite=flasewas ON, the published schemas rejected real output,--timeoutdid not fire on a stalled pipe — plus a boundedbatch --task verifyaba500a→0536020, one regression test per finding (tests/integration/review2-1-1.test.ts,tests/commands/schema-conformance.test.ts); the 11 last engine exports exposed (canonicalize,merge,--range, manifestraw)canonicalizecarried hostile names silently, arawDEFLATE payload could hide trailing bytes, docs gaps; nits:ZIPNATIVE_*values strict887254f/release-auditat99bf8c3: 238 rows over three reports (A 129, B 63, D 46); verifier 1 over A + B: 13 confirmed, 1 rejected, 2 duplicate, 2 raised; verifier 2 over D: 11 confirmed, 2 downgraded, 1 duplicate, 2 raised — 0 blockers, 5 majors, 23 minors, 2 notes. Majors:verifyreported an intact entry over a--max-*bound as a CRC / size failure on every path (the engine's verifiers swallow the refusal — drafted upstream), SECURITY.md denied the ninth bound its flag, the docs said streamed entries > 4 GiB were refused (they take--zip64), a missing file wasE_RUNTIMEon two pathsb04f15f, one regression test per code finding (tests/integration/audit-1-1.test.ts,core-surfaces-1-1, the built-binary smoke test); the one note needs no action; 0 waived9eaecd9: closure 29 / 29 (one observation), the diff99bf8c3..HEADre-read hunk by hunk, release-day bookkeeping (versions, dates, no tag, 1.1.0 unpublished,npm audit0, 10 tarball files, the five engine drafts), the triage re-read throughgh api, and a 96-row 2026 standards sweep (Scorecard, OSPS Baseline, SLSA, Keep a Changelog, Conventional Commits, clig.dev, llms.txt, REUSE, RFC 9116, CITATION.cff, npm metadata, the ten native proofs); verifier 3: 13 confirmed, 2 downgraded, 0 rejected, 1 raised — 0 blockers, 2 majors (ab04f15fregression: a malformed--max-*onbatch --task verifybecame a per-rowE_USAGEwith exit 1 instead of the exit-2 refusal, with and without--dry-run), 7 minors (the rawZIPNATIVE_JSONshadowed by--json,command: nullin that envelope, the instruction files' "15 commands", the inbound = outbound sentence, ADR 0009 "four jobs", thebatch --dry-runwording, the triage delta), 7 notes64089acwith their regression tests (audit-1-1.test.ts, the built-binary smoke test,COUNTED_DOCSnow counts the.github/instruction files); the notes are ROADMAP 1.2 items or pre-existing; 0 waivedThe seven criteria after round 2b:
docs.yml, a Deno/Bun job,THREAT_MODEL.mdare 1.2### Securitywith its migrationdocs/data/core-exports.jsongrades nonebridgedschema manifestpublishes arity, repeatability, path flags and operands; errors and summaries carry the facts a second call used to fetch; bytes measured per taskLedger:
.audit/1.1.0/(local, git-ignored) —review-v1.1.0.md(round 2, every finding withits verdict and commit),
corpus-1.0.0.sha256, then the round-3 filesbrief.md,auditor-a.md,auditor-b.md,verifier-1.md,auditor-d.md,verifier-2.md,ledger.md,verdict.md; the release-day round underround-4/(brief.md,auditor-c.md,auditor-e.md,verifier-3.md,ledger.md,verdict.md)./release-audit release-notes/v1.1.0.md— GO on 2026-10-20 (round-4/verdict.md: blockersopen 0, majors open 0, waived 0; gate 15 / 15 at
64089ac; next: the maintainer's review of thisbranch). The agent-autonomy pass re-measured the
token table:
--summary/--fieldscut stdout by 75–94 % against text (88–98 % against the full--jsonreport); bare--jsonis text-sized oninspectand 2.8–2.9× onlist/verify—the gain is the projection levers', as AGENTS.md §3 says.
Validation (what actually ran, on Windows 11, Node 22.23.2)
npm run typecheck:allclean;npm run lint0 errorsnpm run test:coverage— 1387 passed, 9 skipped, 76 files; 95.91 / 91.48 / 97.81 / 96.77(thresholds 95 / 91 / 97 / 96)
npm run validate:zip— 37 PASS + 4 XFAIL; the 37 inherited archives are byte-identicalto the 1.0.0 build (SHA-256 compared)
node scripts/gate.mjs --publish --require-all— every step green (reproducible build,bundle probe, 10-file tarball, install smoke test)
node samples/run-all.js --clean— 96 / 96create --parallel,doctor(deflate tiernode-zlib,maxEntryCompressedSizein thelimits),
schema diagnostics(12 codes),create --stdin-name --zip64,list --name-encoding shift_jis;inspect --check canonical-form(pass and fail),--timeout 1ms→E_LIMIT,--progress;--timeout 60msmid-entry on a 64 MiB entry:stream -d,stream --cat,extract→E_LIMIT,exit 1, no partial file (1.1.0-dev exited 0 with a truncated file);
--overwite/--constructor x→E_USAGE,--max-entries none→E_USAGE,a planted config →
E_USAGE,--overwriteover a symlink replaces the link..github/rulesets/main.json)Human follow-ups (not performed by any agent)
Import
.github/rulesets/main.jsonandtags.json.Create the
npm-publishenvironment with a required reviewer, and bind npm TrustedPublishing to
publish.ymlwith that environment.After the merge, the open PRs and alerts (read on 2026-10-18 and re-read on 2026-10-20 with
read-only
gh apicalls; nothing was posted, closed or dismissed):codeql-action×4,scorecard-action)5c09933, same SHAs@types/node26)dependabot.ymlzipnativeincluded)a01d7c4+ the release commit);zipnativeis bumped on its ownip-address10.7.0 → 10.7.4), chore(deps): bump http-cache-semantics from 4.2.0 to 4.3.0 #13 (http-cache-semantics4.2.0 → 4.3.0), opened 2026-10-18ip-address, chore(ci): bump github/codeql-action/init from 4.36.0 to 4.37.9 #3http-cache-semantics(dev-only)npm audit0brace-expansion< 1.1.21 (GHSA-q2hr-2g5m-vwhr, medium, dev transitive; opened 2026-10-18)npm audit0main.json+tags.jsonpublish.ymlcontents: writescoped toattest(ADR 0009)codeql.yml, v1.0.0 — the agent-grade ZIP CLI on zipnative 1.0.0 #8 Vulnerabilitiesfast-checkby decisionComment texts:
.github/drafts/triage-v1.1.0.md(local).Confirm the 1.0.x security-fix window (SECURITY.md: until 2027-04-20) and the release date in
CHANGELOG / CITATION / release notes before tagging
v1.1.0.Engine-side drafts in
.github/drafts/(local, git-ignored) for the zipnative maintainer.Checklist
npm run test)npm run typecheck:all)npm run lint)their migration
node dist/cli.cjs --helpoutputs all 17 commands)npm run validate:ziplocally; CI on Linux + Windows)tests/docs/consistency.test.tsgreen)major (
b04f15f,64089ac; verdict GO on 2026-10-20, 0 waived)AI assistance
Drafted with AI assistance under .github/AGENT_RULES.md and
CLAUDE.md. The agent worked on a local branch only and made no GitHub writes; it read alerts
and PRs with read-only
gh apicalls. The quality gates above were run locally. Everydeviation from the plan is recorded in an ADR or in ROADMAP:
dosTimeModenot adopted (ADR 0008);^22.17.1 || >=24.4.1(Windows path CVEs; second review);stream --codecsample: the CLI cannot write a custom-method archive, so the case iscovered by tests;
-latest, as in the sibling repositories.Before this draft, two independent review agents (correctness and security) read the branch
diff. Every finding was reproduced on the built binary, fixed, and pinned by a test
(
tests/integration/review-fixes-1-1.test.ts,built-binary-smoke.test.ts). The engine-sidecause of the
stream --timeouthang is a local draft for zipnative(
.github/drafts/upstream-forward-reader-abort-hang.md).Human review gate applies.