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 44 commits into
Conversation
…dash.js into feature/c2pa-cml-validation
…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
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
…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
…abs/dash.js into feature/c2pa-cml-validation
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.
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.
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.
…abs/dash.js into feature/c2pa-cml-validation
|
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 |
Summary
streams, covering both live signing methods from C2PA 2.3 §19:
§19.3 Manifest Box (per-segment uuid box, c2pa.hash.bmff.v3) and
§19.4 VSI (emsg COSE_Sign1, session keys in the init segment)
engine (
@svta/cml-c2pa) is never imported (dynamic import, code-splitout of both default bundles)
as it's fetched (without touching the fetch-to-MSE path), and emits a
result record per segment through the public event system
automode, classifies segments independently by their ISO-BMFF boxeswhen the init segment was missed or ambiguous
New files
src/streaming/c2pa/C2paController.js— owns the scanning lifecycle,created during
MediaPlayer.initialize()src/streaming/c2pa/C2paScanner.js— thin adapter over the existingresponse interceptor API
src/streaming/c2pa/C2paValidationCoordinator.js— per-track state,classification and validation orchestration
src/streaming/c2pa/C2paOptions.js— settings normalization/validationsrc/streaming/c2pa/C2paEvents.js— event/payload typedefssrc/streaming/c2pa/detection/BoxParsingDetector.js,detection/C2paDetector.js— swappable auto-mode detection strategysrc/streaming/c2pa/README.md— architecture, settings, events andcontinuity semantics
samples/c2pa/index.html— per-segment ✅ / ❌ / ⚠ provenance gridtest/unit/test/streaming/streaming.c2pa.*Modified files
Settings.js/CoreEvents.js—streaming.c2pasettings(
enabled/method/mediaTypes) and runtime-toggle eventMediaPlayer.js/MediaPlayerEvents.js— wiring and the three publicevents (
C2PA_INIT_PROCESSED,C2PA_SEGMENT_VALIDATED,C2PA_ERROR)IsoBox.js— expose theuuidbox'susertypefor C2PA detectionsamples/samples.json— registers the new sampleindex.d.ts— TypeScript definitionspackage.json/package-lock.json— adds@svta/cml-c2pa(optionaldependency)