Skip to content

fix: limit PDF decompression on load and drop invisible characters from drafts - #206

Merged
jerelvelarde merged 2 commits into
CopilotKit:mainfrom
ayuxy027:fix/pdf-decode-limit-invisible-text
Oct 26, 2026
Merged

jerelvelarde merged 2 commits into
CopilotKit:mainfrom
ayuxy027:fix/pdf-decode-limit-invisible-text

Conversation

@ayuxy027

Copy link
Copy Markdown
Contributor

Closes #205

What

Bounded PDF decompression. loadPdf now limits each load to 100 MiB of decompressed output (10× the file size limit), and rejects the file with a 422 when it goes over. Before, pdf-lib decompressed object and cross-reference streams while loading, with no limit on output, so a file under 10 MB could use unbounded memory while it was checked.

  • pdf-lib has no option for this. All its decoders grow their output buffer through DecodeStream.ensureBuffer, so pdf.ts wraps that one method and charges each buffer's growth to the current load. The limit is per load: it's carried in AsyncLocalStorage, so concurrent imports don't share it.
  • pdf-lib's parser can catch the limit error and recover. loadPdf therefore checks the remaining budget both after a successful load and in its catch.
  • inspectPdf and fillPdf both go through loadPdf, so upload, Gmail attachment import, browser download import and computer export are all covered.

Invisible characters in drafts. The email subject/body and the event title/location/description schemas now drop bidi embedding/override/isolate controls, zero-width space, word joiner, invisible operators, BOM and tag characters. This is done with .overwrite(), so the field stays a plain string. Because the schemas are shared by the model tools, POST /api/actions, the review card and the Gmail/Calendar send path, what the review card shows is what gets sent. Zero-width joiners and non-joiners and the LRM/RLM marks are kept, because emoji, Persian and Arabic text use them.

Tests

  • tests/pdf.test.ts: a valid one-page PDF with an object stream that decompresses past the limit is rejected with the limit error, while a sample PDF loaded at the same time still succeeds. Against the previous pdf.ts, the oversized file loads successfully.
  • tests/domain.test.ts: invisible characters are dropped from every free-text draft field, and a subject made only of them is rejected (fails against the previous schema). Text with joiners and directional marks is kept unchanged.

Tested

  • pnpm lint, pnpm typecheck, pnpm --dir apps/worker typecheck, pnpm build:server
  • pnpm test: 512 passed, 0 failed
  • Server and schema change only; native/web builds and browser suites weren't run.

@jerelvelarde jerelvelarde left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed dd68b41adf4dd85fb6e3e51515562b9ff564995e.

Bounds decompression during PDF loads and normalizes hidden draft controls at the shared schema boundary. Both changes address untrusted input; no dependencies or deployment changes. Security: traced upload/import/fill through loadPdf, per-load AsyncLocalStorage accounting and parser-error recovery; action proposal/review/send share the normalization. No actionable security finding. The decoder still allocates before charging its growth, so this is a bounded rejection threshold, not a strict 100 MiB process-memory ceiling; concurrent loads and other PDF parsing allocations remain outside that claim. Verification: 22 PDF/domain tests passed, including an oversized compressed object stream concurrent with an ordinary PDF. Native devices and live provider imports were not exercised.

Local execution used read-only, network-disabled Linux containers with synthetic data and Node 24.17.0. Root dependency fixtures came from main’s manifest rather than the lockfile; exact-head GitHub CI covers locked full checks where green. No production credentials were supplied.

@jerelvelarde
jerelvelarde merged commit dcf277a into CopilotKit:main Oct 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PDF imports have no limit on decompressed size

2 participants