Repository navigation
Releases: Nizoka/zipnative
Release list
v1.1.0 — range access, reproducible-for-any-archive, issues #9–#12, supply-chain parity
zipnative v1.1.0
Released 2026-10-08
The first minor since the freeze, and a statement about what a minor is: zero breaking changes. The public surface grew from 77 to 106 exports with none removed, the 39 error codes are unchanged, every new behaviour is opt-in, and the deterministic: true bytes are unchanged — proven by a byte-level baseline of the 33 pre-existing sample archives and by the previous release's own test suite running against the new sources in CI. Existing inputs produce byte-identical output, with one deliberate exception listed in the Upgrade section: dates outside the DOS range, whose previous output was wrong. The four issues reported by the satellites are closed at the source; the Zip64 streaming opt-in designed in 0.9 ships; five adoption features arrive — random access over remote and huge archives, cancellation and progress, raw transplant, canonicalisation of any archive, legacy name decoding — and the repository reaches hardening parity with pdfnative 1.8.0.
Highlights
- Open a 50 GB archive with the memory of its central directory —
openZipRange()reads an archive served by ranges (HTTPRange,Blob.slice(), a file handle, object storage) through aByteRangeSourceyou inject; the engine never fetches, every limit and cross-check ofopenZip()applies, and a counting source shows one tail window, the central directory, then only the entry you read. Guide · playground. - Make any archive reproducible —
analyzeDeterminism()names what would drift (timestamps, order, extra fields…);canonicalizeZip()andsaveCompact({ canonical: true })rewrite a Gradle, Maven,zipor Explorer archive into the canonical form without recompressing a payload, idempotently, under the frozen byte contract (golden SHA-256 hashes over two committed foreign fixtures);addRaw()/addFromReader()assemble artefacts from existing parts in O(bytes copied). Guide. - Issues #9–#12 closed at the source — UTC DOS timestamps and honest clamping (#9), one error classification for corrupt deflate on every tier (#10), custom codecs streamed by the forward reader through a new
createDecompressor()contract (#11),verifyEntry().skippedwith a reallocalHeaderMatch(#12). The satellites' workarounds become unnecessary. - Zip64 streaming opt-in —
addStream(name, source, { zip64: true })for entries that may exceed 4 GiB: the APPNOTE 4.3.9.2 layout, validated by the ISO/IEC 21320-1 gate and extracted byte-for-byte by the six-tool interop matrix; a real 4 GiB + 1 stream is exercised in the conformance workflow. - Supply-chain parity with pdfnative 1.8.0 —
scripts/gate.tsprofiles, harden-runner and--ignore-scriptson every job, OIDC publishing behind thenpm-publishenvironment with SLSA Build L2 provenance and SBOM attestation, dependency review, weekly audit, committed rulesets, a byte baseline of every sample, 25 new verify-docs rules,release-prepare, and acompat-previousjob that runs the 1.0.0 suite against 1.1.0.
Security
- fix(security):
verifyZip()/verifyEntry()no longer fabricatelocalHeaderMatchfor encrypted entries (it was hard-codedtruein one path andfalsein the other); the local header is cross-checked even when the payload cannot be verified, and an encrypted entry whose local header contradicts the central directory now fails the archive (CWE-1288, issue #12). Verdicts never loosened: unknown methods still fail the archive. - fix(security): corrupt deflate payloads surface as
ZipFormatErrorZIP_DEFLATE_CORRUPT/ZIP_DEFLATE_TRUNCATEDon every inflate tier — node:zlib,DecompressionStream, pure TS — instead of a runtime-dependent class (CWE-703, issue #10);ZIP_DECOMPRESSION_FAILEDis reserved for registered or injected codecs. - feat(security): encrypted entries are labelled by scheme —
feature: 'aes'for WinZip AES (method 99 or the 0x9901 extra),'zipcrypto','strong-encryption'— and still never decrypted in 1.x (CWE-327 context; AES is a 2.0 candidate). - feat(security): one new named bound,
maxEntryCompressedSize(1 GiB + 1 MiB, CWE-770), bounds the byte-range reader's whole-payload fetches; the in-memory reader uses zero-copy views and never consults it, so no 1.0.0 archive is newly refused. AnameDecoderruns before path sanitisation, never around it (CWE-22); a lying byte-range source isZIP_RECORD_TRUNCATED(CWE-20). - feat(ci): every workflow starts with
step-security/harden-runnerand installs withnpm ci --ignore-scripts(.npmrcmakes that the local default); actions stay SHA-pinned;dependency-review.yml(high severity, licence allow-list) and a weeklyaudit.yml;publish.ymlis three jobs with one artifact between them:verify(no publishing token; fetches the previous tag, runs the full publish gate, packs the tarball once with its SBOM andSHA256SUMS) →publish(thenpm-publishenvironment,id-token: write, a pinned npm ≥ 11.5.1, a sparse checkout of.nvmrconly, the handed-on tarball digest-checked then published through OIDC with provenance) →attest(the same tarball compared byte for byte with what the registry serves, then SLSA Build L2 provenance for it and the CycloneDX SBOM throughactions/attest-build-provenance, attached to the GitHub Release); no release job restores a dependency cache; aworkflow_dispatchdry run stops atnpm publish --dry-run; branch and tag rulesets committed under.github/rulesets/. - fix(deps):
npm audit --audit-level=highis green again —adm-zip0.6.1,brace-expansion1.1.21 / 5.0.12 andsource-map-js1.2.2 (dev dependencies only; the engine has none).
Added
- feat(parser):
openZipRange(source, options?),ByteRangeSource,ZipRangeReader,rangeSourceFromBytes()— random access over an injected byte-range source; shared per-entry defences withopenZip()(src/parser/zip-entry-checks.ts); 256 KiB ranged streaming throughcreateDecompressor(); the cached tail window serves the central directory and small archives without a round trip, and local headers are prefetched with 1 KiB of slack. - feat(core,parser):
signal?: AbortSignalandonProgress?: ZipProgressHandleron every asynchronous call — on the options ofextractZipStream,iterateZipEntries,openZip/openZipRange(every read of the reader),createZip/createParallelZip(everystream()andtoBytes()), and per call onreadEntryStream(entry, options)andstream(options); abort rejects withsignal.reasonafter resources are released (stream locks released without cancel, worker pools closed);ZipProgressis monotonic, a skipped entry counts as done, the paralleltoBytes()reports per settled entry. - feat(core):
ZipWriter.addRaw(name, payload, meta)andaddFromReader(reader, entry, options?)— transplant without recompression;addFromReaderverifies the source entry first unlessverify: false;RawEntryMeta,RawEntryReader,AddFromReaderOptions; the modifier's raw-copy plan moved tocore/zip-segments.tsso both writers share one implementation. - feat(parser):
canonicalizeZip(bytes, options?),CanonicalizeOptions;ZipModifier.saveCompact({ canonical })withCanonicalOptions/CompactOptions;analyzeDeterminism(bytes)withDeterminismReport,DeterminismOffender,DeterminismConcern— the verdict the satellites re-derived. - feat(core,parser):
nameDecoderoption on every read path (applied when flag bit 11 is clear, and as the fallback when a UTF-8-flagged name is not valid UTF-8;entry.nameEncodingbecomes'custom');getExtendedTimestamps(entry)(UT 0x5455, NTFS 0x000a, DOS fallback —ExtendedTimestamps) andgetUnixIds(entry)(ux 0x7875 —UnixIds);externalAttributesFromUnixMode(mode, options?), the write-side mirror ofgetUnixMode. - feat(core):
dosTimeMode: 'local' | 'utc'(DosTimeMode) on every option bag that writes or readslastModified; full DOS-date clamping with the newZIP_TIMESTAMP_CLAMPEDdiagnostic (the twelfth; issue #9). - feat(codecs):
ZipCodec.createDecompressor?(maxOutput)→ZipDecompressor { push, end }— the incremental contract the forward reader streams in O(chunk); the built-in store and deflate codecs implement it (issue #11). - feat(core):
AddEntryOptions.zip64— the per-entry Zip64 streaming opt-in foraddStream(): version 45 in both records, sentinel sizes and a (0, 0) Zip64 extra in the local header (lfhZip64Fieldsmoved tocore/zip-extra-fields.ts), 24-byte data descriptor, both sizes in the central record;assertStreamSizesInRangenames the option in its refusal. - feat(parser):
EntryVerification.skipped?: EntrySkipReason('encrypted' | 'stream-only-codec' | 'unsupported-method');verifyZip()delegates every entry toverifyEntry()(issue #12). - feat(samples): five samples —
zip64/zip64-streamed,incremental/merged-from-two,names-encoding/shift-jis-legacy(an expected ISO non-conformant: non-ASCII names without bit 11),attributes/unix-ids,attributes/ntfs-timestamps— 38 in the corpus, 33/5 in the ISO canary; two interop write cases (zip64-streamed,transplanted) — 19 validation cases (6 producer reads + 13 write cases, each run against every extractor present). - feat(tooling):
scripts/gate.ts(--fast/--ci/--publish,--require-all,--only,--from,--json, dist probes),scripts/verify-samples.ts+tests/regression/samples.test.ts+tests/regression/baselines/samples.sha256.json(SHA-256 of every sample with asincechain),scripts/release-prepare.ts(the bump in one pass),scripts/run-compat.ts(compat:previous),scripts/build-claude-rules.ts(agents:rules),scripts/install-git-hooks.mjs(opt-inpre-commit/pre-push), `scri...
v1.0.0 — first stable npm release
zipnative v1.0.0
Released 2026-09-02
The first stable release — and the first npm publication. zipnative 1.0.0 adds no engine behavior over v0.9.0: it is the freeze itself. Everything shipped across 0.1 → 0.9 — random-access reads, secure-by-default extraction, deterministic write, incremental modification, workers, forward streaming, the resumable inflater, one-call verification — becomes a semver commitment: the 77-export API surface, the 39-code error vocabulary and the deterministic: true output bytes are frozen (removing, renaming or changing any of them is semver-major; the promise is written down in SECURITY.md). 100% backward-compatible with v0.9.0. Zero runtime dependencies, 385+ tests, 93.9% statement coverage, published from CI with provenance.
Highlights
- On npm at last — published via Trusted Publishing (OIDC): no long-lived token, the entire quality gate re-runs in the publish workflow, the package carries a provenance attestation (verify with
npm audit signatures), and a CycloneDX SBOM is attached to the GitHub Release. A laptopnpm publishfails by design. - The conformance authority gate ("veraZIP") — ZIP had no veraPDF (JHOVE never shipped a ZIP module;
zip -Tis an alias ofunzip -tqq), so zipnative ships the first open clause-by-clause ISO/IEC 21320-1:2015 validator (npm run validate:zip— an independent raw parser, never the engine's own) plus the existing Archivematica-grade six-parser extraction matrix. Every archive zipnative writes conforms to the ISO profile, verified on Linux and Windows in CI and again before every publish; hostile-but-spec-valid archives (zip-slip et al.) are called out for what they are — conformance is not safety. Full story: the new conformance guide. - The freeze, made checkable — every one of the 77 exports carries TSDoc (a new verify rule fails the build on an undocumented export); the API registry (
api.json), the error registry (errors.json) and the determinism contract are the machine-readable form of the promise. - A new face — the "zipper-cut Z" logo: the Z monogram's diagonal carries interlocking zipper teeth in negative space (reads "Z" at a glance, "zip" up close), declined across favicon, og-image and a new GitHub social preview.
Security
- No parser or writer change — the security posture is exactly v0.9.0's.
- feat(ci): supply-chain hardening around the release: OIDC Trusted Publishing (no
NPM_TOKENsecret to steal), provenance attestations (SLSA-aligned, Sigstore), CycloneDX SBOM per release, SHA-pinned actions, and the full gate (audit included) re-run between the Release click andnpm publish. - docs(security): SECURITY.md now states the supported-versions policy (
1.0.x), how to verify release integrity, and the three-part compatibility promise.
Added
- feat(conformance):
validate:zip— the ISO/IEC 21320-1:2015 validator (the Table-1 structural clauses + APPNOTE well-formedness cross-checks, two-sided expectations,declared.iso21320coverage canary, foreign integrity pass) — blocking inconformance.ymlandpublish.yml; new conformance guide. - feat(agents):
docs/agent-brief.md(paste-ready coding-agent briefing + "Copy as prompt" on the landing) and the completed token budget inllms-index.json(errors.json + agent-brief now listed with byte/approx-token sizes);.github/prompts/quality-gate.prompt.md. - feat(ci):
.github/workflows/publish.yml— Release-triggered, OIDC, full gate re-run, SBOM,npm publish --provenance --access public. - feat(docs): the
tsdoc-completeverify rule (22 rules total); TSDoc for the five previously-undocumented exports. - feat(docs): the zipper-cut Z identity —
logo.svg,favicon.svg, regeneratedog-image, newsocial-preview.svg/png(1280×640). - feat(meta):
release-notes/draft/PR-v1.0.0.md— the release PR with the measured verification table and the human go-live runbook.
Changed
- docs(meta): documentation flipped to the stable-era stance — "through 1.0"/"pre-1.0" → "in 1.x" everywhere; README status is the semver freeze statement; satellites read "planned".
- docs(meta): badges harmonised on the pdfnative set (CI, CodeQL, npm, bundle size, provenance, website; Scorecard on the landing — the npm-fed ones self-heal at first publication);
persist-credentials: falseon every CI checkout. - chore(meta): the npm package now ships
CHANGELOG.md,SECURITY.mdandTHIRD-PARTY-NOTICES.md(21-file tarball, inspected); sourcemaps deliberately included;ecosystem.jsonrecords the freeze contracts.
Install
npm install zipnative@1.0.0Upgrade
No breaking changes. Drop-in replacement for v0.9.0 — and for every 0.x tag: the API at 1.0.0 is the 0.9.0 API, frozen.
Known limitations
- store+bit3, encrypted+bit3 and custom-codec+bit3 remain refused in forward reading — structurally undelimitable without the central directory.
- Zip64 streaming (
addStream> 4 GiB) remains a typed refusal — designed (roadmap ADR), implemented in a 1.x minor. - No encryption in 1.x by policy (ZipCrypto is broken); encrypted entries are detected and refused with a typed error.
- A worker playground is deliberately absent (build-tooling changes; a 1.x docs item).
Downstream integration notes
The satellites can now exist: zipnative-cli and zipnative-mcp pin zipnative ^1.0.0. Their contract inputs are all machine-readable — docs/assets/api.json (surface), docs/data/errors.json (the --json envelope's err.code source of truth), and verifyZip's report shape for a future verify --json.