diff --git a/data/contentseal/README.md b/data/contentseal/README.md new file mode 100644 index 0000000..878b753 --- /dev/null +++ b/data/contentseal/README.md @@ -0,0 +1,90 @@ +# Content Seal oracle corpus + +Muse Image (`muse-image-1.0`) generations with externally verified Content Seal +verdicts, produced through the Meta Model API on 2026-08-26 and checked against +the public detector at `https://meta.ai/identification` (anonymous session, no +login). Follows the `data/synthid/originals` pattern: binaries live in +`originals/`, every derived variant is recorded in `manifest.csv` as a recipe +plus hash and is not stored. + +## What this corpus establishes + +- The Meta Model API image endpoint (`POST /v1/images/generations`) stamps the + same Content Seal pixel watermark as the consumer Meta AI app: all five + generations verified positive with attribution "Muse Image 1 - Meta". +- The detector response carries a per-generation ID and creation timestamp + embedded in the watermark payload. Both survived a 512 px LANCZOS resize and + a full-size JPEG q85 re-encode (same ID returned), so the payload is more + robust than the detection threshold. +- Center crops lose the seal: 50% and 33% linear center crops of two different + images all returned "No AI signatures from Meta were found", consistent with + the Reuters 2026-07-11 analysis (55% missed after cropping). +- API outputs carry XMP `iptcExt:DigitalSourceType = + trainedAlgorithmicMedia`, so local `identify` flags them via the existing + Made-with-AI path. Metadata-stripping transforms fall back to unknown, and + Content Seal has no local decoder in this project: the oracle is the only + reader. +- Drift finding: the 512 px resize of `gen_fox_forest` triggers a + medium-confidence false positive "Tencent Yuanbao (visible 元宝 / AI生成 + mark)" in this project's `identify`. Recorded here as a reproducible case. + +## Oracle limits and wire format + +There is no public or documented checking API. Verified against the official +developer documentation on 2026-08-26 (`https://dev.meta.ai/docs/`): the full +Meta Model API reference lists only Responses, Chat Completions, Messages, +Files, Images (`/v1/images/generations`, `/v1/images/edits`), and Models, with +no identification, detection, or watermark endpoint, and the image-generation, +Muse Image cookbook, and pricing pages never mention watermark, Content Seal, +or provenance at all. The API applies the seal (every generation in this corpus +carries it) while documenting nothing about it. The web tool drives an internal +REST pair, captured from the browser network log on 2026-08-26: + +1. `POST https://rupload.meta.ai/gen_ai_document_gen_ai_tenant/` with the + raw file bytes, `x-entity-type`, `x-entity-length`, `ai_detector_upload: true`, + and an anonymous `authorization: OAuth ecto1:` session token minted by + the page. +2. `POST https://meta.ai/api/ai-detector` with `Bearer ecto1:` and body + `{"media_id": "...", "fileName": "...", "mimeType": "..."}`. + +The rate limit is enforced at that endpoint, server-side, and keyed beyond the +browser session: the API itself returns `429 {"errorType": "rate_limited"}`, +and clearing cookies and storage changed nothing, so driving the internal pair +directly does not bypass it. The Meta Model API (`api.meta.ai/v1`, where the +generation key works) has no identification endpoint; plausible paths all +return 404. Rows with an empty `oracle_verdict` were transformed but not yet +checkable. Read a verdict only from the settled page text after the +result-complete state ("Upload another file"): a wait for a verdict string can +match the previous upload's text, and the fresh-navigation protocol used for +the calibration rows below is the race-free variant. + +## Strength floor calibration (qwen-zimage, seed 0) + +The library resolves strength per vendor with measured floors (OpenAI +0.07675 / Google 0.27 / Microsoft InvisMark 0.15 in +`_internal/watermark_profiles.py`). Meta Content Seal has no floor yet; the +goal of these rows is to measure one by that same methodology: independent +generations, each one's first-clean boundary, floor = worst boundary plus the +observed cross-source spread. + +Measured (2026-08-26/27, oracle `meta.ai/identification`): + +- Default pipeline clears Content Seal: tested samples came back clean at the + default resolution-adaptive strength (~0.1305 at 2.56 MP), including the + worst source. +- Five independent generations bracketed. First-clean boundaries: + lighthouse (0.0525, 0.06], fox (0.03, 0.0375], night_city (0.03, 0.0375], + mug <= 0.03, text <= 0.015. Cross-source spread is wide (a factor of four + between easiest and hardest). +- Derived Meta floor by the existing worst-boundary-plus-cross-source-spread + method: 0.06 + (0.0525 - 0.015) = 0.0975, rounded to **0.1**. +- Not shipped as a constant: no provenance signal routes Muse outputs onto a + vendor cohort, so the default curve stays authoritative and 0.1 is recorded + as the floor to encode if an explicit Meta override is ever added. + +## Regeneration + +API key is not stored in this repository. Regenerate with the script pattern +from the session (env `MUSE_API_KEY`, endpoint +`https://api.meta.ai/v1/images/generations`, model `muse-image-1.0`); prompts +are recorded per file in `manifest.csv`. diff --git a/data/contentseal/manifest.csv b/data/contentseal/manifest.csv new file mode 100644 index 0000000..3c66a7e --- /dev/null +++ b/data/contentseal/manifest.csv @@ -0,0 +1,44 @@ +name,file,origin,source_url,prompt,requested_size,delivered_size,sha256,xmp_iptc_trainedAlgorithmicMedia,oracle_verdict,oracle_attribution,oracle_generation_id,oracle_created_utc,checked_at_utc,notes +gen_fox_forest,originals/gen_fox_forest.webp,meta-model-api,,A red fox resting on a mossy log in a misty forest at dawn photorealistic shallow depth of field,1024x1024,1600x1600,6d7cecf322a5ac4b2547e2f32c767b3cb3e8a0e49f274cfd00aa31be6697eed4,yes,detected,Muse Image 1 - Meta,AXZpZGVvc2VhbHY0X2NhX3NjbLlNjYhvPAxD,2026-08-26T19:07Z,2026-08-26T19:11Z,checked twice; ID stable across uploads +gen_text_poster,originals/gen_text_poster.webp,meta-model-api,,A cheerful flat-design poster that says 'WATERMARK FIELD DAY 2026' in bold rounded letters over a coral and cream striped background,1024x1024,1600x1600,873927662d8198e9c38b4ae71a2919ba5d417dfcd714649db1b0ae231e8cbf9b,yes,detected,Muse Image 1 - Meta,AXZpZGVvc2VhbHY0X2NhX3NjbJuIKXPAYxsd,2026-08-26T19:08Z,2026-08-26T19:08Z, +gen_lighthouse_watercolor,originals/gen_lighthouse_watercolor.webp,meta-model-api,,A watercolor illustration of a lighthouse on a cliff at golden hour seagulls in the distance soft loose washes white paper texture,1024x1024,1600x1600,69bee7c376608568361085602c5c7406900885849bd94c5279c3924cf793b27c,yes,detected,Muse Image 1 - Meta,AXZpZGVvc2VhbHY0X2NhX3NjbB5uWbAmWmwp,2026-08-26T19:08Z,2026-08-26T19:08Z, +gen_night_city,originals/gen_night_city.webp,meta-model-api,,A night city skyline in the rain neon reflections on wet asphalt cinematic wide shot moody,1536x1024,1920x1280,345adf6b9f777b15ef7ce7bbdfb3b355a1d9e882740809e966e9f6caafd94d48,yes,detected,Muse Image 1 - Meta,AXZpZGVvc2VhbHY0X2NhX3NjbH5uWbAmWmwp,2026-08-26T19:08Z,2026-08-26T19:08Z, +gen_studio_mug,originals/gen_studio_mug.webp,meta-model-api,,A minimalist studio photo of a single matte ceramic mug on a pastel blue background soft shadow clean commercial look,1024x1024,1600x1600,2b7bc7fd56fa2250c66ef74f8b3e49d9666fe147d894d5baf9461b5e36077a07,yes,detected,Muse Image 1 - Meta,AXZpZGVvc2VhbHY0X2NhX3NjbAsSozV2XCzw,2026-08-26T19:08Z,2026-08-26T19:08Z, +fox_crop50,,derived:gen_fox_forest,,center crop 0.5 linear each side then WEBP q95,,800x800,80b91995560a7c4b08596a26b3401a29afb82691fad193502697a49785eab7be,no_stripped_by_reencode,not_detected,,,,2026-08-26T19:13Z,seal lost; PIL re-encode strips XMP +fox_crop33,,derived:gen_fox_forest,,center crop 0.33 linear each side then WEBP q95,,528x528,3b3aded8f4fcb98d0599431abccb73b9581854ae34c6bc869724cd5c86a1e35e,no_stripped_by_reencode,not_detected,,,,2026-08-26T19:13Z,seal lost +fox_res512,,derived:gen_fox_forest,,resize long side 512 LANCZOS then WEBP q95,,512x512,19b85f615d56f5e9f55cd97bae0ee343f4f432e6bfa0a6fc7acb07dec2cc1d81,no_stripped_by_reencode,detected,Muse Image 1 - Meta,AXZpZGVvc2VhbHY0X2NhX3NjbLlNjYhvPAxD,2026-08-26T19:07Z,2026-08-26T19:13Z,generation ID and creation time identical to the original generation: watermark payload survives the resize +fox_jpeg85,,derived:gen_fox_forest,,full-size JPEG q85 re-encode,,1600x1600,3bc7dc11bf3c16c83814df1e6485cdb6e49e21141511dda56d210cc920a8c5e3,no_stripped_by_reencode,detected,Muse Image 1 - Meta,AXZpZGVvc2VhbHY0X2NhX3NjbLlNjYhvPAxD,2026-08-26T19:07Z,2026-08-26T19:13Z,payload survives JPEG recompression; local identify returns unknown (metadata stripped) +text_crop50,,derived:gen_text_poster,,center crop 0.5 linear each side then WEBP q95,,800x800,3f0d4075012ee4fec820209c5c1c32bb739998813325f49b1124f379162f9837,no_stripped_by_reencode,not_detected,,,,2026-08-26T19:13Z,seal lost +text_crop33,,derived:gen_text_poster,,center crop 0.33 linear each side then WEBP q95,,528x528,95cef87002961831b21f6f47cd9a0824a67e2ee6f8503356fd5db6a3fcfcc998,no_stripped_by_reencode,,,,,,daily oracle limit reached before this row could be checked +text_res512,,derived:gen_text_poster,,resize long side 512 LANCZOS then WEBP q95,,512x512,b319727d02e908f22e59f148050e6b4686cbd1a622bd69db51fe4072a6f66b34,no_stripped_by_reencode,,,,,,daily oracle limit reached; also the fox twin of this transform triggered the Yuanbao false positive below +text_jpeg85,,derived:gen_text_poster,,full-size JPEG q85 re-encode,,1600x1600,76f00fcde1e974549afa5d90e1879c705e3c3631ef1f35cab901fc912cd15fa4,no_stripped_by_reencode,,,,,,daily oracle limit reached before this row could be checked +blog_qr_manhwa,,meta-blog-cdn,https://lookaside.fbsbx.com/elementpath/media/?media_id=1807972953501959&version=1783318590&transcode_extension=webp,,,1920x1280,8ee7700dff59f21d7de393a619664a0b9360f148d18df289364ecea939b3dea9,yes,detected,Muse Image 1 - Meta,,,2026-08-26T18:59Z,not stored; re-downloadable from source_url +blog_fractal_poster,,meta-blog-cdn,https://lookaside.fbsbx.com/elementpath/media/?media_id=1499727434782014&version=1783727678&transcode_extension=webp,,,2096x992,2a96168db026444b47ac37807fd99a88c8b7fa6866e1c93a54ab427fbe581f76,no,detected,Muse Image 1 - Meta,,,2026-08-26T18:59Z,seal survived CDN transcode without any metadata +blog_flipbook_punch,,meta-blog-cdn,https://lookaside.fbsbx.com/elementpath/media/?media_id=2521328538303639&version=1783396590&transcode_extension=webp,,,1920x1280,35f28dc2cbdf44aed190bc8efaa0fe7bfa288a2196638712dd83bc941cc294d5,yes,detected,Muse Image 1 - Meta,,,2026-08-26T19:00Z, +blog_teen_cat,,meta-blog-cdn,https://lookaside.fbsbx.com/elementpath/media/?media_id=1325391919706734&version=1783319609&transcode_extension=webp,,,1600x1600,c2e7df000ffa6520589d8efc5f0bcbeb759175cdaa0c827189c28a64f75d3dd7,yes,detected,Muse Image 1 - Meta,,,2026-08-26T19:00Z, +blog_hero_1,,meta-blog-cdn,https://lookaside.fbsbx.com/elementpath/media/?media_id=2140990336800598&version=1783727678,,,1344x1792,7655639acb9f826cdb864d869530153efc1e1467ff414b5a63e80aa7b39b2fb7,no,detected,Muse Image 1 - Meta,,,2026-08-26T19:00Z,seal survived CDN transcode without any metadata +control_synth_gradient,,synthetic-local,,PIL gradient with rectangle JPEG q90; never touched any AI pipeline,,1024x768,25f29c0b993ed1a8e41060ed50de778d875a6d94847ced180aafc5a6ad429f82,no,not_detected,,,,2026-08-26T19:01Z,negative control: No AI signatures from Meta were found +fox_nometa,,derived:gen_fox_forest,,metadata --remove only (no pixel change),,1600x1600,685d3a5c5d74e653ec7615fc7a347a0584c277fc7fb59594d388ac619ffe3985,no_stripped,detected,Muse Image 1 - Meta,AXZpZGVvc2VhbHY0X2NhX3NjbLlNjYhvPAxD,2026-08-26T19:07Z,2026-08-26T22:19Z,metadata stripping does NOT remove Content Seal; seal lives in pixels; same generation ID as original +fox_modal_invisible,,derived:gen_fox_forest,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=default-adaptive(approx 0.1305 at 1600x1600),,1600x1600,6daa3e9b5dde5cb443d8071c5294262742f001c02b694243764a50345769d8b8,no_stripped,not_detected,,,,2026-08-26T22:21Z,current default pipeline clears Content Seal +text_modal_invisible,,derived:gen_text_poster,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=default-adaptive(approx 0.1305 at 1600x1600),,1600x1600,8530a82fb856b0a00651076ac1ad8a7544b5d8873cac9bd5780704fbe10f53f8,no_stripped,not_detected,,,,2026-08-26T22:22Z,current default pipeline clears Content Seal +fox_str0.06000,,derived:gen_fox_forest,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.06,,1600x1600,7b5e5ae75ca20f2a3cdfcfa7666104752d9557b08ac236db6f1548434940a1f3,no_stripped,not_detected,,,,2026-08-26T22:28Z,strength calibration ladder +fox_str0.04500,,derived:gen_fox_forest,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.045,,1600x1600,26da15694b3096e2f1717154db213e814cdd27f2fc40e5f924db2324213cf960,no_stripped,not_detected,,,,2026-08-27T02:28Z,strength calibration ladder; re-confirmed clean at 0.045 +fox_str0.03750,,derived:gen_fox_forest,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.0375,,1600x1600,b7eb0616d10042933edf22cfc30aff4df915384828289b409f6cfa4cdf2807d4,no_stripped,not_detected,,,,2026-08-26T22:30Z,fresh-page protocol; fox first-clean boundary is in (0.03 0.0375] +fox_str0.03000,,derived:gen_fox_forest,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.03,,1600x1600,cff80d18d9d00000f47e0f97c419a37c13a13831795768aee8727aa9516d1401,no_stripped,detected,Muse Image 1 - Meta,,,2026-08-26T22:31Z,fresh-page protocol; seal survives 0.03 +fox_str0.02000,,derived:gen_fox_forest,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.02,,1600x1600,b19183ec60b63a5b73d3e1c723e7943584912857d51a4b5f85259c5c77da0e53,no_stripped,detected,Muse Image 1 - Meta,,,2026-08-26T22:28Z,settled-read protocol; monotonic below the 0.03 boundary +text_str0.03750,,derived:gen_text_poster,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.0375,,1600x1600,bacfd4eca7b13616502f74497cbe4f02ef6893918870f13f9e76fdb93b43107f,no_stripped,not_detected,,,,2026-08-26T22:33Z,fresh-page protocol +text_str0.03000,,derived:gen_text_poster,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.03,,1600x1600,d5c6cffbf471ef8bb74ee8c0d22cc17d9da2c446b83e46c4be67e6ea4cdd09de,no_stripped,not_detected,,,,2026-08-26T22:33Z,fresh-page protocol; text first-clean boundary is at or below 0.03 - cross-source spread vs fox confirmed +lh_str0.03750,,derived:gen_lighthouse_watercolor,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.0375,,1600x1600,f5169debc5fd2792399c0ea3fa88cb1d05f523d95004bec240c73db0e9aaee51,no_stripped,detected,Muse Image 1 - Meta,,,2026-08-27T02:28Z,seal survives 0.0375 where fox is already clean +lh_str0.03000,,derived:gen_lighthouse_watercolor,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.03,,1600x1600,110d2112d91dd53df5ccd1839e0e2b41c44aa1832adb0090032660e071fb1fd4,no_stripped,detected,Muse Image 1 - Meta,,,2026-08-27T02:28Z,fresh-page protocol; lighthouse holds its seal below the fox boundary: cross-source spread is wide +text_str0.02500,,derived:gen_text_poster,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.025,,1600x1600,f46a6c53b4f3d39fccb1d62cca82ced2e163e4424d13c98e6468a8e083c0fa54,no_stripped,not_detected,,,,2026-08-27T02:28Z,mid rung inside the clean zone +text_str0.02000,,derived:gen_text_poster,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.02,,1600x1600,a19cbe22313e0f19768c98567ea89f2d9dc30827691fc7ec2971c52f3e7c6cc6,no_stripped,not_detected,,,,2026-08-27T02:28Z,text clean at 0.02: boundary <= 0.02 - the easiest source measured +text_str0.01500,,derived:gen_text_poster,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.015,,1600x1600,aa3816c75e38f3ab5afc378d349caafad56f2bb6da859a5ee68fdea57ad00d73,no_stripped,not_detected,,,,2026-08-27T02:41Z,text clean at 0.015: boundary <= 0.015 - the easiest source measured +lh_str0.02500,,derived:gen_lighthouse_watercolor,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.025,,1600x1600,989c4a7a804bc8420ec4d86b9445bc3a37382c1834782a7f8842f781d69b05e2,no_stripped,,,,,,below the measured detected boundary (0.03) for this source; not individually checked +lh_str0.02000,,derived:gen_lighthouse_watercolor,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.02,,1600x1600,6a4a4ec94292a145e7d73e6900b8ec6d0eede17137f2b91fba7757ec0bdba5ec,no_stripped,,,,,,below the measured detected boundary (0.03) for this source; not individually checked +nc_str0.03750,,derived:gen_night_city,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.0375,,1920x1280,46f322335f801ee54427eb39275216465f9a7cfa3d918c698e4d90a2439a0815,no_stripped,not_detected,,,,2026-08-27T02:28Z,boundary (0.03; 0.0375] +nc_str0.03000,,derived:gen_night_city,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.03,,1920x1280,3f42632319248ad1a91ef2457c7e0b13ed17e1d2569decbbaab67c2072e9a305,no_stripped,detected,Muse Image 1 - Meta,,,2026-08-27T02:28Z,night_city holds seal at 0.03 +mug_str0.03750,,derived:gen_studio_mug,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.0375,,1600x1600,3b6109ae7e86b8ea74ce33315e3f2a7eda1cadb724d429f0951f13c3ead34b0f,no_stripped,not_detected,,,,2026-08-27T02:28Z,upper rung for the mug bracket +mug_str0.03000,,derived:gen_studio_mug,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.03,,1600x1600,1c24c984677e1528da83003ac40594f6e5ef1fa9351f1368b6d31a0101308583,no_stripped,not_detected,,,,2026-08-27T02:28Z,mug clean at 0.03: boundary <= 0.03 +lh_str0.05250,,derived:gen_lighthouse_watercolor,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.0525,,1600x1600,8912ba1ff4a26790f69224c49dd649e46835e277c375276ac9604f5339775b2a,no_stripped,detected,Muse Image 1 - Meta,,,2026-08-27T02:41Z,bisection: lighthouse first-clean boundary bracketed to (0.0525; 0.06] +lh_str0.04500,,derived:gen_lighthouse_watercolor,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.045,,1600x1600,cfdcf7bbb27c740846a1de4edffee3b25a624c2a8e1aba2362feaa25b682e3ae,no_stripped,detected,Muse Image 1 - Meta,,,2026-08-27T02:28Z,seal survives 0.045: lighthouse is the worst source measured +lh_str0.06000,,derived:gen_lighthouse_watercolor,,modal raiw-worker qwen-zimage mode=invisible seed=0 strength=0.06,,1600x1600,4e214be00a89dc9f1f32ec39483c5e9668a1ec7f38b15e5794942479c839fcca,no_stripped,not_detected,,,,2026-08-27T02:28Z,lighthouse first-clean boundary bracketed to (0.045 0.06] diff --git a/data/contentseal/originals/gen_fox_forest.webp b/data/contentseal/originals/gen_fox_forest.webp new file mode 100644 index 0000000..b8c0374 Binary files /dev/null and b/data/contentseal/originals/gen_fox_forest.webp differ diff --git a/data/contentseal/originals/gen_lighthouse_watercolor.webp b/data/contentseal/originals/gen_lighthouse_watercolor.webp new file mode 100644 index 0000000..99dcc53 Binary files /dev/null and b/data/contentseal/originals/gen_lighthouse_watercolor.webp differ diff --git a/data/contentseal/originals/gen_night_city.webp b/data/contentseal/originals/gen_night_city.webp new file mode 100644 index 0000000..cc5b92a Binary files /dev/null and b/data/contentseal/originals/gen_night_city.webp differ diff --git a/data/contentseal/originals/gen_studio_mug.webp b/data/contentseal/originals/gen_studio_mug.webp new file mode 100644 index 0000000..73f48e3 Binary files /dev/null and b/data/contentseal/originals/gen_studio_mug.webp differ diff --git a/data/contentseal/originals/gen_text_poster.webp b/data/contentseal/originals/gen_text_poster.webp new file mode 100644 index 0000000..e3ee0aa Binary files /dev/null and b/data/contentseal/originals/gen_text_poster.webp differ diff --git a/docs/known-limitations.md b/docs/known-limitations.md index 05288a9..e572826 100644 --- a/docs/known-limitations.md +++ b/docs/known-limitations.md @@ -111,6 +111,14 @@ For important outputs: Provider systems can change, so a result verified on one file, seed, or version is not a permanent certification. +Meta Content Seal (Muse Image) is in the same family: no local decoder, presence +recognizable only through the XMP `trainedAlgorithmicMedia` companion tag while +that metadata survives, and removal verifiable only through the anonymous +`meta.ai/identification` oracle, which rate-limits by IP per day. The oracle's +negative is weaker than its positive: Reuters measured it missing 55% of cropped +Muse images, so treat a negative on a cropped or heavily edited file as +inconclusive rather than clean. + ### Video SynthID removal is lossy and content-dependent The `video invisible` command and `remove_video_invisible` API regenerate video diff --git a/docs/module-internals.md b/docs/module-internals.md index 9049b48..71ca860 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -1149,6 +1149,40 @@ sizes the curve's own values verify clean, which is what a resolution-scaled requirement would predict. There is no measured size at which the shipped curve fails, so it is left alone. +### Meta Content Seal boundaries for qwen-zimage + +Measured 2026-08-26 against the anonymous oracle `meta.ai/identification` +(verdicts read from the settled page text; corpus and full row-level provenance in +`data/contentseal/manifest.csv`). Muse Image carries no C2PA, so +`vendor_for_strength` returns None for it and these images ride the default +resolution-adaptive curve; there is deliberately no `meta` entry in the flat vendor +map, because no provenance signal can establish that cohort automatically and an +unreachable constant would be dead code. + +| source (2.56 MP generation) | detected at | clean from | +|---|---|---| +| gen_lighthouse_watercolor | 0.0525 | 0.06 | +| gen_fox_forest | 0.03 | 0.0375 | +| gen_night_city | 0.03 | 0.0375 | +| gen_studio_mug | -- | 0.03 | +| gen_text_poster | -- | 0.015 | + +Full spread: worst first-clean boundary (0.0525, 0.06] on lighthouse, easiest +source already clean at 0.015. Following the same derivation as the OpenAI and +Microsoft floors (worst clean boundary plus one full observed cross-source +spread): 0.06 + (0.0525 - 0.015) = 0.0975, rounded up to **0.1**. This is a +candidate `meta` floor, not yet a shipped constant: Muse outputs carry no C2PA, +so `vendor_for_strength` has no signal that could route them onto the flat +vendor map automatically. Encoding 0.1 as `QWEN_ZIMAGE_META_STRENGTH` makes +sense only once the tool gains an explicit way to opt into a Meta cohort (for +example a CLI `--vendor meta` override); the default resolution-adaptive curve +(~0.1305 at 2.56 MP) already clears every measured source, so default behavior +needs no change. Oracle verdicts carry a per-generation ID and creation timestamp +embedded in the watermark payload; both survived the 512 px resize and JPEG q85 +rows, so payload recovery outlives the detection threshold. Oracle session +limits are per-IP, server-side, and sliding-window: clearing cookies and storage +does not reset them, and a burst exhausts the window minutes after it reopens. + ### Static prompt embeddings Both stages prompt with module constants, and at CFG 1.0 DiffSynth's diff --git a/docs/supported-signals.md b/docs/supported-signals.md index bee0e38..2a71fa8 100644 --- a/docs/supported-signals.md +++ b/docs/supported-signals.md @@ -154,6 +154,31 @@ is the external oracle: it reports pixel `Watermark` and embedded `C2PA` results separately; a control-positive, output-negative pair is the available per-file verification path. +Meta Muse Image stamps every output with Content Seal, a proprietary invisible +pixel watermark, and ships no visible mark (the legacy `Imagined with AI` +corner mark belongs to the pre-2026 Imagine pipeline and is not registered). +This project has no local Content Seal decoder. Meta Model API outputs and +Meta CDN copies carry an XMP `iptcExt:DigitalSourceType = +trainedAlgorithmicMedia` companion tag, which `identify` reports through the +existing Made-with-AI path; that IPTC code is a standard, not a Meta-exclusive +signal, so it cannot key a strength cohort the way the C2PA issuer does. The +external oracle is `https://meta.ai/identification`: anonymous, no login, +accepts image, video, and audio, enforces an unspecified daily identification +limit, and answers with model attribution (`Muse Image 1 - Meta`) plus a +per-generation ID and creation timestamp read from the watermark payload. No +identification endpoint exists in the Meta Model API itself (the reference at +dev.meta.ai documents only generation and edits for images), and the official +documentation never mentions the seal. The +default `qwen-zimage` profile clears Content Seal at the default +resolution-adaptive strength (oracle-verified on 2.56 MP generations); measured +strength boundaries are recorded in `data/contentseal/manifest.csv` and +[module internals](module-internals.md#meta-content-seal-boundaries-for-qwen-zimage) +(derived Meta floor 0.1 by the standard spread method, not shipped as a constant +because no provenance signal can route Muse output onto a vendor cohort). +The seal survives resizing, JPEG recompression, and metadata stripping; it dies +to center crops of a third to a half, matching the Reuters 2026-07-11 finding +that Meta's detector missed 55% of cropped Muse images. + For MP4, MOV, and M4V, `video invisible` or the explicit `video all --invisible` option can regenerate the video through a VAE and strip source metadata. The shipped profile is oracle-certified, but it is not a local @@ -174,6 +199,7 @@ not a universal clean verdict. | Google Gemini | Sparkle | Diffusion regeneration for SynthID | C2PA and related source signals | | Google Veo video | Veo diamond and legacy text | Oracle-certified VAE removal for SynthID | C2PA and related source signals | | OpenAI image generators | None registered | Diffusion regeneration for supported invisible signals | C2PA and generator provenance | +| Meta Muse Image | None on Muse output (legacy `Imagined with AI` unregistered) | Diffusion regeneration for Content Seal, oracle-verified on the default profile | XMP IPTC `trainedAlgorithmicMedia` companion tag; no local Content Seal decoder | | Microsoft Paint and Photos | None registered | External Microsoft oracle for InvisMark; no validated local decoder | Paint C2PA soft-binding algorithm and identifier | | Stable Diffusion and SDXL | None registered | Diffusion regeneration; optional open decoder | Embedded parameters and text metadata | | FLUX | None registered | Diffusion regeneration; optional open decoder | C2PA for supported sources | diff --git a/docs/watermarking-landscape.md b/docs/watermarking-landscape.md index eeafcbb..f661a1a 100644 --- a/docs/watermarking-landscape.md +++ b/docs/watermarking-landscape.md @@ -133,7 +133,7 @@ auditable detection boundary than the provider-specific synthetic path here. **The faint-visible-mark precision/recall wall is fundamental, not a heuristic artifact.** The visible-watermark-detection literature has moved to LEARNED segmentation / object-detection (WDNet WACV'21 arXiv:2012.07616; SLBR ACM MM'21, open code+weights; the PRCV'18 large-scale detector; Su et al. survey 2025), but three verified findings bound what a learned detector actually buys: (1) a claim that a confidence threshold "cleanly separates" true from false matches even with a learned CNN front-end was **REFUTED** in verification (arXiv:1705.08593) -- the precision/recall wall persists even with learned features. (2) Learned detectors need a LARGE, pattern-diverse labeled dataset trained on synthetic composites (PRCV'18: 60k images / 80 watermark classes; CLWD: 60k / 160 marks), and off-distribution degradation is a documented real axis (models trained on limited-pattern LVW transfer worse; diversity of training patterns drives generalization). (3) Inference is cheap (WDNet ~8 ms at 256x256) -- the cost is the data pipeline, not runtime. Net: a learned detector shifts the frontier but does NOT remove the wall; for a SINGLE mark the cheapest next step is a small patch classifier (real-sparkle vs false-positive) on top of the existing NCC localizer, not a full segmentation model. SLBR is a ready baseline. The current NCC + false-positive gate (core-ring brightness margin + gradient-NCC crispness + white-core saturation) is a sound operating point, and the residual miss is the information-theoretic wall the literature confirms. -**Visible-mark landscape beyond the registry.** Meta stamps a visible "Imagined with AI" mark (bottom-LEFT, a small symbol) on its OWN Meta AI / "Imagine" output; for third-party images it relies on C2PA / IPTC, not a visible mark. Samsung Galaxy AI additionally uses a **four-star icon** variant in a corner alongside the localized text wordmark `samsung_engine` calibrates (only the Italian text variant is covered) -- the icon is a distinct, uncovered variant. Every source agrees visible + metadata marks are trivially removable (crop / screenshot, ~2 s), which is the tool's premise. +**Visible-mark landscape beyond the registry.** Since Muse Image (2026-07) Meta's own generation output carries NO visible mark, only the invisible proprietary Content Seal; the legacy visible "Imagined with AI" mark (bottom-LEFT, a small symbol) belongs to the pre-2026 Meta AI / "Imagine" pipeline and remains unregistered (string verified, position not). For third-party images Meta relies on C2PA / IPTC, not a visible mark. Samsung Galaxy AI additionally uses a **four-star icon** variant in a corner alongside the localized text wordmark `samsung_engine` calibrates (only the Italian text variant is covered) -- the icon is a distinct, uncovered variant. Every source agrees visible + metadata marks are trivially removable (crop / screenshot, ~2 s), which is the tool's premise. **Regulatory driver -- China GB 45438-2025 is the strongest VISIBLE-mark mandate.** The CAC / TC260 "Measures for Labeling AI-Generated Synthesized Content" (issued March 2025, **effective 2025-09-01**, technical standard **GB 45438-2025**, building on the TC260 Aug-2023 practice guide) MANDATE a **visible** label for AI images -- a visible textual mark whose height must be **>= 5% of the image's shortest side** -- plus the metadata (implicit) label. Several such CJK text marks are now registered; see [supported signals](supported-signals.md) for the current list. By contrast EU AI Act Article 50 mandates only the MACHINE-READABLE mark (enforceable 2026-08-02, grace to 2026-12-02); a visible label is proposed and modality-specific (visible for images) but is NOT a hard "fixed icon" mandate -- a claim that Art 50 requires a clearly-visible fixed icon for images was refuted in verification. Primary-source dates verified against the article/standard text, not search summaries. @@ -161,6 +161,8 @@ Two consequences we can exploit: (1) the 5% floor is a **scale prior** -- a comp **Tencent Yuanbao: RESOLVED 2026-07-25, registered (`yuanbao_engine.py`).** The standard mark is a compact two-line italic `元宝` over `AI生成` block at bottom-right. It switches between light and dark strokes with the scene, so detection uses polarity-independent local contrast rather than a white top-hat. The separate one-line overlay variant remains evidence-limited to one example. +**Meta Muse Image / Content Seal: current state VERIFIED 2026-08-26 against live artifacts.** Muse Image (launched 2026-07-07, first image model from Meta Superintelligence Labs) puts no visible mark on output. Every output carries Content Seal, a proprietary invisible pixel watermark; research lineage is open (`github.com/facebookresearch/content-seal`, Pixel Seal / VideoSeal / Watermark Anything), but the deployed implementation is proprietary and unpublished. API outputs and Meta CDN copies also carry XMP `iptcExt:DigitalSourceType = trainedAlgorithmicMedia` (a standard IPTC code, not Meta-exclusive). The only reader is the anonymous web oracle `meta.ai/identification`, which returns model attribution plus a per-generation ID and timestamp embedded in the payload. Verified robustness (our corpus, `data/contentseal/`): seal survives CDN WebP transcode, 512 px resize, JPEG q85, and full metadata stripping; it is lost to center crops of 33-50% linear size (Reuters measured 55% detector misses on cropped images, 2026-07-11). The default `qwen-zimage` profile clears it (oracle-verified). Legacy paragraph retained below. + **Meta `Imagined with AI` (string VERIFIED (a) from Meta's own newsroom; POSITION NOT VERIFIED).** Sources conflict on placement. Do not encode a corner without a verified sample. `identify` reads the supported IPTC disclosure; it does not decode Meta's proprietary invisible watermark. Source: `https://about.fb.com/news/2024/02/labeling-ai-generated-images-on-facebook-instagram-and-threads/`. **Samsung English/other locales: still not established.** Samsung's own support page says only that "A Galaxy AI watermark will appear on AI-generated images" -- no string, no corner. Every community thread carrying the exact English string returned HTTP 403 to WebFetch, so the search paraphrase (bottom-left) is deliberately NOT recorded as fact. Feature-tier detail (b): the mark is applied by Generative Edit / sketch-to-image but reportedly NOT by Object Eraser, so Samsung absence is feature-dependent. The four-star icon variant: nothing found. diff --git a/tests/test_contentseal_corpus.py b/tests/test_contentseal_corpus.py new file mode 100644 index 0000000..e6963e0 --- /dev/null +++ b/tests/test_contentseal_corpus.py @@ -0,0 +1,60 @@ +"""Tests for the Content Seal oracle corpus layout. + +Mirrors the synthid corpus guard: the manifest is the source of truth for +which binaries exist, and every recorded hash must match the file it names. +Derived rows are recipes, not stored files, so only originals are checked +against disk. +""" + +from __future__ import annotations + +import csv +import hashlib +import re +from pathlib import Path + +CORPUS_DIR = Path(__file__).resolve().parent.parent / "data" / "contentseal" +MANIFEST = CORPUS_DIR / "manifest.csv" +ORIGINALS = CORPUS_DIR / "originals" + +_SHA256 = re.compile(r"^[0-9a-f]{64}$") +_VALID_VERDICTS = {"detected", "not_detected", ""} +_VALID_ORIGINS = {"meta-model-api", "derived", "meta-blog-cdn", "synthetic-local"} + + +def _manifest_rows() -> list[dict[str, str]]: + with open(MANIFEST, newline="") as f: + return list(csv.DictReader(f)) + + +def test_manifest_original_rows_match_binaries_and_hashes() -> None: + rows = [r for r in _manifest_rows() if r["file"]] + stored = {path.name for path in ORIGINALS.iterdir() if path.is_file()} + assert {r["file"].removeprefix("originals/") for r in rows} == stored + + for row in rows: + digest = hashlib.sha256((CORPUS_DIR / row["file"]).read_bytes()).hexdigest() + assert digest == row["sha256"], row["file"] + + +def test_manifest_rows_are_well_formed() -> None: + rows = _manifest_rows() + assert len({row["sha256"] for row in rows}) == len(rows), "duplicate sha256" + + for row in rows: + assert _SHA256.match(row["sha256"]), row["name"] + assert row["origin"].split(":")[0] in _VALID_ORIGINS, row["name"] + assert row["oracle_verdict"] in _VALID_VERDICTS, row["name"] + # Every oracle verdict must carry its check timestamp. + if row["oracle_verdict"]: + assert row["checked_at_utc"], row["name"] + # Detection rows must name the oracle attribution. + if row["oracle_verdict"] == "detected": + assert "Muse Image 1" in row["oracle_attribution"], row["name"] + + +def test_default_pipeline_clearance_is_recorded() -> None: + """The verified claim that the default profile clears Content Seal must stay.""" + rows = {row["name"]: row for row in _manifest_rows()} + for name in ("fox_modal_invisible", "text_modal_invisible"): + assert rows[name]["oracle_verdict"] == "not_detected", name