Release note · August 4, 2026

A package binding needs a real Reference

By SybilGambleyyu · DocFence 0.48.0

A local URI such as #idPackageObject names an OPC package-specific Object, but a string alone is not an XMLDSIG Reference. The declaration that claims to bind the Object into SignedInfo also has a required child order and transform boundary. A static coverage audit should not credit a carrier when the alleged binding is malformed.

Correction, August 4, 2026: 0.48 established the direct Reference-child and transform-algorithm boundary, but it did not yet reject an XMLDSIG ds:XPath element nested inside an otherwise allowed transform. DocFence 0.49 closes that separate OPC restriction before static coverage is credited.

A URI is only the start of a Reference

The XMLDSIG Reference schema fixes the direct sequence: an optional Transforms element, then DigestMethod, then DigestValue. OPC limits package-signature transform algorithms to canonicalization and its Relationships transform; the latter has defined input only under a Manifest. A binding Reference to the package Object can therefore have no transform, or a direct nonempty list of OPC’s two canonicalization forms.

ds:SignedInfo
  └─ ds:Reference URI="#idPackageObject"
       ├─ optional ds:Transforms
       │    └─ ds:Transform Algorithm="OPC C14N"
       ├─ ds:DigestMethod Algorithm="…"
       └─ ds:DigestValue (plain, nonempty)

DocFence 0.48 requires that exact stored binding shape before it resolves the bound Manifest. The direct DigestValue must remain plain, attribute-free, child-free, and nonempty; DigestMethod needs a nonblank Algorithm. A present Transforms list must be direct, nonempty, and contain only normal or comment-preserving XML Canonicalization transforms.

Bound does not mean verified

An unknown, relationship, empty, or duplicate transform list—and a missing, reordered, malformed, nested, extra, or text-bearing digest child—leaves static declaration coverage unavailable. That is deliberately stricter than treating a local URI as a successful binding, but it is still not XMLDSIG validation.

DocFence does not decode or recompute a digest, execute a transform, verify a signature, inspect a certificate, establish trust, or predict an Office client. It checks only whether the stored declaration has the small standard shape necessary for the bounded coverage interpretation it reports.

version: 1
rules:
  require_complete_package_signature_coverage: true
  no_package_signature_coverage_changes: true

DFP092 and DFP093 are unchanged: they remain review gates over the declared package scope, not claims that an XML signature is valid or trusted.

Evidence and compatibility

The 69-test suite covers a binding with no transforms and both permitted canonicalization forms. It rejects unsupported, relationship, empty, and duplicate transform lists plus missing, misordered, malformed, nested, extra, and text-bearing digest children. DCAB’s complete reference adapter also passed against a fresh DocFence wheel.

A signed baseline from the public OOXML Signature Security artifacts retained complete bounded declaration coverage: six Word parts, one root-document relationship, and five Word relationships. The published content-injection, universal-signature-forgery, duplicate-document, and evil-type variants continue to expose uncovered or unavailable declaration surfaces. This is a stored-structure compatibility smoke test, never a trust or safety verdict.

Main and tagged CI both passed. Source and wheel artifacts were independently rebuilt under the commit timestamp and matched byte-for-byte; fresh wheel and source installations profiled the signed baseline, and the public GitHub release downloads were byte-compared with those verified artifacts.

python -m pip install https://github.com/SybilGambleyyu/docfence/releases/download/v0.48.0/docfence-0.48.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 define the exact boundary. Reliable static coverage starts by demanding that the standard’s binding is actually present, while being candid about all the cryptographic work it does not perform.