Release note · August 6, 2026

A PDF action key needs a path, not just a name

By SybilGambleyyu · PDFFence 1.15.0

A key called /A is not enough to make a PDF dictionary executable. Nor is /AA, /NA, or /PA. In PDF, the owner and route through the document determine the key's meaning. PDFFence 1.15.0 makes that route part of its private active-content evidence.

The same name can mean different things

The distinction is concrete. A navigation node's /NA and /PA actions are meaningful only in a page's /PresSteps navigation structure. A Link annotation's /PA, in contrast, is an archived Web Capture URI rather than an action trigger. Private application data can also contain action-shaped dictionaries under any of those names.

That is why a generic object walk cannot decide behavior from local shape alone. It must first establish a standard document path. The boundary follows the PDF 1.7 reference's separate definitions for presentation navigation and Link annotation data; PDFFence links the relevant behavior to the path instead of treating the spelling of a key as proof of execution.

What PDFFence now recognizes

Version 1.15 performs bounded semantic traversal from catalog document-open and additional actions, catalog JavaScript name-tree values, the actual catalog page tree and its annotations, AcroForm fields and widgets, outline items, and page presentation-step navigation nodes. Its positional /Next handling starts only after one of those roots has been established.

The result is intentionally selective. A private PieceInfo payload shaped like a Link action remains visible as a stored-byte change, but it does not produce active_content_payload_changed or PFP001. Conversely, an unchanged public inventory does not conceal a URI rewrite when it occurs at a supported page, Link, field, outline, or navigation-node action root.

Five negatives and six positives

The companion PDF Change Assurance Benchmark 1.15.0 grows to 150 deterministic pairs. Five passive controls cover four private PieceInfo trigger lookalikes—/A, /AA, /NA, and /PA—and a real Link annotation's archival /PA. They require only stored_pdf_bytes_changed.

Six matching positive controls place a URI action at a real page additional-action, Link direct-action, field additional-action, outline action, or navigation-node next/previous path. They retain the public action inventory while requiring generic active-content evidence and PFP001. PDFFence 1.15.0 scores 150/150; the released 1.14.0 wheel scores 145/150, adding active-content evidence to exactly the five passive controls.

Validation and scope

PDFFence passed 221 tests and Ruff, while PDFCAB passed 80 tests and Ruff. Two fixed-timestamp builds of each package were byte-for-byte reproducible and passed Twine metadata checks. Fresh wheel installs on Python 3.12 and 3.13, plus a Python 3.12 source-archive install, verified and scored the complete fixture corpus.

This is a scoped static-review improvement, not a claim to model every PDF viewer or every action-bearing extension in the format. PDFFence does not render documents, execute actions, follow URIs, open attachments, or infer a viewer's permission decisions. The tagged policy reference, threat model, and validation record state the exact supported boundary.

python -m pip install https://github.com/SybilGambleyyu/pdffence/releases/download/v1.15.0/pdffence-1.15.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.