Release note · August 7, 2026
A signature revision footer must be the PDF's terminal footer
A historical PDF signature should not be treated as broken merely because a later incremental update exists. Its /ByteRange normally stops at the %%EOF that closed the revision which added that signature—not at the file's current end. But there is an equally important converse: that claimed final revision footer must actually be terminal. A parser accepting arbitrary bytes after it is not evidence of a later revision.
PDFFence 1.23.0 hardens the existing PFP013 and PFP014 static review evidence around that distinction. It never turns a stored-layout observation into a claim that a signature is valid, trusted, permitted, or safe.
The missing terminal condition
The PDF 2.0 syntax errata require the last line of a file to contain %%EOF. The signature errata describe a multiple-signature ByteRange as ending at the marker that terminates the incremental update containing the signature dictionary, with a possible optional line ending.
Those statements leave room for normal incremental updates: an older revision footer is followed by the next real revision, with linked cross-reference data, startxref, and a terminal footer. They do not make an arbitrary tail a revision. Error-tolerant PDF consumers may ignore such bytes, which is exactly why positive review evidence must be stricter than parser acceptance.
What PDFFence now credits
For catalog-reachable AcroForm and catalog /Perms signature roots, PDFFence follows a confirmed /Prev-linked xref chain and identifies one bounded, unambiguous footer per linked revision. The final footer must end the physical source at %%EOF or its one optional EOL before any signature earns own-revision credit.
That rule preserves the useful historical case: an older signature can still pass after a valid later update. It fails closed if the chain is unavailable, ambiguous, malformed, over limit, or followed by unlinked bytes. PFP013 therefore remains the policy gate for whether a well-formed semantic ByteRange reaches its own revision. PFP014 continues to add its narrower requirement that a two-pair range omit exactly its own direct hexadecimal /Contents value.
version: 1
rules:
require_signature_byte_range_own_revision_coverage: true
require_contents_bound_own_revision_signature_coverage: true
The public result contains aggregate counts and generic findings only. It does not disclose offsets, object references, footer locations, ranges, signature bytes, digests, certificates, transforms, or trust decisions.
A benchmark that isolates the tail
PDF Change Assurance Benchmark 1.23.0 adds its 160th deterministic pair for this condition. Both PDFs contain a semantic signature followed by a valid later incremental update. Both ByteRanges remain well formed, reach their original signing footer, and are stale relative to the current physical file end.
Only the candidate then adds raw, unlinked bytes after the final PDF footer. The expected result is signature_own_revision_coverage_inventory_changed, PFP013, and generic stored-byte evidence. PFP014 has no redundant separate diff event: its endpoint prerequisite changed, and PFP013 already owns that fact. The held 1.22 release reports only stored-byte evidence for this new pair.
Evidence from a normal signed document
An isolated pyHanko exercise generated a self-signed signature and an ordinary incremental update. PDFFence retained the historical signature's own-revision and own-/Contents counts after the valid update. Appending raw bytes after the final footer preserved the parser's confirmed revision count but reduced both positive aggregates to zero. That is the intended separation: tolerant parsing is not positive terminal-footer evidence.
PDFFence 1.23 passed 260 tests, Ruff, and bytecode compilation; PDFCAB passed 90 tests and Ruff. The public process-bound score is 160/160. Both packages built byte-for-byte reproducibly with fixed timestamps, passed Twine checks, and passed clean paired wheel installs on Python 3.12 and 3.13 plus a Python 3.12 source-archive install. The released assets were re-downloaded, checksum-verified, and installed in a fresh Python 3.13 environment for another 160/160 score.
What this still does not establish
This is static boundary evidence, not cryptographic validation. PDFFence does not calculate a signed digest, validate /Contents, certificates, trust chains, transforms, permissions, or viewer behavior. It does not decide whether an update is authentic, authorized, benign, or malicious; use a conforming validator and the surrounding review process for those decisions.
The tagged policy reference, threat model, and validation record define the supported boundary.
python -m pip install https://github.com/SybilGambleyyu/pdffence/releases/download/v1.23.0/pdffence-1.23.0-py3-none-any.whl
pdffence check before.pdf after.pdf --policy pdffence.yml --format sarif
PDFFence is MIT-licensed and available on GitHub. The release assets include SHA-256 checksums for the wheel and source archive.