Unplug lattice detector from package, expand research docs

This commit is contained in:
Victor Kuznetsov
2026-08-23 11:13:56 -07:00
parent 2445086fa4
commit d22872f84d
34 changed files with 2210 additions and 1709 deletions
+3 -54
View File
@@ -14,7 +14,6 @@ defaults. This page focuses on choosing the right command.
| Command or signal | Required installation |
| --- | --- |
| `metadata` and metadata-only `identify` | Default package |
| `detect-synthid` and the calibrated-size SynthID pixel signal in `identify` | `remove-ai-watermarks[pixels]` |
| `verify-openai-synthid` | `remove-ai-watermarks[verify]`, API access, and `OPENAI_API_KEY` |
| Visible signals in `identify` | `remove-ai-watermarks[visible]` (`pixels` is the minimal runtime) |
| Open DWT-DCT signals in `identify` | `remove-ai-watermarks[detect]` |
@@ -57,58 +56,8 @@ remove-ai-watermarks identify image.png --no-visible
```
Despite the historical option name, `--no-visible` skips all pixel detectors,
including the pipeline lattice described below, visible marks, open DWT-DCT, and
TrustMark. Metadata inspection still runs.
## Detect the generation-pipeline pixel lattice (experimental)
```bash
remove-ai-watermarks detect-synthid image.png
remove-ai-watermarks detect-synthid image.png --json
remove-ai-watermarks detect-synthid native-period.png --fixed-period
```
This route is experimental. Signed provenance, read by `identify` and confirmed
against the provider by `verify-openai-synthid`, remains the supported way to
establish SynthID. The command returns one of `detected`, `indeterminate`, or
`unsupported`, and it does not detect the SynthID watermark: its statistic disappears when the image
is cropped off the tile grid, and it changes when the generator's pipeline
changes, so read a positive as evidence about the pipeline and never as a
watermark claim. The JSON carries `identifies_watermark` and
`tile_aligned_crop_required` for exactly this reason. The
runtime detector covers one frozen periodic lattice family in the
[calibrated image-size range](synthid.md#32-how-our-tool-detects-the-supported-carrier)
and needs the `pixels` extra. The production default uses registered-v3 from
250,000 through 10,000,000 decoded pixels with both sides at least 256 pixels.
An opponent-registered-v1 fallback covers 1 through 10 megapixels, both sides
at least 768 pixels, and selected carrier periods 7.9 through 12.0. Period-8
candidates must also pass an opponent-color block-edge veto for the native JPEG
lattice. The
separately challenged opponent-color large-v1 branch covers above 10,000,000
through 18,000,000 pixels when both sides are at least 2,048 pixels.
Registered-v3 performs a bounded carrier-period search and independent split-
patch confirmation. Its measured positive
scale range is approximately 0.65 through 1.5. The fallback recovered 49/49
lossless 0.5x-0.75x views from seven official positives. Its period-8 veto
rejected 1,790 codec-lattice crossings, and 350 matched 0.5x controls produced no
base crossing. The earlier period-band rule accepted 0/1,000 post-freeze Picsum
controls. `identify` uses this production router.
`--fixed-period` explicitly selects the faster legacy fixed-v2 diagnostic below
10 megapixels. It does not resize or register the carrier and is not a
production positive route. `--register-scale` forces the registered-v3 cascade,
including its opponent fallback, on geometry where the default would select
large-v1.
The native large and opponent-registered branches are codec-sensitive. The
large branch fell from 7/7 to 0/7 after same-size JPEG-95 or JPEG-90; the
fallback retained 0/63 JPEG-95, JPEG-85, and WebP-95 views. A miss on a lossy
re-encode is therefore inconclusive.
It is positive-only: `indeterminate` means the score stayed below this
detector's threshold, while `unsupported` means the image geometry is outside
its scope. Neither result proves that another SynthID epoch or payload is
absent.
JSON output includes the exact reason plus provider scope, backend, pixel
preservation, and metadata-use audit fields.
including visible marks, open DWT-DCT, and TrustMark. Metadata inspection still
runs.
## Verify OpenAI SynthID from pixels
@@ -140,7 +89,7 @@ error code, request id, `Retry-After`, and whether an explicit caller-controlled
retry is appropriate through `OpenAIProvenanceError`; the verifier itself never
retries an upload.
The JSON result uses the same provider-scope, backend, pixel-preservation, and
metadata-use audit fields as the local detector.
metadata-use audit fields as `verify_openai_synthid`.
The Python API enforces the same boundary with the required explicit intent
flag `verify_openai_synthid(path, acknowledge_upload=True)`.
+1 -1
View File
@@ -24,7 +24,7 @@ to run the tool. Use the maintainer references only when changing the code.
| [Verification plan](verification-plan.md) | Verification methods, completed measurements, and remaining validation gaps. |
| [Release and distribution](release-and-distribution.md) | PyPI, Homebrew, Hugging Face Space, and release workflow. |
| [Watermarking landscape](watermarking-landscape.md) | Vendor signals and detection approaches. |
| [SynthID technical reference](synthid.md) | Mechanism, shipped lattice and provenance, robustness, regeneration. |
| [SynthID technical reference](synthid.md) | Mechanism, provenance, robustness, regeneration. |
## Research archive
+7 -66
View File
@@ -64,73 +64,14 @@ identity or exact texture, and each then runs the same face stage.
`qwen-zimage` is the higher fidelity of the two. Both are large, slow, and may
still alter small text or difficult faces.
### The experimental pixel route reads a pipeline lattice, not the watermark
### There is no local SynthID pixel detector in the package
Signed provenance is the primary and supported route for SynthID in this
project; the pixel route described here is experimental. Google does not
publish the proprietary SynthID payload decoder, and nothing
in this project reproduces one. What the local pixel route detects is a
periodic lattice anchored at the image origin: it is destroyed by a crop of
seven pixels on the large branch and by a crop of two pixels on the
ordinary-size registered-v3 route, whereas the published SynthID evaluation
retains 99.97% of its detection rate under aggressive crop and resize. A
positive therefore
identifies the generation pipeline that leaves the lattice, and says nothing
about whether the image carries a watermark. It also changes when a provider
changes pipeline: output from `gemini-2.5-flash-image` scores at the level of
unrelated photographs. The project additionally offers provenance-based
recognition from Google AI C2PA or current OpenAI C2PA with an explicit
watermark action, and that provenance route is the only one that speaks about
a watermark at all.
The ordinary-size route shares the same phase lock. On 2026-08-16 a two-pixel
diagonal crop removed all 28 in-geometry foreign-generator detections and all 8
detected Google provenance positives from registered-v3, with the statistic
recovering only at crop offsets that are multiples of four. The route therefore
fires on other vendors' images because those pipelines leave a matching
origin-anchored lattice, not because a watermark is present.
The published control rates for this route were all measured on photographs.
Against other generators' output, which is the population a provenance tool is
actually pointed at, the shipped runtime accepted 29 of 223 signed non-Google
images on 2026-08-16: 13.0% overall, 24.1% on Adobe Firefly, with a top score
of 3.01 against a threshold of 1.0. Treat a positive as evidence only when the
alternative generators have been ruled out by other means.
Sensitivity has only ever been reported on images the route was calibrated
against. The first out-of-distribution measurement was taken on 2026-08-16: 11
images generated at 5632x3072 by `gemini-3.1-flash-image`, a model that did not
exist when the large branch was calibrated, written straight from the API with
no lossy-codec history and watermarked by Google's documented policy that all
generated images carry SynthID. Eight were detected and three returned
indeterminate, a rate of 72.7% with a one-sided 95% lower bound of 43.6%. The
sample is small and one provider wide, so read it as an order of magnitude
rather than an operating point.
Cropping the same images to 4096x2560 on the tile grid retained 4 of 6. Taking
the identical crop seven pixels off the grid retained 0 of 6, with every score
negative. A crop that is not tile-aligned removes the signal entirely, which is
the sharpest way to see that this route does not read the watermark: the
published SynthID evaluation keeps 99.97% of its detection rate under
aggressive crop and resize.
The route's declared scope is narrower still. It does not cover images outside
the selected mode's size range, crop, strong
JPEG compression, video, or future carrier epochs. The production default
searches a bounded fractional-period range from 250,000 through 10,000,000
pixels and requires both sides to be at least 256 pixels. Its measured positive
range is approximately scale 0.65 through 1.5. A precision-first opponent-color
fallback covers only 1 through 10 megapixels, sides of at least 768 pixels, and
periods 7.9 through 12.0; it recovered the measured lossless 0.5x-0.75x cohort.
At period 8, an additional block-edge veto rejects the ordinary JPEG lattice;
this does not make arbitrary crops or codecs supported. The fallback retained
0/63 lossy JPEG/WebP views, so a lossy miss remains inconclusive. Above 10
through 18
megapixels, the separate large-v1 route requires native, lossless pixels and
both sides at least 2,048 pixels. The fixed-v2 branch is retained only behind
the explicit `detect-synthid --fixed-period` diagnostic. A
`indeterminate` or `unsupported` result is not a negative universal verdict, and
removal still requires the matching provider oracle for confirmation.
Google does not publish the proprietary SynthID payload decoder, and the
package does not ship one. Signed provenance is the supported route:
Google AI C2PA or current OpenAI C2PA with an explicit watermark action.
`verify-openai-synthid` is the official remote pixel check for OpenAI.
Research on a periodic lattice expert is in
[synthid-detector-research.md](synthid-detector-research.md).
For important outputs:
+13 -10
View File
@@ -451,16 +451,20 @@ metadata extraction from verdict logic:
both extractors reach the same answer. It did not, and the record path silently
reported no SynthID for images the file path flagged.
- `identify` preserves the path-based API and adds the optional registered
visible-mark, open invisible-watermark, and generation-pipeline lattice
detectors after extraction.
visible-mark and open invisible-watermark detectors after extraction.
### SynthID periodic carrier detector
### SynthID periodic carrier detector (research only)
[`synthid_detector.py`](../src/remove_ai_watermarks/synthid_detector.py) is the
The local lattice expert is not part of the public package. Runtime code lives
in [`scripts/synthid_runtime/`](../scripts/synthid_runtime/) and the campaign
log is [`synthid-detector-research.md`](synthid-detector-research.md). The
notes below are the calibration history of that research expert.
[`synthid_detector.py`](../scripts/synthid_runtime/synthid_detector.py) is the
runtime form of the frozen 2048x2048 periodic-tile experiment. It folds a
Gaussian high-pass residual modulo 16x16 within a calibrated pixel-count range
and compares the normalized RGB tile with the bundled float64 template
`assets/synthid_periodic_tile_2048_v1.npz`. Exact multiples use the original
`scripts/synthid_runtime/synthid_periodic_tile_2048_v1.npz`. Exact multiples use the original
reshape-and-mean path; other sizes use count-correct modulo folding,
without resize. Channels are filtered and folded sequentially, and partial edge
blocks are accumulated without a full-frame padding buffer so the 18-megapixel
@@ -644,8 +648,8 @@ Every one of those control rates is photographic. Against 223 corpus images
whose C2PA names a non-Google generator, the unchanged entry point accepted 29
(`0.130`, Adobe Firefly `0.241`, highest foreign score `3.01`), all from
registered-v3. The branch reads a lattice shared across generation pipelines,
which is why `identify` reports it as the experimental `pipeline_lattice`
signal and never as a watermark.
which is why it must not be reported as a watermark. The public `identify`
path no longer calls this expert.
The branch is also phase-locked to the image origin, exactly like the large
expert. A two-pixel diagonal crop killed all 28 in-geometry foreign detections
@@ -668,9 +672,8 @@ rule accepted 0/1,000 post-freeze Picsum controls. Period 12.8 remains excluded,
and lossy JPEG/WebP views remain inconclusive.
The runtime precedence is registered-v3, the bounded opponent fallback, then
large-v1 above 10 megapixels. Passing
`register_scale=False`, or the CLI's `--fixed-period`, selects the legacy fixed
diagnostic explicitly. The research bank in
large-v1 above 10 megapixels. Passing `register_scale=False` selects the legacy
fixed diagnostic explicitly. The research bank in
`scripts/synthid_routed_expert_bank.py` keeps all three observations for audits.
Neither runtime nor research routing returns a clean-image verdict.
+6 -41
View File
@@ -12,52 +12,18 @@ path that still runs on CPU and combines `video` and `diffusion`. Add `heif`
independently when path-based pixel APIs must decode HEIC, HEIF, or AVIF. See
the complete [feature-extra matrix](installation.md#feature-extras).
## Detect the generation-pipeline lattice (experimental)
Install `remove-ai-watermarks[pixels]`, then call the lazy top-level API:
## Verify OpenAI SynthID
```python
import remove_ai_watermarks as raiw
result = raiw.detect_synthid("input.png")
print(result.status) # "detected" | "indeterminate" | "unsupported"
print(result.score) # float for a supported image size, otherwise None
print(result.threshold) # frozen operating point
print(result.reason) # support or non-detection reason, otherwise None
# Force the legacy native-period diagnostic only when auditing that expert.
fixed_diagnostic = raiw.detect_synthid("native-period.png", register_scale=False)
result = raiw.verify_openai_synthid("input.png", acknowledge_upload=True)
print(result.status)
```
This is experimental and is not a watermark detector. Signed provenance through
`identify` is the supported SynthID route. Its statistic is destroyed by a seven-pixel
crop while the published SynthID evaluation survives aggressive crop and
resize, so a positive identifies the generation pipeline. The result exposes
`identifies_watermark` and `tile_aligned_crop_required` so a caller cannot
reach the wrong conclusion from `status` alone. It is positive-only and covers
one measured periodic lattice family
in the [calibrated image-size range](synthid.md#32-how-our-tool-detects-the-supported-carrier).
The default and `identify` use registered-v3 over 250,000 through 10,000,000
decoded pixels, with both sides at least 256 pixels. An opponent-registered-v1
fallback covers 1 through 10 megapixels, sides of at least 768 pixels, and
periods 7.9 through 12.0; period-8 candidates also require the opponent-color
block-edge codec veto. Large-v1 covers 10 through 18 megapixels. Each score
uses a threshold of `1.0`; registered-v3 also requires independent split-patch
confirmation. The fallback is qualified for measured lossless 0.5x-0.75x
views, not lossy retranscodes.
`register_scale=True` forces the registered-v3 cascade, including its bounded
opponent fallback, while `False` explicitly selects the legacy native-period
fixed-v2 diagnostic below the large-image boundary.
`indeterminate` means only that the selected local model did not find its
carrier; `unsupported` means no local expert covers the input geometry. Neither
is proof that the image contains no SynthID watermark.
Both local and official OpenAI JSON results expose `signal_family`,
Official OpenAI JSON results expose `signal_family`,
`provider_scope`, `backend`, `metadata_used_for_verdict`, and
`pixels_preserved`; the local result adds `identifies_watermark` and
`tile_aligned_crop_required`, so callers do not need to infer the evidence boundary from
the detector name.
Remote transport and response failures raise `OpenAIProvenanceError`. Its
`pixels_preserved`. Remote transport and response failures raise `OpenAIProvenanceError`. Its
`status_code`, `error_code`, `request_id`, `retry_after`, and `retryable`
attributes let a caller implement bounded backoff or a circuit breaker without
turning an API outage into a false `not_detected` result. One function call still
@@ -353,8 +319,7 @@ such as a thumbnail and perceptual hash; aggregate feature families do not.
`identify_from_evidence` does not reopen the source file by default: it evaluates
metadata only, and the pixel-backed checks remain in the path-based `identify`
call: registered visible marks, open invisible-watermark decoders, and the
experimental generation-pipeline lattice signal.
call: registered visible marks and open invisible-watermark decoders.
Pass `image_path` together with `check_visible` or `check_invisible` to add those
pixel detectors on top of the SAME evidence. That is how a caller asking one file
+7 -48
View File
@@ -79,9 +79,6 @@ The inspection and stripping code handles signals in these groups:
- xAI and Grok EXIF signature fields;
- Samsung AI editing markers;
- Hugging Face job metadata;
- one positive-only generation-pipeline pixel lattice in a calibrated image-size
range, experimental, which identifies the pipeline and not the SynthID
watermark; signed provenance remains the supported SynthID route;
- open Stable Diffusion style DWT-DCT watermarks with the `detect` extra;
- Adobe TrustMark with the `trustmark` extra.
@@ -128,53 +125,15 @@ Current pipeline values, both CUDA-only:
The `controlnet`, `sdxl`, `qwen` and `default` values were removed. A retired name
is rejected at parse time rather than remapped onto a surviving profile.
Google does not publish the SynthID payload decoder. This project ships a
positive-only detector for one measured periodic image-lattice family in a
calibrated image-size range, available through `detect-synthid`
and the default pixel pass in `identify` when the `pixels` extra is installed.
That lattice is not the watermark. It is anchored at the image origin: a
seven-pixel crop removes it from the large branch and a two-pixel crop removes
it from registered-v3 (all 36 tested detections across foreign-generator and
Google images), while the published SynthID evaluation survives aggressive crop
and resize, so every control rate below describes a generation-pipeline
signature and not watermark detection. Every rate quoted
below was also measured on photographs; on 223 signed non-Google generator
images the same runtime accepted 29, a rate of 13.0%. Sensitivity outside the
calibration distribution was measured once, on 11 fresh 5632x3072 images from
`gemini-3.1-flash-image`: 8 detected, 72.7%, one-sided 95% lower bound 43.6%.
The same images cropped seven pixels off the tile grid returned 0 of 6.
The default ordinary-size route uses registered-v3, including independent
split-patch phase and codeword confirmation. It accepted none of 5,993
supported controls across two nonoverlapping Open Images test cohorts and none
of 2,366 supported controls in a second-family COCO challenge. A precision-first
opponent-registered-v1 fallback covers 1 through 10 megapixels, sides of at
least 768 pixels, and carrier periods 7.9 through 12.0. Period-8 candidates must
also pass an opponent-color block-edge codec veto. It recovered 49/49 lossless
0.5x-0.75x views from seven separate official positives. The veto rejected all
1,790 measured period-8 codec crossings, 350 matched 0.5x controls had no base
crossing, and the earlier period-band rule accepted 0/1,000 post-freeze
controls. Above 10 through
18 megapixels, the production router uses a
separately challenged large branch over phase-aligned windows and opponent-color
phase agreement; both sides must be at least 2,048 pixels. It retained all seven
officially verified large Google pixel positives and accepted none of 2,637
feature-unseen, decoded-pixel-unique natural controls. A smaller post-freeze
Open Images acquisition also produced 0/41 detections. Registered-v3 has a
measured scale range of approximately 0.65 through 1.5; the narrower fallback
adds the measured lossless 0.5x-0.75x range. The large branch retained 0/7
official positives after either
JPEG-95 or JPEG-90 re-encoding, and the opponent-registered fallback retained
0/63 JPEG-95, JPEG-85, and WebP-95 views. Their scope does not include lossy
retranscodes. `detect-synthid --fixed-period` exposes
fixed-v2 only as a legacy diagnostic; its fresh-source false-positive rate
disqualified it as a production positive route. No local production expert
attributes a provider.
Google does not publish the SynthID payload decoder. This package does not
ship a local pixel detector for that watermark. Research on a periodic
lattice expert is in [synthid-detector-research.md](synthid-detector-research.md)
and `scripts/synthid_runtime/`.
The tool also recognizes presence from supported provenance: Google AI C2PA
The tool recognizes presence from supported provenance: Google AI C2PA
under Google's all-media watermark policy, and current OpenAI C2PA carrying an
explicit `c2pa.watermarked.*` action. Legacy OpenAI C2PA without that action
does not assert SynthID. A local pixel result of `indeterminate` or `unsupported`
remains inconclusive for other sizes, epochs, codecs, and payloads.
does not assert SynthID.
The optional `verify-openai-synthid` command is a separate official remote
verifier for supported OpenAI watermarks. It strips AI provenance metadata from
@@ -201,7 +160,7 @@ not a universal clean verdict.
| Provider or family | Visible | Invisible path | Metadata or provenance |
| --- | --- | --- | --- |
| Google Gemini | Sparkle | Local positive-only calibrated-size detector; diffusion regeneration | C2PA and related source signals |
| Google Gemini | Sparkle | Diffusion regeneration | 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 | Official remote pixel verifier; diffusion regeneration | C2PA and generator provenance |
| Stable Diffusion and SDXL | None registered | Diffusion regeneration; optional open decoder | Embedded parameters and text metadata |
+53 -2
View File
@@ -71,8 +71,59 @@ Open, if this head is ever considered for a product cut: a graphics/CGI
abstain. CLIP treats non-camera imagery as generation; that is the remaining
error, not Gemini contamination.
### Wild extras, not SynthID
| Hypothesis | 2026-08-23 | Use |
| --- | --- | --- |
| Missing camera PRNU | Gray `gpt-image-2` highpass RMS 0.25 vs COCO 14.6 | Texture confound. A Wiener PRNU residual on *photographs* vs Model 1 errors is the real test |
| JPEG ELA | COCO 3.13, s1 1.97, gray stamp 0.49 | Export history, leaks PNG vs JPEG, not a provider |
| CFA / Bayer presence | Untested as a *detector* | Camera photos demosaic; many generators do not. Inverse of the Bayer remover arm |
| Double-JPEG ghosts | Untested | ChatGPT download codec fingerprint, not a payload |
| Perfect-circle / text-edge rate | Untested | Graphics abstain for Model 1, not Gemini-vs-OpenAI |
None of these should be named a SynthID score.
## External literature (surveyed 2026-08-23)
AWPD / FSNet ([arXiv:2603.06723](https://arxiv.org/abs/2603.06723)) is
the published "is there any invisible watermark" task. Leave-one-algorithm-out
SynthID Acc 0.894 is *not* Model 1 and *not* a payload decoder. UniFreq's
SynthID split is 2,000 Imagen-API AIGC crops at 256x256, no photographs,
no Firefly, no OpenAI. A head trained that way can pass as watermark
presence while actually reading generator/size texture, which is the L1
failure mode.
Model 1 remains AI-versus-camera on CLIP-L-ft. That is a published
task, not a watermark task. Adjacent papers:
| Source | Claim | Map to Model 1 |
| --- | --- | --- |
| Ojha, Li, Lee, [arXiv:2302.10174](https://arxiv.org/abs/2302.10174) (CVPR 2023, UnivFD) | A classifier trained to see "fake" treats unseen generators as the real sink. Frozen CLIP + nearest neighbor / linear probe generalizes better than a trained CNN | This is the architecture. We finetuned the last two CLIP-L vision blocks instead of freezing, and put Firefly and a locked Open Images fresh set in the gate |
| Cozzolino et al., [arXiv:2312.00195](https://arxiv.org/abs/2312.00195) | CLIP linear probe, few shots from one generator, holds on DALL-E 3 / Midjourney / Firefly | Firefly is the cell we required. Their paper is why Firefly belongs in the test, not as a surprise |
| Corvi et al., [arXiv:2304.06408](https://arxiv.org/abs/2304.06408) | Spectral peaks and mid-high power differences, GAN and diffusion | Generator fingerprint, not a payload. Explains why a Fourier codebook lights up Google *and* Open Images |
| Zhong, Xu, Zou, [arXiv:2601.22778](https://arxiv.org/abs/2601.22778) (DCCT) | Self-supervised color-channel prediction under a Bayer mask; theoretical gap between photo CFA correlations and AIGC | The published "CFA as a camera vote". Untested here. Inverse of the Bayer remover arm |
| Klier and Baier, DFRWS EU 2026 | AI noise is not predominantly additive. Standard PCE vs smartphone PRNU: FPR 61% Firefly Image 4, 100% ChatGPT 5. Center crop kills those false positives without hurting true camera matches | Do not call missing PRNU a SynthID score. If we ever add a Wiener residual, crop and a recorded PCE threshold come with it |
| Popescu and Farid, IEEE Trans. Signal Process. 2005 | CFA interpolation leaves neighbor correlations; splicing breaks them | Classical forgery localization, not generation detection |
| Wang, Wang, Zhang, Owens, Efros, [arXiv:1912.11035](https://arxiv.org/abs/1912.11035) (CVPR 2020, CNNDetect) | Classifier on ProGAN + JPEG/crop aug transfers to many CNNs | The "one generator is enough" claim. Ojha is the correction once diffusion exists |
| Wang et al., DIRE, [arXiv:2303.09295](https://arxiv.org/abs/2303.09295) (ICCV 2023) | Reconstruction error under a frozen diffusion model | Open Model 1 sibling. Needs a GPU diffusion pass per image. Not a watermark |
They do not substitute for `verify-openai-synthid`.
Krawetz's Gemini-chat TPR critique is a verifier-quality claim, not a
feature we can ship. [Lead Stories, 2026-07](https://leadstories.com/analysis/2026/07/google-gemini-synthid-detector-confuses-results-within-same-chat.html)
documented Gemini repeating the first file's SynthID verdict inside a
chat; Google said that was fixed 2026-07-16. The OpenAI provenance API is
a different endpoint.
## Closed: provider names from pixels
After the keyless mark hunt closed, the remaining ask was: given a file
with no metadata, is this OpenAI, Gemini, or not AI, with almost no
errors on camera photographs. That is this section. It is not a SynthID
detector. Firefly, PixelBin, and other generators have to sit in the
test, because a head that only sees OpenAI versus Gemini versus COCO
will call Firefly a provider.
Three-way `openai` / `google` / `other` on Model 1 embeddings fails the
Firefly gate. CLIP-L-ft test accuracy 0.53; Firefly 35/31/18. CLIP-H 0.57;
Firefly 36/33/15. OpenAI versus Gemini AUC on CLIP-L-ft is 0.845; on the
@@ -128,9 +179,9 @@ PixelBin and HuggingFace jobs lean `google` (shared renderer lineage).
FLUX, NovelAI, and Reve stay `no_ai`. Local probe:
`uv run python .local-eval/synthid/prc-oklab-attack-2026-08-15/classify_openai_gemini.py image.png`.
## Production `pipeline_lattice` (google-lineage renderer)
## Research lattice expert (google-lineage renderer)
Experimental signal in `identify`, never a watermark. Production
Not a watermark and not in `identify`. `scripts/synthid_runtime/`
`detect_synthid` re-check on 628 frozen holdouts, seed 20260822, threshold
1.0.
+355 -10
View File
@@ -23,26 +23,363 @@ scene. Keyless energy, TrustMark transfer, L1 distillation, a flat-field
stamp, and a 16-32 band student all failed to read the official oracle
contrast on photographs.
S4, 2026-08-15: the two providers are not doing the same thing. Cross-image
correlation of the folded residual is high for Google (tile16 pos-pos
`+0.326`, chance `0.036`) and at chance for OpenAI (`+0.032`). Google
shares one fixed phase-coherent pattern, also present in its controls at
about half the amplitude. OpenAI shares nothing, which is what a
content-dependent post-hoc encoder produces. Comb experts that work on
Google therefore cannot be reused as an OpenAI watermark detector.
M2, 2026-08-15, re-verified 2026-08-16: Google unwatermarked pairs cannot
be minted. `imagen-*` ids 404 from the model garden. `gemini-*-image`
rejects `addWatermark` (`Cannot find field`). Current Gemini API docs
state that all generated images include a SynthID watermark. There is no
encoder-off Google path.
What the product uses for the *watermark* is signed provenance and
`verify-openai-synthid`. The experimental pixel route in `identify` is
`pipeline_lattice`, a generation-pipeline ID, not SynthID. Lineage rates
for that route are in [classifier models](synthid-classifiers.md).
`verify-openai-synthid`. The periodic-lattice expert is research-only under
`scripts/synthid_runtime/` and is not called from `identify` or the CLI.
Lineage measurements of that expert are in
[classifier models](synthid-classifiers.md).
## Closed detector routes
| Route | Close | Why |
| --- | --- | --- |
| Wavelets / FFT / cepstrum as a single-image detector | 2026-08-09 | TrustMark proxy: 318-d wavelet/spectral summary AUC 0.653 and 0 TPR at a clean calibration cut. Complex FFT maps AUC 0.516. Spatial RGB still won |
| `aloshdenny/reverse-SynthID` V3 phase codebook | 2026-08-09 | Pickle-free numeric audit. 5/5 Google positives, 0/194 then-available negatives. Discovery only: not 0.1% FPR, no same-provider hard negatives |
| `aloshdenny/reverse-SynthID` V4 | 2026-08-13 | Commit `b110836`. Better-of-two profiles: 141/355 Google positives and 191/499 controls. Frozen 1,000 Open Images: 386 accepted. Paired AUC 0.517. Threshold sits 0.02 above chance phase similarity |
| `cebeuq/Synthid-Bypass` as a local decoder | 2026-08-13 | 12 public before/after diffusion pairs. Fixed carrier 10/12 before, 0/12 after. Gemini Verify still saw SynthID on two `before` files the carrier missed. Reconstruction removal, not a payload read |
| S4: one content-independent pattern for both providers | 2026-08-15 | Google has a shared phase-coherent tile. OpenAI is at chance. See Result |
| M2: mint Google encoder-off pairs | 2026-08-15 | No current Google path emits an unwatermarked image. See Result |
| U1: one feature space for both providers | 2026-08-15 | Transfer 0.535 and 0.469 |
| Hand statistics vs a perceptually masked keyed mark (M1b) | 2026-08-15 | At equal RMS, fixed 0.670, keyed 0.669, keyed+masked 0.546. Masking, not keying, defeats hand features |
| Decoder-substitution / foreign VAE as a verified negative | 2026-08-17 | OpenAI oracle stayed `detected` at 22.3 dB. Attacking a positive does not mint a labelled negative |
| `aoxo/reverse-synthid` as extra Google templates | 2026-08-19 | Hugging Face dump scored against the frozen era bank. Extra family means raised foreign scores instead of isolating a mark |
| Origin-locked lattice as SynthID | 2026-08-16 | Two-pixel crop kills it; published SynthID keeps 99.97% TPR under aggressive crop. See classifiers for `google_lineage_renderer` |
| Keyless energy in 16-32 px | 2026-08-20 | L1 AUC 0.53; official decoder reads phase structure, not energy |
| TrustMark / open-encoder transfer | 2026-08-20 | E3 leave-one-encoder-out at chance on TrustMark (0.505 ridge, 0.510 conv) |
| L1 distillation of oracle bits | 2026-08-20 | Geometry-only AUC 0.78-0.83 beats pixel 124-d (0.64). ChatGPT export and `opened` C2PA predict `not_detected`. Inside 1254x1254, permutation p=0.45 |
| Flat 16-32 matched filter | 2026-08-21 | Leave-one-out residual correlation 0.645 on gray flats; 0.007-0.025 on COCO photo residuals. L1 AUC 0.70 was a size confound (size-matched 0.59 / 0.44) |
| `gpt-image-1` as encoder-off pair | 2026-08-21 | Does not stamp. `gpt-image-2` does |
| CNN on raw L1 bits | 2026-08-21 | Do not train. Labels are export geometry and presentation, not the mark |
| Origin-locked lattice as SynthID | 2026-08-16 | Two-pixel crop kills it; published SynthID keeps 99.97% TPR under aggressive crop. See classifiers for the honest `google_lineage_renderer` name |
| 128-photo causal wavelet student on the flat template | 2026-08-21 | Photo 16-32 jam flips only at a=24 / 18.6 dB (`a*_photo < 0.53` failed). A student cannot beat a matched filter if the photo-domain mark is that quiet |
| Photo 16-32 amplitude without the flat-arm template | 2026-08-23 | 11 COCO `gpt-image-2` edits plus the red-dot pair. Residual is the camera JPEG resized onto the edit raster, top-left 32 px dropped. Median 16-32 residual RMS 12.2, same order as 8-16 (9.0) and 32-64 (16.3). On the five tightest pairs (full residual RMS < 16): 16-32 median 2.69, still ~24x the 0.11 flat stamp and not louder than 8-16 (3.81). The photo residual does not isolate a 16-32 codeword |
| Sort-by-luma 1D chroma stego | 2026-08-23 | Flat LOO 0.914 but a luma-order shuffle is already 0.792. COCO plus tiny noise: 0.642 vs shuffle 0.640. The sort invents the correlation |
| Retinex / local-gain then 16-32 LOO | 2026-08-23 | 0.742 vs plain 16-32 LOO 0.752 on the same eight gray flats. No new carrier |
| Distant 16-32 patch coupling | 2026-08-23 | Near lag 0.13 of a tile: -0.013. Far lag ~500 px: +0.054. No nonlocal lock |
| Bispectrum of the 16-32 ring | 2026-08-23 | Degenerate: 1.0 on flats and 1.0 on COCO. Not a statistic |
| Length-255 m-sequence vs 16x16 fold | 2026-08-23 | Max abs corr 0.059. Chance for n=255 is `1/sqrt(255) ≈ 0.063` |
| Row-as-audio pitch peak | 2026-08-23 | Peak-to-mean 51 on flats, 46 on COCO noise residuals. Shared FFT peakiness, not a pitch |
| 16x16 origin search of the flat 16-32 template on photos | 2026-08-23 | Zero-shift NCC 0.006-0.024, best of 256 shifts 0.013-0.026. Origin is not hiding a lock |
Open: Google (no oracle); `chatgpt-image-latest` once the organization is
verified. A size-specific whitened 16-32 template on photographs is not
justified until amplitude is measured without the flat-arm G.
## Information budget on photographs (2026-08-21)
On 11 `gpt-image-2` photo edits, the flat-derived 16-32 template at full
amplitude against per-image whitening has median `d' = 0.93` (range
0.68-2.46). That is a best-case single-image AUC of about 0.75 even with a
perfect, perfectly aligned template. A P5 gate (FPR 0.1%, TPR 90%) needs
`d' >= 4.37`. The deficit is 4.7x in amplitude, 13.4 dB, *unless* the
photo-domain mark is louder than the flat measurement. Titration said it
is not: fish 16-32 additive jam stays `detected` through a=16 / 21.9 dB.
2026-08-23, without using that flat stamp as `G`: the aligned photo
residual in 16-32 is redraw, not a codeword. Median RMS 12.2 across 12
pairs; 2.69 on the five tightest. Neighboring octaves are as loud.
`.local-eval/synthid/prc-oklab-attack-2026-08-15/photo-band-amplitude-2026-08-23.json`.
Do not report an AUC from those 11 pairs as a detector result. With n=11
the standard error on AUC is about 0.12. Notes:
`.local-eval/synthid/prc-oklab-attack-2026-08-15/agent-detector-claude.md`.
## External literature (surveyed 2026-08-23)
Primary sources, not abstracts. Each row is mapped onto a closed or open
route in this campaign. Mechanism detail stays in
[synthid.md](synthid.md). Removal papers are on
[mark removal](synthid-removal-research.md). Classifier papers are on
[classifier models](synthid-classifiers.md).
### Official mark, not a public decoder
| Source | What it is | Map to this campaign |
| --- | --- | --- |
| Gowal et al., [arXiv:2510.09263](https://arxiv.org/abs/2510.09263) | Post-hoc encoder `f` / decoder `g`. Detection logit is not payload recovery. SynthID-O (partner variant) 136 bits at 512x512. TPR at 0.1% FPR 99.98% aggregated, 99.97% on the hardest spatial crop+resize. Trains against sampled semantics-preserving transforms, including weak VAE regeneration. Production decoder unpublished | Matches the architecture we treat as keyed `x' = x + g(x)`. Explains why a two-pixel crop kills `pipeline_lattice` but not the official oracle, and why a 22.3 dB foreign VAE still reads `detected` |
| DeepMind [US12094474B1](https://patents.google.com/patent/US12094474B1/en) and continuation [US20250149048A1](https://patents.google.com/patent/US20250149048A1/en) | Residual U-Net encoder, separate decoder, optional key, encoder/decoder ensembles that need not recognize each other | Constraint, not a recipe. Ensemble non-recognition is why one recovered Google tile cannot be reused as an OpenAI detector (S4) |
| Dathathri et al., [Nature 634:818-823 (2024)](https://www.nature.com/articles/s41586-024-08025-4) | SynthID-Text: tournament sampling of LLM tokens, open-source | Different system. Image/audio/video remain proprietary |
| OpenAI, [advancing content provenance](https://openai.com/index/advancing-content-provenance/) (2026-05-19, audio 2026-07-31) and [content provenance API](https://developers.openai.com/api/docs/guides/content-provenance) | ChatGPT / API / Codex images carry C2PA plus SynthID. Audio from 2026-07-31. `POST /v1/content_provenance_checks`. `not_detected` does not rule out another vendor | This is the oracle. C2PA and SynthID are independent entries. Do not abuse the endpoint as an adaptive reverse-engineering loop |
### Keyless presence detectors in the literature
| Source | Claim | Caveat against our gates |
| --- | --- | --- |
| Ao et al., [arXiv:2603.06723](https://arxiv.org/abs/2603.06723) (AWPD / FSNet, SAFE@CVPR 2026) | Leave-one-algorithm-out presence detection. SynthID held out: FSNet Acc 0.894 / F1 0.886, ResNet-50 Acc 0.845 / F1 0.812, ConvNeXt V2 Acc 0.866. LSB and Patchwork both fail below 60%. Hypothesis: modern invisible marks share dense high-frequency spectral anomalies | UniFreq Table 3: SynthID is 2,000 images, all AIGC, zero photographs, from `imagen-4.0-fast-generate-001` only, resized to 256x256. No OpenAI. No 0.1% FPR. Closest published analog to E3/L1 distillation. Our TrustMark leave-one-encoder-out was chance (0.505 ridge). Their high-frequency commonality is the same energy that L1 failed to read as an OpenAI payload |
| `aloshdenny/reverse-SynthID` V3/V4; Google to [The Verge, 2026-04-14](https://www.theverge.com/ai-artificial-intelligence/911579/google-synthid-ai-watermarking-system-reverse-engineered) | Spectral codebook from averaged Gemini flats. Spokesperson Myriam Khan: "It is incorrect to say this tool can systematically remove SynthID watermarks." The author also said the bypass confuses the decoder rather than deleting a payload | Our pickle-free V4 audit: paired AUC 0.517, frozen Open Images 386/1000 accepted. Not 0.1% FPR |
| Krawetz, [Hacker Factor "Reversing SynthID"](https://www.hackerfactor.com/blog/index.php?/archives/1092-Reversing-SynthID.html) and ["Meta's Un-Stable Signature"](https://hackerfactor.com/blog/index.php?/archives/1098-Metas-Un-Stable-Signature.html) | Gemini chat TPR closer to 1/20 than the paper's 99.97%. Detector weak on flats. reverse-SynthID field accuracy ~70% vs the author's 90% | Gemini-app chat is not the OpenAI provenance API. Our gray `gpt-image-2` flats are `detected` on the official OpenAI oracle. Do not mix the two verifiers |
| vitotitto LAB-a logistic (community, tiny holdout) | Reported 97.7% AUC on 20/20 | Not a P5 gate. Ignore as a detector claim |
### Different embedding loci (not post-hoc SynthID)
These papers are often cited as if they were SynthID. They are not.
| Source | Locus | Why it is not this mark |
| --- | --- | --- |
| Gunn, Zhao, Song, [arXiv:2410.07369](https://arxiv.org/abs/2410.07369) (PRC, ICLR 2025) | Pseudorandom error-correcting code in the *initial diffusion latent* | Cryptographic undetectability is a latent-code property. SynthID-Image is applied after the VAE has already decoded pixels. Our OKLab "PRC-style" noise was a category error |
| Francati et al., [arXiv:2509.10577](https://arxiv.org/abs/2509.10577) (EuroS&P 2026) | Crop-and-resize flipped about half of PRC latent signs and blocked belief-propagation decode | Confirms PRC is origin-locked in latent space. Matches why a 2 px shift kills `pipeline_lattice` and does not kill OpenAI SynthID |
| Fernandez et al. Stable Signature; Wen et al. Tree-Ring | Fine-tuned VAE decoder, or ring constraints on initial noise | In-generation. Google's paper is explicit that SynthID-Image does not modify the generator |
| TrustMark ([arXiv:2311.18297](https://arxiv.org/abs/2311.18297)), HiDDeN, StegaStamp | Open post-hoc encoder/decoder pairs | Transfer from these is E3: chance on TrustMark. WAVES ([arXiv:2401.08573](https://arxiv.org/abs/2401.08573)) stress-tests this family, not production SynthID |
Do not train a student on AWPD's Imagen-only 256 crop and call it an
OpenAI SynthID detector. That would repeat L1's contamination: a
generator/size cue, not a payload.
### Adjacent literature, not SynthID
These papers are the published versions of the wild hypotheses and of
Model 1. They detect *something else*: a camera pipeline, a generator
fingerprint, an open watermark, or steganography. None of them is a
keyless OpenAI payload decoder.
**Classical spread-spectrum and open neural marks**
| Source | What it is | Map |
| --- | --- | --- |
| Cox, Kilian, Leighton, Shamoon, IEEE TIP 1997 | i.i.d. Gaussian vector in perceptually significant spectral components. Informed detector. Collusion-resistant by construction | Ancestor of keyed CDMA. Our length-255 m-sequence vs 16x16 fold was at chance because we did not have the key, and because OpenAI is content-adaptive, not a fixed Gold code |
| Zhu et al., HiDDeN, ECCV 2018 | Joint encoder / noise layer / decoder | Open pair. AWPD leave-one-out Acc 0.985. Transfer to SynthID is E3 |
| Tancik, Mildenhall, Ng, StegaStamp, 2020 | 100 bits at 400x400, trained through print-scan | Open pair. Survives recapture. WAVES: TPR at 1% FPR collapses from 1.00 to 0.01 under regeneration |
| Wen et al., [arXiv:2305.20030](https://arxiv.org/abs/2305.20030) (Tree-Ring, NeurIPS 2023) | Pattern in the *initial diffusion noise*, recovered by DDIM inversion | In-generation, origin-locked in latent Fourier space. Crop-invariant by design. Not post-hoc SynthID |
| Fernandez et al., Stable Signature, ICCV 2023 | Fine-tune the LDM VAE decoder so every decode carries a bit string | In-generation. Gowal is explicit that SynthID-Image does not modify the generator |
| Lin and Juarez, [arXiv:2506.10502](https://arxiv.org/abs/2506.10502) (USENIX 2025) | Public-knowledge attack that removes Tree-Ring | Confirms Tree-Ring is a different object with a different kill |
**Steganalysis as a presence detector**
Fridrich and Kodovsky Spatial Rich Models (TIFS 2012) and Boroumand,
Chen, Fridrich SRNet (TIFS 2018) detect sub-bit-per-pixel spatial
stego by high-pass residuals, with pooling disabled in the front of
SRNet so the weak signal is not averaged away. AWPD cites both and
says they drift on modern deep / generative marks. That matches our
wavelet/FFT single-image detector (AUC 0.653, 0 TPR at a clean cut)
and the 16-32 energy miss on photographs: a residual energy detector
without the matching key is steganalysis of a mark that was trained
not to look like LSB.
**Generator fingerprints in the Fourier domain**
Corvi, Cozzolino, Poggi, Nagano, Verdoliva,
[arXiv:2304.06408](https://arxiv.org/abs/2304.06408) (CVPRW 2023):
GAN, diffusion, and VQ-GAN images show spectral peaks and anomalous
autocorrelation; real vs synthetic differ in mid-high radial and
angular power. reverse-SynthID averaged Gemini flats and called the
peak a watermark codebook. Corvi's result says many generators leave
*some* peak. Our V4 Open Images 386/1000 is what a generator-fingerprint
detector looks like when you calibrate it as if it were a payload.
Yao and Juarez, [arXiv:2512.11771](https://arxiv.org/abs/2512.11771)
("Smudged Fingerprints"): 14 fingerprinting methods across RGB,
frequency, and learned features; removal attacks >80% white-box, >50%
black-box. A fingerprint you can see without a key is a fingerprint
you can wipe without a key.
### Image investigation and data hiding (any method)
These are not SynthID papers. They are the rest of the toolkit: how
people hide bits in pictures, and how people tell a picture was
touched. Several of our wild hypotheses already had a published form
here.
Hiding is not one problem. Cover modification (change an existing
image), coverless / generative (sample an image that already carries
the bits), and signed metadata (C2PA) fail under different attacks.
**Cover modification, classical**
| Source | Hide how | Detect / limit |
| --- | --- | --- |
| LSB, Patchwork (Bender et al., IBM SJ 1996) | Flip low bits, or luminance of random pixel pairs | AWPD Acc < 0.60. Sparse or ±1 amplitude. SRNet / FSNet average it away |
| Westfeld F5 (2001), Fridrich nsF5 | JPEG DCT coefficients, matrix embedding | Histogram attacks on F5; nsF5 was the shrinkage fix. Domain is the codec, not a mid-band residual |
| Pevny, Filler, Bas HUGO (2010); Holub and Fridrich WOW (2012); Holub, Fridrich, Denemark UNIWARD (IH&MMSec 2013 / EURASIP 2014) | Content-adaptive costs, bits placed in texture via syndrome-trellis codes (Filler, IH 2011) | The modern spatial/JPEG floor. Distortion is *designed* to look like the cover. A 16-32 energy detector is the wrong statistic |
| Cox et al. 1997 | Spread-spectrum in significant DCT | Keyed. Already mapped above |
**Cover modification, neural (hide a whole image, not 32 bits)**
| Source | What it does | Map |
| --- | --- | --- |
| Baluja, NeurIPS 2017, "Hiding Images in Plain Sight" | Full-resolution secret image distributed across all bits of a same-size cover | Capacity is the point. Not robust, not keyed provenance. HiDDeN later added a noise layer so the secret survives JPEG |
| Jing et al., HiNet, ICCV 2021 | Invertible neural net: hide and recover as one bijection | High-capacity lossless-ish hiding. Recovery needs the exact inverse, not an official oracle |
| Yang et al., PRIS, [arXiv:2309.13620](https://arxiv.org/abs/2309.13620) | Invertible net plus robustness modules | Same family, trained through distortion |
A HiNet-style student on OpenAI pairs would learn the *edit*, not a
SynthID codeword. That is the L1 contamination again.
**Coverless / generative hiding**
The cover is never modified because there is no cover. The sampler
emits an image whose latents already encode the message.
| Source | Locus | Map |
| --- | --- | --- |
| Yang et al., Gaussian Shading, [arXiv:2404.04956](https://arxiv.org/abs/2404.04956) (CVPR 2024) | Map bits onto Gaussian latents indistinguishable from ordinary noise. Recover by DDIM inversion | Sibling of Tree-Ring / PRC. Training-free, performance-lossless *for the generator*. Not a post-hoc pixel stamp |
| Peng et al., StegaDDPM (ACM MM 2023) and later LDStega | Bits in the diffusion sampling distribution | Spatial SRNet is the wrong detector (NS-DSer, [arXiv:2602.10219](https://arxiv.org/abs/2602.10219): move steganalysis into noise space) |
| CRoSS, Pulsar, MDDM | Message-to-noise projections | Same locus. A pixel scramble does not invert the sampler |
**Passive forensics (the picture was touched, no secret assumed)**
| Source | Cue | Map |
| --- | --- | --- |
| Krawetz, "A Picture's Worth", 2007 (ELA) | Re-JPEG at lower quality, subtract | Already measured: COCO 3.13, s1 1.97, gray stamp 0.49. Codec history, not a payload. Farid publicly called ELA as likely to mislabel originals as it is to catch edits |
| Farid, IEEE TIFS 2009, JPEG ghosts | Difference energy vs a sweep of JPEG qualities; spliced regions ghost at their original Q | Untested here. The remaining JPEG check after ELA. Only defined on quantized JPEG |
| Popescu and Farid, TR2004-515 | Copy-move via duplicated regions | Not generation, not a watermark |
| Popescu and Farid, IEEE TSP 2005 | Resampling periodic correlations | Affine search cousin. A rotated SynthID residual is a different question |
| Wang et al., DIRE, [arXiv:2303.09295](https://arxiv.org/abs/2303.09295) (ICCV 2023) | Diffusion reconstruction error: generated images reconstruct, cameras do not | Model 1 sibling, needs a diffusion model. Inverse of our VAE round-trip: there the mark survived 22.3 dB; here the *error map* is the feature |
| Wang, Wang, Zhang, Owens, Efros, [arXiv:1912.11035](https://arxiv.org/abs/1912.11035) (CVPR 2020, CNNDetect) | One ProGAN classifier, heavy JPEG/crop aug, transfers to many CNNs | Ancestor of "train on one generator". Ojha showed the sink-class failure once diffusion arrived. We required Firefly for that reason |
C2PA is the non-pixel stack: a signed manifest, stripped by
`metadata --remove`. Durable Content Credentials (spec 2.4) add a
soft binding that can re-link a stripped file to a repository. That
is provenance, not hiding.
Do not train on ELA, JPEG ghosts, DIRE, or a HiNet reconstruction and
name the score SynthID.
### Waveforms that can live in a picture
A mark is a function on the pixel lattice. The literature does not
use one wave. It picks a basis whose symmetries match the attack it
fears, then hides a keyed coefficient vector in that basis. Cartesian
16-32 is one shell. Polar, scale, and diffraction are different
shells.
**Standing waves on a rectangle.** DFT / DCT / DST. A 2-D sinusoid
`cos(2π(ux + vy)/N)`. JPEG lives here. Our octave scramble destroys
one annular *radius* of these frequencies, not one orientation. A
Gabor packet is the same sinusoid windowed in space.
**Circular and log-radial waves.** Functions of `(r, θ)`, not
`(x, y)`.
| Basis | Wave | Invariance it buys |
| --- | --- | --- |
| Fourier-Mellin / log-polar (Ó Ruanaidh and Pun, Signal Processing 1998) | `r^{iα} exp(ikθ)` after a DFT magnitude | Rotation and scale become translations |
| Logarithmic radial harmonics (IH 2002) | Same family, added in pixels, detected by complex correlation | RST without going through the unstable log-polar resample |
| Polar harmonic transforms: PCET, PCT, PST | `exp(±i2π n r²)` and polar cos/sin | Rotation. Moments, not a dense codebook |
| Zernike / pseudo-Zernike | Orthogonal polynomials on the disk, radial part related to Bessel | Rotation. Classical moment watermark |
| Circular chirp (SPIE 6072, 2006) | Polar map of a 1-D chirp onto a ring | JPEG (tune chirp rate) plus rotation (the ring) |
| Tree-Ring (Wen 2023) | Concentric rings in the *latent* Fourier plane | Crop/flip by construction. Not a pixel wave |
Our prepared polar 16-32 scramble is the attack on this family: keep
the Cartesian octave energy, randomize angle. If the official decoder
still reads `detected`, the OpenAI mark is not a polar harmonic. If it
flips near 25.6 dB, angular structure mattered.
**Scale chirps.** Hyperbolic / Mellin monomials (arXiv:1208.5842):
real 1-D Mellin patterns tiled in 2-D, detected after a Radon-like
projection. Affine search of a 16-32 template is the cheap version.
Not yet run.
**Directional packets.** Dual-tree complex wavelets (approximate
analytic wave), Gabor/Morlet, ridgelets, curvelets, shearlets,
contourlets, bandelets. Multiplicative spread-spectrum on curvelet
coefficients is a published detector-design paper, not a SynthID
decoder. Our wavelet summary AUC 0.653 already said a *generic*
packet energy is not the OpenAI payload.
**Optical diffraction, actual wave physics.**
| Transform | What the wave is | Map |
| --- | --- | --- |
| Fresnel | Quadratic phase `exp(iπ r² / λz)`, a radial chirp. Energy concentrates with distance | Holographic watermarks embed the diffracted field, not the picture |
| Fractional Fourier | Rotation of the time-frequency plane. Order is an extra key | Extra key space on top of DFT |
| Double random phase encoding (Réfrégier and Javidi, Optics Letters 1995) | Random phase at input *and* Fourier plane. Ciphertext is stationary white noise | Closest optical analog of a keyed `x' = x + g(x)` that looks like noise without both screens |
| Cascaded Fresnel / CGH | Two holograms at different depths | Hardware key. Not a pixel residual we can match-filter |
A Fresnel/FrFT student without the distance or the fractional order
is another unmatched filter.
**Cyclostationary and clock-warped waves.** Periodic clock change
(Martin, Chabert, Lacaze, EUSIPCO 2005) turns a stationary host into
a cyclostationary one by a periodic time warp. Our H2 multiplicative
cyclostationarity test already put official positives *below*
controls. Do not rerun the same statistic in a new basis and call it
new evidence.
**How to read a candidate wave.** Name the coordinates `(x,y)` vs
`(r,θ)` vs `log r`, the generating function (sinusoid, chirp,
Bessel, Zernike), additive vs multiplicative, and the symmetry it
claims. Then pick one attack that kills that symmetry and one that
should not. Cartesian 16-32 scramble, polar 16-32 scramble, 2 px
shift, and a small rotation are the four cheap probes. Shift already
left OpenAI `detected`. Polar scramble is prepared. Affine/rotation
search is not.
## Still open or incomplete
- Google: no public payload decoder and no usable programmatic oracle.
The Gemini app remains quota-limited. Vertex was 403.
- `chatgpt-image-latest` encoder-off pairs: `models.retrieve` returns the
id, `images.generate` is 403 until the organization is verified
(re-checked 2026-08-23). `gpt-image-2` remains the working pair mint.
- Adding unmarked context (frame, pad, hstack, collage) to hunt a
residual is a presentation gate, not a mark statistic. Those rungs
belong with identity and layout below, and with
[mark removal](synthid-removal-research.md).
- Camera-to-ChatGPT "unchanged crop" pairs are dirty counterfactuals.
The instructed edit redraws; aligned PSNR on the frame pair was
16.6 dB. Compare pixels only after registration and a frozen residual
bound, as in the `gpt-image-2` red-dot row.
A Gemini app caption that an image "looks like a photograph" is not a
SynthID verdict. Use the provider pixel check or signed provenance.
## Wild hypotheses
Battery 2026-08-23, no official oracle. Local numbers:
`.local-eval/synthid/prc-oklab-attack-2026-08-15/wild-hypotheses-2026-08-23.json`.
Prepared attack rasters wait in `wild-attacks-2026-08-23/` for a later
verifier window. Jacobian / adaptive queries against
`verify-openai-synthid` stay out: the endpoint forbids reverse-engineering.
### Tested locally, not a mark
| Hypothesis | Result |
| --- | --- |
| Chroma ordered by luma is a 1D payload | Correlation is an artifact of the sort |
| Mark is `g(x)` of a Retinex envelope | LOO unchanged vs the raw 16-32 residual |
| Nonlocal patch agreement in 16-32 | Far patches are not coupled |
| Quadratic phase coupling (bispectrum) | Statistic saturates on camera images too |
| CDMA Gold / m-sequence in the 16x16 fold | At chance for length 255 |
| Rows as a pitched waveform | COCO noise has the same peak-to-mean |
| The photo mark is the flat stamp at an unknown 16-phase | Exhaustive shift does not lift NCC |
| Self-keyed predictor from a 16 px luma thumb | Already 0.48 vs L1, 0.50 vs COCO (`self-keyed.json`) |
| Global multiplicative cyclostationarity (H2) | Official positives below controls, 2026-08-14 |
### Not a watermark, might still be an AI-vs-camera extra
| Hypothesis | Result |
| --- | --- |
| Absence of camera PRNU | Gray `gpt-image-2` highpass RMS 0.25 vs COCO 14.6, that is missing texture, not a missing sensor. A Wiener PRNU residual on photographs is still open as a Model 1 sibling, not as SynthID |
| JPEG ELA | Flat 0.49, s1 1.97, COCO 3.13. Compression history, not a payload |
### Still crazy, not yet measured
- Content-locked affine search (small rotation plus 16-32 NCC), not origin-locked. Published analog: Fourier-Mellin / log-radial harmonics (Ó Ruanaidh and Pun 1998). Polar 16-32 scramble is the remover twin, already rastered at 25.6 dB.
- CFA presence as a camera vote (Bayer as a *detector* feature, the inverse of the remover arm). Published analog: Zhong, Xu, Zou [arXiv:2601.22778](https://arxiv.org/abs/2601.22778) (DCCT). Farid's 2005 CFA-interpolation test is the classical form.
- Wavelet Wiener PRNU on `gpt-image-2` photo edits versus Kodak. Published analog: Klier and Baier, DFRWS EU 2026, PCE vs smartphone fingerprints (ChatGPT-5 FPR 100% until a center crop).
- PNG Paeth / filter-byte residual after a lossless rewrite.
- Double-JPEG ghosting as an export fingerprint. Farid TIFS 2009. ELA already failed as a payload; the ghost sweep is the remaining codec check, JPEG-only.
- Face-gated 16-32: maybe the encoder spends budget off faces.
- Baker-map / area-preserving scramble of the mid band.
- Print-scan (physical).
- Nested open watermark plus SynthID.
- Mark in ICC / cICP / gamma, not in pixels. Strip-and-reencode already used for the oracle path; a pixel-preserving ICC rewrite is the remaining check.
- Encoder ensemble that does not recognize itself (patent). Untestable without that pair.
- Photon-shot / Poisson-domain embedding.
- CLIP-space watermark. Would show up as Model 1, not as a payload.
Do not train on official decoder bits beyond the frozen L1 study. Do not
mint a 128-photo student on the flat stamp. The information budget and
the 2026-08-23 residual measurement both say the photo-domain mark is
under the scene.
## Oracle and seeds
@@ -61,7 +398,8 @@ All three `detected` before attacks.
## Source-matched pairs (2026-08-21)
`gpt-image-2` (ChatGPT Images 2.0) can mint encoder-on/off pairs.
`chatgpt-image-latest` is 403 until the org is verified.
`chatgpt-image-latest` still 403 on generate (org verification),
re-checked 2026-08-23; retrieve of the model id succeeds.
| Input | C2PA | Oracle | Aligned PSNR |
| --- | --- | --- | ---: |
@@ -105,6 +443,14 @@ canvas `not_detected` (the same 0.20x file uploaded alone was `detected`).
## L1 is not a mark task
A missing `watermarked.unbound` assertion is not a clean negative. On
2026-08-16 the official verifier called 65 of 94 OpenAI rows without that
assertion `detected` (69%), with 9 of 9 interleaved health positives
detected so the endpoint was answering. Google's own API emits watermarked
images with no assertion, per its documentation. Corpus AUCs that treat
"no assertion" as unmarked rest on a negative class that is roughly
two-thirds positive.
Control-only 283 rows: 203 detected, 80 not_detected. Forward-temporal
nested ridge on 124 pixel features: AUC 0.649 / 0.641. Geometry-only:
0.781 / 0.826. All 21 `claim_generator=ChatGPT` controls are
@@ -119,5 +465,4 @@ not see the oracle mark contrast.
## Product remainder for the watermark
Signed provenance (`identify`) and `verify-openai-synthid` (remote, explicit
upload). A local `indeterminate` from `detect-synthid` is not a clean
SynthID negative.
upload). A research lattice miss is not a clean SynthID negative.
+11 -1
View File
@@ -13,7 +13,7 @@
| [SynthID local detector research](synthid-detector-research.md) | Hunt for a keyless local mark detector. Closed. |
| [Classifier models](synthid-classifiers.md) | Model 1 AI-versus-camera result, rejected provider CLIP, `pipeline_lattice` as google-lineage. |
| [SynthID mark removal research](synthid-removal-research.md) | Quiet-remover hunt. Closed except ~19-24 dB 16-32 scramble and blur sigma 7. |
| [Mechanism reference](synthid.md) | How SynthID works, provenance, shipped lattice, regeneration. |
| [Mechanism reference](synthid.md) | How SynthID works, provenance, robustness, regeneration. |
| [Chronological plan archive](synthid-detector-removal-plan.md) | Dated H-gates, corpora, and session notes in original order. |
## Where former sections went
@@ -28,3 +28,13 @@
| `pipeline_lattice` re-check, Spaces census | [classifiers](synthid-classifiers.md) |
| Attack / kill / flip / add / surround ladders | [removal](synthid-removal-research.md) |
| 16-32 titration, E2 scramble, Bayer and geometry | [removal](synthid-removal-research.md) |
| S4 provider split, M2 Imagen `addWatermark`, reverse-SynthID, Bypass | [detector](synthid-detector-research.md) |
| Photo `d'` budget 13.4 dB, 128-photo student, 16-32 residual without flat `G` | [detector](synthid-detector-research.md) |
| OKLab codeword replacement, add-context as presentation | [removal](synthid-removal-research.md) |
| Three-class OpenAI / Gemini / photo ask | [classifiers](synthid-classifiers.md) |
| Wild hypotheses 2026-08-23 (sort, CDMA, bispectrum, PRNU, affine-not-run) | [detector](synthid-detector-research.md) |
| Prepared polar / band-transplant / palette64 | [removal](synthid-removal-research.md) |
| External literature 2026-08-23 (Gowal, AWPD, PRC, Zhao, UnMarker, CtrlRegen, MarkNull, reverse-SynthID) | [detector](synthid-detector-research.md), [removal](synthid-removal-research.md), [classifiers](synthid-classifiers.md) |
| Adjacent literature, not SynthID (Cox, HiDDeN, StegaStamp, Tree-Ring, Ojha CLIP, Corvi Fourier, DCCT CFA, PRNU PCE) | [detector](synthid-detector-research.md), [classifiers](synthid-classifiers.md), [removal](synthid-removal-research.md) |
| Image investigation and data hiding (LSB, UNIWARD, Baluja, HiNet, Gaussian Shading, ELA, JPEG ghosts, DIRE, CNNDetect) | [detector](synthid-detector-research.md), [classifiers](synthid-classifiers.md) |
| Waveforms in a picture (DFT, Fourier-Mellin, Zernike, chirps, Fresnel, DRPE, cyclostationary) | [detector](synthid-detector-research.md) |
+65 -17
View File
@@ -12,8 +12,9 @@
## Result
The quality-preserving OpenAI SynthID remover hunt closed 2026-08-20.
Bayer, VNG demosaic, upscale-then-Bayer, barrel distortion, and scanline
jitter closed 2026-08-22: they leave the official oracle `detected`.
Bayer, VNG demosaic, upscale-then-Bayer, barrel distortion, scanline
jitter, and a 2 px shift closed 2026-08-22 on s1/s2 and 2026-08-23 on s3
and fish: they leave the official oracle `detected`.
Working residual kills on photographs cost about 19-24 dB:
@@ -41,9 +42,13 @@ presentation gate, not residual damage. Those rungs are in
| --- | --- | --- |
| Quality-preserving photo remover | 2026-08-20 | Every residual `not_detected` that is not a collage is below usable quality |
| Additive in-band jam as a quiet remover | 2026-08-21 | Fish flips only at 18.6 dB; 4-8 px at the same PSNR stays `detected` |
| Bayer bilinear / VNG / upscale-Bayer | 2026-08-22 | s1/s2 still `detected`; VNG on s2 is dirtier than scramble (18.5 vs 19.0 dB) and the mark remains |
| Barrel k1=0.06 and scanline ±0.8 px | 2026-08-22 | s1/s2 `detected` even at 14-20 dB barrel |
| Bayer bilinear / VNG / upscale-Bayer | 2026-08-23 | s1/s2/s3 and fish still `detected`. s3 bilinear 37.1 dB, fish 34.7 dB. VNG on s2 is dirtier than scramble (18.5 vs 19.0 dB) and the mark remains |
| Barrel k1=0.06, scanline ±0.8 px, and shift 2 px | 2026-08-23 | s1/s2/s3/fish `detected` even at 14-20 dB barrel. Fish scanline 32.5 dB and shift 22.7 dB still `detected` |
| TrustMark-style micro-warp | 2026-08-21 | 0.25 px / 32 dB still 100% detect on TrustMark P; OpenAI elastic ~21 dB unreplicated |
| OKLab random-codeword replacement as a quiet wipe | 2026-08-15 | On four public Google-oracle positives, replacing the period-16 tile at 0.95 dropped the *local* lattice score below 0.173 at ~54 dB. Gemini pixel verify never accepted the candidates (`Connecting to Verify AI`). Local-score intervention, not an official wipe |
| PRC-style OKLab a/b noise plus spectral peak cleaning | 2026-08-15 | Independent high-pass OKLab noise at 32.6 dB moved the median local score by only `-0.0219`. Comb suppression moved one of four below threshold at 58 dB. Not a quiet official kill |
| Add unmarked context (frame, pad, hstack, collage) | 2026-08-19 | Official `not_detected` is a presentation gate. White pad 40% linear stays `detected`; photo fill at the same geometry is `not_detected`. See detector identity/layout |
| `cebeuq/Synthid-Bypass` diffusion reconstruction | 2026-08-13 | 12 public pairs. Lattice and Gemini Verify go quiet on the *after* images because the image was redrawn, not because a payload was deleted. Faces and text are a quality claim for regeneration, not a local wipe |
Oracle: `verify_openai_synthid` after AI-metadata strip. Seeds s1, s2, s3
as in [detector research](synthid-detector-research.md). Raw files:
@@ -85,23 +90,24 @@ Preregistered close `a*_photo < 0.53` failed (`a* > 16`).
## Bayer and geometry (2026-08-22)
Frozen one-pass batch. Lattice scores on these OpenAI sources are already
`indeterminate` and are not the label. s3 and the fish photo were queued
after an OpenAI 429.
`indeterminate` and are not the label.
| Attack | s1 PSNR / oracle | s2 PSNR / oracle |
| --- | --- | --- |
| source | detected | detected |
| Bayer bilinear | 32.7 detected | 28.4 detected |
| Bayer VNG | 25.0 detected | 18.5 detected |
| upscale 1.15 then Bayer | 33.9 detected | 29.8 detected |
| barrel k1=0.06 | 19.9 detected | 14.2 detected |
| scanline ±0.8 px | 31.5 detected | 26.7 detected |
| shift 2 px | 23.1 detected | (429, not submitted) |
| 16-32 phase scramble | 24.6 not_detected | 19.0 not_detected |
| Attack | s1 | s2 | s3 | fish |
| --- | --- | --- | --- | --- |
| source | detected | detected | detected | detected |
| Bayer bilinear | 32.7 detected | 28.4 detected | 37.1 detected | 34.7 detected |
| Bayer VNG | 25.0 detected | 18.5 detected | 26.4 detected | 25.4 detected |
| upscale 1.15 then Bayer | 33.9 detected | 29.8 detected | 38.4 detected | 36.0 detected |
| barrel k1=0.06 | 19.9 detected | 14.2 detected | 20.2 detected | 18.5 detected |
| scanline ±0.8 px | 31.5 detected | 26.7 detected | 35.7 detected | 32.5 detected |
| shift 2 px | 23.1 detected | 15.9 detected | 24.7 detected | 22.7 detected |
| 16-32 phase scramble | 24.6 not_detected | 19.0 not_detected | 25.2 not_detected | 23.2 not_detected |
Camera pipeline and mild geometry do not hit the decoder basis. A 2 px
shift, which kills `pipeline_lattice`, left s1 `detected`.
shift, which kills `pipeline_lattice`, left s1/s2/s3/fish `detected`.
Scramble remains the residual kill on 4/4 sources.
`.local-eval/synthid/prc-oklab-attack-2026-08-15/bayer-geometry-2026-08-22/`.
Manifest `status` is `oracle_complete` (2026-08-22 22:07).
## Attack ladders (OpenAI oracle)
@@ -134,6 +140,48 @@ Surround: photo fill at the white-pad geometries is `not_detected` on
between 25% width (`detected`) and 35% (`not_detected`). Photo frame 15%
each side is `not_detected` on 3/3; 12% does not hold.
## Wild removers, oracle not yet run (2026-08-23)
Prepared on s1. PSNR only. Bayer fish is complete. Submit these when the
rate limit allows, not as a new training loop against the verifier.
| Attack | PSNR | Note |
| --- | ---: | --- |
| Replace 16-32 with a COCO photo's 16-32 | 25.2 | Band transplant. If this ever reads `not_detected` near 25 dB it is the quietest residual kill so far; if `detected`, the mark is not a replaceable additive layer in that annulus |
| Polar phase scramble of 16-32 | 25.6 | Same band as the cartesian scramble (24.6 dB `not_detected`). Tests circular / Fourier-Mellin / polar-harmonic families: energy stays, angle dies |
| 64-color median-cut palette | 35.6 | Quieter than scramble. Posterize-4 already stayed `detected`; this is a smarter quantizer |
| PIL RGB to CMYK to RGB | inf | No-op on this PNG. Not an attack |
Files: `.local-eval/synthid/prc-oklab-attack-2026-08-15/wild-attacks-2026-08-23/`.
Also untested: seam carving of the mid band, CMYK via a real ICC printer
profile, HEIF/AV1 round-trip, background-only 16-32 scramble (face mask),
print-scan.
## External literature (surveyed 2026-08-23)
Primary sources. Detector papers live in
[detector research](synthid-detector-research.md). Forensic stealth of
regeneration is already in [synthid.md](synthid.md#23-removal-attacks-and-forensic-detectability).
| Source | Attack | Against SynthID? | Map to this campaign |
| --- | --- | --- | --- |
| Zhao et al., [arXiv:2306.01953](https://arxiv.org/abs/2306.01953) (NeurIPS 2024) | Add noise, then denoise or regenerate (VAE / diffusion). Pixel-level invisible marks are provably removable. Semantic watermarks proposed as the alternative | Open post-hoc schemes, not production SynthID | This is the family our product uses (`qwen-zimage` / `sdxl-zimage`). Gowal trains SynthID-O against *weak* VAE regeneration. Our foreign-VAE round-trip at 22.3 dB stayed `detected`. Regeneration works when it redraws, not when it is a light codec |
| Liu et al., [arXiv:2410.05470](https://arxiv.org/abs/2410.05470) (CtrlRegen, ICLR 2025) | Controllable diffusion from clean noise, with a knob on how many noise steps to add | SOTA open watermarks | Same family. Goonatilake later finds CtrlRegen+ the *most* forensically detectable remover (AUROC 0.9999) |
| Kassis and Hengartner, [arXiv:2405.08363](https://arxiv.org/abs/2405.08363) (UnMarker, IEEE S&P 2025) | No decoder feedback. Two adversarial spectral optimizations. Breaks even some semantic watermarks (best remaining detection 43%) | Not production SynthID | Spectral disruption without an oracle is the honest analog of our 16-32 scramble, except UnMarker is optimized and we used a one-octave phase shuffle. Goonatilake: UnMarker TPR 98.28% at 0.1% FPR as a *forensic* leftover |
| Tallam et al., [arXiv:2505.08234](https://arxiv.org/abs/2505.08234) (SemanticRegen) | Partial, label-free regeneration of main objects | Tree-Ring, StegaStamp, StableSig, DWT/DCT. Not SynthID | Partial redraw. Our collage / photo-pad `not_detected` is a presentation gate, not this attack |
| Cao et al., [arXiv:2608.10166](https://arxiv.org/abs/2608.10166) (MarkNull, USENIX Security 2026) | On-manifold latent decorrelation via a public diffusion proxy. Claims 100% on 20 Imagen-3 Gemini-verify images. PSNR 25.36 dB, SSIM 0.80 | Small Gemini-verify set | Independent evidence that a no-box latent reconstruction can confuse Gemini. Does not meet this project's 40 dB / 0.99 SSIM release gate. Still generation, not a pixel-only wipe |
| Goonatilake and Ateniese, [arXiv:2605.09203](https://arxiv.org/abs/2605.09203) | Six removers all leave a forensic residue a ResNet-50 sees at >98% TPR @ 1% FPR | Applies to UnMarker, Zhao's WatermarkAttacker, CtrlRegen+ | Defeating `verify-openai-synthid` is not deniability. This is the product remainder |
| An et al., [arXiv:2401.08573](https://arxiv.org/abs/2401.08573) (WAVES, ICML 2024) | 26 attacks on StegaStamp, Stable Signature, Tree-Ring. Regeneration, not JPEG, is the attack that matters. StegaStamp TPR@1%FPR 1.00 to 0.01; Tree-Ring 0.99 to 0.12 | Open watermarks | Protocol. Our blur-sigma-7 and 16-32 scramble are closer to WAVES "distortion" than to regeneration |
| Wen et al., [arXiv:2305.20030](https://arxiv.org/abs/2305.20030) (Tree-Ring) | In-generation Fourier pattern in initial noise | Not SynthID | Kill is DDIM inversion plus latent wipe, not a pixel scramble. Lin and Juarez [arXiv:2506.10502](https://arxiv.org/abs/2506.10502) (USENIX 2025) remove it from public knowledge |
| Fernandez et al., Stable Signature, ICCV 2023 | Fine-tuned VAE decoder | Not SynthID | Regeneration that replaces the decoder is in-family. Our foreign VAE at 22.3 dB did not kill OpenAI SynthID, which is the expected mismatch |
| Google to [The Verge, 2026-04-14](https://www.theverge.com/ai-artificial-intelligence/911579/google-synthid-ai-watermarking-system-reverse-engineered) | reverse-SynthID does not systematically remove SynthID | Google spokesperson on the spectral bypass | Agrees with our V4 audit and with Bypass: reconstruction or decoder confusion, not a payload delete |
Quiet pixel-only removal is not a published result on production
OpenAI or Google SynthID. Every paper that "beats" a watermark either
redraws the image, attacks an open encoder, or reports PSNR in the same
19-25 dB band as our 16-32 scramble.
## Product remainder for removal
Invisible removal in this package regenerates through `qwen-zimage` or
+99 -1137
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -68,7 +68,6 @@ payloads. Removal remuxes either container through ffmpeg with stream copy.
Amazon Titan Image Generator + Nova Canvas (Bedrock
`DetectGeneratedContent` API), Kakao (new SynthID image adopter, May 2026),
and NVIDIA Cosmos (SynthID video). No public payload decoder is available;
unlike the project's calibrated-size generation-pipeline lattice expert,
these signals have no measured local payload decoder here.
- **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. (Our parsers target the stable embedded-manifest format documented in C2PA 2.1 §11; that format is unchanged in 2.4 -- the new pieces are repository/soft-binding infra, not the on-file box layout, so no parser change is implied.) Spec: https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html We now READ the soft-binding `alg` (`C2PA_SOFT_BINDINGS` / `soft_binding_vendors_in`) to name the forensic-watermark vendor, and locally DECODE the one open scheme, Adobe TrustMark (`trustmark_detector`); the rest (Digimarc/Imatag/Steg.AI/...) stay name-only (proprietary decoders).
- **Built in the dated batch:** soft-binding vendor detection, IPTC Photo