Release note · August 7, 2026
A PDF's terminal footer is a review boundary
A PDF parser accepting a file is not the same as a review system having positive structural evidence about that file. In particular, a parser can tolerate raw bytes after a final %%EOF marker. Those bytes are not automatically a valid PDF revision—and they should not disappear into a generic byte-difference result when a team needs to decide whether a handoff has a clean structural end.
PDFFence 1.24.0 adds PFP015, require_terminal_revision_footer: a privacy-safe, fail-closed policy for that condition. It is a stored-layout review boundary, not PDF conformance or signature validation.
What terminal means
The PDF 2.0 syntax errata describe the last line of a file as %%EOF. PDFFence follows the structurally confirmed /Prev-linked cross-reference chain and maps one bounded, unambiguous footer for each linked revision. It then exposes exactly one redacted status:
terminal: the map is established and the final linked footer ends the physical source, apart from its optional EOL;nonterminal: the map is established, but raw bytes follow the final linked footer; orunavailable: the bounded footer map cannot be established.
The public diff event is revision_terminal_footer_changed. It contains only those status labels—never a footer location, offset, object reference, source bytes, or a hash.
Independent from “one revision only”
PFP015 intentionally does not mean “reject all incremental PDFs.” A normal later update with linked cross-reference data can have two or more revisions and still be terminal. Conversely, a PDF with one confirmed revision can have unlinked trailing bytes. That is why the new policy is distinct from PFP006, require_single_revision.
version: 1
rules:
require_terminal_revision_footer: true
This is useful for teams that accept normal PDF history but require every submitted source to end at a structurally linked footer. The check also runs on the structural information available before an encrypted document is left uninspected; it neither decrypts the source nor needs a signature to be present.
Why this also matters for signatures
The PDF 2.0 signature errata describe a historical signature's ByteRange as ending at the footer of the update that added its signature dictionary. PDFFence's existing PFP013 and PFP014 evidence already needs a terminal footer map before granting own-revision credit. PFP015 makes that prerequisite independently visible for every PDF, including unsigned ones.
This preserves the useful historical case: a correctly bounded older signature can pass after a valid later incremental update. But an arbitrary tail after the final linked footer cannot preserve positive own-revision evidence merely because a tolerant parser accepts it.
A 161-pair benchmark contract
PDF Change Assurance Benchmark 1.24.0 adds an unsigned terminal-footer control. Its baseline is a normal one-revision PDF; the candidate appends unlinked bytes after the final footer without adding objects, a cross-reference section, or a trailer. Both revision counts remain confirmed at one. The expected result is only revision_terminal_footer_changed, stored-byte evidence, and PFP015.
The existing semantic-signature tail control now expects that same general event alongside signature_own_revision_coverage_inventory_changed, while retaining its focused PFP013 policy contract. This keeps the general source-boundary signal separate from the signature-specific coverage signal.
PDFFence passed 264 tests, Ruff, and bytecode compilation; PDFCAB passed 91 tests and Ruff. The public process-bound score is 161/161. 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 161/161 score.
Evidence, not a security verdict
PFP015 does not authenticate a revision, validate a signature, calculate a digest, inspect /Contents, validate certificates or trust chains, evaluate transforms or permissions, establish PDF conformance, or predict viewer behavior. It does not decide whether a linked update is 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.24.0/pdffence-1.24.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.