Skip to content

fix: guard drawing export against invalid structures and zero IDs (SD-824)#2363

Open
caio-pizzol wants to merge 1 commit intomainfrom
caio/sd-824-drawing-export-guards
Open

fix: guard drawing export against invalid structures and zero IDs (SD-824)#2363
caio-pizzol wants to merge 1 commit intomainfrom
caio/sd-824-drawing-export-guards

Conversation

@caio-pizzol
Copy link
Contributor

@caio-pizzol caio-pizzol commented Mar 11, 2026

Supersedes #1271 — rebased onto current main and incorporates review feedback.

  • Drawing translator guard: skip w:drawing wrap when child is not wp:inline or wp:anchor, preventing corrupt OOXML when translateImageNode falls back to a text run (e.g., invalid signature data)
  • Inline + anchor node guards: bail out of wp:inline / wp:anchor construction when translateImageNode returns a non-drawing result (missing wp:extent)
  • Non-zero IDs: generate positive random IDs for wp:docPr and pic:cNvPr instead of defaulting to 0, which violates OOXML uniqueness requirements (ECMA-376 §20.4.2.5)

OOXML compliance

Element Spec section Finding
wp:docPr/@id §20.4.2.5 id=0 passes XSD but violates uniqueness when multiple objects default to it
w:drawing children §17.3.3.9 Only wp:inline and wp:anchor are standard children
wp:inline required children §20.4.2.8 wp:extent is mandatory in CT_Inline sequence

Changes from #1271

  • Rebased onto current main (code was refactored significantly since Nov 2025)
  • Simplified guard: plain !== check instead of Set (review feedback)
  • Simplified inline guard: single wp:extent check covers all cases (review feedback)
  • Added Math.max(1, ...) to prevent theoretical id=0 from parseInt (review feedback)
  • Computed drawingId once, shared by both buildDocPrElement and buildNvPicPrElement
  • Added anchor path guard (was missing in original PR)
  • Updated existing anchor tests to include wp:extent in mock data

Test plan

  • drawing-translator.test.js — 12 tests pass (guard + existing)
  • drawing-guards.test.js — 7 new tests (inline guard, anchor guard, ID generation)
  • translate-anchor-node.test.js — 30 tests pass (mock updated for guard)

…-824)

- Add guard in drawing-translator decode to skip w:drawing wrap when
  child is not wp:inline or wp:anchor (prevents corrupt OOXML)
- Add guard in translateInlineNode to bail when translateImageNode
  returns a non-drawing result (e.g. text fallback for invalid signatures)
- Add same guard in translateAnchorNode for parity
- Generate positive random IDs for wp:docPr and pic:cNvPr instead of
  defaulting to 0 (violates OOXML uniqueness requirement per §20.4.2.5)
- Ensure both elements share the same generated ID per image
@linear
Copy link

linear bot commented Mar 11, 2026

@github-actions
Copy link
Contributor

Status: PASS

The changes are spec-compliant. Here's what I verified against ECMA-376:

wp:docPr / pic:cNvPr ID handling (decode-image-node-helpers.js:295–337)

The PR fixes the previous attrs.id || 0 fallback, which could write id="0" to both wp:docPr and pic:cNvPr. Both elements use ST_DrawingElementId (§20.1.10.21), defined as a restriction of unsignedInt — which technically permits 0, so the inline comment "OOXML requires id > 0" overstates it slightly. That said, Word never emits id="0" and most parsers treat it as invalid, so avoiding 0 is the right call in practice.

The generated fallback uses Math.max(1, parseInt(generateDocxRandomId(), 16)), and since generateDocxRandomId() caps its internal value at 0x7FFFFFFF, the result is always within unsignedInt bounds — no overflow risk.

Shared drawingId between wp:docPr and pic:cNvPr

§20.4.2.5 and §20.2.2.3 both say IDs must be unique within the document, but sharing the same ID between wp:docPr and its corresponding pic:cNvPr within a single drawing is the standard Word behavior — the uniqueness constraint applies across drawings, not between these two sibling roles within one drawing. This is correct.

w:drawing child guard (drawing-translator.js:73–77)

The guard that only wraps wp:inline or wp:anchor is correct. Per §20.4.2.4 / §20.4.2.8, w:drawing must contain exactly one of those two elements. Passing through any other result unchanged is the right fallback.

wp:extent sentinel (translate-inline-node.js:13–15, translate-anchor-node.js:71–73)

wp:extent is a required child of both wp:inline and wp:anchor (§20.4.2.7), so using its presence to distinguish a valid drawing result from a text-run fallback is structurally sound given the shape of translateImageNode's output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant