Skip to content

feat(c2pa): custom continuity method support in manifest-box validation#54

Open
valentinamgiusti wants to merge 12 commits into
c2pa-merkle-validationfrom
c2pa-custom-continuity
Open

feat(c2pa): custom continuity method support in manifest-box validation#54
valentinamgiusti wants to merge 12 commits into
c2pa-merkle-validationfrom
c2pa-custom-continuity

Conversation

@valentinamgiusti

@valentinamgiusti valentinamgiusti commented Jul 15, 2026

Copy link
Copy Markdown

Adds support for implementer-defined continuity methods (C2PA §19.3.2) in validateC2paManifestBoxSegment:

  • New livevideo.continuityMethod.unsupported status code, emitted alongside the spec-mandated continuityMethod.invalid (§19.7.2) so consumers can distinguish an unverifiable custom method from a broken chain.
  • Optional continuityValidator: { method, validate } to register a validator for a custom method. The callback receives the decoded c2pa.livevideo.segment assertion (with its method-specific fields) and the parsed manifest. The built-in c2pa.manifestId method cannot be overridden.

Motivated by third-party PSM streams (e.g. EZDRM's com.ezdrm.anchor-chain) that validate as broken chain with no way to tell why.

Stacked on #53: depends on its shared exclusion-parsing refactor. Changelog entries added to the 1.1.0 section alongside VOD Merkle.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>
…dation

Signed-off-by: Valentina <valentina.giusti@qualabs.com>
Signed-off-by: Valentina <valentina.giusti@qualabs.com>
Signed-off-by: Valentina <valentina.giusti@qualabs.com>
Signed-off-by: Valentina <valentina.giusti@qualabs.com>
CONTINUITY_METHOD_INVALID's description was stale: a broken
c2pa.manifestId chain or a failed custom validator report
SEGMENT_INVALID, not this code. Fix the table and note SEGMENT_INVALID's
continuity-related sources too. Also move this PR's changelog entries
from the 1.1.0 section (cut by streaming-video-technology-alliance#378's release commit) to Unreleased,
since streaming-video-technology-alliance#378 may publish before this merges. Addresses review feedback
on streaming-video-technology-alliance#379.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>
The existing custom-method tests patch a signed fixture in place,
which breaks its hashedURI binding and always adds
assertion.hashedURI.mismatch; none of them can reach isValid: true.
Build an unsigned segment with its own matching c2pa.livevideo.segment
and c2pa.hash.bmff.v3 assertions instead, so a registered validator
can be exercised on a genuinely clean happy path. Addresses review
feedback on streaming-video-technology-alliance#379.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>
@valentinamgiusti
valentinamgiusti force-pushed the c2pa-custom-continuity branch from 15db728 to b367fec Compare July 21, 2026 17:46
littlespex and others added 5 commits July 21, 2026 10:47
…echnology-alliance#387)

Bump @microsoft/api-extractor 7.53.1 -> 7.58.12 (drops lodash entirely,
pins patched minimatch 10.2.3) and eslint-plugin-tsdoc 0.4.0 -> 0.5.2
(moves to @microsoft/tsdoc-config 0.18.1 with patched ajv). All other
fixes are lockfile-only updates within existing semver ranges.

Fixes Dependabot alerts (all development-scope, package-lock.json):
- js-yaml 4.1.0 -> 4.3.0 (streaming-video-technology-alliance#72 high, streaming-video-technology-alliance#69 medium, #31 medium)
- brace-expansion 1.1.12 -> 1.1.16, 2.0.2 -> 2.1.2 (streaming-video-technology-alliance#70, streaming-video-technology-alliance#71 high)
- ws 7.5.10 -> 7.5.13 (streaming-video-technology-alliance#66 high)
- markdown-it 14.1.0 -> 14.3.0 (streaming-video-technology-alliance#65 medium)
- lodash removed from tree (streaming-video-technology-alliance#63 high, streaming-video-technology-alliance#62 medium, #34 medium)
- defu 6.1.4 -> 6.1.7 (streaming-video-technology-alliance#61 high)
- picomatch 2.3.1 -> 2.3.2, 4.0.3 -> 4.0.5 (streaming-video-technology-alliance#58, streaming-video-technology-alliance#56 medium)
- flatted 3.3.3 -> 3.4.2 (#54 high)
- minimatch 3.1.2 -> 3.1.5, 9.0.5 -> 9.0.9, 10.0.3 -> 10.2.3 (#51, #49, #46 high)
- rollup 4.52.5 -> 4.62.2 (#48 high)
- koa 2.16.3 -> 2.16.4 (#47 high)
- min-document 2.19.0 -> 2.19.2 (#29 low)

Also clears remaining npm audit findings not yet surfaced as alerts:
ajv ReDoS (GHSA-2g4f-4pwh-qvx6), diff DoS (GHSA-73rr-hh4g-fpgx),
yaml stack overflow (GHSA-48c2-rrv3-qjmp). npm audit now reports 0
vulnerabilities.

Signed-off-by: Casey Occhialini <1508707+littlespex@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…logy-alliance#378)

* refactor(c2pa): extract shared exclusion parsing and uuid helpers

Move parseExclusions to bmff/, and toUint8Array, asInteger, matchesUuid,
JUMBF_UUID to utils so upcoming Merkle validation can reuse them.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* feat(c2pa): add VOD Merkle segment validation

validateC2paMerkleSegment verifies fragmented MP4 media segments
against the init manifest merkle maps (C2PA §15.12.2 / §18.6):
parses the auxiliary uuid box (spec 2.4 and c2pa-rs formats),
recomputes the leaf hash, walks the Merkle proof, and enforces
location continuity via caller-held state. Adds status codes
assertion.bmffHash.malformed / .mismatch.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* feat(c2pa): extract merkle maps in init segment validation

validateC2paInitSegment reads the merkle field of the
c2pa.hash.bmff.v3 assertion, validates each entry's initHash binding
against the raw init bytes, and returns the maps as merkleMaps.
VOD Merkle streams no longer report SESSIONKEY_INVALID.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* chore(c2pa): release 1.1.0

Changelog, version bump, and API report for VOD Merkle validation.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* docs(c2pa): shorten merkle validation comments

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* refactor(c2pa): move merkle map extraction to the merkle module

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* docs(c2pa): tighten validateC2paMerkleSegment doc comment

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* fix(c2pa): bound merkle proof path to the committed row and reject unconsumed hashes

Bound the proof path against the committed manifest row instead of
the full tree depth, and reject inputs that leave unconsumed proof
hashes. Addresses review feedback on streaming-video-technology-alliance#378/streaming-video-technology-alliance#379.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* fix(c2pa): stop advancing merkle location state on discontinuity

Only advance per-track location state when the location continuity
check also passes, not just the Merkle proof. Addresses review
feedback on streaming-video-technology-alliance#379.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* fix(c2pa): parse the spec-defined merkle auxiliary box format only

Per §A.5.1.2/A.5.4.1.4, the auxiliary 'uuid' C2PA box for merkle data
is the same ContentProvenanceBox used for the manifest box (version/
flags + null-terminated box_purpose + raw data), not a second box
type with a bare CBOR map. Remove the fabricated MERKLE_AUX_UUID and
its bare-CBOR-map parser, and the invented integer-key path in
bmff-merkle-map (the CDDL only defines string keys). Addresses review
feedback on streaming-video-technology-alliance#378.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* fix(c2pa): tolerate fixed-size padding after the merkle CBOR item

§A.5.4.1.4 requires padding auxiliary uuid boxes to a fixed size when
a Merkle tree has more than one. cbor-x's decode() throws on trailing
bytes, so a spec-conforming padded box was flagged malformed. Decode
via decodeMultiple and stop after the first item instead. Addresses
review feedback on streaming-video-technology-alliance#378.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* fix(c2pa): flag malformed BMFF hash when merkle initHash or alg is missing

Per the merkle-map CDDL, initHash is required for fragmented assets
(the only case validateMerkleMaps handles), and alg has no default
when absent from both the entry and the enclosing assertion. Both
were previously tolerated silently: a missing initHash left the init
segment unbound, and a missing alg defaulted to SHA-256. Addresses
review feedback on streaming-video-technology-alliance#378.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* refactor(c2pa): export and reuse BmffHashExclusion in MerkleMap

BmffHashExclusion/BmffHashConstraint were already @public but not
exported from the barrel, so MerkleMap.exclusions duplicated their
shape inline. Export them and reuse the type to avoid drift. No
runtime or shape change. Addresses review feedback on streaming-video-technology-alliance#378.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* docs(c2pa): document merkle location state and its status code reuse

Rename MerkleSegmentState.lastLocation to lastLocations (it's a map
of per-track locations) and document the uniqueId:localId key format
and the leniency on the first location seen per track. Also document
that VOD Merkle reuses livevideo.assertion.invalid for per-track
location discontinuity, which its §19.7.2 doc comment didn't cover.
Addresses review feedback on streaming-video-technology-alliance#378.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* docs(c2pa): fix BMFF hash status code citation and document them

C2PA 2.3, which this package targets, has no Annex D — the failure
code table lives at §15.2.2.3. Fix the citation on both BMFF hash
codes, add them to results-and-error-codes.md, and note the
SESSIONKEY_INVALID behavior change for VOD Merkle streams. Addresses
review feedback on streaming-video-technology-alliance#378.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* refactor(c2pa): require non-empty merkleMaps instead of returning null

The only caller of this validator (SegmentRouter in the toolkit)
already gates on merkleMaps.length > 0 before invoking it, so the
null return for the empty case was dead weight at every call site.
Document the precondition instead. Addresses review feedback on streaming-video-technology-alliance#378.

BREAKING CHANGE: validateC2paMerkleSegment no longer returns null.
Callers that branched on a null result should remove that check.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* fix(c2pa): resync merkle continuity after a proof-verified gap

Freezing the location baseline on any discontinuity (to stop a
replay from laundering itself as the new baseline) had a worse side
effect: one out-of-order or dropped segment left every later segment
flagged relative to a stale baseline for the rest of the session,
even when those later segments were sequential among themselves.

Advance the baseline on any segment whose Merkle proof verifies,
discontinuous or not — it's genuinely signed content, so a forward
gap resyncs immediately instead of cascading. A replayed/reordered
segment is still flagged invalid at the point it occurs.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* refactor(c2pa): remove dead malformed-flag plumbing in aux box extraction

readAuxPayload can only return a payload or 'other' since 5046af2
removed the bare-CBOR-map parser that used to signal 'malformed';
the flag was always false. Simplify extractMerkleAuxBoxes to return
just the payloads array, and note the unplumbed claim-level alg
fallback in validateMerkleMaps. Addresses review feedback on streaming-video-technology-alliance#378.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* Update CHANGELOG with version 1.1.0 details

Removed unreleased version section and added details for version 1.1.0.

Signed-off-by: Casey Occhialini <1508707+littlespex@users.noreply.github.com>

* chore(c2pa): revert manual version bump

Version bumps go through a separate release script, not feature
PRs. Addresses review feedback on streaming-video-technology-alliance#378/streaming-video-technology-alliance#379.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

* docs(c2pa): add VOD Merkle validation guide

The README still described only the two segment-level validation
methods and didn't link a dedicated guide for VOD Merkle, unlike VSI
and Manifest Box. Add docs/merkle-validation.md following the same
structure, and update the README's method list and doc links.
Addresses review feedback on streaming-video-technology-alliance#378.

Signed-off-by: Valentina <valentina.giusti@qualabs.com>

---------

Signed-off-by: Valentina <valentina.giusti@qualabs.com>
Signed-off-by: Casey Occhialini <1508707+littlespex@users.noreply.github.com>
Co-authored-by: Casey Occhialini <1508707+littlespex@users.noreply.github.com>
Signed-off-by: Casey Occhialini <1508707+littlespex@users.noreply.github.com>
…lback (streaming-video-technology-alliance#388)

Fixes all five open code scanning alerts:

- isCmcdCustomKey (alert #7): the ambiguous pattern
  /^[a-zA-Z0-9-.]+-[a-zA-Z0-9-.]+$/ backtracked quadratically on inputs
  like '---...-!'. Replaced with a single unambiguous character-class
  regex plus an interior-hyphen index check. Behavior is unchanged
  (verified by exhaustive sweep over all strings up to length 5).

- iso8601DurationToNumber (alerts #4-6): the unanchored /([.,\d]+)H/
  patterns rescanned from every start position, going quadratic on
  long digit/comma runs. Each pattern now requires a run boundary via
  a (?:^|[^.,\d]) prefix, which pins matches to the start of a number
  run and keeps scanning linear. Matches and captures are unchanged.

- uuid (alert #3): the final fallback derived UUIDs from Math.random.
  It now uses crypto.getRandomValues with RFC 4122 version/variant
  bits, formatted through the existing arrayBufferToUuid helper. The
  blob URL trick remains as the last resort and Math.random is gone.
  This also fixes the fallback in Node, where blob URLs contain no
  slash and the previous code returned the whole blob: URL.

Adversarial inputs of 50k characters drop from 4.7s / 14.2s to under
2ms, scaling linearly (10M chars in ~40ms).

Signed-off-by: Casey Occhialini <1508707+littlespex@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants