Release note · August 6, 2026

A PDF dictionary is not necessarily an action

By SybilGambleyyu · PDFFence 1.14.0

A dictionary with /Type /Action, /S /URI, and /URI looks like a PDF action. That shape alone does not establish that a viewer can reach it. It may instead be application-private document data. PDFFence 1.14.0 makes that boundary explicit: it tracks action behavior only when the dictionary is reached through a standard action trigger.

Shape is not an execution path

The catalog PieceInfo entry is intended to carry application-specific piece data. A producer can place arbitrary private data under a standard PieceInfo record, including a dictionary whose keys happen to resemble an action. That is different from a document-open action, an annotation or page action, an additional action, or a continuation in an action Next chain.

The generic PDF object walk must still see that private data so a file-byte rewrite is not invisible. In earlier releases, however, an eligible behavior field in such an action-shaped dictionary could enter PDFFence's private active-content signature merely because of its shape. A change then produced active_content_payload_changed and policy finding PFP001, despite no standard action root reaching it.

What 1.14 changes

PDFFence now admits type-specific action behavior fields to the active-content comparison only in a recognized root context. The existing positional handling of Next action chains remains intact. Private PieceInfo data is therefore not silently trusted: changing it still produces the generic stored_pdf_bytes_changed result. It simply no longer claims an active-content behavior rewrite without an execution path.

This is a selectivity correction, not a claim about any viewer or an assertion that private data is harmless. PDFFence does not render PDFs, execute actions, follow URIs, open attachments, or infer viewer permission decisions. It is a static review gate whose active-content findings are deliberately scoped to standard trigger reachability.

Eleven paired controls, not one special case

The companion PDF Change Assurance Benchmark 1.14.0 adds 11 deterministic pairs. Each places an action-shaped dictionary under catalog PieceInfo private data, omits OpenAction, and changes one behavior-bearing field: Thread, URI IsMap, Sound, Movie, Hide, Named, SubmitForm, ResetForm, Rendition, Trans, or RichMediaExecute.

For every pair, the expected semantic result is byte-level only. PDFFence 1.14.0 scores 139/139 across the packaged corpus. The released 1.13.0 wheel scores 128/139, flagging exactly these 11 controls under its former shape-based treatment. The same behavior fields still trigger review when they occur in the supported document-open action path.

Validation and use

The release passed 207 tests and Ruff, while PDFCAB passed 69 tests and Ruff. Fixed-timestamp wheel and source-archive builds were byte-for-byte reproducible, passed Twine metadata checks, and were exercised by fresh wheel installs on Python 3.12 and 3.13 plus a Python 3.12 source-archive install. Each clean install verified and scored all 139 fixture pairs.

The exact trigger boundary and report-privacy guarantees are documented in the tagged policy reference, threat model, and validation record.

python -m pip install https://github.com/SybilGambleyyu/pdffence/releases/download/v1.14.0/pdffence-1.14.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. It is not a renderer, malware sandbox, signature validator, or proof that two PDFs are safe or visually identical.