Add C2PA Monitor experiment#459
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #459 +/- ##
=============================================
+ Coverage 69.07% 70.25% +1.17%
- Complexity 957 1130 +173
=============================================
Files 60 66 +6
Lines 4511 4979 +468
=============================================
+ Hits 3116 3498 +382
- Misses 1395 1481 +86
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
ec97690 to
43d3bde
Compare
… tests - JSON postmeta contract; @see DIF wpai-monitor-record schema - Partial README (postmeta + constraints) Made-with: Cursor
- Format_Detector for JPEG/PNG/WebP C2PA segments - Synthetic fixtures and Format_DetectorTest Made-with: Cursor
- Streaming Manifest_Reader and Sidecar_Writer - README: sidecar layout, rationale, test fixtures Made-with: Cursor
- capture_for_attachment, sidecar, Record persistence - C2pa_MonitorTest; README: full flow, DIF schema cross-links, out of scope Made-with: Cursor
Made-with: Cursor
1e33baa to
8257f84
Compare
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Synthetic fixtures are not valid renderable images. GD's WebP codec fatals when create_upload_object triggers wp_generate_attachment_metadata. Suppress intermediate_image_sizes_advanced in setUp/tearDown so GD is never invoked on fixture bytes. Made-with: Cursor
…kability Adds CONTEXT_URL constant to C2pa_Monitor pointing at the wpai-monitor-record context.json in DIF credential-schemas (interim raw.githubusercontent.com URL; @todo migrate to https://w3id.org/openverifiable/v1 at Phase 3). Every stored _wpai_monitor_record now includes: "@context": ["https://schema.org/", C2pa_Monitor::CONTEXT_URL] This makes the record a self-describing JSON-LD document that can be lifted directly into a credentialSubject without transformation. SCHEMA_VERSION stays at 1 — the addition is backwards-compatible (unknown keys are ignored by existing readers). Updates Record::REQUIRED_KEYS and default_for() so normalize() always fills @context even when callers omit it. Adds assertions to RecordTest and C2pa_MonitorTest. Updates README example block. Made-with: Cursor
| * for every uploaded image. The capture is read-only, fail-open, and never | ||
| * blocks the upload pipeline. | ||
| * | ||
| * @since 0.7.0 |
There was a problem hiding this comment.
We'll want to update all @since statements in this PR to be x.x.x as we'll replace those with the proper version number during the release process
| * JSON-LD context URL embedded in every stored postmeta record. | ||
| * | ||
| * Interim URL resolving via raw.githubusercontent.com. Migrate to | ||
| * https://w3id.org/openverifiable/v1 once the w3id.org PR is merged |
There was a problem hiding this comment.
This mentions a PR, can we add the link to that here so it can easily be tracked?
| 'label' => __( 'C2PA Monitor', 'ai' ), | ||
| 'description' => __( 'Detects C2PA Content Credentials in uploaded images and stores the raw manifest plus a structured record in postmeta. Read-only and fail-open; never blocks an upload.', 'ai' ), | ||
| 'category' => Experiment_Category::ADMIN, | ||
| 'stability' => 'experimental', |
There was a problem hiding this comment.
We now have a new capability property that we'll want to set to none here
| if ( '' === $path || ! is_readable( $path ) ) { | ||
| $errors[] = array( | ||
| 'stage' => 'resolve_path', | ||
| 'message' => 'Attachment file is not readable.', |
There was a problem hiding this comment.
Haven't seen where we output these error messages yet but if these end up being user facing, we should pass these through translation. Example:
| 'message' => 'Attachment file is not readable.', | |
| 'message' => esc_html__( 'Attachment file is not readable.', 'ai' ), |
| * @return string Absolute filesystem path, or empty string when unresolved. | ||
| */ | ||
| private static function get_original_path( int $attachment_id ): string { | ||
| if ( function_exists( 'wp_get_original_image_path' ) ) { |
There was a problem hiding this comment.
Do we need this check? Our minimum WP version is 7.0 and this function appears to have been introduced in 5.3
There was a problem hiding this comment.
Right now documentation for each experiment lives under docs > experiments so probably want to move this there
| ### Added | ||
| - New Experiment: C2PA Monitor — read-only detection of [C2PA Content Credentials](https://c2pa.org/) in uploaded JPEG/PNG/WebP images. Captures the raw manifest store to a sidecar file under `wp-content/uploads/ai-c2pa/` and stores a structured `_wpai_monitor_record` postmeta entry for downstream consumers. Fail-open and never blocks an upload. JUMBF/CBOR claim decoding deferred to a follow-up PR. |
There was a problem hiding this comment.
No need to add changelog entry here, just within the PR description. We'll populate this when we do a release
|
|
||
| * **Abilities Explorer** – Browse and interact with registered AI abilities from a dedicated admin screen. | ||
| * **Alt Text Generation** - Generate descriptive alt text for images to improve accessibility. | ||
| * **C2PA Monitor** – Detects [C2PA Content Credentials](https://c2pa.org/) in uploaded images and stores the raw manifest plus a structured record alongside the attachment. Read-only and fail-open; never blocks an upload. |
There was a problem hiding this comment.
Likely want a similar line in the README.md file
| return; | ||
| } | ||
|
|
||
| $rules = "# C2PA Monitor sidecar files - block direct web access (Apache).\n" |
There was a problem hiding this comment.
Seems this only applies to Apache but won't impact nginx or other providers. How concerned are we with these files being publicly accessible? If they shouldn't be accessed, may be best to store these in a non-public location instead of relying on an htaccess file
Read-only feature that detects [C2PA Content Credentials](https://c2pa.org/) in uploaded JPEG/PNG/WebP images at the
add_attachmenthook, captures the raw manifest store to a sidecar file underwp-content/uploads/ai-c2pa/, and persists a structured_wpai_monitor_recordpostmeta entry for downstream consumers.What?
Closes
Adds a new
C2pa_Monitorexperiment that:caBX, WebP RIFFC2PA. Hard byte caps throughout.c2pa/jumbtoken in their first 64 bytes.uploads/ai-c2pa/<attachment_id>.<format>.c2pa, hashing in flight (SHA-256). Postmeta stores the hash, length, and relative sidecar path — not the bytes themselves..htaccess(Apache deny) andindex.phphardening. nginx operators must add a deny rule manually (documented in the experiment README).try / catch ( Throwable )boundary: errors land in the record'serrors[]array and the upload itself is never blocked.Why?
C2PA Content Credentials are increasingly embedded in images uploaded to WordPress sites (camera firmware, AI image generators, editorial signing tools), but WordPress's image processing pipeline destroys them — APP11 segments don't survive GD/Imagick re-encoding. Capturing the manifest at
add_attachment, before subsize generation, is the only point in the WordPress lifecycle where the original bytes are still intact.This PR establishes that capture as read-only infrastructure: nothing user-facing changes, but the manifest data becomes available to downstream consumers (admin UI, REST endpoints, verification tooling) without each of them having to re-parse containers.
How?
Implementation lives entirely under
includes/Experiments/C2pa_Monitor/with one entry point:C2pa_Monitor::capture_for_attachment()is hooked toadd_attachmentat priority 20, gated by MIME onimage/jpeg,image/png, andimage/webp.Format_Detectorwalks containers and returns a location descriptor (segments + total length) without reading payload bytes.Manifest_Readerconsumes that descriptor, streams the bytes viafreadinto a hash context and an in-memory buffer, and produces an immutableRaw_Manifestvalue object.Sidecar_Writerpersists the bytes, ensuring the directory and hardening files exist exactly once.Recordnormalizes the structured payload and persists it as JSON-encoded postmeta at_wpai_monitor_record.Reviewing this PR. The diff is large but cleanly partitioned. The work was developed in five layers and the integration branch's commits are organized that way:
Raw_Manifest,Record, tests).Format_Detector, fixtures, tests).Manifest_Reader,Sidecar_Writer, tests).register()body,capture_for_attachment, end-to-end tests).Each layer is independently coherent if you'd rather review incrementally; otherwise, the integrated diff stands on its own.
Use of AI Tools
AI assistance: Yes
Tool(s): Cursor, Claude
Model(s):
Used for: Architecture and PR-segmentation planning, initial code drafts, test scaffolding. Final implementation, all design decisions, and the byte-level format work were reviewed and edited by me.
Testing Instructions
c2patoolcan also generate test files)._wpai_monitor_record— it should be JSON withc2pa.present: true, a SHA-256 hash, and asidecar_path_relativevalue.wp-content/uploads/ai-c2pa/<attachment_id>.jpeg.c2paand that its bytes matchmanifest_sha256.c2pa.present: falseand no sidecar should be written..txtfile) — no postmeta should be written at all.Automated coverage:
Format_Detector: magic bytes, single-segment APP11, multi-segment reassembly, interleaved APP0/APP1/APP2 around C2PA, generic JUMBF (non-C2PA) ignored, truncated input,JPEG_MAX_SEGMENTScap (positive and negative), PNG/caBX, WebP simple + extended (VP8X) + odd-length padding.Manifest_Reader: byte-exact roundtrip for JPEG/PNG/WebP, multi-segment reassembly, deterministic SHA-256,MAX_MANIFEST_BYTESrejection, missing file, empty segments, bad offsets.Sidecar_Writer: write + roundtrip, hardening files, format sanitization, overwrite, multi-attachment coexistence, custom.htaccesspreserved acrossensure_dir().Record: roundtrip, defaults on empty input, JSON-not-serialize storage format, null on corrupt JSON, null when absent.C2pa_Monitorend-to-end: JPEG/PNG/WebP present, JPEG absent, unsupported MIME, fail-open on bogus ID, truncated JPEG,duration_msrecorded,add_attachmenthook actually fires, file-deleted-on-disk produceserrors[0].stage = 'resolve_path'.Synthetic fixtures are generated at runtime so no binary blobs land in the repo and there is no third-party fixture licensing question.
Deferred (out of scope for this PR)
c2pa.decodedclaim summary (claim generator, digital source type, action history).Screenshots or screencast
No UI changes in this PR.
Changelog Entry