Release/10.6.0 - #660
Open
iobrado wants to merge 10 commits into
Open
Release/10.6.0#660iobrado wants to merge 10 commits into
iobrado wants to merge 10 commits into
Conversation
C2paManifestVerifier did two jobs: resolve every embedded stream the way a PDF reader would, and judge whether the bytes are a C2PA manifest. The structural walk becomes EmbeddedFileVerifier and the JUMBF logic moves unchanged into C2paPayloadValidator, behind a new EmbeddedPayloadValidatorInterface. Every embedded stream must now be accepted by at least one supplied validator. The walk also reads each file specification's /F and /UF name, taken from the innermost dictionary around the /EF, so validators can match on it. Names are optional, since C2PA signers often write neither. A name that is present but unreadable, duplicated, non-ASCII, or that differs between /F and /UF rejects the file, and so does one stream reached under two different names. No behaviour change for C2PA: the existing harnesses pass unmodified apart from class names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Europass CV builder attaches a machine-readable copy of every CV it exports as an embedded XML file, so the PDF scanner rejected all Europass CVs. The real export has no /Subtype, /AFRelationship or /AF, so only the payload bytes can identify it. StructuredXmlPayloadValidator accepts an embedded file only when its file specification name is an exact key in FILE_UPLOAD_PDF_STRUCTURED_XML_ALLOWLIST (attachment.xml, Europass-XML-Attachment.xml) and the payload: - is at most 5 MB, - is valid UTF-8 with no control bytes, - contains no DOCTYPE or ENTITY, - parses completely with XMLReader under LIBXML_NONET, - has the allowlisted root element and namespace. Opt-in through the new fileSecurityPdfAllowStructuredXml filter, which is off by default, compared with === true and requires qpdf, exactly as fileSecurityPdfAllowC2pa does. The diagnostics tab gains an xmlreader row. tests/security/verify-structured-xml.php is a dependency-free harness. Its fixtures are synthetic PDFs shaped like a real Europass export, so no real CV is committed. It covers: - name tricks, - spoofed roots and namespaces, - XXE and DTDs, - appended payloads, - executables under an allowlisted name, - mixed attachments, - the size cap and cost bounds, - the scanner's decision with and without the filter and qpdf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- EmbeddedFileVerifier::fileSpecName() now returns null (not false) for an unreadable /F or /UF, so a C2PA-only site no longer rejects a valid manifest just because its file name is non-ASCII, empty, or the /F and /UF values disagree. Only the XML validator needs a name, and it already rejects null. - Extract PDF literal/hex string reading (skipLiteral, readAsciiText) into a new PdfStrings helper, and the /EF owner-dictionary lookup into innermostOpenDictionary(), to keep EmbeddedFileVerifier focused on structure. - Config::FILE_UPLOAD_PDF_STRUCTURED_XML_ALLOWLIST entries use named root/namespace keys instead of positional tuples. - Add regression checks in verify-c2pa.php for the name-handling fix (absent, ASCII, non-ASCII UTF-16, mismatched, and empty names). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- EmbeddedFileVerifier: replace the by-reference innermostOpenDictionary() walk with owningDictionaryOffsets(), a pure single-pass lookup over all /EF offsets in a region. - Document that name pinning across two file specifications applies whichever payload validators are enabled, and add C2PA coverage for a stream reached under one name twice vs. two different names. - FileSecurityDiagnostics: mark the xmlreader extension row optional, matching the qpdf row. - Config: collapse the C2PA/structured-XML exemption docblock to one paragraph pointing at EmbeddedFileVerifier and its validators, rather than repeating their per-class docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Move the shared "structure only, not a safety proof" warning to the EmbeddedPayloadValidatorInterface docblock instead of repeating it in both C2paPayloadValidator and StructuredXmlPayloadValidator, and shorten several comments in EmbeddedFileVerifier and StructuredXmlPayloadValidator that restated what the adjacent code already shows. No behavioural change; all three security test harnesses still pass (57/22/56 checks). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each nesting level that owns an /EF re-read the dictionaries inside it, so a ~1 MB body with 100 nested Filespecs took 7.5s to verify. Name reads are now capped at twice the region; owners past the cap go unnamed, which the structured XML validator rejects and C2PA ignores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
composer test never executes tests/security, so attachment validation could break with CI green. The new job installs qpdf, generates the fixtures, runs all three harnesses and fails on any SKIP, so a missing qpdf or fixture cannot pass silently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An owner left unnamed by the name budget also fails the one-stream, one-name rule when the same stream is reached from a named owner, so the cap can reject a C2PA-only upload too, not just a structured XML one. PdfStrings has a single caller, so its header no longer claims several scanners share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add opt-in structured XML exemption for Europass CV PDFs
goranalkovic-infinum
approved these changes
Sep 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This adds an opt-in exemption so the PDF scanner stops rejecting Europass CVs, whose builder attaches a machine-readable XML copy of every CV it exports.
Why
attachment.xml, a<Candidate>document in thehttp://www.europass.eu/1.0namespace./Subtype,/AFRelationshipor/AF, so only the payload bytes can identify it./EmbeddedFilewas only exempt for C2PA manifests, so every Europass CV came back asvalidationFilePdfUnsafe.Embedded-file verifier refactor
C2paManifestVerifiertoEmbeddedFileVerifier, which keeps only the structural walk.C2paPayloadValidator.EmbeddedPayloadValidatorInterface(maxBytes(),accepts($payload, $name))./ObjStm,/RF, duplicate objects,/Filter, theendstreamextent check and the backstop.File specification names
/Fand/UFfrom the innermost dictionary that contains the/EF, with a single linear pass over the brackets./Fand/UF.attachment.xml, saved aspayload.exe".Structured XML exemption
StructuredXmlPayloadValidator, which checks in this order:attachment.xml,Europass-XML-Attachment.xml)<!DOCTYPEor<!ENTITYin the raw bytesXMLReaderparse withLIBXML_NONETConfig::FILE_UPLOAD_PDF_STRUCTURED_XML_ALLOWLISTandFILE_UPLOAD_PDF_STRUCTURED_XML_MAX_BYTES.fileSecurityPdfAllowStructuredXmlfilter, registered inFilters.php.trueand needs qpdf, the same asfileSecurityPdfAllowC2pa.xmlreaderrow to the File security diagnostics tab.Deliberately not exempted
Original.odt), which are ZIPs that can carry macros.attachfile/embedfile.Accepted residual risk
/JS,/Launch,/RichMediaand the other blocked keys still apply to the whole PDF.Tests
verify-c2pa.phpandverify-pdf-scanner.phpchanged only in class names, and every existing case passes.tests/security/verify-structured-xml.phpharness has 59 checks, and its synthetic fixtures mean no real CV is committed.1, and without qpdf.composer test,bun run lintJsandbun run lintStyleall pass.Changelog (for the release)
fileSecurityPdfAllowStructuredXmlfilter. When enabled, the PDF scanner accepts Europass CVs, whose only dangerous key is the embedded machine-readable CV XML. The file name must be on an allowlist and the payload must parse as the expected Europass document with no DTD. Disabled by default and requires qpdf, likefileSecurityPdfAllowC2pa.Known follow-ups
SkillsPassportformat against a real 2013–2020 export.tests/PHP files, because PHPStan excludestests/.🤖 Generated with Claude Code