fix: detect AI markers in compressed PNG text (#127)

Co-authored-by: Guillaume Meyer (The Opinionated Man) <1385518+guillaumemeyer@users.noreply.github.com>
This commit is contained in:
初心Yearth
2026-08-18 11:56:48 -07:00
committed by GitHub
co-authored by Guillaume Meyer
parent e2170a8e17
commit 360c8e8e4d
2 changed files with 38 additions and 11 deletions
+17 -11
View File
@@ -295,16 +295,23 @@ def _generator_product_hits(entries: list[tuple[str, str]]) -> list[str]:
return hits
def _png_text_hits(payload: bytes, ctype: bytes) -> tuple[list[str], list[str]]:
"""Return flat-marker and product-name hits from a PNG text chunk."""
entries = _png_text_entries(payload, ctype)
decoded = "\n".join(value for _key, value in entries).encode("utf-8")
hits = _contains_any(payload + b"\n" + decoded, AI_META_HINTS + C2PA_MARKERS)
return hits, _generator_product_hits(entries)
def _text_chunk_is_ai(payload: bytes, ctype: bytes) -> bool:
"""True when a PNG text chunk carries AI/C2PA markers.
Flat markers (AI_META_HINTS + C2PA_MARKERS) match anywhere in the
payload; generator product names only match generator-bearing key
Flat markers (AI_META_HINTS + C2PA_MARKERS) match the raw payload and
decoded text; generator product names only match generator-bearing key
values (see _generator_product_hits).
"""
if _contains_any(payload, AI_META_HINTS + C2PA_MARKERS):
return True
return bool(_generator_product_hits(_png_text_entries(payload, ctype)))
hits, product_hits = _png_text_hits(payload, ctype)
return bool(hits or product_hits)
def inspect_png(data: bytes) -> tuple[bool, bool, list[str]]:
@@ -329,12 +336,11 @@ def inspect_png(data: bytes) -> tuple[bool, bool, list[str]]:
has_c2pa = True
findings.append(f"PNG chunk {name} (possible C2PA container)")
if ctype in (b"tEXt", b"zTXt", b"iTXt", b"eXIf"):
hits = _contains_any(payload, AI_META_HINTS + C2PA_MARKERS)
product_hits = (
_generator_product_hits(_png_text_entries(payload, ctype))
if ctype in (b"tEXt", b"zTXt", b"iTXt")
else []
)
if ctype in (b"tEXt", b"zTXt", b"iTXt"):
hits, product_hits = _png_text_hits(payload, ctype)
else:
hits = _contains_any(payload, AI_META_HINTS + C2PA_MARKERS)
product_hits = []
if hits or product_hits:
has_ai = True
if any(h.lower() in ("c2pa", "contentcredentials", "jumb") for h in hits):
+21
View File
@@ -137,6 +137,27 @@ def test_itext_software_matches():
assert any("AI generator" in f for f in findings)
@pytest.mark.parametrize(
("ctype", "payload"),
[
(b"zTXt", b"Comment\x00\x00" + zlib.compress(b"Generated by AI")),
(
b"iTXt",
b"Comment\x00\x01\x00\x00\x00" + zlib.compress(b"Generated by AI"),
),
],
)
def test_compressed_text_flat_hint_matches_and_is_dropped(ctype: bytes, payload: bytes):
data = _minimal_png_with_text_chunk(ctype, payload)
_has_c2pa, has_ai, findings = inspect_png(data)
assert has_ai is True
assert any("Generated by" in f for f in findings)
cleaned, actions = strip_png(data, strip_all_text=False)
assert ctype not in cleaned
assert any("drop" in action for action in actions)
def test_strip_keep_mode_drops_generator_tagged_chunk():
data = _minimal_png_with_text_chunk(b"tEXt", _text("Software", "ChatGPT"))
cleaned, actions = strip_png(data, strip_all_text=False)