Release note · August 6, 2026

An action inventory needs an execution path too

By SybilGambleyyu · PDFFence 1.16.0

In a PDF review, an action count can look comfortably objective: count every dictionary whose /S value names an action. But the dictionary can be ordinary private application data. Its local shape does not establish that a viewer can execute it. PDFFence 1.16.0 applies the same path requirement to its public action inventory that 1.15 introduced for behavior-field evidence.

The remaining boundary leak

Version 1.15 correctly required a semantic document path before treating fields such as an action payload as behavior. Its generic object inventory, however, still counted every reachable action-shaped dictionary and every reachable /AA container. A private value below /PieceInfo could therefore manufacture active_content_inventory_changed, even though it was not a document action.

That distinction matters in an approval workflow. A reviewer should see that a document's stored bytes or reachable-object count changed. They should not be told that its active-content surface grew merely because a producer added metadata resembling an action.

Inventory follows a semantic owner

PDFFence 1.16 records action categories and additional-action-container counts only after bounded traversal establishes a supported execution owner: catalog actions, catalog JavaScript name-tree values, the actual catalog page tree and its annotations, AcroForm fields and widgets, outline items, or page presentation-step navigation nodes. A successor is included only through a /Next chain that begins at one of those roots.

The approach is deliberately narrower than recursively interpreting every dictionary in a PDF. It follows the format's context-sensitive meaning—an action name is meaningful because of its standard owner and route—not just a familiar key spelling. The relevant structures are described in the PDF 1.7 reference.

Two controls that would otherwise look active

The companion PDF Change Assurance Benchmark 1.16.0 adds two deterministic negative controls. One rewrites a private PieceInfo action subtype while keeping the object count fixed. The other adds a private /AA container and action. The expected results are, respectively, stored_pdf_bytes_changed, and reachable_object_count_changed plus stored_pdf_bytes_changed—not active-content inventory evidence or PFP001.

PDFFence 1.16.0 scores all 152 benchmark pairs. The released 1.15.0 wheel scores 150/152, adding an active-content inventory change to exactly these two private controls. Standard page additional-action and semantic action-chain inventory cases remain covered.

Validation and scope

PDFFence passed 227 tests and Ruff; PDFCAB passed 82 tests and Ruff. Two fixed-timestamp builds of each package were byte-for-byte reproducible and passed Twine metadata checks. Clean paired wheel installs on Python 3.12 and 3.13, plus a clean paired Python 3.12 source-archive install, passed dependency checks, fixture verification, and the complete 152-pair score.

This remains a static-review boundary, not a claim to emulate every PDF viewer or extension. PDFFence does not render documents, execute actions, follow URIs, or reveal private values in its public reports. 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.16.0/pdffence-1.16.0-py3-none-any.whl

pdffence init pdffence.yml
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.