Skip to content

fix(copilot): guard document-style extraction against zip bombs - #6176

Merged
waleedlatif1 merged 2 commits into
stagingfrom
worktree-fix+document-style-zip-guard
Aug 2, 2026
Merged

fix(copilot): guard document-style extraction against zip bombs#6176
waleedlatif1 merged 2 commits into
stagingfrom
worktree-fix+document-style-zip-guard

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #6166, which fixed the document-parser paths. extractDocumentStyle was the remaining unguarded ZIP consumer.

  • It handed an attacker-controlled archive straight to JSZip.loadAsync and inflated named parts (word/theme/theme1.xml, word/styles.xml, ppt/presentation.xml, ppt/slideMasters/slideMaster1.xml) with no size bound
  • Reachable from GET /api/workspaces/[id]/files/[fileId]/style and from the workspace VFS (workspace-vfs.ts:1233)
  • Reading only a few entries is not protection: the bomb just has to live at one of those paths

It now calls assertOoxmlArchiveWithinLimits — the same guard the document parsers use — and the hand-rolled ZIP_MAGIC byte check is replaced by isZipShaped from that module. zip-guard is already shared this way by lib/uploads/archive.ts and lib/copilot/tools/handlers/upload-file-reader.ts, so this is the established pattern rather than new coupling. Net effect is a deleted bespoke check plus the protection.

Why not a cheaper targeted check

Bounding only the entries this path actually reads would be less work, but JSZip reports the size the archive declares — the same attacker-controlled field a bomb lies about, which is exactly the bypass #6166 had to fix. A targeted check would have to re-derive that verification to be sound, so reusing the shared guard is both cleaner and the only correct option short of duplicating it.

Error behavior

The guard sits inside the existing try, so a rejection is logged (the guard warns with details before throwing) and the function returns null. The route already answers 422 with a descriptive message and the VFS already returns null when no summary can be produced — neither caller changes.

Type of Change

  • Bug fix (security — resource exhaustion / DoS)

Testing

The return value alone cannot demonstrate this fix: JSZip also rejects these archives, but only after inflating the entry — which is the memory cost the guard exists to avoid. The tests therefore assert JSZip is never handed the buffer, mirroring the approach in #6166's doc-parser tests.

  • Verified both bomb tests go red when the guard call is removed (confirmed against the now-merged hardened guard, not just the old declared-size one)
  • All 17 real Word-produced .docx fixtures from mammoth's test data are accepted by the guard; 13 still yield a style summary and the 4 that return null are pre-existing (empty.docx, endnotes.docx, external-picture.docx, utf8-bom.docx have no theme1.xml) — confirmed each with guard=accepts
  • Archives are constructed byte by byte in the test rather than generated-then-patched, so a declared size that never matched the payload can be expressed directly

Gates: tsc clean, check:api-validation passed, biome clean, 124 tests across lib/copilot/vfs and lib/file-parsers.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 2, 2026 12:52am

Request Review

@cursor

cursor Bot commented Aug 2, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Security fix on a reachable file-processing path; behavior for invalid archives is unchanged (null) but adds pre-parse bounds that affect all docx/pptx style extraction.

Overview
extractDocumentStyle for docx/pptx now rejects unsafe ZIPs before JSZip.loadAsync, using the shared assertOoxmlArchiveWithinLimits and isZipShaped from zip-guard instead of a local magic-byte check.

That closes the same zip-bomb / memory-exhaustion gap left after document-parser hardening: this path still inflated attacker-controlled OOXML parts (theme, styles, presentation XML) with no declared-size bounds. Guard failures stay inside the existing try and still surface as null to callers (API 422 / VFS), with no route changes.

New tests build malformed archives byte-by-byte (lying declared uncompressed sizes) and assert JSZip is never called for oversize and implausible-ratio bombs, plus happy-path docx/pptx theme extraction and non-ZIP buffers.

Reviewed by Cursor Bugbot for commit 52c52be. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR protects document-style extraction from malicious OOXML archives before they reach JSZip.

  • Reuses the shared OOXML archive limits and ZIP-shape detection.
  • Adds DOCX and PPTX extraction coverage plus compact absolute-size and compression-ratio bomb fixtures.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported expensive fixture now uses a small payload with only an overridden declared size, and the guard rejects it before decompression.

Important Files Changed

Filename Overview
apps/sim/lib/copilot/vfs/document-style.ts Applies the shared archive guard before JSZip loads attacker-controlled DOCX or PPTX data.
apps/sim/lib/copilot/vfs/document-style.test.ts Covers normal extraction and verifies oversized or implausibly compressed archives never reach JSZip without constructing large payloads.

Reviews (2): Last reviewed commit: "test(copilot): declare the ratio-test ex..." | Re-trigger Greptile

Comment thread apps/sim/lib/copilot/vfs/document-style.test.ts Outdated
extractDocumentStyle handed an attacker-controlled archive straight to
JSZip and inflated named parts (word/theme/theme1.xml, word/styles.xml,
ppt/presentation.xml, ppt/slideMasters/slideMaster1.xml) with no size
bound, reachable from GET /api/workspaces/[id]/files/[fileId]/style and
from the workspace VFS. Reading only a few entries is no protection: the
bomb just has to live at one of those paths.

It now calls assertOoxmlArchiveWithinLimits, the same guard the document
parsers use, and the hand-rolled ZIP_MAGIC check is replaced by isZipShaped
from that module — zip-guard is already shared this way by
lib/uploads/archive.ts and lib/copilot/tools/handlers/upload-file-reader.ts.

Checking each entry's size through JSZip instead would have been cheaper,
since only a handful of parts are read, but JSZip reports the size the
archive declares — the same attacker-controlled field a bomb lies about —
so it would need to re-derive the guard's verification to be sound.

The guard sits inside the existing try, so a rejection logs and returns
null: the route already answers 422 and the VFS already returns null when
no summary can be produced, and neither caller changes.
The compression-ratio case built a real 400 MiB string and deflated it
synchronously, costing the parallel test runner memory and CPU for no added
coverage. The guard reads the total the archive declares, so declaring the
expansion exercises the same ratio path with a few-hundred-byte fixture.

Still fails when the guard call is removed, and the file now runs in 286 ms
instead of seconds.

Caught by Greptile review.
@waleedlatif1
waleedlatif1 force-pushed the worktree-fix+document-style-zip-guard branch from d71466d to 52c52be Compare August 2, 2026 00:52
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 52c52be. Configure here.

@waleedlatif1
waleedlatif1 merged commit 30820fc into staging Aug 2, 2026
27 checks passed
@waleedlatif1
waleedlatif1 deleted the worktree-fix+document-style-zip-guard branch August 2, 2026 00:56
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.

1 participant