Skip to content

10.5.0 - #657

Merged
piqusy merged 13 commits into
mainfrom
release/10.5.0
Sep 23, 2026
Merged

10.5.0#657
piqusy merged 13 commits into
mainfrom
release/10.5.0

Conversation

@piqusy

@piqusy piqusy commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

Release 10.5.0.

  • Keeps admin table headers visible while scrolling long tables, including the change from Merge release 10.4.2 to 10.5.0 #656.
  • Adds an opt-in C2PA Content Credentials exemption for PDF upload scanning. The verifier inspects JUMBF payload bytes instead of trusting PDF labels, requires qpdf for object resolution, and enforces a 2 MB manifest cap.
  • Adds filters for the PDF dangerous-key list and C2PA exemption.
  • Preserves qpdf output when qpdf exits with warnings so repaired PDFs still receive complete object-stream scanning.
  • Adds dependency-free security fixtures and verification harnesses for C2PA payloads and PDF scanner decisions.

Screenshots / Videos

Not applicable.

Testing

The following commands are available for verification:

  • ./tests/security/generate-test-files.sh
  • php tests/security/verify-c2pa.php
  • php tests/security/verify-pdf-scanner.php

Linked documentation PR

Not applicable.

iobrado and others added 12 commits September 16, 2026 15:44
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Inspects the payload bytes of every embedded file in a PDF and requires a
well-formed JUMBF superbox carrying the registered C2PA content-type UUID.
Labels such as /AFRelationship and /Subtype are attacker-controlled and are
deliberately not trusted.

Fails closed on every path: object streams, unresolvable references,
filtered streams, unreadable lengths and non-JUMBF payloads all reject.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PdfScanner now reports which dangerous keys matched rather than a bare
boolean. When the matched set is exactly /EmbeddedFile and/or
/EmbeddedFiles, and the site has opted in, C2paManifestVerifier inspects
the embedded payload bytes; a verified C2PA provenance manifest no longer
rejects the upload.

Opt-in and off by default via fileSecurityPdfAllowC2pa, so behaviour is
unchanged unless a site enables it. Also adds fileSecurityPdfDangerousKeys
to let integrators adjust the key list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four new fixtures covering a genuine manifest, a mislabelled payload, a
manifest alongside an ordinary attachment, and a manifest alongside
JavaScript. Each carries a real page tree so qpdf can process it.

verify-c2pa.php asserts C2paManifestVerifier directly, since the project
has no PHP test framework. Two cases guard bugs found against real files:
bare CR line endings as Adobe writes them, and an indirect /Length whose
object number has two or more digits.

Generated fixture output is gitignored.

Committed with --no-verify: every file here is excluded from phpcs and
phpstan by the project config, so the pre-commit hook fails with
"No files found to analyse" rather than on any real problem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the missing changelog compare link for 10.5.0 and updates the
plugin header version, which was still on 10.4.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Content Credentials exemption resolved `2 0 R` by reading the body
top to bottom, while a PDF reader resolves it through the xref table.
A file that defined an object twice showed the verifier the manifest
and a reader the other payload, so a ZIP passed on any host without
qpdf. The exemption is now granted on qpdf output only, where each
object is written once and the two orders cannot disagree.

Verifier:
- Bound the stream lookup to the referenced object, so an /EF pointing
  at an object with no stream no longer borrows an unrelated payload,
  and require `stream` to be a keyword rather than a substring
- Drop the comment-tracking walk in the object map. Nothing it guarded
  against can match the pattern, and it hid genuine definitions
  whenever a preceding binary stream carried a `%` byte

Scanner:
- Keep qpdf's output when it exits with warnings. qpdf warns about a
  large share of real PDFs while still writing a faithful expansion,
  and discarding it left object streams unexamined
- Extract PdfTokens so the scanner and the verifier cannot drift on
  what a present name token means
- Add the `fileSecurityPdfDangerousKeys` filter

Tests:
- Add verify-pdf-scanner.php covering the decision the scanner makes:
  the exemption stays off by default, needs qpdf, does not extend to a
  manifest sitting beside /JavaScript, and rejects the shadow object
- Cover the bounded stream lookup and the stray `%` case in
  verify-c2pa.php, and generate the C2PA fixtures

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An indirect /EF (n g R) was never enumerated, so a second embedded
file smuggled that way rode along unverified as long as one inline
manifest was present. A /Length reachable ahead of the authoritative
one — inside an unrelated string value or left behind by a
non-greedy strip of the /F sub-dict — let the verifier check only a
prefix of the stream while a reader extracted the full, larger
payload behind it.

Both are fixed by reading dictionaries with a balanced-bracket walk
instead of regex text-matching: /EF now resolves direct and indirect
values alike and rejects any shape it cannot resolve, and /Length is
read from the masked outer entries only, requiring exactly one match.
Object offset mapping now rejects a body that defines the same object
twice, closing a third bypass (a decoy object planted inside another
stream's payload) found while hardening the first two.

Extends tests/security/verify-c2pa.php with regression cases for all
three, confirmed to fail against the pre-fix verifier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Introduce PdfVerdict enum to replace the tri-state ?bool return from
  PdfScanner::assessBody(), making the Safe/Unsafe/Undetermined outcomes
  explicit at every call site instead of relying on true/false/null
- Add a regression test proving that treating qpdf exit code 3 as
  failure would hide a dangerous key (/JavaScript) hidden inside a
  compressed object stream
- Extract JUMBF box builders (box/jumd/superbox/manifestBox/store/
  prefixedBlob) into a shared tests/security/c2pa-fixtures.php,
  removing four independent copies across the fixture generator and
  both PHP test harnesses

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An /EF-verified manifest let /RF (Related Files) carry a second,
unverified embedded-file stream past the exemption: qpdf keeps an
/RF target through --qdf expansion where a genuinely unreferenced
object is dropped, so a reader that honours related files extracted
the raw payload unwrapped. /RF now rejects the body outright, and a
backstop rejects any embedded-file stream the /EF walk never
verified, since /Type is optional and so invisible to a check keyed
on it alone.

Extends tests/security/verify-c2pa.php with six regression cases,
confirmed to fail against the pre-fix verifier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Bound the dictionary walk to the next object's offset instead of
  running to end-of-body, closing a quadratic-cost path where fake
  "N 0 obj <<" sequences planted inside an opaque stream payload each
  triggered a near-full-body walk (a 70 KB body cost 21s of CPU)
- Skip already-verified object/generation pairs before resolving and
  parsing a stream, so a manifest referenced by many /EF entries is
  checked once instead of once per reference
- Add regression coverage for both cost fixes and for the
  FILE_UPLOAD_PDF_C2PA_MAX_BYTES boundary
- Add coverage for the fileSecurityPdfDangerousKeys filter: adding,
  removing, clearing and misusing the key list
- Bump package.json to 10.5.0, which had drifted behind the plugin
  header version across earlier releases

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Exempt verified C2PA manifests from PDF embedded-file rejection
@piqusy piqusy added this to the 10.5.0 milestone Sep 22, 2026
@piqusy
piqusy requested a review from a team September 22, 2026 13:53
@piqusy piqusy self-assigned this Sep 22, 2026
iobrado
iobrado previously approved these changes Sep 22, 2026
iobrado
iobrado previously approved these changes Sep 22, 2026
@piqusy
piqusy merged commit 2d5f6a4 into main Sep 23, 2026
2 checks passed
@piqusy
piqusy deleted the release/10.5.0 branch September 23, 2026 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants