feat(c2pa): add native C2PA §19.3/§19.4 provenance validation for live streams - #164
feat(c2pa): add native C2PA §19.3/§19.4 provenance validation for live streams#164valentinamgiusti wants to merge 36 commits into
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
* Add a check to verify that the npm bundle works in a sample application * Fix broken default export in modern ESM bundles. libraryExport: 'default'` made webpack emit the ESM bundle's default export as `dashjs.default`, which does not exist — so `import dashjs from 'dashjs'` was `undefined` for all ESM consumers; only named imports worked. Remove the option from the ESM config so the entry namespace is exported as-is. UMD configs keep it, where it is correct. * Fix findings identified by Fable5 code review * Do not test npm package for each PR * Fix parsing error * Revert prepack command
* Use other free port if the default debugging port is taken * Pass timestampOffset in Textsourcebuffer to fix wrong position of text cues in multiperiod content * Calculate right cue start and endtime * Add unit tests
* Migration to vitepress for the docs, initial commit * Additional documentation changes * Adjust index of docs * Update feature list * Update CMCD and DRM info * Add information on server certificates * Update landing page of the docs * Rebuild docs when source folder is updated
* Fix JSDoc errors * Upgrade node version and test GH actions * Use right branch * Fix URL to API docs * Revert action trigger to development branch
* Pin all GitHub actions to specific package versions for security reasons * Refine the workflow to publish to NPM * Fix zizmor warnings
N1Knight
left a comment
There was a problem hiding this comment.
Reviewed against the dash.js contribution rules (CONTRIBUTING.md, AGENTS.md, FactoryMaker/context DI, Settings.js + index.d.ts parity, samples.json registration, BSD-3 headers) and against clean-code/SOLID criteria. I did not review the C2PA cryptography itself — that is correctly delegated to @svta/cml-c2pa.
What is already right: every new file carries the BSD-3 header, all modules follow the closure-factory + context pattern, the settings are added to both Settings.js and index.d.ts, the sample is registered in samples.json, the test files follow the streaming.c2pa.* mirror naming, npx eslint src/streaming/c2pa test/unit/test/streaming/streaming.c2pa.*.js is clean, and the 58 new unit tests pass. Nice use of the public response-interceptor API instead of patching the fetch-to-MSE path, and the C2paDetector strategy seam is the right call.
Detailed comments inline. Grouping them by weight:
Must fix before merge
_checkSequencebuilds an unbounded array from a sequence number taken from a segment that may have failed validation — a forgedsequenceNumberfreezes the tab. This is the one finding I'd call a blocker, because it is triggerable by exactly the attacker this feature exists to detect.- C2PA state is never reset on source change, and
trackKeyis derived from the URL filename, so two streams whose segments are both namedchunk-stream0-*.m4sshare state (stale session keys, stalelastManifestId, stale sequence number). - Two docstrings describe wiring that does not exist (
reset()"on source change",resetSequenceForTrack()"on seek / period change").
Should fix
4. Silent failure modes: a failed engine import, a swallowed interceptor error and a swallowed box-parse error all produce zero diagnostics, and none of the five new modules uses the dash.js Debug/logger that 14 of 20 streaming/controllers/ modules use.
5. optionalDependencies + a literal specifier in import() — please confirm npm ci --omit=optional && npm run build still succeeds.
6. Dead code: C2paEvents.js has no importers anywhere, and C2paOptions' normalization API has no production callers (only tests), while its stated job is duplicated inline in two modules.
Design / nits
7. C2paValidationCoordinator at 614 lines carries six responsibilities; extracting a sequence tracker and a record factory would also remove five near-duplicate record literals and the parameter mutation.
8. Segment bytes are copied two to three times per media segment.
9. ADR-0002 and 12 AC#NN references point at documents that are not in this repo.
None of this is structural rework — the module boundaries are sound.
|
Addressed all three must-fix items, all three should-fix items, and all the design/nits, across several commits. Must fix
Should fix
Design / nits
On the |
…for ManifestBox segments An unrecognized continuityMethod and a broken manifest-id chain both surfaced as continuityInvalid, even though the CML engine always pairs CONTINUITY_METHOD_UNSUPPORTED with CONTINUITY_METHOD_INVALID for the former. Split them so the sample can render unsupported as a distinct warning instead of an error.
…olkit in the sample
…essage - Empty URL field by default, with two live example buttons (§19.4 VSI / §19.3 Manifest Box) that fill the URL and load immediately - Match the repo's sample-side-panel convention instead of custom CSS - Clarify the continuityUnsupported details message
Must-fix: - Cap the sequence gap loop and only sequence-check valid records, so a segment that fails validation can't claim a forged sequenceNumber and trigger an unbounded loop/OOM - Stop mutating the emitted record in place - Reset per-track C2PA state on attachSource() via a new C2paController.resetForNewSource(), and clear initPromises in reset() (two different streams can otherwise share a filename-derived trackKey) - Wire PLAYBACK_SEEKED/PERIOD_SWITCH_COMPLETED to reset active tracks' sequence state, matching what the docstrings already claimed Should-fix: - Add Debug/logger to the three previously-silent catches - Emit C2PA_ERROR (c2pa.engineUnavailable) once per session if the validation engine fails to load, instead of degrading silently forever - Document as a known limitation that a malformed init segment reads the same as a never-signed one (the engine has no stable, documented way to tell them apart, only an unstable exception message) - Move @svta/cml-c2pa from optionalDependencies to dependencies: the dynamic import() uses a literal specifier, so webpack resolves it at build time regardless, and npm ci --omit=optional broke the build - Wire normalizeC2paOptions as the actual single normalization point instead of three ad-hoc reimplementations; drop the now-dead isValidC2paMethod/getDefaultC2paOptions non-usage; trim C2paEvents.js to its typedefs (the runtime object had no importers) Nits: - Fix _copySegmentBytes and BoxParsingDetector._toArrayBuffer to copy the segment bytes once instead of two or three times - Remove dangling AC#NN / ADR-0002 references to documents not in this repo - Move SEGMENT_KIND_INIT/MEDIA to C2paOptions.js so the scanner and coordinator share one definition of their contract - Drop defensive typeof settings.get checks not used anywhere else in the codebase; use Constants.VIDEO/AUDIO instead of raw string literals - Fix _resolveMediaMethod: a track already classified from its init now trusts that classification instead of re-parsing every media segment through the detector, matching the documented "auto" behavior
A chip is appended per segment and never pruned; on a long-running live stream that's thousands of nodes each holding a closure over its record.
…ilding The coordinator owned six responsibilities in one 614-line closure; pull out the two pieces that are genuinely independent of validation: - C2paSequenceTracker: the signed-sequence-number bookkeeping (replay / reorder / gap detection), with its own test file. This is where the DoS fix lives, so it's worth testing directly rather than only through the coordinator. - _createSegmentRecord: a single default SegmentRecord shape used by all five record builders instead of each repeating the same 11 fields, with an injectable clock (config.now) so tests can assert timestamps.
Matches protectionController/metricsReportingController, which are both reset and nulled; c2paController was only reset. Harmless in practice (it's a singleton re-fetched on the next initialize()), but the asymmetry was worth removing.
9366b8a to
1988f6c
Compare
Video and audio chips were interleaved in one row with the media type only in the tooltip, so a track validating both read as a jumbled sequence.
1988f6c to
e30444c
Compare
Summary
Adds native, opt-in C2PA provenance validation for live CMAF/DASH streams, per C2PA 2.4 §19, "Live Video". A live stream isn't one complete file you can hash and sign upfront: segments go out continuously as the broadcast happens. §19 solves this by making each segment provable on its own, and gives two ways to do it. We support both, plus auto-detection between them.
uuidbox, hashed withc2pa.hash.bmff.v3and chained to the previous segment (previousManifestId,continuityMethod).emsgbox.Both bind the init segment to its media segments (hash covers the
moovbox) to stop replay/substitution attacks, so our per-track state (session keys, sequence/continuity baseline) is scoped to that binding and gets reset on source change.Off by default behind
streaming.c2pa.enabled, it's brand new. While disabled nothing gets parsed and@svta/cml-c2pa(the actual validation engine) is never imported: it's a dynamicimport(), code-split out of both bundles.automode classifies each track from its init segment, falling back to per-segment ISO-BMFF box inspection if the init was missed or ambiguous. Results come through three new events:C2PA_INIT_PROCESSED,C2PA_SEGMENT_VALIDATED,C2PA_ERROR.Each
C2PA_SEGMENT_VALIDATEDcarries a status:valid/invalidfor the manifest/hash check,replayed/reordered/missingfrom cross-checking the manifest's signed sequence number (independent per representation, so an ABR switch or a seek doesn't read as an attack), plus two §19.3-only continuity statuses for a broken link vs. acontinuityMethodwe just don't recognize. Anything we can't evaluate degrades tounverifiedinstead of being dropped or treated as valid. Full docs insrc/streaming/c2pa/README.md;samples/c2pa/index.htmlhas a working ✅ / ❌ / ⚠ grid to try against a real stream.Testing
Tested against real signed streams for both methods, plus the full unit suite (~1950 tests). All cert/signature validation is delegated to
@svta/cml-c2pa, we don't reimplement any C2PA crypto here.