Conversation
xlsx files were download-only. Add a read-only, virtualized preview (sheet tabs, number formats, merges, theme colours) parsed entirely in a browser Web Worker with exceljs and fflate, loaded only when a spreadsheet is opened. The workbook is checked against ZIP-bomb, entry and cell limits before exceljs loads; cell text is written with textContent, formulas are never evaluated and nothing referenced by the workbook is fetched. On the server xlsx only joins the existing allowlist and classification, with a 10 MB cap on ?preview=true. xls and ods stay download-only.
|
Thanks a lot for this, @aakhter. It adds a read-only XLSX preview to the file-preview overlay, parsed entirely in a browser worker, and the overall design is exactly right: no server-side parsing, no new path handling, lazy worker-only vendor bundles, While testing it with real ExcelJS workbooks through the worker I hit three bugs that need fixing before merge:
Two smaller things that fit in the same round:
Everything else (the route changes, the allowlist addition, the packaging and the tests) is in good shape, so once these land it is ready to merge. Thanks again for the careful work on this. |
- Normalize the value shapes ExcelJS loads before formatting: Date cells are formatted from their serial (UTC), so they no longer render as a local-time string a day early at negative UTC offsets; rich text joins its runs, hyperlinks show their text, error values show the error, and formula and shared-formula results (including error results) recurse. Excel serials are rounded to whole milliseconds so 00:05 no longer shows as 00:04. - sendTile() skips hidden rows and columns, and at the 2500-cell cap returns a truncated tile with a warning instead of failing the whole preview. - ExcelJS now parses a STORE-only archive rebuilt from exactly the entries admitXlsx() inflated and counted, never the fetched bytes. Admission walks local headers while JSZip reads the central directory, so overlapping entries could show the two readers different sheets. A duplicate local entry name is refused. The theme fallback reads the admitted entry too. - Row and column headings take their size from the same axis math as cells. - Document the admission, worker-only loading and SPREADSHEET_ASSET_VERSION rules in architecture-invariants, and list .xlsx in the attachments panel help and the `codeman attach` error text (built from the accepted list).
|
Thanks for the careful review, and for testing with real ExcelJS workbooks. All of it is addressed in 4edb7b8.
The small ones are done too: headings are sized from the same |
|
Thanks again, @aakhter. This PR adds a read-only XLSX preview to the file-preview overlay, parsed in a browser worker behind admission caps. I checked every item from the first round at 4edb7b8 and all of them are fixed: the date and value shapes (with the TZ test), hidden rows and the truncated tile, the STORE-only rebuilt archive with its overlapping-entry fixture, heading sizes, and the docs and help text. Typecheck, lint, format, the asset checks, the full One more thing needs fixing before merge, in the same family as the round 1 admission bypass:
Two smaller ones that fit in the same round:
At merge time I will move the Once the admission fix lands, this is ready to merge. Thanks for the careful work on both rounds. |
What
.xlsxfiles were download-only. This adds a read-only preview in the file-preview overlay: sheet tabs, number formats, merged cells and theme colours, virtualized so large sheets scroll smoothly. It works for workspace files, attachments, and xlsx paths printed in the terminal (added to the file-path link pattern andFILE_PREVIEW_EXTENSIONS)..xlsand.odsstay download-only, since ExcelJS only reads xlsx; tests pin both.How
spreadsheet-preview-worker.js+spreadsheet-xlsx-core.js) usingexceljs@4.4.0andfflate@0.8.2(both MIT, pinned exactly as devDependencies and copied intovendor/by postinstall and the build, like the other vendored bundles). The server does no parsing.resolveFileTarget,resolveServableAttachmentPath). There is no new path handling.?preview=trueis capped at 10 MB (413 above it) on bothfile-rawand the attachment raw route; downloads are unchanged._openSpreadsheetPreview/_disposeSpreadsheetPreview), torn down from_stopFilePreviewMediaon open and close. The worker is created viaCodemanBase.urland loads its scripts by relative URL, so--base-urlmounts work.Safety
The workbook is untrusted input:
admitXlsx): at most 5000 ZIP entries, 64 MB inflated in total, 32 MB per entry, a 100:1 compression ratio, 50 worksheets, 250k cells (100k per sheet), 5000 merges per sheet and 5000 styles. Encrypted and ZIP64 files are refused. There is also a 20 s timeout.textContent. The generated style block only accepts validated#rrggbbcolours and a fixed keyword set. At most 2500 cells are drawn per tile.Cost
Page load gains only
spreadsheet-preview.js(5.0 KB gz). Opening a spreadsheet then loads the worker (3.0 KB), core (7.4 KB), fflate (12.5 KB) and exceljs (256 KB gz, 948 KB raw), about 284 KB gz in total. ExcelJS is only fetched after the workbook passes the checks.check:public-assetsenforces a 1.1 MB vendor budget, and a content-hashSPREADSHEET_ASSET_VERSIONcache-busts the worker.dependency-security.test.tsgains one exact exemption: exceljs pinsuuid@8.3.2(our own uuid stays >= 14). The advisory is MODERATE, outside that suite's CRITICAL/HIGH policy, and covers v3/v5/v6 with a caller-supplied buffer, while exceljs only callsv4(). The browser also loads exceljs's own prebuiltdistbundle, and nothing server-side imports exceljs.Testing
BROWSER_TEST_GLOBS). Also 6 new route tests acrossfile-routesand the attachment path guard.textContentswapped forinnerHTMLtypecheck,lint,check:frontend-syntax,format:check,check:public-assets,check:lockfileandbuildare clean.<img onerror>displayed as text with no image element created, no console errors, and none of the four preview requests were made until a spreadsheet was opened.