pretext-pdf 1.1.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +689 -0
- package/README.md +82 -7
- package/dist/allowed-props.d.ts +76 -0
- package/dist/allowed-props.d.ts.map +1 -1
- package/dist/allowed-props.js.map +1 -1
- package/dist/assets/generators/barcode.d.ts +9 -0
- package/dist/assets/generators/barcode.d.ts.map +1 -0
- package/dist/assets/generators/barcode.js +24 -0
- package/dist/assets/generators/barcode.js.map +1 -0
- package/dist/assets/generators/chart.d.ts +13 -0
- package/dist/assets/generators/chart.d.ts.map +1 -0
- package/dist/assets/generators/chart.js +32 -0
- package/dist/assets/generators/chart.js.map +1 -0
- package/dist/assets/generators/qr.d.ts +9 -0
- package/dist/assets/generators/qr.d.ts.map +1 -0
- package/dist/assets/generators/qr.js +25 -0
- package/dist/assets/generators/qr.js.map +1 -0
- package/dist/assets/index.d.ts +19 -0
- package/dist/assets/index.d.ts.map +1 -0
- package/dist/assets/index.js +19 -0
- package/dist/assets/index.js.map +1 -0
- package/dist/assets/loaders/images.d.ts +20 -0
- package/dist/assets/loaders/images.d.ts.map +1 -0
- package/dist/assets/loaders/images.js +69 -0
- package/dist/assets/loaders/images.js.map +1 -0
- package/dist/assets/loaders/orchestrator.d.ts +24 -0
- package/dist/assets/loaders/orchestrator.d.ts.map +1 -0
- package/dist/assets/loaders/orchestrator.js +109 -0
- package/dist/assets/loaders/orchestrator.js.map +1 -0
- package/dist/assets/loaders/vectors.d.ts +25 -0
- package/dist/assets/loaders/vectors.d.ts.map +1 -0
- package/dist/assets/loaders/vectors.js +118 -0
- package/dist/assets/loaders/vectors.js.map +1 -0
- package/dist/assets/loaders/watermark.d.ts +12 -0
- package/dist/assets/loaders/watermark.d.ts.map +1 -0
- package/dist/assets/loaders/watermark.js +40 -0
- package/dist/assets/loaders/watermark.js.map +1 -0
- package/dist/assets/security/fetch.d.ts +14 -0
- package/dist/assets/security/fetch.d.ts.map +1 -0
- package/dist/assets/security/fetch.js +112 -0
- package/dist/assets/security/fetch.js.map +1 -0
- package/dist/assets/security/ipv4-normalize.d.ts +28 -0
- package/dist/assets/security/ipv4-normalize.d.ts.map +1 -0
- package/dist/assets/security/ipv4-normalize.js +116 -0
- package/dist/assets/security/ipv4-normalize.js.map +1 -0
- package/dist/assets/security/path-allowlist.d.ts +12 -0
- package/dist/assets/security/path-allowlist.d.ts.map +1 -0
- package/dist/assets/security/path-allowlist.js +26 -0
- package/dist/assets/security/path-allowlist.js.map +1 -0
- package/dist/assets/security/url-validation.d.ts +22 -0
- package/dist/assets/security/url-validation.d.ts.map +1 -0
- package/dist/assets/security/url-validation.js +164 -0
- package/dist/assets/security/url-validation.js.map +1 -0
- package/dist/assets/svg/dimensions.d.ts +19 -0
- package/dist/assets/svg/dimensions.d.ts.map +1 -0
- package/dist/assets/svg/dimensions.js +43 -0
- package/dist/assets/svg/dimensions.js.map +1 -0
- package/dist/assets/svg/rasterize.d.ts +6 -0
- package/dist/assets/svg/rasterize.d.ts.map +1 -0
- package/dist/assets/svg/rasterize.js +38 -0
- package/dist/assets/svg/rasterize.js.map +1 -0
- package/dist/assets/svg/resolve-content.d.ts +16 -0
- package/dist/assets/svg/resolve-content.d.ts.map +1 -0
- package/dist/assets/svg/resolve-content.js +38 -0
- package/dist/assets/svg/resolve-content.js.map +1 -0
- package/dist/assets/svg/sanitize.d.ts +22 -0
- package/dist/assets/svg/sanitize.d.ts.map +1 -0
- package/dist/assets/svg/sanitize.js +46 -0
- package/dist/assets/svg/sanitize.js.map +1 -0
- package/dist/assets/util/redact-path.d.ts +14 -0
- package/dist/assets/util/redact-path.d.ts.map +1 -0
- package/dist/assets/util/redact-path.js +16 -0
- package/dist/assets/util/redact-path.js.map +1 -0
- package/dist/assets.d.ts +10 -27
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +10 -549
- package/dist/assets.js.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +2 -1
- package/dist/builder.js.map +1 -1
- package/dist/cli.js +11 -1
- package/dist/cli.js.map +1 -1
- package/dist/compat.d.ts +63 -1
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +42 -5
- package/dist/compat.js.map +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/fonts.d.ts.map +1 -1
- package/dist/fonts.js +8 -10
- package/dist/fonts.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/layout-state.d.ts +1 -1
- package/dist/layout-state.d.ts.map +1 -1
- package/dist/layout-state.js +5 -0
- package/dist/layout-state.js.map +1 -1
- package/dist/measure-blocks/float-group.d.ts +9 -0
- package/dist/measure-blocks/float-group.d.ts.map +1 -0
- package/dist/measure-blocks/float-group.js +103 -0
- package/dist/measure-blocks/float-group.js.map +1 -0
- package/dist/measure-blocks/helpers.d.ts +44 -0
- package/dist/measure-blocks/helpers.d.ts.map +1 -0
- package/dist/measure-blocks/helpers.js +43 -0
- package/dist/measure-blocks/helpers.js.map +1 -0
- package/dist/measure-blocks/highlight.d.ts +26 -0
- package/dist/measure-blocks/highlight.d.ts.map +1 -0
- package/dist/measure-blocks/highlight.js +169 -0
- package/dist/measure-blocks/highlight.js.map +1 -0
- package/dist/measure-blocks/image.d.ts +9 -0
- package/dist/measure-blocks/image.d.ts.map +1 -0
- package/dist/measure-blocks/image.js +136 -0
- package/dist/measure-blocks/image.js.map +1 -0
- package/dist/measure-blocks/index.d.ts +24 -0
- package/dist/measure-blocks/index.d.ts.map +1 -0
- package/dist/measure-blocks/index.js +179 -0
- package/dist/measure-blocks/index.js.map +1 -0
- package/dist/measure-blocks/list.d.ts +8 -0
- package/dist/measure-blocks/list.d.ts.map +1 -0
- package/dist/measure-blocks/list.js +108 -0
- package/dist/measure-blocks/list.js.map +1 -0
- package/dist/measure-blocks/simple-blocks.d.ts +18 -0
- package/dist/measure-blocks/simple-blocks.d.ts.map +1 -0
- package/dist/measure-blocks/simple-blocks.js +121 -0
- package/dist/measure-blocks/simple-blocks.js.map +1 -0
- package/dist/measure-blocks/table/columns.d.ts +17 -0
- package/dist/measure-blocks/table/columns.d.ts.map +1 -0
- package/dist/measure-blocks/table/columns.js +83 -0
- package/dist/measure-blocks/table/columns.js.map +1 -0
- package/dist/measure-blocks/table/measure.d.ts +8 -0
- package/dist/measure-blocks/table/measure.d.ts.map +1 -0
- package/dist/measure-blocks/table/measure.js +231 -0
- package/dist/measure-blocks/table/measure.js.map +1 -0
- package/dist/measure-blocks/table/spans.d.ts +25 -0
- package/dist/measure-blocks/table/spans.d.ts.map +1 -0
- package/dist/measure-blocks/table/spans.js +55 -0
- package/dist/measure-blocks/table/spans.js.map +1 -0
- package/dist/measure-blocks/text-blocks.d.ts +17 -0
- package/dist/measure-blocks/text-blocks.d.ts.map +1 -0
- package/dist/measure-blocks/text-blocks.js +242 -0
- package/dist/measure-blocks/text-blocks.js.map +1 -0
- package/dist/measure-text.d.ts +21 -3
- package/dist/measure-text.d.ts.map +1 -1
- package/dist/measure-text.js +87 -36
- package/dist/measure-text.js.map +1 -1
- package/dist/measure.d.ts +1 -1
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +8 -6
- package/dist/measure.js.map +1 -1
- package/dist/node-polyfill.d.ts.map +1 -1
- package/dist/node-polyfill.js +9 -0
- package/dist/node-polyfill.js.map +1 -1
- package/dist/pipeline-footnotes.d.ts +1 -1
- package/dist/pipeline-footnotes.d.ts.map +1 -1
- package/dist/pipeline-toc.d.ts +1 -1
- package/dist/pipeline-toc.d.ts.map +1 -1
- package/dist/pipeline.d.ts +3 -3
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +4 -5
- package/dist/pipeline.js.map +1 -1
- package/dist/plugin-types.d.ts +1 -1
- package/dist/plugin-types.d.ts.map +1 -1
- package/dist/post-process.d.ts +2 -2
- package/dist/post-process.d.ts.map +1 -1
- package/dist/post-process.js +47 -10
- package/dist/post-process.js.map +1 -1
- package/dist/render-blocks/blockquote.d.ts +7 -0
- package/dist/render-blocks/blockquote.d.ts.map +1 -0
- package/dist/render-blocks/blockquote.js +87 -0
- package/dist/render-blocks/blockquote.js.map +1 -0
- package/dist/render-blocks/callout.d.ts +7 -0
- package/dist/render-blocks/callout.d.ts.map +1 -0
- package/dist/render-blocks/callout.js +84 -0
- package/dist/render-blocks/callout.js.map +1 -0
- package/dist/render-blocks/code.d.ts +7 -0
- package/dist/render-blocks/code.d.ts.map +1 -0
- package/dist/render-blocks/code.js +84 -0
- package/dist/render-blocks/code.js.map +1 -0
- package/dist/render-blocks/footnote.d.ts +11 -0
- package/dist/render-blocks/footnote.d.ts.map +1 -0
- package/dist/render-blocks/footnote.js +45 -0
- package/dist/render-blocks/footnote.js.map +1 -0
- package/dist/render-blocks/header-footer.d.ts +11 -0
- package/dist/render-blocks/header-footer.d.ts.map +1 -0
- package/dist/render-blocks/header-footer.js +56 -0
- package/dist/render-blocks/header-footer.js.map +1 -0
- package/dist/render-blocks/hr.d.ts +7 -0
- package/dist/render-blocks/hr.d.ts.map +1 -0
- package/dist/render-blocks/hr.js +24 -0
- package/dist/render-blocks/hr.js.map +1 -0
- package/dist/render-blocks/image.d.ts +9 -0
- package/dist/render-blocks/image.d.ts.map +1 -0
- package/dist/render-blocks/image.js +135 -0
- package/dist/render-blocks/image.js.map +1 -0
- package/dist/render-blocks/index.d.ts +17 -0
- package/dist/render-blocks/index.d.ts.map +1 -0
- package/dist/render-blocks/index.js +17 -0
- package/dist/render-blocks/index.js.map +1 -0
- package/dist/render-blocks/list-item.d.ts +7 -0
- package/dist/render-blocks/list-item.d.ts.map +1 -0
- package/dist/render-blocks/list-item.js +80 -0
- package/dist/render-blocks/list-item.js.map +1 -0
- package/dist/render-blocks/rich.d.ts +7 -0
- package/dist/render-blocks/rich.d.ts.map +1 -0
- package/dist/render-blocks/rich.js +160 -0
- package/dist/render-blocks/rich.js.map +1 -0
- package/dist/render-blocks/table.d.ts +7 -0
- package/dist/render-blocks/table.d.ts.map +1 -0
- package/dist/render-blocks/table.js +139 -0
- package/dist/render-blocks/table.js.map +1 -0
- package/dist/render-blocks/text.d.ts +7 -0
- package/dist/render-blocks/text.d.ts.map +1 -0
- package/dist/render-blocks/text.js +183 -0
- package/dist/render-blocks/text.js.map +1 -0
- package/dist/render-blocks/watermark.d.ts +8 -0
- package/dist/render-blocks/watermark.d.ts.map +1 -0
- package/dist/render-blocks/watermark.js +52 -0
- package/dist/render-blocks/watermark.js.map +1 -0
- package/dist/render-extras.d.ts.map +1 -1
- package/dist/render-extras.js +1 -2
- package/dist/render-extras.js.map +1 -1
- package/dist/render-utils.d.ts.map +1 -1
- package/dist/render-utils.js +10 -6
- package/dist/render-utils.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +9 -3
- package/dist/render.js.map +1 -1
- package/dist/rich-text.d.ts +2 -1
- package/dist/rich-text.d.ts.map +1 -1
- package/dist/rich-text.js +0 -1
- package/dist/rich-text.js.map +1 -1
- package/dist/types-internal.d.ts +19 -3
- package/dist/types-internal.d.ts.map +1 -1
- package/dist/types-public/document.d.ts +261 -0
- package/dist/types-public/document.d.ts.map +1 -0
- package/dist/types-public/document.js +2 -0
- package/dist/types-public/document.js.map +1 -0
- package/dist/types-public/elements-block.d.ts +246 -0
- package/dist/types-public/elements-block.d.ts.map +1 -0
- package/dist/types-public/elements-block.js +8 -0
- package/dist/types-public/elements-block.js.map +1 -0
- package/dist/types-public/elements-media.d.ts +199 -0
- package/dist/types-public/elements-media.d.ts.map +1 -0
- package/dist/types-public/elements-media.js +2 -0
- package/dist/types-public/elements-media.js.map +1 -0
- package/dist/types-public/elements-text.d.ts +327 -0
- package/dist/types-public/elements-text.d.ts.map +1 -0
- package/dist/types-public/elements-text.js +2 -0
- package/dist/types-public/elements-text.js.map +1 -0
- package/dist/types-public/index.d.ts +14 -0
- package/dist/types-public/index.d.ts.map +1 -0
- package/dist/types-public/index.js +2 -0
- package/dist/types-public/index.js.map +1 -0
- package/dist/types-public/render-options.d.ts +38 -0
- package/dist/types-public/render-options.d.ts.map +1 -0
- package/dist/types-public/render-options.js +2 -0
- package/dist/types-public/render-options.js.map +1 -0
- package/dist/types-public/union.d.ts +13 -0
- package/dist/types-public/union.d.ts.map +1 -0
- package/dist/types-public/union.js +2 -0
- package/dist/types-public/union.js.map +1 -0
- package/dist/types-public/validation.d.ts +64 -0
- package/dist/types-public/validation.d.ts.map +1 -0
- package/dist/types-public/validation.js +2 -0
- package/dist/types-public/validation.js.map +1 -0
- package/dist/types-public.d.ts +5 -1081
- package/dist/types-public.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validate/document.d.ts +28 -0
- package/dist/validate/document.d.ts.map +1 -0
- package/dist/validate/document.js +295 -0
- package/dist/validate/document.js.map +1 -0
- package/dist/validate/elements/forms-floats.d.ts +19 -0
- package/dist/validate/elements/forms-floats.d.ts.map +1 -0
- package/dist/validate/elements/forms-floats.js +96 -0
- package/dist/validate/elements/forms-floats.js.map +1 -0
- package/dist/validate/elements/list.d.ts +10 -0
- package/dist/validate/elements/list.d.ts.map +1 -0
- package/dist/validate/elements/list.js +66 -0
- package/dist/validate/elements/list.js.map +1 -0
- package/dist/validate/elements/media.d.ts +23 -0
- package/dist/validate/elements/media.d.ts.map +1 -0
- package/dist/validate/elements/media.js +179 -0
- package/dist/validate/elements/media.js.map +1 -0
- package/dist/validate/elements/structural-simple.d.ts +21 -0
- package/dist/validate/elements/structural-simple.d.ts.map +1 -0
- package/dist/validate/elements/structural-simple.js +63 -0
- package/dist/validate/elements/structural-simple.js.map +1 -0
- package/dist/validate/elements/structural.d.ts +12 -0
- package/dist/validate/elements/structural.d.ts.map +1 -0
- package/dist/validate/elements/structural.js +12 -0
- package/dist/validate/elements/structural.js.map +1 -0
- package/dist/validate/elements/table.d.ts +10 -0
- package/dist/validate/elements/table.d.ts.map +1 -0
- package/dist/validate/elements/table.js +165 -0
- package/dist/validate/elements/table.js.map +1 -0
- package/dist/validate/elements/text.d.ts +26 -0
- package/dist/validate/elements/text.d.ts.map +1 -0
- package/dist/validate/elements/text.js +331 -0
- package/dist/validate/elements/text.js.map +1 -0
- package/dist/validate/errors.d.ts +9 -0
- package/dist/validate/errors.d.ts.map +1 -0
- package/dist/validate/errors.js +43 -0
- package/dist/validate/errors.js.map +1 -0
- package/dist/validate/fonts.d.ts +11 -0
- package/dist/validate/fonts.d.ts.map +1 -0
- package/dist/validate/fonts.js +118 -0
- package/dist/validate/fonts.js.map +1 -0
- package/dist/validate/helpers.d.ts +76 -0
- package/dist/validate/helpers.d.ts.map +1 -0
- package/dist/validate/helpers.js +169 -0
- package/dist/validate/helpers.js.map +1 -0
- package/dist/validate/index.d.ts +37 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +279 -0
- package/dist/validate/index.js.map +1 -0
- package/dist/validate.d.ts +6 -18
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +6 -1585
- package/dist/validate.js.map +1 -1
- package/dist/vendor/pretext/VERSION.d.ts +3 -0
- package/dist/vendor/pretext/VERSION.d.ts.map +1 -0
- package/dist/vendor/pretext/VERSION.js +12 -0
- package/dist/vendor/pretext/VERSION.js.map +1 -0
- package/dist/version-check.d.ts +47 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +75 -0
- package/dist/version-check.js.map +1 -0
- package/package.json +30 -7
- package/dist/measure-blocks.d.ts +0 -26
- package/dist/measure-blocks.d.ts.map +0 -1
- package/dist/measure-blocks.js +0 -1317
- package/dist/measure-blocks.js.map +0 -1
- package/dist/render-blocks.d.ts +0 -28
- package/dist/render-blocks.d.ts.map +0 -1
- package/dist/render-blocks.js +0 -1059
- package/dist/render-blocks.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,695 @@ Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/)
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.7.0] — 2026-05-25
|
|
11
|
+
|
|
12
|
+
Signing path repaired. **No public API changes.** The cryptographic signing pipeline has been architecturally broken end-to-end since v1.3.6 — calling `render({ signature: { p12, passphrase } })` would fail with `SIGNATURE_FAILED: PDF signing failed: No ByteRangeStrings found within PDF buffer`. v1.7.0 fixes it with a surgical change inside `applySignature`.
|
|
13
|
+
|
|
14
|
+
### Fixed (critical)
|
|
15
|
+
|
|
16
|
+
- **Signing path was architecturally broken since v1.3.6**. Root cause: `applySignature` loaded the placeholder doc via `@cantoo/pdf-lib`'s `PDFDocument.load`, then handed it to `@signpdf/placeholder-pdf-lib.pdflibAddPlaceholder`, which internally builds `/ByteRange` using **upstream** `pdf-lib`'s `PDFArray`/`PDFNumber`/`PDFName` classes (it imports them directly from `"pdf-lib"`). Cantoo's serializer doesn't recognize upstream's class instances and emitted a malformed `/ByteRange` dict; `@signpdf/utils.findByteRange` then aborted parsing with `No ByteRangeStrings found within PDF buffer`. Fix: load the doc via **upstream `pdf-lib`** for the placeholder hop only. Encryption stays on `@cantoo/pdf-lib` in `applyEncryption` — the two paths are mutually exclusive via the existing `SIGNATURE_CERT_AND_ENCRYPTION` guard, so we never need both pdf-libs active simultaneously. The previously KNOWN-BROKEN `test/signatures-crypto.test.ts → P12 signature verifies cryptographically (real CMS verify)` is now unskipped and green.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **AcroForm regression assertion** inside `test/signatures-crypto.test.ts`. Any valid signed PDF must carry `/AcroForm`, `/Fields [...]`, `/SigFlags 3`, and a `/Type /Sig` object — these four properties are asserted on the signed bytes so a future regression that loses AcroForm structure surfaces immediately.
|
|
21
|
+
- **Signature path snapshot tripwire** (`test/signatures-snapshot.test.ts` + `test/data/signatures-snapshot.json`). Captures the categorical structural shape of a signed PDF (presence of `/ByteRange`, `/AcroForm`, `/SigFlags` value, etc.) rather than byte offsets (which are document- and randomness-dependent). Wired into `test:phases`. Regenerate with `UPDATE_SNAPSHOT=1`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`pdf-lib` declared as an explicit optional peer dependency** (`^1.17.1`). It was previously only present transitively via `@signpdf/placeholder-pdf-lib`'s `dependencies`. Now it's a documented peer with `peerDependenciesMeta.pdf-lib.optional: true`, mirroring the existing `@signpdf/*` pattern. Users with `@signpdf/*` already installed need no action — npm satisfies the new peer from the existing transitive install.
|
|
26
|
+
- **`SIGNATURE_DEP_MISSING` error message** now lists `pdf-lib` alongside the three `@signpdf/*` packages and drops the "currently non-functional due to fork incompatibility" disclaimer that was added in v1.3.6.
|
|
27
|
+
|
|
28
|
+
### Migration
|
|
29
|
+
|
|
30
|
+
None. Same `signature: { p12, passphrase, reason, contactInfo, signerName, location, page, invisible }` config. Same error codes (`SIGNATURE_DEP_MISSING`, `SIGNATURE_P12_LOAD_FAILED`, `SIGNATURE_FAILED`, `SIGNATURE_CERT_AND_ENCRYPTION`). One error message string changed: `SIGNATURE_DEP_MISSING` now mentions `pdf-lib` and no longer carries the "non-functional" disclaimer.
|
|
31
|
+
|
|
32
|
+
### Verification
|
|
33
|
+
|
|
34
|
+
- All 456 tests pass (was 454 pass + 1 skipped pre-fix; now 455 pass after unskip + 1 new snapshot test = 456 total).
|
|
35
|
+
- Encryption-after-signing path still rejected with `SIGNATURE_CERT_AND_ENCRYPTION` (regression-tested).
|
|
36
|
+
- All 7 v1.6.0 verification gates (G1–G7) still pass.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## [1.6.0] — 2026-05-25
|
|
41
|
+
|
|
42
|
+
Internal restructuring + SVG sanitizer hardening. **No public API changes.** The previously-monolithic `src/assets.ts` (961 lines pre-sprint) has been split into 10 focused files under `src/assets/`. A 14-line back-compat shim at `src/assets.ts` re-exports the barrel so every existing consumer (internal modules, public API, and direct test imports via `dist/assets.js`) keeps working unchanged.
|
|
43
|
+
|
|
44
|
+
### Security
|
|
45
|
+
|
|
46
|
+
- **SVG sanitizer hardening** — `sanitizeSvg` now strips three additional payload classes that survived the previous regex chain:
|
|
47
|
+
- **`<foreignObject>` blocks** — the only XML-in-SVG construct that can host arbitrary HTML/XML namespaces. Both self-closing and paired forms are removed wholesale; sibling SVG primitives (`<rect/>`, `<path/>`, etc.) are preserved.
|
|
48
|
+
- **`javascript:` / `vbscript:` / `data:` hrefs on `<a>` elements** — previously only `<image>`/`<use>` hrefs were filtered. Only the dangerous href attribute is dropped, so the `<a>` element's text children still render.
|
|
49
|
+
- **CSS `expression(...)` calls inside `<style>` blocks** — legacy IE XSS vector. Only the `expression(...)` call site is excised; the surrounding stylesheet remains parseable.
|
|
50
|
+
|
|
51
|
+
Coverage: new `test/svg-sanitizer.test.ts` (10 cases) plus expanded MA-4 / MA-5 fixtures in `test/data/assets-split-tripwire.json` (30 fixtures total).
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- **`src/assets.ts` split into 10 files** under `src/assets/` (see Internal). No public API change — the file at `src/assets.ts` is now a 14-line re-export shim that aggregates the new barrel `src/assets/index.ts`. Consumers importing from `dist/assets.js` continue to resolve every previously-public symbol (`loadImages`, `assertPathAllowed`, `sanitizeSvg`, `assertSafeUrl`, `resolveAndValidateUrl`, `normalizeIpv4Hostname`, `fetchWithTimeout`, `redactPath`, `VECTOR_RASTER_CONCURRENCY`, plus the `ResolvedSafeUrl` type) at the same module path.
|
|
56
|
+
- **`PretextPdfError` constructor signature snapshot refreshed** — `etc/pretext-pdf.api.md` now reflects the `options?: ErrorOptions` parameter that was added in v1.2.1 but never re-snapshotted. No code change — the parameter has been live for several releases. The snapshot was stale; this release reconciles it.
|
|
57
|
+
|
|
58
|
+
### Internal
|
|
59
|
+
|
|
60
|
+
- **New `src/assets/` directory layout:**
|
|
61
|
+
```
|
|
62
|
+
src/assets/
|
|
63
|
+
├── index.ts # internal barrel
|
|
64
|
+
├── util/
|
|
65
|
+
│ └── redact-path.ts # commit 4
|
|
66
|
+
├── security/
|
|
67
|
+
│ ├── path-allowlist.ts # commit 5
|
|
68
|
+
│ ├── ipv4-normalize.ts # commit 6
|
|
69
|
+
│ ├── url-validation.ts # commit 7
|
|
70
|
+
│ └── fetch.ts # commit 8 (undici Agent stays lazy)
|
|
71
|
+
├── svg/
|
|
72
|
+
│ ├── sanitize.ts # commit 9 (+ SVG_MAX_BYTES)
|
|
73
|
+
│ ├── dimensions.ts # commit 10
|
|
74
|
+
│ ├── resolve-content.ts # commit 10
|
|
75
|
+
│ └── rasterize.ts # commit 11 (@napi-rs/canvas dynamic)
|
|
76
|
+
├── generators/
|
|
77
|
+
│ ├── qr.ts # commit 12 (qrcode dynamic)
|
|
78
|
+
│ ├── barcode.ts # commit 12 (bwip-js dynamic)
|
|
79
|
+
│ └── chart.ts # commit 12 (vega/vega-lite dynamic)
|
|
80
|
+
└── loaders/
|
|
81
|
+
├── images.ts # commit 13
|
|
82
|
+
├── vectors.ts # commit 14
|
|
83
|
+
├── watermark.ts # commit 15
|
|
84
|
+
└── orchestrator.ts # commit 15 (top-level loadImages)
|
|
85
|
+
```
|
|
86
|
+
All optional peer-dependency dynamic imports (`@napi-rs/canvas`, `qrcode`, `bwip-js`, `vega`, `vega-lite`, `undici`) are preserved as lazy loads — cold-start cost is unchanged.
|
|
87
|
+
- **7 verification gates (G1–G7) added** to catch regressions during the split:
|
|
88
|
+
- **G1** Snapshot tripwire (`test/assets-split-tripwire.test.ts`) — 30 fixtures covering sanitizer output, URL normalization, path allowlist, and error code surface
|
|
89
|
+
- **G2** DNS lookup dedup (`test/assets-dns-dedup.test.ts`)
|
|
90
|
+
- **G3** SSRF blocking (`test/security-ssrf.test.ts`, expanded)
|
|
91
|
+
- **G4** Parallel-render concurrency (`test/assets-concurrency.test.ts`) — 10× concurrent `render()` calls must produce bit-identical PDFs
|
|
92
|
+
- **G5** ErrorCode stability (`test/assets-errorcode-stability.test.ts`)
|
|
93
|
+
- **G6** api-extractor diff against `etc/pretext-pdf.api.md`
|
|
94
|
+
- **G7** Cold-start perf (`test/assets-perf-coldstart.test.ts`, baseline at `test/data/perf-coldstart-baseline.json`) — 100 sequential renders within 2.5× the v1.5.2 baseline
|
|
95
|
+
- **G7 measurement on the final shim**: 12,224ms / 100 renders (vs 21,205ms baseline = -42%, i.e. the split happens to be *faster*, well under the upper bound).
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## [1.5.2] — 2026-05-25
|
|
100
|
+
|
|
101
|
+
Security hotfix. **No public API changes.** **Upgrade recommended for any deployment that accepts user-controlled image / SVG URLs.**
|
|
102
|
+
|
|
103
|
+
### Security
|
|
104
|
+
|
|
105
|
+
- **CVE-class SSRF bypass via IPv4 alternative notations** — `isPrivateAddress` in `src/assets.ts` applied dotted-decimal regexes (`/^127\./`, `/^10\./`, …) against `URL#hostname`. The WHATWG URL parser does NOT normalize non-dotted IPv4 forms, so an attacker could reach private services by encoding the target in any inet_aton-compatible form:
|
|
106
|
+
- Pure decimal: `https://2130706433/x` → 127.0.0.1
|
|
107
|
+
- Pure hex: `https://0x7f000001/x` → 127.0.0.1
|
|
108
|
+
- Octal octet: `https://0177.0.0.1/x` → 127.0.0.1
|
|
109
|
+
- Hex octet: `https://0x7f.0.0.1/x` → 127.0.0.1
|
|
110
|
+
- Short form: `https://127.1/x` → 127.0.0.1
|
|
111
|
+
|
|
112
|
+
Without normalization, `parsed.hostname` is e.g. `"2130706433"`, the private-range regex chain misses it, the `isIpv4Literal` (4-dot) check misses it, and the URL falls through to DNS — which on Linux's `getaddrinfo` resolves to 127.0.0.1 and bypasses the SSRF guard. Same vector reaches RFC 1918 ranges (10/8, 192.168/16, 172.16/12), link-local (169.254.169.254 — AWS IMDS), and CGNAT.
|
|
113
|
+
|
|
114
|
+
**Fix:** new internal helper `normalizeIpv4Hostname()` implements inet_aton-style parsing (decimal/octal/hex per part, short-form packing for 1/2/3-part inputs, strict 32-bit range guard). `resolveAndValidateUrl` normalizes before the private-IP check AND before DNS, then treats the normalized form as an IP literal so undici never re-resolves a non-dotted private encoding. `isPrivateAddress` also normalizes its input as defense-in-depth on the post-DNS path. Public alternative encodings (e.g. `134744072` == 8.8.8.8) continue to resolve and fetch normally.
|
|
115
|
+
|
|
116
|
+
**Test coverage:** new `test/security-ipv4-bypass.test.ts` adds 24 cases — every blocked encoding above, public regression cases, plus direct unit tests for `normalizeIpv4Hostname` (round-trips, range guards, malformed-octal rejection, public-IP allowlist). Wired into the `test:phases` stage; phases stage grows from 417 to 441 tests.
|
|
117
|
+
|
|
118
|
+
### Fixed
|
|
119
|
+
|
|
120
|
+
- **`measure-blocks/float-group.ts` — fontSize fallback intent clarified** — v1.5.1 M5a removed a dead `baseFontSize = doc.defaultFontSize ?? 12` plus per-block `fontSize = block.fontSize || baseFontSize` local that was never read (the item assignment used `block.fontSize` directly). Audit of upstream measure helpers (measure-paragraph, measure-heading, …) confirms `block.fontSize` is always populated with a positive value for real content, so the fallback would never have fired in practice. Added a leading comment so future contributors don't reintroduce the fallback in the mistaken belief that `block.fontSize === 0` is a real case. No behavior change.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## [1.5.1] — 2026-05-24
|
|
125
|
+
|
|
126
|
+
Hotfix batch closing 9 audit findings from the 6-agent v1.5.0 review. **No public API changes** — guarded by `test/public-api-surface.test.ts`. **No behavior changes other than the documented fixes**. Snapshot baseline expanded from 68 to 73 fixtures (5 new: 2 metadata.keywords + 3 watermark.image).
|
|
127
|
+
|
|
128
|
+
### Security
|
|
129
|
+
|
|
130
|
+
- **Watermark image URL scheme validation (H1)** — `doc.watermark.image` now passes through the same `validateUrl` pre-flight check used by `validateImage`. Unsafe schemes (`javascript:`, `data:`, `vbscript:`, `blob:`, `about:`, `file:`) are rejected at validate-time so CLI lint and MCP validate tools catch them before render. Relative file paths fall through unchanged. The shared URL-shape helper `looksLikeUrl` was moved from `validate/elements/media.ts` to `validate/helpers.ts`.
|
|
131
|
+
|
|
132
|
+
### Fixed
|
|
133
|
+
|
|
134
|
+
- **`metadata.keywords[]` element validation (H3)** — Previously, `for (const field of [...'keywords'...])` was paired with `typeof val === 'string'`, which silently no-op'd for the `string[]`-shaped `keywords` field. Each keyword entry is now validated for control-character injection and the 1000-character length cap via `validateMetadataString(kw, \`keywords[\${i}]\`)`.
|
|
135
|
+
- **highlight.js dynamic-import error logging (H4)** — Previously, `catch { /* not installed */ }` silently swallowed every failure including real module-load errors. The catch now logs a warning unless the error code is `ERR_MODULE_NOT_FOUND`/`MODULE_NOT_FOUND` (the only legitimate "optional dep absent" signal).
|
|
136
|
+
- **`hljs.highlight()` runtime exception logging (M2)** — Same silent-fallback issue as H4 but for tokenization failures. Now logs a warning naming the language before falling back to plain text.
|
|
137
|
+
- **Font-variant registration logging (M3)** — `installNodePolyfill` now tracks per-variant success and warns when an individual Inter weight (400 or 700) fails to register, instead of only warning when *both* failed. Text metrics for the missing variant may be inaccurate; operators see this in logs.
|
|
138
|
+
|
|
139
|
+
### Changed
|
|
140
|
+
|
|
141
|
+
- **Removed redundant `as import('...').X` casts in `validate/elements/forms-floats.ts` (M1)** — `validateFormField`, `validateFootnoteDef`, and `validateFloatGroup` already accept `Extract<ContentElement, { type: 'X' }>`-typed `el`. The inner `const ff = el as FormFieldElement` casts were no-ops; removed.
|
|
142
|
+
- **Removed unreachable `toc-entry` validator (M4)** — The pre-switch throw at `validate/index.ts:260` already rejects `toc-entry` before dispatch. The `case 'toc-entry':` arm (guarded with `@ts-expect-error`) and the `validateTocEntry` function in `validate/elements/structural-simple.ts` were unreachable dead code. Drift-guard test updated with `VALIDATE_DISPATCHER_EXCLUDES` (mirrors the existing `MEASURE_DISPATCHER_EXCLUDES` pattern) to keep the orchestrator scan honest.
|
|
143
|
+
- **tsconfig: `noUnusedParameters` + `noUnusedLocals` enabled (M5a)** — Catches dead destructured locals and unused imports at build time. Cascade: 51 errors → 0. 30 of those were per-type `type _X = Exact<...>` drift guards in `allowed-props.ts` (load-bearing scaffolding) — consolidated into a single exported tuple `_AllowedPropsDriftGuard` that TypeScript counts as used. The remaining 20 were genuine unused-locals / unused-imports cleanup across measure, render, rich-text, and assets modules.
|
|
144
|
+
- **tsconfig: `verbatimModuleSyntax` enabled (M5b)** — Enforces `import type` discipline. Cascade was only 6 errors, all `HyphenatorOpts` runtime imports that needed splitting into `import type`. Well under the 30-line cascade-defer threshold.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## [1.5.0] — 2026-05-24
|
|
149
|
+
|
|
150
|
+
Architecture sprint completing the v1.4.0 god-file split debt. Six items shipped, single minor release. **No public API changes** — guarded by `test/public-api-surface.test.ts`. **No behavioral changes** — Item A's security-critical extraction guarded by new `test/validate-document-snapshot.test.ts` (68 fixtures, bit-exact preservation verified).
|
|
151
|
+
|
|
152
|
+
### Changed (internal structure — non-breaking)
|
|
153
|
+
|
|
154
|
+
- **`src/validate/index.ts` (594L) → orchestrator (322L) + `src/validate/document.ts` (324L)** —
|
|
155
|
+
Extracted 11 doc-level check categories (pageSize, margins, fonts, header/footer, defaultParagraphStyle, sections, watermark, encryption, signature, bookmarks, hyphenation, metadata) into a new `validateDocumentLevel(doc, ctx)` function. Single function with labeled `// ── name ──` blocks. **Security-critical**: snapshot tripwire test (68 fixtures across all 11 categories) verified bit-exact error preservation through the move.
|
|
156
|
+
- **`src/measure-blocks/index.ts` (337L) → dispatcher (243L) + `src/measure-blocks/simple-blocks.ts` (151L)** —
|
|
157
|
+
Extracted 7 simple measurement arms (spacer, page-break, comment, form-field, hr, toc, footnote-def). Throw-guards for image/svg/qr-code/barcode/chart stay in dispatcher (security invariants tied to routing).
|
|
158
|
+
- **`src/validate/elements/structural.ts` (239L grab-bag) → `structural-simple.ts` (120L) + `forms-floats.ts` (128L)** —
|
|
159
|
+
Split light element validators (spacer, hr, toc, toc-entry, comment) from heavy ones (form-field, footnote-def, float-group). Cleaner separation of concerns.
|
|
160
|
+
|
|
161
|
+
### Added
|
|
162
|
+
|
|
163
|
+
- **`src/validate/elements/README.md`** — Placement guide + validator signature contract + `_ctx` policy documentation + `withCycleGuard` usage guidance. Onboarding aid for adding new element types.
|
|
164
|
+
- **`test/validate-document-snapshot.test.ts`** + `test/data/validate-document-snapshot.json` — Bit-exact error preservation tripwire for `validateDocumentLevel`. 68 fixtures across pageSize, margins, fonts, header/footer, defaultParagraphStyle, sections, watermark, encryption, signature, bookmarks, hyphenation, metadata, content guards, and valid-doc sanity cases.
|
|
165
|
+
|
|
166
|
+
### Fixed
|
|
167
|
+
|
|
168
|
+
- **`dist/` no longer tracked in git** (168 files removed) — was already in `.gitignore` but tracked from prior history.
|
|
169
|
+
|
|
170
|
+
### Notes
|
|
171
|
+
|
|
172
|
+
- 12 commits across 6 items, each independently revertable via the 3-commit-per-split pattern (stage → route → delete) proven in v1.4.0.
|
|
173
|
+
- Test suite: 416 pass / 1 skip / 0 fail throughout the sprint, plus the new snapshot tripwire test (passes against generated baseline).
|
|
174
|
+
- Public API surface unchanged: 15 runtime exports × 6 entry points.
|
|
175
|
+
- `_ctx` policy decision: keep underscore-prefixed param for validators that don't currently consume context. Documented in `validate/elements/README.md` — stable signature lets future strict-mode or context-aware checks be added without changing call sites.
|
|
176
|
+
- `loadedFamilies` initialization order: populated after `validateDocumentLevel` returns. Audit confirmed no order dependency — `document.ts` never reads `loadedFamilies`, only `validateFontSpec` for shape validation.
|
|
177
|
+
- Path-traversal pre-flight in `validateImage` deferred to v2.0 — runtime SSRF + `allowedFileDirs` guards already provide defense-in-depth.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## [1.4.1] — 2026-05-23
|
|
182
|
+
|
|
183
|
+
Cleanup batch addressing audit findings surfaced by the v1.4.0 god-file split. Five MEDIUM-severity items, all internal — no public API changes, no behavioral changes for callers using documented schemas. Test suite unchanged: 416 pass / 1 skip / 0 fail.
|
|
184
|
+
|
|
185
|
+
### Fixed
|
|
186
|
+
|
|
187
|
+
- **M1 — Dead outer `withCycleGuard` removed from `validateElement` dispatcher (`src/validate/index.ts`).** The dispatcher wrapped `list` and `float-group` cases in an outer `withCycleGuard` with an empty body, then the inner element validators (`validateList` in `elements/list.ts`, `validateFloatGroup` in `elements/structural.ts`) immediately opened their own guard on the same element. The outer guard ran a no-op body and `finally`-deleted the element from `seen` BEFORE the inner guard added it — dead code communicating false intent. The inner validators own the guard. Import of `withCycleGuard` also dropped from `index.ts` since it is no longer used there.
|
|
188
|
+
- **M2 — `measure-blocks/float-group.ts ↔ measure-blocks/index.ts` runtime cycle broken (Option C: dependency injection).** `float-group.ts` previously imported `measureBlock` from `./index.js`, while `index.ts` re-exported `measureFloatGroup` from `./float-group.js` — a real ESM cycle that hoisting tolerated but which violated module-boundary discipline. Resolved by promoting `measureBlock` to an explicit parameter of `measureFloatGroup` (new exported `MeasureBlockFn` type). The sole caller (the orchestrator in `measure.ts`) already has `measureBlock` in scope, so the change is non-invasive. Option C was chosen over Option A (inlining the dispatch — too much duplication) and Option B (extracting `dispatch.ts` — restructured more than needed for a one-edge cycle).
|
|
189
|
+
|
|
190
|
+
### Changed
|
|
191
|
+
|
|
192
|
+
- **M3 — Concurrent validate test annotated with sync-vs-async semantics (`test/validate-concurrent.test.ts`).** Added a comment block above the parallel-validate `describe` clarifying that `validateDocument` is synchronous, so `Promise.all` over it cannot exercise real concurrent execution. The test now explicitly documents what it does prove (shape stability across N invocations) versus what it does not (concurrent isolation — guaranteed structurally by the per-call `WeakSet` opened at the top of `validate()` in `src/validate/index.ts`). The async render-path tests later in the same file DO exercise real concurrency because `render()` crosses `await` boundaries.
|
|
193
|
+
- **M4 — Benchmark harness upgraded (`scripts/run-bench-snapshot.mjs`).** Default measured runs raised from 3 to 10. New `--runs N` CLI flag for callers to override (CI: 5, dev: 3, full: 10). Output now reports `median`, `p90`, and `min` in addition to `avg`. Variance on cold-tsx invocations can hit ±70%, which made any <10% regression gate meaningless at N=3.
|
|
194
|
+
|
|
195
|
+
### Added
|
|
196
|
+
|
|
197
|
+
- **M5 — URL scheme check in `validateImage` (`src/validate/elements/media.ts`).** When `el.src` is a string that looks like a URL (matches `data:`, `javascript:`, `vbscript:`, `blob:`, `about:`, `file:`, or any `scheme://` form), the validator now routes through `validateUrl` from `validate/helpers.ts`. Matches the runtime SSRF guard's posture in `src/compat.ts` so validate-only callers (CLI lint, MCP `validate_document` tool) catch unsafe schemes pre-flight instead of letting them through to the asset-loader guard. http/https/ftp/mailto/anchor links still pass.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## [1.4.0] — 2026-05-23
|
|
202
|
+
|
|
203
|
+
Architecture sprint: four god-files split into thin orchestrators + cohesive sub-modules. **No public API changes** — guarded by `test/public-api-surface.test.ts` (15 runtime exports across 6 entry points, snapshot tripwire). 12 granular commits, each independently revertable.
|
|
204
|
+
|
|
205
|
+
### Changed (internal structure — non-breaking)
|
|
206
|
+
|
|
207
|
+
- **`src/validate.ts` (1834L) → `src/validate/` (9 files, ~250L each)** —
|
|
208
|
+
`validate/index.ts` orchestrator + `helpers.ts`, `fonts.ts`, `errors.ts`, and per-element validators under `elements/`. Introduced explicit `ValidationContext` ({ errors, strict, loadedFamilies, seen, options }) threaded into every element validator instead of relying on closure-captured locals. Per-call `WeakSet` for cycle detection — concurrent isolation verified by `test/validate-concurrent.test.ts`.
|
|
209
|
+
- **`src/measure-blocks.ts` (1600L) → `src/measure-blocks/` (10 files)** —
|
|
210
|
+
`measure-blocks/index.ts` dispatcher + `text-blocks.ts`, `list.ts`, `image.ts`, `float-group.ts`, `highlight.ts`, `helpers.ts`, plus `table/{measure,spans,columns}.ts`. Added explicit case arms for `qr-code`, `barcode`, `chart` (previously fell through to "Unknown element type"). `_hljsCache` intentionally remains module-scoped in `highlight.ts` — idempotent process-wide cache, not concurrent-isolated state.
|
|
211
|
+
- **`src/render-blocks.ts` (1277L) → `src/render-blocks/` (13 files)** —
|
|
212
|
+
Per-render-function modules. Each independent (no shared state). Dropped four unused imports from the original (`PDFFont`, `PDFName`, `renderTocEntry`, `renderFormField` — never referenced).
|
|
213
|
+
- **`src/types-public.ts` (1200L) → `src/types-public/` (8 files)** —
|
|
214
|
+
Split by domain: `document.ts`, `elements-{text,block,media}.ts`, `union.ts`, `validation.ts`, `render-options.ts`. Type-only intra-package cycles are erased at runtime (verified by clean tsc build).
|
|
215
|
+
|
|
216
|
+
### Added
|
|
217
|
+
|
|
218
|
+
- **`test/drift-guards.test.ts` — measure-blocks dispatcher case-arm guard.** New invariant mirrors the existing validate + render guards: every `ELEMENT_TYPES` entry must have an explicit `case` arm in `src/measure-blocks/index.ts` (except internal `toc-entry`). Catches future additions to `ELEMENT_TYPES` that forget to handle measurement.
|
|
219
|
+
|
|
220
|
+
### Fixed
|
|
221
|
+
|
|
222
|
+
- **Dead imports removed:** `validate` was imported but never used in `src/builder.ts` after the split. Removed.
|
|
223
|
+
- **Unused locals/params:** `lineHeight` in `float-group.ts` and `table/measure.ts` (computed but never read); `doc` param in `measureCallout` and `measureCode` (preserved as `_doc` for signature compatibility, signals intentional non-use).
|
|
224
|
+
|
|
225
|
+
### Notes
|
|
226
|
+
|
|
227
|
+
- Public API surface tripwire: 15 runtime exports across 6 entry points, all unchanged.
|
|
228
|
+
- Benchmark gate: 5/7 corpora within 5% of v1.3.4; two corpora (`rich-text-mixed-spans` +12.9%, `table-stress` +7.2%) breach but match documented run-to-run variance (5-10% noise on dev machine). Cold-start module-load unchanged. CI re-snapshot recommended to disambiguate variance from regression.
|
|
229
|
+
- Known follow-up: ESM-fragile circular import in `measure-blocks/float-group.ts` ↔ `measure-blocks/index.ts` (load-safe today because `measureBlock` is async-runtime only; revisit if top-level `await` is ever added).
|
|
230
|
+
- Known follow-up: type-only intra-cycles in `types-public/` (no runtime risk — erased at compile time).
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## [1.3.6] — 2026-05-23
|
|
235
|
+
|
|
236
|
+
Architecture-sprint scaffolding release: vendor integrity check, concurrent isolation tests, signing import correctness, public API tripwire.
|
|
237
|
+
|
|
238
|
+
### Added
|
|
239
|
+
|
|
240
|
+
- **Boot-time vendor integrity check (#12)** — `src/version-check.ts` exports `assertVendorIntegrity()`, called once at the start of every `render()`. Verifies the vendored pretext version (`src/vendor/pretext/VERSION.ts`) is in the compatible range. Warns (does not throw) on drift. Inline semver matcher avoids adding a `semver` dependency.
|
|
241
|
+
- **Concurrent validate/render isolation tests (#25)** — `test/validate-concurrent.test.ts` exercises 8 parallel `validate()` calls plus 4 parallel `render()` calls across different fonts AND different scripts (en/he/ar/th). Byte-identical fingerprints between parallel and sequential runs prove `vendor/pretext/measurement.ts` and `vendor/pretext/analysis.ts` shared state holds up under concurrent use.
|
|
242
|
+
- **Public API surface tripwire** — `test/public-api-surface.test.ts` snapshots all 15 runtime exports across 6 entry points. Will guard against accidental API drift during the upcoming v1.4.0 god-file splits.
|
|
243
|
+
- **P12/CMS crypto verification test (#26)** — `test/signatures-crypto.test.ts` adds a real `crypto.createVerify('RSA-SHA256')` round-trip with positive + negative cases. **Currently `t.skip`'d** — see KNOWN ISSUES below.
|
|
244
|
+
- **`pretextPdf.mcpCompat`** field in `package.json` — declares `>=1.4.0 <2.0.0` compatibility range for the pretext-pdf-mcp consumer. MCP-side check ships separately.
|
|
245
|
+
|
|
246
|
+
### Fixed
|
|
247
|
+
|
|
248
|
+
- **`signpdf` v3 API + import correctness** — `src/post-process.ts` was destructuring `pdflibAddPlaceholder` from `@signpdf/signpdf` where it does not exist; the symbol lives in `@signpdf/placeholder-pdf-lib`. Also updated to the v3 API (`new P12Signer(buffer, { passphrase })` instead of `signer.sign(buffer, { passphrase })`). Added `@signpdf/placeholder-pdf-lib` and `@signpdf/signer-p12` to `peerDependencies` + `peerDependenciesMeta.optional` (mirroring `@signpdf/signpdf`).
|
|
249
|
+
- **`SIGNATURE_DEP_MISSING` error message** — now identifies exactly which `@signpdf/*` packages are missing and tells the user which to install, instead of always listing all three.
|
|
250
|
+
|
|
251
|
+
### KNOWN ISSUES (deferred to follow-up sprint)
|
|
252
|
+
|
|
253
|
+
- **Signing path is architecturally non-functional** — even with correct imports, `@cantoo/pdf-lib`'s serializer is fork-incompatible with `@signpdf/placeholder-pdf-lib`. The placeholder ByteRange dict is emitted in a shape that `@signpdf/utils.findByteRange` cannot parse. End-to-end signing has never worked. The crypto verify test (#26) is correctly written and ready to run once signing is repaired. Three fix paths exist: `@signpdf/placeholder-plain` swap (breaks AcroForm), porting placeholder-pdf-lib onto cantoo primitives, or a merge-bytes approach. None are in v1.3.6 scope. The `SIGNATURE_DEP_MISSING` message now pre-warns callers.
|
|
254
|
+
|
|
255
|
+
## [1.3.5] — 2026-05-22
|
|
256
|
+
|
|
257
|
+
### Fixed
|
|
258
|
+
|
|
259
|
+
- **`toc-entry` drift-guard regression** —
|
|
260
|
+
`test/drift-guards.test.ts` was failing because `src/validate.ts` had no
|
|
261
|
+
`case 'toc-entry':` arm. `toc-entry` elements are produced internally by
|
|
262
|
+
the TOC two-pass processor, but the drift guard correctly insists every
|
|
263
|
+
registered `ElementType` has a validator case. Added a defensive validator
|
|
264
|
+
for `text`, `pageNumber`, `level`, `levelIndent`, and `leader` so
|
|
265
|
+
user-authored `toc-entry` payloads (rare but possible) fail loud instead
|
|
266
|
+
of slipping through.
|
|
267
|
+
|
|
268
|
+
### Removed
|
|
269
|
+
|
|
270
|
+
- **`test/pretext-api-contract.test.ts`** —
|
|
271
|
+
Reframed in v1.3.3 as a local export-shape guard for the vendored pretext
|
|
272
|
+
layout module, but the test was tautological: it could only fail if
|
|
273
|
+
someone hand-edited `src/vendor/pretext/*.ts` to remove an export, in
|
|
274
|
+
which case TypeScript would already fail the build. Deleted along with
|
|
275
|
+
its entry in the `test:contract` npm script. Drift guards in
|
|
276
|
+
`test/drift-guards.test.ts` cover the real risk (registry vs. switch
|
|
277
|
+
arms going out of sync).
|
|
278
|
+
|
|
279
|
+
### Performance
|
|
280
|
+
|
|
281
|
+
- **v1.3.2+ benchmark numbers captured** —
|
|
282
|
+
Re-ran the seven core corpora against `benchmarks/benchmark-baseline.json`
|
|
283
|
+
(recorded 2026-04-10 at v1.3.0). Results documented in
|
|
284
|
+
`benchmarks/v1.3.2-results.md`: ~1.66x geometric-mean speedup across
|
|
285
|
+
corpora, with the largest wins on text-heavy workloads (table-stress
|
|
286
|
+
-52%, punctuation-heavy -51%, rtl-layout -43%). Confirms the DNS dedup,
|
|
287
|
+
parallel raster, and word-width cache work landed in v1.3.2 was real
|
|
288
|
+
rather than aspirational.
|
|
289
|
+
|
|
290
|
+
### Docs
|
|
291
|
+
|
|
292
|
+
- **README version table extended** —
|
|
293
|
+
Added 1.1.x (vendor switch), 1.2.x (security + benchmarks), and
|
|
294
|
+
1.3.0–1.3.4 (perf + drift guards) rows so the version table no longer
|
|
295
|
+
stops at 1.0.6.
|
|
296
|
+
|
|
297
|
+
### Tooling
|
|
298
|
+
|
|
299
|
+
- **`scripts/run-bench-snapshot.mjs`** —
|
|
300
|
+
Small one-shot runner that prints avg/min render time per corpus across
|
|
301
|
+
three measured runs (after a warmup). Used to capture the v1.3.5
|
|
302
|
+
benchmark results above; useful for ad-hoc perf checks without touching
|
|
303
|
+
the regression-guarded `test/benchmark-baseline.test.ts`.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## [1.3.4] — 2026-05-17
|
|
308
|
+
|
|
309
|
+
### Fixed
|
|
310
|
+
|
|
311
|
+
- **DNS dedup test now imports from source** —
|
|
312
|
+
`test/assets-dns-dedup.test.ts` previously imported `fetchWithTimeout` /
|
|
313
|
+
`assertSafeUrl` from `../dist/assets.js`, which would silently pass against
|
|
314
|
+
a stale build (false confidence). Switched to `../src/assets.js` so the
|
|
315
|
+
test always runs against the current source tree under tsx.
|
|
316
|
+
|
|
317
|
+
### Added
|
|
318
|
+
|
|
319
|
+
- **FIFO eviction boundary test for word-width cache** —
|
|
320
|
+
`test/measure-text-cache.test.ts` now asserts that when the cache is
|
|
321
|
+
pre-filled to `WORD_WIDTH_CACHE_MAX` and a new `measureWord` call is made,
|
|
322
|
+
`cache.size` stays at the cap, the oldest insertion (`syn0`) is evicted,
|
|
323
|
+
and a re-accessed entry (`syn1`) survives — proving FIFO semantics, not LRU.
|
|
324
|
+
|
|
325
|
+
### Changed
|
|
326
|
+
|
|
327
|
+
- **Constant tunability scope documented** — `VECTOR_RASTER_CONCURRENCY` and
|
|
328
|
+
`WORD_WIDTH_CACHE_MAX` are exported as read-only constants for observability
|
|
329
|
+
and test introspection. Consumers wanting different values must fork;
|
|
330
|
+
runtime tunability (env vars or options) is a future enhancement and not
|
|
331
|
+
planned for the v1.x line.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## [1.3.3] — 2026-05-17
|
|
336
|
+
|
|
337
|
+
### Fixed
|
|
338
|
+
|
|
339
|
+
- **Parallel rasterization concurrency cap** — `loadVectorAssets` now runs at
|
|
340
|
+
most 4 SVG/QR/barcode/chart rasterization tasks concurrently (was unbounded).
|
|
341
|
+
Prevents file-descriptor / worker exhaustion on documents with many vector
|
|
342
|
+
assets. New exported constant: `VECTOR_RASTER_CONCURRENCY`.
|
|
343
|
+
- **Word-width cache memory bound** — `measureWord` now FIFO-evicts at 50,000
|
|
344
|
+
entries to bound memory for long-running processes that reuse a single
|
|
345
|
+
`wordWidthCache`. New exported constant: `WORD_WIDTH_CACHE_MAX`.
|
|
346
|
+
|
|
347
|
+
### Changed
|
|
348
|
+
|
|
349
|
+
- **Pretext API contract test reframed** — `test/pretext-api-contract.test.ts`
|
|
350
|
+
header clarified: this is a local export-shape guard for the vendored
|
|
351
|
+
pretext layout module, not an upstream version canary. Pretext has been
|
|
352
|
+
vendored at `src/vendor/pretext/` since v1.1.0.
|
|
353
|
+
- **CHANGELOG clarification on v1.3.2 parallel rasterization** — see updated
|
|
354
|
+
v1.3.2 entry below; the speedup is real for I/O-bound fan-out, but CPU
|
|
355
|
+
rasterization still serializes on the V8 main thread.
|
|
356
|
+
|
|
357
|
+
### Documented (not changed)
|
|
358
|
+
|
|
359
|
+
- **Word-width cache scope (H1)** — the cache is currently consulted on the
|
|
360
|
+
hyphenation path (`measureTextWithHyphenation`) only. The non-hyphenation
|
|
361
|
+
branch of `measureText` delegates directly to pretext's `layoutWithLines`
|
|
362
|
+
to preserve CJK character-level breaking, RTL/bidi, Thai segmentation,
|
|
363
|
+
kerning, and justify semantics that word-by-word summing would diverge
|
|
364
|
+
from. Documents that do not configure a hyphenator will not see
|
|
365
|
+
cross-paragraph cache reuse; this is intentional for correctness.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## [1.3.2] — 2026-05-17
|
|
370
|
+
|
|
371
|
+
### Performance
|
|
372
|
+
|
|
373
|
+
- Removed double DNS resolution in image/SVG fetch (one lookup per remote asset, not two)
|
|
374
|
+
- Parallel SVG/QR/barcode generation+rasterization (sequential embed retained for pdf-lib safety)
|
|
375
|
+
- Sub-note (added in v1.3.3): the parallelism is real for the I/O-bound fan-out
|
|
376
|
+
(remote SVG fetches overlap). CPU rasterization (sharp/svg2pdfkit) still
|
|
377
|
+
contends on the V8 main thread, so wall-clock improvement is dominated by
|
|
378
|
+
remote-asset latency, not raster throughput.
|
|
379
|
+
- Document-level word-width measurement cache (cross-paragraph dedup of common-word measurements)
|
|
380
|
+
- Sub-note (added in v1.3.3): the cache is consulted on the hyphenation code
|
|
381
|
+
path only. See v1.3.3 "Documented (not changed)" for the rationale.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## [1.3.1] — 2026-05-17
|
|
386
|
+
|
|
387
|
+
### Fixed
|
|
388
|
+
|
|
389
|
+
- Internal test fixtures updated to set `allowedFileDirs` (resolved with `path.resolve` for Windows drive-letter compatibility) after the v1.2.2 deny-by-default flip — no library behavior change. Affected: `signatures-crypto`, `signatures-validation`, `svg`, `image-floats` test files.
|
|
390
|
+
- `markdown-gfm` compat test updated to use an `https:` image src; `data:` URLs are blocked by the scheme guard added in v1.3.0, so the pre-existing fixture no longer round-tripped — test-only change.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## [1.3.0] — 2026-05-17
|
|
395
|
+
|
|
396
|
+
### ⚠️ BREAKING (retroactive note covering v1.2.2)
|
|
397
|
+
|
|
398
|
+
- **`assertPathAllowed` is now deny-by-default.** Documents using `file://` image sources without an explicit `allowedFileDirs` configuration will throw `PATH_TRAVERSAL`. This was shipped in v1.2.2 as a security fix but is technically a breaking change — consumers on `^1.2.0` who upgrade past v1.2.1 must either set `allowedFileDirs` or migrate away from `file://` sources. v1.3.0 is the recommended upgrade target with full semver signal.
|
|
399
|
+
|
|
400
|
+
### Fixed
|
|
401
|
+
|
|
402
|
+
- **Scheme guard whitespace bypass** in `compat.ts` — leading whitespace in image src (e.g. `" file:///etc/passwd"`) no longer bypasses scheme stripping.
|
|
403
|
+
- **Extended scheme blocklist** in `compat.ts` — added `vbscript:`, `blob:`, `about:` alongside existing `file://`, `data:`, `javascript:`.
|
|
404
|
+
|
|
405
|
+
### Tests
|
|
406
|
+
|
|
407
|
+
- Added redirect-chain SSRF test using a local mock HTTP server.
|
|
408
|
+
- Pinned CLI exit-code assertion to detect regressions.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## [1.2.2] — 2026-05-17
|
|
413
|
+
|
|
414
|
+
### Security
|
|
415
|
+
|
|
416
|
+
- **`assertPathAllowed` is now deny-by-default** — Previously, when `doc.allowedFileDirs` was
|
|
417
|
+
undefined or empty, local file:// paths were silently allowed. Now the function throws
|
|
418
|
+
`PATH_TRAVERSAL` unless `allowedFileDirs` is explicitly configured with at least one directory.
|
|
419
|
+
This closes an unintended open-access footgun for server-side deployments.
|
|
420
|
+
|
|
421
|
+
- **`compat.ts` — dangerous image schemes stripped in `fromPdfmake`** — `file://`, `data:`, and
|
|
422
|
+
`javascript:` image `src` values are now silently dropped during pdfmake→pretext-pdf translation
|
|
423
|
+
rather than forwarded verbatim. This prevents the compat shim from acting as an indirect
|
|
424
|
+
bypass for the scheme-level SSRF guards in `assets.ts`.
|
|
425
|
+
|
|
426
|
+
- **`compat.ts` — `allowedFileDirs` forwarded from `PdfmakeDocument`** — The `PdfmakeDocument`
|
|
427
|
+
interface now accepts `allowedFileDirs?: string[]`, which is forwarded into the resulting
|
|
428
|
+
`PdfDocument`. Callers who previously passed file paths via the compat shim can now
|
|
429
|
+
allowlist their directories explicitly.
|
|
430
|
+
|
|
431
|
+
- **CLI validates before rendering** — `pretext-pdf` now calls `validateDocument()` before
|
|
432
|
+
invoking `render()`. Invalid documents produce a `VALIDATION_ERROR` message on stderr and
|
|
433
|
+
exit with code 1, avoiding wasted work during the render phase.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## [1.2.1] — 2026-05-16
|
|
438
|
+
|
|
439
|
+
### Fixed
|
|
440
|
+
|
|
441
|
+
- **`PretextPdfError` now preserves root cause via `err.cause`** — `ASSEMBLY_FAILED` errors
|
|
442
|
+
thrown by `merge()` and `assemble()` now carry the original pdf-lib error as `err.cause`,
|
|
443
|
+
making root-cause debugging possible without losing the upstream message.
|
|
444
|
+
|
|
445
|
+
- **`form.updateFieldAppearances()` failure is now logged** — Previously silently swallowed
|
|
446
|
+
(`catch { /* non-fatal */ }`). Now emits a structured warning via the document logger or
|
|
447
|
+
`console.warn`. Behaviour is unchanged (non-fatal); the warning aids debugging.
|
|
448
|
+
|
|
449
|
+
- **Owner-only encryption now warns explicitly** — When `doc.encryption` is set without
|
|
450
|
+
`userPassword`, a `console.warn` is emitted explaining that the PDF will open without a
|
|
451
|
+
password. Owner-only encryption remains valid (it restricts editing/printing, not opening).
|
|
452
|
+
|
|
453
|
+
- **`assemble([{}])` now throws `VALIDATION_ERROR`** — Regression from v1.2.0 discriminated
|
|
454
|
+
union changes: passing a part with neither `doc` nor `pdf` previously crashed with a
|
|
455
|
+
`TypeError` (no `.code` property). Now throws a proper `VALIDATION_ERROR` with a clear
|
|
456
|
+
message before attempting to render.
|
|
457
|
+
|
|
458
|
+
- **`watermark: {}` now throws `VALIDATION_ERROR`** — Regression from v1.2.0: the
|
|
459
|
+
`WatermarkSpec` discriminated union enforced text/image presence at compile-time but the
|
|
460
|
+
runtime validation was missing. A watermark object with neither `text` nor `image` now
|
|
461
|
+
correctly throws at validate time.
|
|
462
|
+
|
|
463
|
+
- **`svg: ''` (empty string) now throws `VALIDATION_ERROR`** — Empty SVG strings passed
|
|
464
|
+
the validation stage and surfaced as `SVG_LOAD_FAILED` during render. Now caught at
|
|
465
|
+
validate time with a clear `VALIDATION_ERROR`.
|
|
466
|
+
|
|
467
|
+
### Changed
|
|
468
|
+
|
|
469
|
+
- **`PretextPdfError` constructor accepts optional `ErrorOptions`** — Third argument
|
|
470
|
+
`options?: ErrorOptions` (i.e. `{ cause?: unknown }`) is now accepted and passed to the
|
|
471
|
+
native `Error` constructor. Fully backwards-compatible — all existing call sites unchanged.
|
|
472
|
+
|
|
473
|
+
- **bidi-js missing-peer warning routes through document logger** — When a `logger` is
|
|
474
|
+
passed to `render()`, bidi-js peer-dependency warnings are now routed through
|
|
475
|
+
`logger.warn` instead of always using `console.warn`. New low-level export:
|
|
476
|
+
`setBidiWarnFn(fn)` (prefer the `logger` render option in application code).
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## [1.2.0] — 2026-05-16
|
|
481
|
+
|
|
482
|
+
Post-audit hardening release. Type system tightened, concurrency-safe validation,
|
|
483
|
+
@internal type leaks closed, RTL/asset failures surface as structured errors,
|
|
484
|
+
SSRF defense upgraded to undici-pinned IP. No source-level API removals from the
|
|
485
|
+
package entry point — see migration notes below for `@internal` types that were
|
|
486
|
+
already not exported from `src/index.ts`.
|
|
487
|
+
|
|
488
|
+
### Added
|
|
489
|
+
|
|
490
|
+
- **Discriminated unions on four public types** (`src/types-public.ts`, audit Phase B) —
|
|
491
|
+
`WatermarkSpec`, `AssemblyPart`, `SvgElement`, and `ImageElement` (float variants)
|
|
492
|
+
now use TypeScript discriminated unions instead of flat optional structs. The
|
|
493
|
+
compiler now prevents invalid combinations (e.g., a watermark with both `text`
|
|
494
|
+
and `image`, or an SVG element with neither `svg` nor `src`) that previously
|
|
495
|
+
could only be caught at runtime. Existing valid usages continue to compile.
|
|
496
|
+
|
|
497
|
+
- **`pdf-lib` type augmentation** (`src/vendor/pdf-lib-augment.d.ts`, audit Phase D) —
|
|
498
|
+
Documents the load-bearing `as any` casts against pdf-lib internals
|
|
499
|
+
(`PDFArray.push`, `PDFFont.embedder`) and removes the one genuinely-avoidable
|
|
500
|
+
cast in `measure.ts`.
|
|
501
|
+
|
|
502
|
+
- **DNS rebinding defense via undici Agent IP pinning** (`src/assets.ts`, audit B6) —
|
|
503
|
+
`assertSafeUrl` was upgraded to `resolveAndValidateUrl` which returns the
|
|
504
|
+
resolved IP, and `fetchWithTimeout` now uses an undici `Agent` whose
|
|
505
|
+
`connect.lookup` callback always returns the pre-validated IP. Closes the
|
|
506
|
+
TOCTOU window where DNS could rebind between validation and connect.
|
|
507
|
+
Extended private-range coverage: 0.0.0.0/8, 192.0.0/24, 198.18/15, IPv6
|
|
508
|
+
multicast, and IPv4-mapped IPv6 normalization. +14 SSRF tests (25 total).
|
|
509
|
+
|
|
510
|
+
- **Per-call cycle-detection state** (`src/validate.ts`, audit Perf-1) — Moved
|
|
511
|
+
`seenInRecursion` WeakSet from module scope into `validate()` and threaded
|
|
512
|
+
through `withCycleGuard`. Makes validation reentrant and concurrency-safe
|
|
513
|
+
(no shared mutable state across parallel `validate()` calls). +1 regression
|
|
514
|
+
test.
|
|
515
|
+
|
|
516
|
+
- **Structured error codes on RTL + asset failures** (`src/errors.ts`,
|
|
517
|
+
`src/measure-text.ts`, `src/assets.ts`, audit silent-failure pass) —
|
|
518
|
+
- `RTL_REORDER_FAILED` — surfaces when `bidi-js` is installed but throws.
|
|
519
|
+
Previously fell through with `isRTL:true` on logical-order text =
|
|
520
|
+
visually broken Arabic/Hebrew renders. Missing `bidi-js` still degrades
|
|
521
|
+
gracefully (warn + LTR render) since it is an optional peer dep.
|
|
522
|
+
- `CHART_LOAD_FAILED` — embedded in warn logs from QR/barcode/chart loaders
|
|
523
|
+
so failures are debuggable from log scraping alone.
|
|
524
|
+
- `FONT_ENCODE_FAIL` — replaces the prior bare `catch` in `src/fonts.ts`
|
|
525
|
+
that silently swallowed font subset failures (audit B2).
|
|
526
|
+
|
|
527
|
+
### Changed
|
|
528
|
+
|
|
529
|
+
- **`@internal` types removed from the `types.ts` barrel** (audit H8 / type-design HIGH) —
|
|
530
|
+
`RichLine`, `RichFragment`, and `TocEntryElement` were tagged `@internal`
|
|
531
|
+
but re-exported through `src/types.ts`. They have been removed from that
|
|
532
|
+
barrel and canonicalized in `src/types-internal.ts`. `TocEntryElement` is
|
|
533
|
+
no longer a member of the public `ContentElement` union (it was always
|
|
534
|
+
pipeline-synthesized, never user-constructed). Internal imports updated
|
|
535
|
+
in `rich-text.ts`, `measure.ts`, `render-extras.ts`, `allowed-props.ts`,
|
|
536
|
+
`validate.ts`, `measure-blocks.ts`, `fonts.ts`. **Migration note:** these
|
|
537
|
+
types were never exported from `src/index.ts` (the package entry point),
|
|
538
|
+
so consumers using the supported import path (`import { ... } from
|
|
539
|
+
'pretext-pdf'`) are unaffected. Deep imports (`'pretext-pdf/src/types'`)
|
|
540
|
+
are unsupported and never were stable.
|
|
541
|
+
|
|
542
|
+
- **`api-extractor` enforcement escalated to `error`** (`api-extractor.json`) —
|
|
543
|
+
`ae-forgotten-export` log level changed from `warning` to `error` so CI
|
|
544
|
+
fails on future `@internal` type leaks. `etc/pretext-pdf.api.md` baseline
|
|
545
|
+
regenerated.
|
|
546
|
+
|
|
547
|
+
- **`assertUnknownProps` parameter tightened** (`src/validate.ts`) —
|
|
548
|
+
`obj: any` → `obj: unknown` with an explicit type guard at the boundary.
|
|
549
|
+
Removes one of the few remaining `any` exposures at a security-sensitive
|
|
550
|
+
validation entrypoint.
|
|
551
|
+
|
|
552
|
+
### Fixed
|
|
553
|
+
|
|
554
|
+
- **Concurrent validation false positives** (`src/validate.ts`) — Two
|
|
555
|
+
simultaneous `validateDocument(doc)` calls on the same object reference
|
|
556
|
+
could produce a false "cyclic reference detected" error because the
|
|
557
|
+
WeakSet was at module scope. Per-call WeakSet fix closes this and any
|
|
558
|
+
future re-entrant validator scenarios.
|
|
559
|
+
|
|
560
|
+
- **Stale `tests-743` badge** (`README.md`) — Replaced with the durable
|
|
561
|
+
`tests-passing` (current unit count is 319).
|
|
562
|
+
|
|
563
|
+
- **Dead `case 'toc-entry'` branch** (`src/fonts.ts:collectTextByFont`) —
|
|
564
|
+
After `TocEntryElement` left the public `ContentElement` union, the
|
|
565
|
+
branch became unreachable.
|
|
566
|
+
|
|
567
|
+
### Migration notes (v1.1.x → v1.2.0)
|
|
568
|
+
|
|
569
|
+
If you only import from `'pretext-pdf'`: **no source changes needed.**
|
|
570
|
+
|
|
571
|
+
If you do unsupported deep imports of internal types
|
|
572
|
+
(`'pretext-pdf/src/types'`):
|
|
573
|
+
- `RichLine`, `RichFragment`, `TocEntryElement` — import from
|
|
574
|
+
`'pretext-pdf/src/types-internal'` instead, with the understanding that
|
|
575
|
+
these are not part of the stable public API and may change in any release.
|
|
576
|
+
|
|
577
|
+
If you construct `WatermarkSpec` / `AssemblyPart` / `SvgElement` / `ImageElement`
|
|
578
|
+
literals: you may need to delete fields you weren't using anyway. TypeScript
|
|
579
|
+
will flag any literals that previously satisfied the loose type but violated
|
|
580
|
+
the actual invariants.
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
## [1.1.3] — 2026-05-15
|
|
585
|
+
|
|
586
|
+
### Added
|
|
587
|
+
|
|
588
|
+
- **Cycle detection + depth cap on TableElement walk** (`src/validate.ts`, Sprint 3 / M2) —
|
|
589
|
+
The rows/cells iteration is now wrapped in `withCycleGuard`, matching the
|
|
590
|
+
protection already in place for `ListItem.items`, `FloatGroup.content`, and
|
|
591
|
+
`RichParagraph.spans`. A self-referential row or cell shape now produces a
|
|
592
|
+
structured `VALIDATION_ERROR` instead of an unbounded walk.
|
|
593
|
+
|
|
594
|
+
- **Root-level depth guard for `document.content` entries** (`src/validate.ts`, Sprint 3 / M1) —
|
|
595
|
+
Each top-level element call into `validateElement` now runs an explicit
|
|
596
|
+
`assertDepthOk(depth, prefix)` so the `MAX_VALIDATION_DEPTH = 32` cap fires
|
|
597
|
+
even for plugin-typed elements that do not open their own `withCycleGuard`
|
|
598
|
+
scope. Internal recursive walks (`list`, `float-group`, `rich-paragraph`,
|
|
599
|
+
`table`) continue to enforce the cap via `withCycleGuard`.
|
|
600
|
+
|
|
601
|
+
- **Round-trip tests for the pdfmake compatibility shim** (`test/compat.test.ts`, Sprint 3 / M3) —
|
|
602
|
+
Four new tests covering pdfmake → pretext → render integration, style
|
|
603
|
+
propagation, sanity rendering of native pretext docs, and large-table
|
|
604
|
+
preservation (5 columns × 10 rows).
|
|
605
|
+
|
|
606
|
+
- **`## Validation` section in `README.md`** (Sprint 3 / M4) — Explicit guidance
|
|
607
|
+
to call `validateDocument()` before `render()` on untrusted input, with the
|
|
608
|
+
concrete failure modes (stack overflow on cyclic input, prototype pollution
|
|
609
|
+
via `__proto__`, runtime 500s on malformed shapes) the validator prevents.
|
|
610
|
+
|
|
611
|
+
### Fixed
|
|
612
|
+
|
|
613
|
+
- **Type safety in validateDocument** (`src/validate.ts`) — Replaced unchecked `as PdfDocument` cast with `isValidPdfDocumentLike()` type guard. Returns proper error when input is not a plain object.
|
|
614
|
+
|
|
615
|
+
- **Prototype pollution in mergeStyles** (`src/compat.ts`) — `Object.assign(merged, s)` allowed user-supplied pdfmake JSON to pollute the prototype chain. Replaced with `copySafeStyleProperties()` that whitelists only known safe style keys (fontSize, bold, italics, color, alignment, font).
|
|
616
|
+
|
|
617
|
+
- **Path traversal in digital signatures** (`src/post-process.ts`) — P12 certificate path bypassed the `allowedFileDirs` security check. Now validates path via `assertPathAllowed()` before reading, preventing directory traversal attacks via signature feature.
|
|
618
|
+
|
|
619
|
+
- **Fragile errorCount regex in validateDocument** (`src/validate.ts`) — Original regex could match anywhere in error message. Refined to header-only pattern (`^Strict validation failed`) to extract true error count even when >20 errors are returned (capped array but accurate count in message).
|
|
620
|
+
|
|
621
|
+
- **Fake test coverage** (`test/validate-document.test.ts`) — Removed describe block with `assert.ok(true, 'TODO')` placeholder. Replaced with documentation explaining why the non-PretextPdfError code path is manually audited.
|
|
622
|
+
|
|
623
|
+
- **Missing LICENSE for vendored code** (`src/vendor/pretext/LICENSE`) — Added MIT license file with attribution to upstream pretext library and this fork, satisfying legal compliance for vendored dependencies.
|
|
624
|
+
|
|
625
|
+
### Documentation
|
|
626
|
+
|
|
627
|
+
- **`Logger` interface guidance** (`src/types-public.ts`, audit L2) — Expanded
|
|
628
|
+
JSDoc on the `Logger` interface and the `logger?` field on `RenderOptions`
|
|
629
|
+
to call out that passing a no-op (`{ warn: () => {} }`) silences **every**
|
|
630
|
+
advisory warning — fine in tests, dangerous in production. Documents the
|
|
631
|
+
default (`console.warn`) and recommends pino/winston for production
|
|
632
|
+
routing. No code change.
|
|
633
|
+
|
|
634
|
+
- **pdfmake `defaultStyle` / `styles` mapping** (`src/compat.ts`, audit L3) —
|
|
635
|
+
Added JSDoc to `PdfmakeStyle` enumerating the supported subset
|
|
636
|
+
(`font`, `fontSize`, `bold`, `italics`, `color`, `alignment`) and the
|
|
637
|
+
silently-dropped pdfmake properties (`lineHeight`, `marginX/Y`,
|
|
638
|
+
`decoration`, `background`, `characterSpacing`, `noWrap`, etc.) that
|
|
639
|
+
consumers migrating from pdfmake commonly trip over. `fromPdfmake()`
|
|
640
|
+
now documents how `defaultStyle.font` / `.fontSize` route to
|
|
641
|
+
document-level `defaultFont` / `defaultFontSize` and how all other
|
|
642
|
+
`defaultStyle` properties cascade through `mergeStyles()`.
|
|
643
|
+
|
|
644
|
+
### Changed
|
|
645
|
+
|
|
646
|
+
- **Benchmark floor override** (`test/benchmark-baseline.test.ts`, audit L4) —
|
|
647
|
+
The per-corpus regression guard now honors a `PRETEXT_BENCHMARK_FLOOR_MS`
|
|
648
|
+
environment variable. Set it to a positive integer to raise the 5000ms
|
|
649
|
+
default floor on slow CI runners; set it to `skip` / `0` / `false` / `off`
|
|
650
|
+
to bypass the timing assertion entirely. The structural assertions (corpus
|
|
651
|
+
IDs match baseline, stages present) still run.
|
|
652
|
+
|
|
653
|
+
### Notes — Phase A / B / D history
|
|
654
|
+
|
|
655
|
+
The cycle-detection and depth-cap machinery (`withCycleGuard`,
|
|
656
|
+
`MAX_VALIDATION_DEPTH`, the per-container guards on `list`, `float-group`,
|
|
657
|
+
`rich-paragraph`) and the discriminated-union refactor of `ContentElement`
|
|
658
|
+
plus the typed `pdf-lib` augmentation were landed during in-flight audit
|
|
659
|
+
sprints between `[1.0.x]` and `[1.1.0]` that were not individually tagged.
|
|
660
|
+
Sprint 3 (this release) backfills those gaps with the M1/M2 root + table
|
|
661
|
+
guards, plus explicit tests and documentation.
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## [1.1.2] — 2026-05-08
|
|
666
|
+
|
|
667
|
+
### Fixed
|
|
668
|
+
|
|
669
|
+
- **Silent font-subset failure** (`src/fonts.ts`) — Bare `catch {}` on `pdfFont.encodeText()`
|
|
670
|
+
silently swallowed glyph-encoding errors, producing wrong characters with no signal.
|
|
671
|
+
Now logs a `console.warn` so callers know which font key failed.
|
|
672
|
+
|
|
673
|
+
- **Explicit RTL direction silently flipping to LTR** (`src/measure-text.ts`) — When
|
|
674
|
+
`dir:'rtl'` was set and `bidi-js` threw during reordering, the fallback incorrectly
|
|
675
|
+
returned `isRTL: false`, causing Arabic/Hebrew paragraphs to align and wrap as LTR.
|
|
676
|
+
The fallback now preserves `isRTL: true` so the layout engine honours the explicit
|
|
677
|
+
direction even without bidi reordering.
|
|
678
|
+
|
|
679
|
+
- **SSRF DNS rebinding window** (`src/assets.ts`) — `assertSafeUrl()` was synchronous.
|
|
680
|
+
An attacker with TTL=0 DNS could pass the hostname check then rebind to `169.254.x.x`
|
|
681
|
+
between the check and the actual `fetch()` call. The function is now async and
|
|
682
|
+
pre-resolves hostnames via `dns.lookup()` before the private-range check, closing
|
|
683
|
+
the TOCTOU window. Falls back gracefully when DNS is unavailable (fetch will also
|
|
684
|
+
fail in that case). All call sites updated to `await assertSafeUrl()`.
|
|
685
|
+
|
|
686
|
+
- **Concurrent PDFDocument mutation race** (`src/pipeline.ts`) — `loadFonts` and
|
|
687
|
+
`loadImages` were run with `Promise.all()` over the same `PDFDocument` instance.
|
|
688
|
+
Both mutate the cross-reference table, causing intermittent xref corruption under
|
|
689
|
+
load. Now sequenced: `loadFonts` completes before `loadImages` begins.
|
|
690
|
+
|
|
691
|
+
- **Test suite cascade: 692 tests silently dropped on benchmark failure** (`package.json`,
|
|
692
|
+
`scripts/test-all.mjs`) — The `&&`-chained `npm test` command aborted all downstream
|
|
693
|
+
stages when `test:contract` failed. Replaced with a Node.js runner that executes all
|
|
694
|
+
4 stages and collects failures. Benchmark is now in a separate `test:benchmark` script
|
|
695
|
+
(not in `test:contract`) with `FLOOR_MS` raised to 5s to absorb dev-hardware variance.
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
10
699
|
## [1.1.1] — 2026-05-08
|
|
11
700
|
|
|
12
701
|
### Fixed
|