From single-quoted XML quirks to non-finite geometry guards — the story behind 270 merged pull requests in
genspark-ai/genoffice, the world's first full-featured open-source AI Office suite.
GenOffice is a free, open-source alternative to Microsoft Office (macOS / Windows / Linux). It opens and saves native .docx, .xlsx, .pptx, edits PDF, Markdown and HTML, and puts a reviewable AI agent next to every document. Real formats, byte-preserving. Local by design.
I contributed 270 merged PRs (PR #131 → #733), backed by ~268+ commits in my local clone. Almost all are fix: / test: — small, reviewable, CI-green hardening patches across Docs, Sheets, Slides, PDF, DOCX engine, PPTX engine, xlsx-gateway, html2docx, pdf2docx, shell, CLI, electron-utils, ai-provider, ai-search, and a11y/i18n.
Repo: https://github.com/genspark-ai/genoffice
How it started
I came for the file-format rabbit hole and stayed for the edge cases. Office files are hostile inputs in the wild: single-quoted attributes, uppercase True, paired protection tags, BOM-less UTF-16 CSVs, backslash pptx targets, lowercase xlsx refs, broken slide sizes, 1904 date systems. Each one breaks a naive parser. My loop became: reproduce → clamp/validate/normalize → add regression test → keep the diff minimal.
Early work (#131–#160) was Sheets/Slides streaming and save reliability: Ctrl+F beyond the loaded window, filter-hidden rows in find, whole-file error checking on streamed workbooks, row-batch sidecar reads, temp-file TTL sweeps, serialized concurrent saves, in-flight save waits, SSE reader release + malformed-frame skips, IPC failure surfacing.
What I actually did — by theme
1. Parser hardening: read what Word really writes
- DOCX: single-quoted
fldCharType, protection attrs, checkbox glyphs, footnote separators, TOC tab stops,xml:space, numeric char refs, NBSP edges, paired tags, vanish/footnote bold variants. - PPTX: single-quoted slide ids / hyperlink r:id / showMasterSp, preset colors case-insensitive,
true/Truetable flags, tab stops as tabs, slides in presentation order. - XLSX / CSV: lowercase cell refs, escaped quotes + quote-state across lines, BOM-less UTF-16, over-precision integers as text, delimiter sniffing that ignores single-column prose.
2. Numeric safety: kill NaN / Infinity / overflow
A huge slice is one pattern: non-finite in → sane out. Chart axis min/max, font sizes, ink geometry, connector widths, border widths, rect dims, table colSpan, EQ columns, slide-id scans, CSV row/col caps, bitmap dims, outline JSON caps, SSE buffers, tool-call counts, chat limits, timeline limits, print geometry, AI length parsers (parseTableLength #730). Boring? Yes. That's why it matters — one NaN can DoS a renderer or corrupt an export.
3. Security + IPC + SSRF + save reliability
Traversal-id rejection, canonical path comparison for tabs, renderer file resolution hardening, SSRF maxRedirects normalization, MCP host/file checks, sheet-address hardening, zip-handle closing + central-directory validation, protection spinCount clamping, lazy-media hash/part-path hardening, redaction-region caps, outline-depth caps, link-rect validation, serialized saves (#155), in-flight save waits (#149), SSE cleanup (#150/#151).
4. i18n / a11y / RTL
Bidi direction tests, Southeast Asian + CJK word counts, kana/radical/astral-CJK font mapping, hardcoded cm strings moved into i18n (#733), existing i18n keys for aria labels, dialog role/focus/escape, diagram preview keyboard operable, toasts + outline empty-state announcements, localized labels, RTL-aware AI panels.
5. Tests that lock it in
Touched-blocks coverage, bidi units, east-asian rFonts round-trip, CSV import round-trip, formula-shift + csv-import units, executor/units/op-docs sync, deck-spec + layout-audit, legacy doc/ppt + html2docx tables, CLI help/registry/README/skill sync, xlsx-gateway + pptx-ops wired into CI.
6. Features (small, sharp)
-
feat(sheets): infer table range from current region (fixes #298)(#371) -
feat(sheets): cross-highlight the active cell's row and column(#132) feat(pdf): add print range dialog (all/current/custom)feat(markdown): add document outline sidebar with click-to-jumpfeat(ai-search): add Tavily web search fallback via TAVILY_API_KEY-
feat(docs): per-side and inside table borders in ribbon+ RTL-aware panels
Highlights (12 to start with)
- #131 fix(sheets): extend Ctrl+F beyond the loaded window on streamed workbooks
- #155 fix(slides): serialize concurrent saves to prevent file corruption
- #150 fix(ai-provider): release SSE reader when the consumer abandons the stream mid-flight
- #371 feat(sheets): infer table range from current region (fixes #298)
- #132 feat(sheets): cross-highlight the active cell's row and column
- #501 fix(project-store): reject traversal ids and clamp chat limit
- #515 fix(docx-engine): close zip handles and validate central directory
- #468 fix(docx-engine): clamp protection spinCount to prevent digest DoS
- #689 fix(cli): validate JPEG markers and image dims in image-size
- #687 fix(ai-provider): tolerate non-string media settings values
- #684 fix(pptx-render): sanitize flattenSvgPath coords and segs
- #730 fix(docs): cap AI parseTableLength at sane magnitudes
- #733 fix(docs): move hardcoded cm strings into i18n
The full list — all merged GenOffice PRs
Every PR below is merged in genspark-ai/genoffice by @aniruddhaadak80. Count in this post: 270 (complete via GitHub Search API, 719 total merged PRs of mine across 62 repos).
a11y (1)
agent-core (1)
ai-panels (3)
- #192 fix(ai-panels): clear preview marker on read rejection so thumbnails retry
- #203 feat(ai-panels): RTL-aware chrome and auto-direction messages in slides/sheets/pdf/markdown (part of #13)
- #209 fix(ai-panels): clear restored history transcript on New chat (fixes #195)
ai-provider (21)
- #150 fix(ai-provider): release SSE reader when the consumer abandons the stream mid-flight
- #151 fix(ai-provider): skip malformed SSE frames instead of killing the AI turn
- #187 fix(ai-provider): sanitize Gemini tool schemas for union types and refs (fixes #186)
- #191 fix(ai-provider): return ok:false on non-JSON 200 instead of throwing
- #193 fix(ai-provider): keep default temperature for the Gemini 3 family
- #229 fix(ai-provider): allow keyless custom endpoints for local models
- #232 fix(ai-provider): trim model whitespace and remap padded retired ids
- #261 fix(ai-provider): omit temperature for OpenAI o-series reasoning models
- #303 fix(ai-provider): match JSON Content-Type case-insensitively
- #343 fix(ai-provider): match fixed-sampling models case-insensitively on every route
- #383 fix(ai-provider): bound fixed-sampling match and guard missing model
- #393 fix(ai-provider): match vision guard case-insensitively
- #415 fix(ai-provider): treat whitespace-only keys, URLs, and models as unconfigured
- #427 fix(ai-provider): flush TextDecoder at SSE stream end
- #469 fix(ai-provider): surface rate-limit and server errors in media test
- #518 fix(ai-provider): normalize watchdog timeouts
- #533 fix(ai-provider): cap streamed tool-argument buffers per tool
- #540 fix(ai-provider): cap SSE line buffer in shared stream parser
- #574 fix(ai-provider): cap streamed tool call count per turn
- #594 fix(ai-provider): validate custom and override base URLs
- #687 fix(ai-provider): tolerate non-string media settings values
ai-search (8)
- #194 feat(ai-search): add Tavily web search fallback via TAVILY_API_KEY
- #358 fix(ai-search): normalize media MIME type case-insensitively
- #426 fix(ai-search): scope copyright-host filter to the image hostname
- #465 fix(ai-search): parse gsk JSON when trailing logs follow the payload
- #503 fix(ai-search): match copyright hosts by registrable suffix
- #532 fix(ai-search): use shared host check for gsk image copyright filter
- #555 fix(ai-search): cap GSK web result count and field lengths
- #602 fix(ai-search): clamp search maxResults and query length
cli (5)
- #497 test(cli): sync help/registry/README/skill plus missing doc rows
- #512 fix(cli): harden MCP http host and file handling
- #559 fix(cli): cap safe file name length preserving extension
- #564 fix(cli): bound typo suggestion inputs and candidate scan
- #689 fix(cli): validate JPEG markers and image dims in image-size
contributing (1)
dialogs (1)
docs (24)
- #200 feat(docs): per-side and inside table borders in ribbon (fixes #165)
- #202 feat(docs): RTL-aware AI panel chrome and auto-direction messages (part of #13)
- #223 fix(docs): complete ruler tab cycle with Bar, hide clear stops
- #224 fix(docs): drop staged attachments on New chat
- #242 fix(docs): decline Windows reserved names for auto file naming
- #257 test(docs): cover mixed-width section page boxes (issue #246)
- #258 fix(docs): preserve scroll position when zooming
- #305 fix(docs,slides): accept spaces around dashes in print ranges
- #310 fix(docs): include write-protection flags in dirty check
- #311 fix(docs): count Greek, Cyrillic, Hebrew, and Arabic words
- #321 fix(docs): detect paired bidi section tags
- #352 fix(docs): clear cross-document edit state on open, new, and save
- #353 fix(docs): count Southeast Asian scripts and full surrogate range in word count
- #412 fix(docs): exclude supplementary private-use and variation selectors from asian char count
- #494 test(docs): add bidi direction unit tests
- #511 test(docs): add touched-blocks coverage and refresh stale positions
- #550 fix(docs): validate project chat IPC payloads and bounds
- #591 fix(docs): bound page-number formatters against non-finite input
- #595 fix(docs): cap note-number repeat expansion
- #610 fix(docs): validate print-pdf-buffer page size and scale
- #660 fix(docs): guard HTML export against out-of-range hex escapes
- #662 fix(docs): cap AI length parsers at sane magnitudes
- #730 fix(docs): cap AI parseTableLength at sane magnitudes
- #733 fix(docs): move hardcoded cm strings into i18n
docx (19)
- #317 fix(docx): respect off/none/case variants in TOC bold detection
- #318 fix(docx): keep heavy leader for ptab stops
- #319 fix(docx): decode numeric char refs in notes and text patches
- #320 fix(docx): preserve edge spaces in field patches
- #348 fix(docx): split and patch on spaced and paired run-tab variants
- #355 fix(docx): read single-quoted protection attributes
- #359 fix(docx): handle numeric char refs, NBSP edges and empty self-closing runs in field patch
- #376 fix(docx): read single-quoted protection hash salt spin sid
- #381 fix(docx): read single-quoted checkbox glyph values
- #382 fix(docx): inline EQ fields with single-quoted fldCharType
- #392 fix(docx): balance single-quoted field char runs
- #402 fix(docx): honor off and case in footnote bold flags
- #403 fix(docx): honor none and case in vanish collapse
- #410 fix(docx): fold field code split across paragraphs with single-quoted fldCharType
- #421 fix(docx): read single-quoted footnote separator types and ids
- #422 fix(docx): read uppercase checkbox checked values as checked
- #423 fix(docx): read single-quoted TOC tab stop values and leaders
- #424 fix(docx): honor single-quoted xml:space preserve on part roots
- #495 test(docx): add east-asian latin rfonts round-trip test
docx-engine (11)
- #312 fix(docx-engine): read paired protection tags and on enforcement
- #468 fix(docx-engine): clamp protection spinCount to prevent digest DoS
- #509 fix(docx-engine): base-26 chart columns and hole size default
- #515 fix(docx-engine): close zip handles and validate central directory
- #556 fix(docx-engine): reject non-finite and out-of-range font sizes
- #563 fix(docx-engine): harden lazy media URL hash and part path
- #569 fix(docx-engine): sanitize non-finite ink geometry before writing
- #577 fix(docx-engine): keep overflowing measures as original bytes
- #592 fix(docx-engine): clamp EQ array column count to finite range
- #601 fix(docx-engine): bound table colSpan before grid build
- #683 fix(docx-engine): bound hostile numbering values in formatNumber
electron-utils (9)
- #240 fix(electron-utils): skip drop files whose path resolution throws
- #274 fix(electron-utils): trim and normalize URLs in safeExternalUrl
- #313 fix(electron-utils): validate fallback save dir instead of crashing later
- #396 fix(electron-utils): block trailing-dot internal hostnames
- #517 fix(electron-utils): harden renderer file resolution
- #557 fix(electron-utils): cap renderer query params and path lengths
- #562 fix(electron-utils): cap drop path resolution count and length
- #593 fix(electron-utils): normalize fetchWithSsrfGuard maxRedirects
- #612 fix(electron-utils): cap generated-image bytes and mime
file-parse (8)
- #378 fix(file-parse): keep pptx tab stops as tabs
- #400 fix(file-parse): read lowercase xlsx cell refs
- #425 fix(file-parse): include footnote and endnote text in docx extraction
- #457 fix(file-parse): extract PPTX slides in presentation order
- #464 fix(file-parse): keep empty shared-string cells empty and preserve malformed-ref text
- #508 fix(file-parse): resolve backslash and above-root pptx targets
- #530 fix(file-parse): normalize xlsx workbook rel targets
- #565 fix(file-parse): bound xlsx extraction rows, sheets, and wild columns
font-metrics (1)
general (2)
- #487 chore: wire xlsx-gateway and pptx-ops tests into CI
- #489 test: add legacy doc/ppt and html2docx tables tests
html (3)
- #398 fix(html): escape ampersands in single-quoted attrs
- #414 fix(html): escape apostrophes in single-quoted style attributes on set_style
- #571 fix(html): bound breadcrumb chain and crumb labels
html2docx (7)
- #436 fix(html2docx): map kana extensions, radicals, and astral CJK to CJK fonts
- #482 fix(html2docx): validate IR, abort image wait, restore safely
- #510 fix(html2docx): validate page background color hex
- #521 fix(html2docx): narrow shotId with type predicate (un-breaks main typecheck)
- #541 fix(html2docx): validate IR numeric geometry as finite and non-negative
- #573 fix(html2docx): sanitize render context numerics to finite geometry
- #614 fix(html2docx): guard zero-area image scale in renderer
i18n (2)
- #302 fix(i18n): require BCP-47 boundary in normalizeLang prefix match
- #394 fix(i18n): use own-property check in format
markdown (11)
- #149 fix(markdown): wait for in-flight saves during close instead of silently failing
- #222 feat(markdown): localize zoom control labels
- #260 fix(markdown): improve print quality with @page margins and high-DPI styles
- #262 test(markdown): cover print HTML builder output
- #289 feat(markdown): add document outline sidebar with click-to-jump
- #366 fix(markdown): resolve sibling images for filesystem-root files
- #390 fix(markdown): normalize pasted math delimiters to dollars
- #420 fix(markdown): serve sibling images of filesystem-root documents
- #513 fix(markdown): guard diagram rasterization and wavedrom errors
- #535 fix(markdown): drop duplicated openSource helper in roundtrip browser spec
- #549 fix(markdown): make diagram preview keyboard-operable and announced
pdf (18)
- #237 fix(pdf): suffix Windows reserved auto-rename bases
- #288 feat(pdf): add print range dialog (all/current/custom)
- #306 fix(pdf): ignore invalid font sizes when decoding text styles
- #307 fix(pdf): clamp and round channels in rgb255ToHex
- #316 fix(pdf): raise print raster to 200 DPI within a pixel budget
- #328 fix(pdf): validate colors and toggles when decoding text styles
- #344 fix(pdf): validate style-key fonts and sizes on decode and patch
- #354 fix(pdf): stream print pages with per-page budget and cleanup
- #399 fix(pdf): use length-preserving case folding in search
- #413 fix(pdf): fold AI-tool search queries the same way as the search index
- #417 fix(pdf): sanitize fields in encodeStyle so crafted styles cannot shift key fields
- #428 fix(pdf): drop non-integer page targets in printPdf
- #516 fix(pdf): harden redaction validation and layer behavior
- #520 fix(pdf): announce toasts and outline empty state
- #561 fix(pdf): cap redaction region count per request
- #576 fix(pdf): cap outline tree nesting depth
- #578 fix(pdf): validate link rects and cap overlays per page
- #600 fix(pdf): validate markup color and quads before appearance stream
pdf2docx (9)
- #304 fix(pdf2docx): merge touching intervals at minGap boundary
- #329 fix(pdf2docx): count roman-numeral TOC leader lines
- #347 fix(pdf2docx): detect underscore leaders and 4-digit leaderless pages
- #377 fix(pdf2docx): detect underscore leaders in hasDotLeaderRun
- #401 fix(pdf2docx): detect hyphen leaders in TOC entries
- #416 fix(pdf2docx): anchor arabic leader detection to line end and cap page digits
- #507 fix(pdf2docx): ignore non-finite inputs in median
- #531 fix(pdf2docx): cap empty-frame bitmap dimensions
- #615 fix(pdf2docx): require finite gap in spacing chain
pipelines (2)
- #485 test(pipelines): add deck-spec and layout-audit tests
- #609 fix(pipelines): cap outline raw JSON and image queries
pptx (7)
- #326 fix(pptx): replace solidFill when recoloring preset and system runs
- #346 fix(pptx): replace non-solid fills instead of duplicating on recolor
- #441 fix(pptx): read and strip single-quoted showMasterSp
- #443 fix(pptx): honor true/True table style flags and merges
- #446 fix(pptx): resolve and preserve single-quoted hyperlink r:id
- #447 fix(pptx): read single-quoted slide ids
- #448 fix(pptx): resolve preset colors case-insensitively
pptx-engine (6)
- #461 fix(pptx-engine): honor the 1904 date system in chart categories
- #466 fix(pptx-engine): fall back to defaults on malformed slide size
- #506 fix(pptx-engine): bound slide id allocation scan
- #567 fix(pptx-engine): land non-finite font sizes on lower bound
- #613 fix(pptx-engine): drop non-finite chart axis min/max
- #682 fix(pptx-engine): cap chartEx level sizes and idx range
pptx-ops (4)
- #484 test(pptx-ops): add executor, units, and op-docs sync tests
- #528 fix(pptx-ops): reject non-finite and negative rect dimensions in insert ops
- #538 fix(pptx-ops): reject non-finite and oversized borderWidthPt in setTableStyle
- #566 fix(pptx-ops): reject overflowing connector line widths
pptx-render (4)
- #470 fix(pptx-render): bound imageDpi cache with hashed keys
- #502 fix(pptx-render): guard viewport against degenerate sizes
- #554 fix(pptx-render): return zero for non-finite coordinate inputs
- #684 fix(pptx-render): sanitize flattenSvgPath coords and segs
print-range (1)
project-store (3)
- #501 fix(project-store): reject traversal ids and clamp chat limit
- #547 fix(project-store): clamp timeline limit and cap project names
- #575 fix(project-store): bound pending opening messages with early flush
sheets (37)
- #131 fix(sheets): extend Ctrl+F beyond the loaded window on streamed workbooks
- #132 feat(sheets): cross-highlight the active cell's row and column
- #133 fix(sheets): keep filter-hidden rows out of full-sheet find results
- #134 fix(sheets): scan the whole file for error checking on streamed workbooks
- #140 fix(sheets): row-batch over-cap sidecar reads so streaming survives big viewports
- #190 fix(sheets,slides): forward stopReason so max_tokens truncation recovers
- #214 fix(sheets): reject out-of-grid Go To refs at validation
- #215 fix(sheets): keep trailing newlines in clipboard TSV fields
- #216 fix(sheets): trim spaces in whole-cell replace paths
- #233 fix(sheets): guard replaceOccurrences against empty needle
- #239 fix(sheets): drop staged attachments on New chat
- #247 fix(sheets): label force-string popup as warning not error
- #250 test(sheets): cover multi-match-per-row find navigation (issue #220)
- #270 fix(sheets): use -1 for unfrozen axis when opening frozen panes
- #275 fix(sheets): handle escaped quotes in CSV delimiter sniffing
- #308 fix(sheets): preserve consecutive CR breaks in clipboard TSV
- #309 fix(sheets): distinguish types in aggregate distinct counts
- #322 fix(sheets): keep over-precision integers as text on CSV import
- #323 fix(sheets): carry quote state across lines when sniffing delimiter
- #324 fix(sheets): copy empty booleans as empty cells
- #325 fix(sheets): shift whole-column spans on structural edits
- #341 fix(sheets): shift whole-row spans on structural edits
- #342 fix(sheets): handle apostrophe sheet names in live formula shift
- #349 fix(sheets): preserve numbers and booleans in out-of-window Replace All
- #350 fix(sheets): clamp copy materialization to the screen extent
- #351 fix(sheets): keep single-column prose out of delimiter sniffing on import
- #360 fix(sheets): satisfy exactOptionalPropertyTypes for LazyCellMatch rawValue
- #371 feat(sheets): infer table range from current region (fixes #298)
- #379 fix(sheets): map common OOXML paper sizes instead of A4 fallback
- #418 fix(sheets): shift whole-row spans on copy/fill like whole-column spans
- #419 fix(sheets): flag modern Excel error values in Error Checking taxonomy
- #438 fix(sheets): decode BOM-less UTF-16 CSV instead of keeping NUL garbage
- #514 fix(sheets): harden MCP sheet addressing
- #548 fix(sheets): bound CSV import size before conversion
- #551 fix(sheets): validate project chat IPC payloads and bounds
- #619 fix(sheets): sanitize print-geometry numbers before emitting print HTML
- #671 fix(sheets): guard defaultThreshold against zero count
shell (12)
- #156 fix(shell): surface project IPC failures and AI settings save/test errors
- #221 fix(shell): harden Home rename validation
- #230 fix(shell): reject Windows reserved and trailing-dot rename names
- #231 fix(shell): normalize recents ext filter dots case and whitespace
- #273 fix(shell): include xls in the sheets recents filter family
- #357 fix(shell): share ext family between recents and starred filters
- #397 fix(shell): reject trailing spaces in rename gate
- #411 fix(shell): validate raw rename name before trimming so trailing-space gate holds
- #529 fix(shell): route tab lookups through canonical path comparison
- #537 fix(shell): validate tab activate and close IPC ids
- #560 fix(shell): validate home openPath IPC string and length
- #622 fix(shell): cap CLI-runner output buffering
slides (21)
- #141 fix(slides): sweep generated-page temp files with a TTL at startup
- #155 fix(slides): serialize concurrent saves to prevent file corruption
- #160 fix(slides): skip width overflow check on vertical text bodies
- #218 fix(slides): route saveAs through the save queue
- #219 fix(slides): clear answered clarifications on New chat
- #234 fix(slides): clear staged attachments on New chat
- #287 fix(slides): respect AI composer spellcheck pref
- #327 fix(slides): escape image sources in print HTML
- #345 fix(slides): normalize corrupt print and export page geometry
- #380 fix(slides): use theme tokens in cutout dialog
- #384 fix(slides): localize find replace close button label
- #395 fix(slides): reject non-RRGGBB input in recent colors
- #439 fix(slides): cap and dedupe recent colors on read
- #534 fix(slides): reset save state on stale current-slide index
- #552 fix(slides): validate project chat IPC payloads and bounds
- #570 fix(slides): guard print dialog against stale current-slide index
- #599 fix(slides): validate ink descr payload before decode
- #623 fix(slides): cap metafile raster input bytes in image loader
- #668 fix(slides): reject out-of-range start slide in play order
- #672 fix(slides): cap pasted-image IPC bytes at 5MB
- #673 fix(slides): correct Custom Show counts and dialog semantics
tests (1)
xlsx-gateway (7)
- #467 fix(xlsx-gateway): harden cell address parsing and cap range expansion
- #483 test(xlsx-gateway): add formula-shift and csv-import unit tests
- #496 test(xlsx-gateway): add csv import round-trip tests
- #504 fix(xlsx-gateway): reject impossible DV dates and times
- #519 fix(xlsx-gateway): validate style color hex input
- #553 fix(xlsx-gateway): cap CSV row and column counts during parse
- #598 fix(xlsx-gateway): order non-finite keys deterministically in sort
What I learned
- Real-world files don't follow the spec. If Word can emit it, you must parse it: single quotes, uppercase booleans, paired tags, backslashes.
- Clamp at the boundary, not in the middle. Validate IPC, IDs, sizes, counts, and numerics where they enter — renderers stay simple.
-
Non-finite is a vulnerability class.
NaN/Infinityhandling deserves the same discipline as XSS/traversal. - Tiny PRs merge faster. One fix + one test beats a 2,000-line refactor.
- A11y/i18n is correctness. If a screen reader or RTL user can't use it, it's broken.
What's next
Continuing the hardening pass (latest: #730 AI parseTableLength caps, #733 cm→i18n, #689 CLI JPEG validation, #687 media-settings tolerance, #684 SVG flattening). If you maintain an Office parser, start with hostile-input fuzzing around numbers, quotes, and encodings — you'll find bugs in an afternoon.
All PRs merged in https://github.com/genspark-ai/genoffice — author: https://github.com/aniruddhaadak80
Top comments (0)