Release note · August 4, 2026

An allowed transform cannot hide an XPath

By SybilGambleyyu · DocFence 0.49.0

A transform URI can look compatible while its stored parameter markup says otherwise. XMLDSIG’s generic Transform schema allows an XPath parameter, but OPC explicitly disallows the ds:XPath element. A bounded package-signature coverage audit should not give that declaration authority merely because the outer algorithm name is canonicalization.

OPC narrows a generic XMLDSIG surface

The ECMA-376 Open Packaging Conventions standard limits package-signature transforms to its canonicalization forms and its Relationships transform. Its XPath rule is even more direct: XPath filtering is not allowed in an OPC signature. XMLDSIG, however, defines ds:Transform as a generic parameter carrier, so a syntactically ordinary canonicalization transform can still contain a nested XPath element.

ds:Transform Algorithm="OPC C14N"
  └─ ds:XPath true()

That is not a valid OPC declaration. The danger for a static parser is subtle: checking only the outer Algorithm attribute would credit the transform without noticing that its nested content crosses the OPC boundary.

One narrow rejection rule, three coverage paths

DocFence 0.49 now rejects a ds:XPath element anywhere inside a transform that participates in its bounded declaration chain. That applies to the SignedInfo Reference that binds idPackageObject, an ordinary package-part Reference in the bound Manifest, and either transform in a relationship declaration.

A malformed binding leaves declaration coverage unavailable. A manifest reference carrying XPath remains aggregate unsupported: it cannot lend coverage to a Word part, and a relationship transform cannot lend even partial coverage to an otherwise valid selector. The public report continues to expose only aggregate coverage and unsupported-reference counts; XPath content, selectors, URIs, paths, and digest material remain private.

Rejecting syntax is not executing it

This is deliberately a stored-structure check. DocFence does not parse or evaluate an XPath expression, execute a transform, recompute a digest, verify XMLDSIG, inspect certificates, establish trust, or predict an Office client. It merely refuses to turn a declaration with an OPC-forbidden element into an assurance signal.

version: 1
rules:
  require_complete_package_signature_coverage: true
  no_package_signature_coverage_changes: true

DFP092 and DFP093 retain their existing meaning: they gate the bounded stored declaration surface, rather than making a claim about cryptographic validity or trust.

Evidence and compatibility

The 69-test suite now covers XPath-bearing binding, part, and relationship transforms. It proves that a valid relationship selector next to an XPath parameter receives no partial coverage, while the existing no-transform and two canonicalization-form cases remain accepted. DCAB’s complete reference adapter also passed against a fresh DocFence wheel.

A scan of the public OOXML Signature Security artifacts found no XPath element in their 22 XML signature parts. The signed content-injection and duplicate-document baselines retained full bounded coverage—six Word parts, one root-document relationship, and five Word relationships—while the published content-injection, universal-signature-forgery, duplicate-document, and evil-type variants retained their expected uncovered or unavailable surfaces. This is a compatibility smoke test for stored structure, not a trust or safety verdict.

Main and tagged CI both passed. Fresh wheel and source-distribution installations produced byte-identical signed-baseline profiles, DCAB passed against the wheel, and the public GitHub release downloads were byte-compared with the verified artifacts.

python -m pip install https://github.com/SybilGambleyyu/docfence/releases/download/v0.49.0/docfence-0.49.0-py3-none-any.whl

docfence check approved.docx candidate.docx --policy docfence.yml --format sarif --output docfence.sarif

The tagged policy reference, threat model, and validation notes describe the exact boundary. The important distinction is simple: a permitted transform name does not rescue prohibited transform markup.