mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-10-14 21:40:47 +02:00
Attribute Amazon Bedrock C2PA and scope the signer fallback scan
Nova Canvas images published in aws-samples were reported as Canva: with no Amazon row, the byte fallback matched the Canva token inside the "Nova Canvas" software agent. Add an Amazon Web Services row ahead of Canva, which also names Nova Reel video. When the reader cannot open a manifest, scan for signers in the C2PA store the container locates (JPEG APP11, PNG caBX) instead of the whole metadata region. A vivo X300 camera capture was labeled "C2PA signer: Adobe" from its XMP toolkit string. The SynthID provenance scan reads the same store. Adds the Nova Canvas and Nova Reel samples as MIT-0 fixtures and records the public-sample search for the other conformance-list signers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7ba07869d2
commit
fb92c7dbc8
12 files changed
+147
-5
No files matched your search
@@ -226,6 +226,27 @@ software agent `Runway Image Generation` 4.0.0 or `Runway Video Generation`
|
||||
- License: repository license; generated and explicitly cleared by the
|
||||
maintainer
|
||||
|
||||
## Amazon Bedrock Nova Canvas image and Nova Reel video
|
||||
|
||||
Third-party sample outputs published in
|
||||
[`aws-samples/amazon-nova-samples`](https://github.com/aws-samples/amazon-nova-samples)
|
||||
at commit `ad2fe2074cbc260b9f1dbcec94a4ec3561c1159a`, copied unmodified.
|
||||
|
||||
| File | Upstream path | Size | SHA-256 |
|
||||
| --- | --- | --- | --- |
|
||||
| `amazon-bedrock-nova-canvas.png` | `multimodal-embeddings/getting-started/sample_assets/images/dog.png` | 1280 x 720 PNG | `b2d38079222ca8499f2cbe3adca3f5521add6b383c99bf28d61021503bb3b7c1` |
|
||||
| `amazon-bedrock-nova-reel.mp4` | `multimodal-understanding/workshop/1-getting-started-with-nova-2/video/cell-phone.mp4` | 1280 x 720 H.264, 24 fps, 6 s | `36d2d68f9be53d9f123bd16127c6fbcbd838faa57cfdb28af44a515458a3cf64` |
|
||||
|
||||
Both carry a valid C2PA manifest signed by `Amazon Web Services, Inc.` with claim
|
||||
generator `Amazon Bedrock`, recording one `c2pa.created` action typed
|
||||
`trainedAlgorithmicMedia` with software agent `Nova Canvas` or `Nova Reel 1.1`.
|
||||
The image was reported as Canva before the Amazon vendor row existed: the byte
|
||||
fallback found the `Canva` token inside `Nova Canvas`.
|
||||
|
||||
- Expected signal: C2PA AI-generated, platform `Amazon Bedrock (Nova)`
|
||||
- License: MIT No Attribution, see
|
||||
[`../licenses/aws-samples-amazon-nova-MIT-0.txt`](../licenses/aws-samples-amazon-nova-MIT-0.txt)
|
||||
|
||||
## Higgsfield and Dreamina downloads
|
||||
|
||||
Original downloads generated by the maintainer on paid Higgsfield and Dreamina
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
MIT No Attribution
|
||||
|
||||
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -663,6 +663,10 @@ metadata extraction from verdict logic:
|
||||
compressed data one turns up by chance, and the entry it hits may assert AI. The
|
||||
trim happens only when the container parses -- a malformed or unknown one is left
|
||||
whole, because dropping real evidence to avoid a chance match is the wrong trade.
|
||||
When the reader could not open the manifest, the issuer and signer scan narrows
|
||||
further to the manifest store the container locates (`_c2pa_store_bytes`: JPEG
|
||||
APP11, PNG `caBX`): an XMP packet names Adobe in its toolkit string, and a vivo
|
||||
X300 capture was labeled "C2PA signer: Adobe" from it (2026-09-26).
|
||||
- `identify_from_evidence` evaluates that evidence without reopening the source. Rules
|
||||
that decide a verdict live here, not in extraction: extraction has two
|
||||
implementations, and a rule in only one of them is a rule the other lacks. The
|
||||
|
||||
@@ -42,7 +42,11 @@ This page is an index. Each finding lives in its canonical page.
|
||||
records SynthID in its manifest.
|
||||
- **Vidu and Wan marks on more samples.** Both are registered from one real
|
||||
export each (`vidu` video, `wan` image); their gates are provisional until a
|
||||
wider cohort is measured, and a Wan video label is not yet captured.
|
||||
wider cohort is measured, and a Wan video label is not yet captured. Public
|
||||
Vidu feed videos (2026-09-26) carry the TC260 label naming ShengShu, and one
|
||||
names Tongyi Yunqi as producer with Vidu as propagator, but none shows the
|
||||
visible `Vidu` mark; Wan's public demo videos show no mark either. Both marks
|
||||
appear only on account downloads.
|
||||
- **TC260 practice guides.** The audio placements from TC260-PG-202510A are
|
||||
now read (the guide PDFs load in a browser from
|
||||
`tc260.org.cn/tc260/sjzn/list.shtml`, not to scripted requests). The text-file
|
||||
@@ -70,6 +74,14 @@ This page is an index. Each finding lives in its canonical page.
|
||||
real sample per vendor is still needed to confirm the manifest shape before
|
||||
adding a row. Runway is done: its own models sign as `RUNWAY AI, INC.`
|
||||
(measured 2026-09-24) and the row covers the conformance-list spelling too.
|
||||
Amazon is done: Nova Canvas and Nova Reel outputs published in
|
||||
`aws-samples/amazon-nova-samples` sign as `Amazon Web Services, Inc.` with
|
||||
claim generator `Amazon Bedrock` (2026-09-26), and had been reported as Canva
|
||||
because the byte fallback matched `Canva` inside `Nova Canvas`. A search on
|
||||
2026-09-26 found no public Getty, iStock, Jasper, RefaceAI or vivo Albums file
|
||||
carrying C2PA: their marketing and model-page images are unsigned. A vivo
|
||||
X300 camera capture carries a vivo device manifest (`digitalCapture`) that
|
||||
the bundled reader cannot open (`unknown algorithm`).
|
||||
- **Chinese phone galleries.** Huawei's help pages say the Xiaoyi photo-edit AI
|
||||
watermark can be switched off; Honor, OPPO and Xiaomi label behavior is not
|
||||
documented in any primary source found. vivo's Album setting is already
|
||||
|
||||
@@ -286,6 +286,7 @@ not a universal clean verdict.
|
||||
| Kling AI | Kling AI image and video marks | No registered pixel decoder | TC260 AIGC |
|
||||
| Vidu (ShengShu) | Vidu video mark | No registered pixel decoder | TC260 AIGC in the MP4 `AIGC` key |
|
||||
| Runway | None measured on paid-plan output | No registered pixel decoder | C2PA signed `RUNWAY AI, INC.` on its own models; third-party models keep their vendor's manifest |
|
||||
| Amazon Bedrock (Nova Canvas, Nova Reel) | None | Invisible watermark readable only through the Bedrock API | C2PA signed `Amazon Web Services, Inc.`, claim generator `Amazon Bedrock` |
|
||||
| Wan (Alibaba) | Wan image mark | No registered pixel decoder | TC260 AIGC in XMP |
|
||||
| Hailuo AI / MiniMax video | Hailuo AI composite video label | No registered pixel decoder | TC260 AIGC where present |
|
||||
| Baidu | Baidu mark | No registered pixel decoder | TC260 AIGC |
|
||||
|
||||
@@ -87,7 +87,7 @@ payloads. Removal remuxes either container through ffmpeg with stream copy.
|
||||
may carry IPTC metadata but no registered C2PA or pixel watermark. The open
|
||||
DWT-DCT decoder only applies when the producing pipeline actually ran its
|
||||
encoder and the carrier remains decodable.
|
||||
- **Invisible but NOT locally detectable (proprietary, API/oracle only — same wall as SynthID):** Amazon Titan Image Generator + Nova Canvas (Bedrock `DetectGeneratedContent` API; Amazon's Nova Reel service card also claims a watermark on every video), Kakao (new SynthID image adopter, May 2026), NVIDIA Cosmos (SynthID video), ElevenLabs and OpenAI audio (SynthID, 2026), Apple Image Playground and Photos edits (SynthID announced 2026-09-14, not shipped). Microsoft Foundry lists provenance (C2PA and/or invisible watermark; the page does not split them per model) for Azure OpenAI GPT Image, MAI-Image-2.5, Azure-hosted Black Forest Labs Flux (1.1-Pro, Kontext-pro, 2-flex, 2-Pro) and Azure audio models ([Microsoft Learn, 2026-09-17](https://learn.microsoft.com/en-us/azure/foundry/responsible-ai/content-safety/provenance-disclosure)). No local detector possible; treat like SynthID.
|
||||
- **Invisible but NOT locally detectable (proprietary, API/oracle only — same wall as SynthID):** Amazon Titan Image Generator + Nova Canvas (Bedrock `DetectGeneratedContent` API; Amazon's Nova Reel service card also claims a watermark on every video; both also sign C2PA as `Amazon Web Services, Inc.`, attributed as Amazon Bedrock), Kakao (new SynthID image adopter, May 2026), NVIDIA Cosmos (SynthID video), ElevenLabs and OpenAI audio (SynthID, 2026), Apple Image Playground and Photos edits (SynthID announced 2026-09-14, not shipped). Microsoft Foundry lists provenance (C2PA and/or invisible watermark; the page does not split them per model) for Azure OpenAI GPT Image, MAI-Image-2.5, Azure-hosted Black Forest Labs Flux (1.1-Pro, Kontext-pro, 2-flex, 2-Pro) and Azure audio models ([Microsoft Learn, 2026-09-17](https://learn.microsoft.com/en-us/azure/foundry/responsible-ai/content-safety/provenance-disclosure)). No local detector possible; treat like SynthID.
|
||||
- **C2PA 2.4 "Durable Content Credentials" (April 2026; verified against the spec) raise the bar for metadata stripping.** 2.4 defines soft bindings (an invisible watermark or a content fingerprint) plus a server-side manifest repository and a new `c2pa.repository-receipt` assertion. Per the spec: "if a C2PA manifest is removed from an asset, but a copy of that manifest remains in a provenance store elsewhere, the manifest and asset may be matched using available soft bindings." So our local `metadata --remove` deletes the *embedded* manifest, but a fingerprint/watermark soft binding can still re-link the image to its manifest in a repository server-side. Stripping the file is becoming necessary-but-not-sufficient against durable provenance. (Correction 2026-09-23: 2.3 and 2.4 did change what a parser must read. 2.3 added OGG Vorbis and AVIX embedding; 2.4 added the `c2pa.ai-disclosure` assertion (section 18.28: `modelType`, `modelName`, `modelIdentifier`, `contentProfile.humanOversightLevel`), `digitalSourceType` on ingredient assertions, IFD0 embedding for single-IFD TIFF, and HTML and text embedding. The reader now handles `c2pa.ai-disclosure` and ingredient `digitalSourceType`; HTML and text embedding are out of scope for an image and video tool.) Spec: https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html We READ the soft-binding `alg`, preserve the structured assertion's signed `value`, and generate the display/type registry from the official C2PA JSON with `scripts/sync_c2pa_soft_bindings.py`. The packaged snapshot keeps runtime inspection offline and pins the exact upstream revision. Registration is name-only support: Adobe TrustMark remains the one locally decoded scheme (`trustmark_detector`) unless another compatible decoder is independently verified. Fingerprint entries remain re-linkability signals and are not mislabeled as pixel watermarks.
|
||||
- **Microsoft Paint and Photos InvisMark (reverse-engineered 2026-08-20):** Paint receives a per-generation GUID from remote prompt moderation, embeds it into locally generated pixels, and records the same value in `c2pa.soft-binding` under `com.microsoft.invismark.1`. The C2PA soft-binding registry independently identifies that algorithm as Microsoft Responsible AI InvisMark for image and video. Paint's 144-bit writer framing does not match the public repository's 100-bit pretrained checkpoint interface, so compatibility is not assumed. The parser reports the signed identifier; there is no validated local pixel decoder. Microsoft's external Content Provenance Detection API is the removal oracle because it reports `Watermark` separately from `C2PA`; a metadata-stripped, pixel-identical control must remain watermark-positive before an output-negative result is attributed to pixel removal. The no-account web page for the same check is <https://ai.azure.com/nextgen/validate>; its clean outcome is rendered as `Inconclusive`, and that public provider oracle is the surface used for the measured removal certification. Sources: https://xusheng.dev/posts/reversing/mspaint_invisible_watermark/main/, https://github.com/c2pa-org/softbinding-algorithm-list/blob/main/softbinding-algorithm-list.json, and https://learn.microsoft.com/en-us/azure/ai-services/content-safety/how-to/how-to-provenance-detection
|
||||
- **Built in the dated batch:** soft-binding vendor detection, IPTC Photo
|
||||
|
||||
@@ -106,6 +106,11 @@ C2PA_AI_VENDORS: tuple[C2paAiVendor, ...] = (
|
||||
"Dreamina",
|
||||
asserts_ai=True,
|
||||
),
|
||||
# Amazon Bedrock signs Nova Canvas images and Nova Reel video as "Amazon Web
|
||||
# Services, Inc." with claim generator "Amazon Bedrock" (aws-samples outputs,
|
||||
# checked 2026-09-26). Listed before Canva: the Nova Canvas software agent
|
||||
# contains the Canva token, so a byte scan finds both.
|
||||
_vendor(b"Amazon Web Services", "Amazon", "Amazon Bedrock (Nova)", "Amazon"),
|
||||
_vendor(b"Canva", "Canva", "Canva (Magic Media)", "Canva"),
|
||||
_vendor(b"Eleven Labs", "ElevenLabs", "ElevenLabs", "ElevenLabs"),
|
||||
_vendor(b"fal-ai", "fal.ai", "fal.ai", "fal.ai", asserts_ai=True),
|
||||
|
||||
@@ -41,6 +41,7 @@ from remove_ai_watermarks._internal.c2pa import (
|
||||
from remove_ai_watermarks._internal.constants import (
|
||||
C2PA_AI_TOOLS,
|
||||
C2PA_AI_VENDORS,
|
||||
C2PA_CHUNK_TYPE,
|
||||
C2PA_CLAIM_GENERATOR_PLATFORMS,
|
||||
C2PA_IDENTITY_AI_ORGS,
|
||||
C2PA_ISSUERS,
|
||||
@@ -731,6 +732,40 @@ def _metadata_region(head: bytes) -> bytes:
|
||||
return head
|
||||
|
||||
|
||||
def _c2pa_store_bytes(head: bytes, fallback: bytes) -> bytes:
|
||||
"""The C2PA manifest-store bytes (JPEG APP11, PNG ``caBX``) of ``head``, else ``fallback``.
|
||||
|
||||
The issuer registry is matched by substring, and a metadata region also holds
|
||||
XMP, whose boilerplate names Adobe (``Adobe XMP Core``, ``ns.adobe.com``): a
|
||||
vivo X300 camera capture whose manifest the reader could not open was labeled
|
||||
"C2PA signer: Adobe" from its XMP packet. Signer identity lives in the store, so
|
||||
the fallback scan reads only the store where the container locates it. ``head``
|
||||
must be the raw buffer: the trimmed metadata region has lost the PNG framing.
|
||||
"""
|
||||
size = min(len(head), _SCAN_BYTES)
|
||||
out = bytearray()
|
||||
if head.startswith(b"\xff\xd8"):
|
||||
index = 2
|
||||
while index + 4 <= size and head[index] == 0xFF:
|
||||
marker = head[index + 1]
|
||||
if marker in (0xDA, 0xD9):
|
||||
break
|
||||
length = int.from_bytes(head[index + 2 : index + 4], "big")
|
||||
if length < 2:
|
||||
break
|
||||
if marker == 0xEB:
|
||||
out += head[index + 4 : min(index + 2 + length, size)]
|
||||
index += 2 + length
|
||||
elif head.startswith(b"\x89PNG\r\n\x1a\n"):
|
||||
position = 8
|
||||
while position + 8 <= size:
|
||||
(length,) = struct.unpack(">I", head[position : position + 4])
|
||||
if head[position + 4 : position + 8] == C2PA_CHUNK_TYPE:
|
||||
out += head[position + 8 : min(position + 8 + length, size)]
|
||||
position += 12 + length
|
||||
return bytes(out) or fallback
|
||||
|
||||
|
||||
def _first_token_match(head: bytes, table: tuple[tuple[bytes, str], ...]) -> str | None:
|
||||
"""First platform in ``table`` whose token appears in ``head``, else None.
|
||||
|
||||
@@ -1225,8 +1260,9 @@ def _identify_from_evidence(
|
||||
# The reader already named which failures moved a dimension; re-deriving that here
|
||||
# by substring made the displayed reason a second, looser rule than the verdict.
|
||||
failed_c2pa_codes = [str(code) for code in cast("list[object]", info.get("c2pa_failed_codes", []))]
|
||||
issuers = [info["issuer"]] if info.get("issuer") else _issuers_in(region)
|
||||
signer_label = _signer_platform(region, issuers)
|
||||
store = _c2pa_store_bytes(head, region)
|
||||
issuers = [info["issuer"]] if info.get("issuer") else _issuers_in(store)
|
||||
signer_label = _signer_platform(store, issuers)
|
||||
# Full AI generation (trainedAlgorithmicMedia) vs an AI-enhanced real photo
|
||||
# (compositeWithTrainedAlgorithmicMedia). The structured kind is parsed once in
|
||||
# _internal.c2pa._structured_manifest_fields (covers PNG + any container the c2pa-python
|
||||
@@ -1368,7 +1404,7 @@ def _identify_from_evidence(
|
||||
and trained_source
|
||||
and c2pa_marker_in(head)
|
||||
and not soft_binding_blocks_synthid
|
||||
and (vendors := synthid_evidence_vendors_in(region))
|
||||
and (vendors := synthid_evidence_vendors_in(store))
|
||||
):
|
||||
synthid = synthid_verdict(", ".join(vendors))
|
||||
if synthid:
|
||||
|
||||
@@ -883,6 +883,43 @@ class TestIdentifyRealSamples:
|
||||
assert any("SynthID" in watermark for watermark in r.watermarks)
|
||||
assert any("Google Photos AI edit" in caveat for caveat in r.caveats)
|
||||
|
||||
def test_amazon_bedrock_nova_canvas_is_not_canva(self):
|
||||
# aws-samples Nova Canvas output: valid C2PA signed "Amazon Web Services,
|
||||
# Inc.". Before the Amazon row, the byte fallback matched "Canva" inside the
|
||||
# "Nova Canvas" software agent and reported Canva (Magic Media).
|
||||
r = identify(SAMPLES_DIR / "amazon-bedrock-nova-canvas.png", check_visible=False, check_invisible=False)
|
||||
assert r.is_ai_generated is True
|
||||
assert r.platform == "Amazon Bedrock (Nova)"
|
||||
assert not any("Canva" in watermark for watermark in r.watermarks)
|
||||
|
||||
def test_xmp_boilerplate_does_not_name_a_c2pa_signer(self):
|
||||
# A vivo X300 capture whose manifest the reader could not open was labeled
|
||||
# "C2PA signer: Adobe" from its XMP packet ("Adobe XMP Core"). The fallback
|
||||
# scan reads the manifest store (APP11), not the whole metadata region.
|
||||
from remove_ai_watermarks.identify import _c2pa_store_bytes, _issuers_in, _metadata_region
|
||||
|
||||
def segment(marker: int, payload: bytes) -> bytes:
|
||||
return bytes([0xFF, marker]) + (len(payload) + 2).to_bytes(2, "big") + payload
|
||||
|
||||
xmp = b"http://ns.adobe.com/xap/1.0/\x00<x:xmpmeta x:xmptk='Adobe XMP Core 5.1.2'/>"
|
||||
store = b"JP\x00\x01jumbjumdc2pa vivo C2PA Device CA1 c2pa.claim.v2"
|
||||
jpeg = b"\xff\xd8" + segment(0xE1, xmp) + segment(0xEB, store) + b"\xff\xda\x00\x02" + b"\x00" * 16
|
||||
|
||||
region = _metadata_region(jpeg)
|
||||
assert "Adobe" in _issuers_in(region)
|
||||
assert _issuers_in(_c2pa_store_bytes(jpeg, region)) == []
|
||||
|
||||
def test_png_store_is_read_from_the_raw_buffer(self):
|
||||
# The trimmed region drops the PNG signature and chunk lengths, so the store
|
||||
# must come from the raw buffer to narrow a PNG at all.
|
||||
from remove_ai_watermarks.identify import _c2pa_store_bytes, _metadata_region
|
||||
|
||||
head = (SAMPLES_DIR / "amazon-bedrock-nova-canvas.png").read_bytes()
|
||||
region = _metadata_region(head)
|
||||
store = _c2pa_store_bytes(head, region)
|
||||
assert b"Amazon Web Services" in store
|
||||
assert len(store) < len(region) // 4
|
||||
|
||||
def test_apple_image_playground_attributed(self):
|
||||
# Real Image Playground export (2026-09-23): XMP photoshop:Credit plus the IPTC
|
||||
# digitalSourceType, no C2PA. The credit names the platform; the IPTC
|
||||
|
||||
@@ -3236,6 +3236,15 @@ class TestYouTubeReencodedSamples:
|
||||
assert contradicts_video_provenance("kling", veo.metadata_markers)
|
||||
assert not contradicts_video_provenance("kling", kling.metadata_markers)
|
||||
|
||||
def test_amazon_bedrock_nova_reel_is_attributed(self):
|
||||
# aws-samples Nova Reel 1.1 output: C2PA from Amazon Web Services with
|
||||
# claim generator "Amazon Bedrock"; before the vendor row the platform was None.
|
||||
from remove_ai_watermarks.video import identify_video
|
||||
|
||||
report = identify_video(self._DIR / "amazon-bedrock-nova-reel.mp4", check_visible=False)
|
||||
assert report.is_ai_generated is True
|
||||
assert report.platform == "Amazon Bedrock (Nova)"
|
||||
|
||||
def test_original_grok_video_is_xai_without_synthid(self):
|
||||
from remove_ai_watermarks.video import identify_video
|
||||
|
||||
|
||||
Reference in new issue
Block a user