Repository navigation
Read Word, Excel, PowerPoint and OpenDocument files in the sandboxed reader - #57
Merged
Merged
Conversation
…reader
Design PR 4 of the document-reading plan (docs/document-reader.md).
- Word (.docx) as Markdown-like text: headings, lists, tables, image alt
text; character positions with start/next. Word 97-2003 (.doc): best-effort
text from the piece table.
- Excel (.xlsx, .xlsm, .xls), OpenDocument spreadsheets and CSV: per-sheet
tables with cell addresses, a sheet list and preview by default, A1 ranges
("Sheet1!A1:H200"), cell and character caps with the next range named,
cached values with optional formula text. SheetJS CE 0.20.3 from
cdn.sheetjs.com, pinned by integrity in the lockfile; it never sees the ZIP
(parts are inflated by the guarded reader) and never gets macro projects.
- PowerPoint (.pptx) and OpenDocument presentations slide by slide with
speaker notes; .ppt, .xlsb and Word 95 get a "save as" message.
- OpenDocument text (.odt) like Word.
- A hardened XML parser: DTDs refused (no XXE, no billion laughs), depth and
element caps, namespace-resolved names. Office part and package size caps
on top of the zip-bomb limits. reply.ts checks the new shapes.
- read_document takes sheet, range and include_formulas; Office attachments
are sent as text through the byte-bounded cache, and their chips are no
longer greyed.
Co-authored-by: omnigent <noreply@omnigent.ai>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ck OLE files - sheets.ts / unsaved.ts: a formula cell saved without a result (openpyxl-style <f> with no <v>, an empty <v/>, a t="str" formula with no <v>, an OpenDocument formula with no value, or a SheetJS stub that carries a formula) renders as "=SUM(A1:A3) (no saved value)" with a note to recalculate in Excel and save, never as a number that was not stored. A cached 0 still shows as 0. - reply.ts: checkView parses every sheet range (used, shown, next) against Excel's 1,048,576 x 16,384 limits, requires shown inside used, and caps the cells the sections claim to show at office.maxCellsPerCall, in the broker and again in main. - cfb.ts: the OLE header, DIFAT, FAT, every FAT chain SheetJS walks up front, the directory (entry cap, link trees), mini FAT, mini stream and stream chains are checked before SheetJS's CFB reader sees the file, with each sector claimed once; loops, overlaps, truncation and tangled chains fail as 'malformed', streams declared larger than the file as 'zip-bomb'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: omnigent <noreply@omnigent.ai>
The raw-XML scan for formulas saved without a value now stores bands of rows holding sorted runs of columns: an OpenDocument cell repeated over rows and columns is one rectangle, equal adjacent bands merge (a filled-down column is one band), and a lookup is two binary searches. Work and memory are linear in the part's length whatever its repeat attributes claim; repeats are clamped to the sheet. The 100,000-cell cap is gone. Past MAX_RUNS stored runs, cells out of order, or two tables of one name, the sheet is marked unchecked and its formulas are shown as formulas with a warning, never as values that could not be vouched for. Tests: 150,000 unsaved xlsx shared formulas and 600,000 repeated ODS ones read at the end of the sheet; a hostile ODS with 3,000 rows of 1,048,576-row / 16,384+-column repeats reads in well under 5 s; the unchecked fallback; an .xls fixture with patched FORMULA records whose cached results are placeholders, asserting no 0 is shown. Co-authored-by: omnigent <noreply@omnigent.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…holders Writers that do not calculate (such as xlwt) store 0 as a BIFF8 formula's result, which cannot be told from a computed 0. An .xls formula cell whose cached result is numeric 0 now shows as `0 (=formula; may be unsaved)`, and the view notes it once per sheet. Non-zero results and .xlsx/.ods are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: omnigent <noreply@omnigent.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Design PR 4 of the document-reading plan (
docs/document-reader.md): Word, Excel, PowerPoint and OpenDocument support in the sandboxed document reader (worker + broker from #52), exposed throughread_documentand attachments.What changed
Readers (all inside the sandboxed worker, same 30 s timeout and memory ceiling)
xml.ts: our own XML parser for hostile input. It refuses any DOCTYPE, so there is no XXE and no entity expansion (billion laughs). Only predefined entities and character references are decoded. Nesting depth is capped at 256 and elements per part at 4M. Names are namespace-resolved, and each reader keeps only the elements it uses.package.ts: OOXML/ODF packages go through the existing guardedzip.ts(entry count, declared sizes and ratios, real inflated bytes counted). Newlimits.officecaps sit on top: 64 MB per part and 256 MB per package, uncompressed.docx.ts:basedOn), bullet and numbered lists fromnumbering.xml, tables as Markdown (spans padded, nested tables flattened), text boxes, and[Image: alt]placeholders.mc:Fallbackare dropped.start/nextcontinuation cut at line breaks.pptx.ts: slides insldIdLstorder, each with its title, shape text, tables, pictures and speaker notes; hidden slides are marked.sheets.ts:.xlsx,.xlsm,.xlsand.ods. It never sees the ZIP: parts are inflated by the guarded reader and handed toparse_zipas an in-memory container.vbaProject.bin, media and embedded objects are never handed over. Macros are never run or read.Sheet1!A1:H200,'Q3 sales'!B2:F40,A:C,5:20,A21:H.include_formulasadds the formula text.sheetRowsbounds parsing.odf.ts:.odtreads like Word and.odplike PowerPoint;.odsgoes through SheetJS.ole.ts:.xlsgoes to SheetJS..docgets best-effort text from the piece table (8-bit and UTF-16 pieces, field instructions dropped)..ppt,.xlsband Word 95 get a clear "save it as .pptx/.xlsx/.docx" message.password-protected.reply.tschecks the new shapes:slide(treated likepage) andsheet;meta.sheets: at most 200, consistent with the position;section.sheetranges must be A1 references (regex) and sheet names at most 200 characters;Tool and attachments
read_documenttakes newsheet,rangeandinclude_formulasarguments. These are exclusive withpages,startandregion, and recorded in the audit entry.READABLE_DOCUMENT_MIMES. They are sent as fenced untrusted text, with no images, through the existing byte-bounded cache (cachedPdfReading, which now takes a MIME type).canReceive(kind, vision, mime). Older PowerPoint, RTF and mail are still greyed.Dependencies added
xlsx0.20.3 (SheetJS Community Edition), Apache-2.0:https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz, not the outdated npm package.package.jsonand by its sha512 integrity inpnpm-lock.yaml; I checked the hash against the downloaded tarball.out/document-worker/assets/index.js.out/document-worker/licenses/sheetjs-LICENSE.txt, andTHIRD-PARTY-NOTICES.mdis updated.evalornew Function, so the worker's CSP is unchanged.How I verified
pnpm typecheck,pnpm lint,pnpm format:check: clean.pnpm test: 2865 test cases passed (34 skipped) across 133 test files. New coverage:xml.test.tsandoffice.test.ts: every format from real files built in memory (workbooks written by SheetJS; Word, PowerPoint and ODF from their XML parts; .doc and .ppt as OLE files), plus ranges, cell caps and continuation.office.test.ts: zip bombs by ratio and by a lying entry, XXE, billion laughs, a DTD in a part handed to SheetJS, oversized shared strings, tables nested past the depth cap, too many elements. Every view is also put throughcheckReply.reply.test.ts,read-document.test.ts,attachments.test.tsand the renderer chip tests.pnpm build, thenxvfb-run -a pnpm test:e2e: all 132 passed.e2e/document-worker.spec.tscovers every format and the hostile Office corpus inside the real sandboxed worker.e2e/read-document.spec.tscovers Word, a workbook listing followed by a range, and slide-by-slide continuation through the tool.🤖 Generated with Claude Code