Is this photo real, or did a machine make it? SynthCheck takes an image file apart and shows you the evidence — Content Credentials, generator metadata, camera EXIF, and the fingerprints left in the file's own structure.
Live at synthcheck.app. Free, no account, no limits.
Nothing is uploaded. Every byte is parsed by JavaScript in your own browser. There is no backend to send anything to — switch off your internet and the tool still works, which is the easiest proof there is. This repository exists so that claim can be checked rather than believed.
Most detectors show a confidence percentage. Ask where the number comes from and you will not get a straight answer. SynthCheck shows the evidence and grades it, and says plainly when the honest answer is "cannot tell".
It verifies C2PA signatures locally. Other tools read a Content Credentials manifest and repeat what it claims. SynthCheck checks the mathematics — in the browser, with the image never leaving the device. As far as we know it is the only tool that does this without an upload.
Verification answers three questions that fail separately, so they are reported separately:
| Question | What is checked | What a pass proves |
|---|---|---|
| Is the signature valid? | COSE_Sign1 verified via WebCrypto against the certificate in the manifest | The manifest was signed by the holder of that key and has not been altered |
| Does it bind to this image? | The c2pa.hash.data hash recomputed over the image bytes, honouring the manifest's exclusion ranges |
The signed record describes these exact bytes — it was not lifted off another file |
| Who signed it? | X.509 subject, issuer, validity, and internal chain consistency | Only what the certificate asserts. Confirming the signer is genuinely that party needs a trust list, which this tool does not ship — and it says so |
The middle row is the one that matters and the one naive readers skip. A manifest transplanted from a genuinely signed photo onto a fake keeps a perfectly valid signature. Anything that asks "is it signed?" instead of "does the signature cover these bytes?" waves that through. There is a test fixture for exactly this case, and it must fail.
- C2PA / JUMBF Content Credentials — parsed, then cryptographically verified
- IPTC
DigitalSourceType—trainedAlgorithmicMediaand its relatives - XMP packets, including generative-edit markers
- Generator leftovers — Stable Diffusion, ComfyUI, InvokeAI and Fooocus write the prompt, seed, sampler and model name into the file, and nobody removes it
- Camera EXIF — body, lens, exposure, timestamp, GPS
- Structural fingerprints — this is the part that still works after a social platform has stripped every scrap of metadata: diffusion-preset output dimensions, JPEG quantization tables no camera firmware would write, progressive coding and thin Huffman tables that betray a software encoder
Formats: JPEG, PNG, WebP, AVIF, HEIC/HEIF, TIFF. PNG zTXt/iTXt chunks are
inflated, which most tools skip.
Stated here as plainly as on the site, because a detection tool that oversells itself is worse than none.
- No pixel classifier. We do not guess from pixels and will not invent a percentage.
- SynthID is out of reach. Google DeepMind's watermark lives in the pixels and has no public reader.
- Trust lists are out of scope. We prove a signature is intact and belongs to the image. Whether the certificate belongs to the company it names is a separate question needing data we do not ship.
- A clean file is not a real photo. Metadata removal is trivial — a screenshot does it by accident. Absence of an AI marker is never evidence of authenticity. Markers are strong evidence in one direction and none at all in the other.
npm install
node build.js # generates ./site — 71 pages across 5 locales
npm test # parser fixtures + the built site executed in a real DOM
node test/audit.mjs # hostile inputs, XSS, CSP, performance budgetsOpen site/index.html, or node serve.js for a local server.
test/make-c2pa.mjs builds genuinely signed images with real certificates, plus
three that are broken in one specific way each:
| Fixture | Expected |
|---|---|
14_c2pa_signed.jpg |
Origin verified |
15_c2pa_tampered_pixels.jpg |
One image byte flipped after signing — binding fails |
16_c2pa_transplanted.jpg |
Manifest moved to a different photo — signature still valid, binding fails |
17_c2pa_broken_claim.jpg |
Claim edited after signing — signature fails |
A verifier that only ever sees valid input is not a verifier.
engine.js container parsing, metadata extraction, structural analysis
c2pa.js JUMBF, CBOR, COSE_Sign1, X.509 — the cryptographic verification
build.js static site generator, 5 locales
src/ app.js (UI), app.css, i18n/*.json
content/ markdown for articles and legal pages
test/ fixtures and the two test suites
No framework, no bundler, no runtime dependencies. The whole site is 39 KB over the wire.
There is no server, so there is nothing to log. Analytics (Microsoft Clarity) loads only after consent, and the panel showing analysis results is excluded from recording. The map link for GPS coordinates is a link, never an embedded tile — coordinates leave the browser only if the visitor clicks it.
GNU AGPL-3.0-or-later. You may read, audit, modify and share it. If you run a modified version as a service, you must publish your changes.
SynthCheck reads and removes nothing. It will never gain a feature for stripping provenance markers.