DEV Community

강춘수
강춘수

Posted on

Detecting and Stripping AI Metadata (C2PA, EXIF, XMP) from Generated Images — A Developer's Guide

If you ship anything that touches AI-generated images — a thumbnail pipeline, a user-upload feature, a design tool — you've probably noticed something: the images your model spits out are heavier than they should be, and they carry baggage you never asked for.

That baggage is provenance metadata. Modern generators (GPT Image / DALL·E, Google's Nano Banana / Gemini, Midjourney, many hosted Stable Diffusion endpoints) stamp each output with tags that mark it as machine-made. Some of it is harmless. Some of it survives a Photoshop round-trip. And most developers have no idea it's even there until a downstream platform flags an image or a QA person asks "why does this PNG have a certificate chain in it?"

This is a hands-on guide to seeing that metadata and removing it — from the CLI, from Node, from Python, and (when you just want it gone) from the browser.

What actually gets embedded

There are four layers worth knowing about, because they don't all come off the same way:

  1. EXIF fields — the classic camera-metadata block. Generators repurpose fields like Software, ImageDescription, or a custom Make/Model to identify themselves. Trivial to read, trivial to strip.
  2. XMP packets — an XML blob (Adobe's format) holding richer provenance: model name, generation timestamp, sometimes a prompt hash. Lives in its own segment of the file.
  3. C2PA manifests — the interesting one. The Coalition for Content Provenance and Authenticity standard embeds a cryptographically signed manifest (stored in a JUMBF box) that records the asset's origin. Because it's signed, it's designed to be tamper-evident — which also means naive metadata strippers often miss it.
  4. Pixel-level watermarks — e.g. SynthID-style signals baked into the pixels themselves. These are not metadata at all; no EXIF tool touches them. (More on the limits below.)

The mistake I see repeatedly: someone runs a one-liner that clears EXIF, sees "no EXIF" in their viewer, and assumes the image is clean. The C2PA manifest and XMP packet are often still sitting there.

Step 1 — Look before you strip

Install ExifTool (brew install exiftool, apt install libimage-exiftool-perl, etc.) and dump everything:

exiftool -G1 -a -s generated.png
Enter fullscreen mode Exit fullscreen mode

-G1 shows the group each tag belongs to, -a allows duplicates, -s uses short tag names. On a fresh AI export you'll typically see groups like [ExifIFD], [XMP-xmp], and — the tell — a [JUMBF] or C2PA-related group. To specifically probe for a provenance manifest:

exiftool -jumbf:all -a generated.png
Enter fullscreen mode Exit fullscreen mode

If that returns anything, you have an embedded C2PA manifest, not just plain EXIF.

Step 2 — Strip EXIF and XMP

The blunt instrument:

exiftool -all= -overwrite_original generated.png
Enter fullscreen mode Exit fullscreen mode

-all= sets every writable tag group to empty. This reliably clears EXIF and XMP. Re-run your exiftool -G1 -a -s check and confirm those groups are gone.

Caveat: -all= operates on tags ExifTool knows how to write. Depending on your build and the file, the C2PA/JUMBF payload may not be fully removed by this alone — which is why you verify instead of trusting.

Step 3 — The C2PA gotcha

A signed C2PA manifest is deliberately sticky. Two reliable ways to get rid of it:

Option A — re-encode the pixels. A manifest is bound to specific bytes; decode the image to a raw bitmap and re-encode, and the manifest no longer validates and is dropped by most encoders:

# via ImageMagick — strip + re-encode in one shot
magick generated.png -strip clean.png
Enter fullscreen mode Exit fullscreen mode

Option B — use c2pa tooling directly. The c2patool CLI can read and detach manifests explicitly, which is the honest way to confirm one existed and is now gone.

Whichever you pick, finish with the same verification from Step 1. "It looks clean in Preview" is not verification.

Step 4 — Doing it in code

Most of us don't want a manual CLI step in a pipeline. Two common runtimes:

Node (sharp). sharp drops metadata by default when you re-encode — you have to opt in with .withMetadata() to keep it. So the clean path is simply not opting in:

import sharp from "sharp";

// Re-encoding without .withMetadata() produces an output with
// EXIF/XMP stripped. The pixel re-encode also breaks a bound C2PA manifest.
await sharp("generated.png")
  .png()
  .toFile("clean.png");
Enter fullscreen mode Exit fullscreen mode

Python (Pillow). Open, copy the pixel data into a fresh image, save. The new object carries no info dict from the original:

from PIL import Image

src = Image.open("generated.png")
clean = Image.new(src.mode, src.size)
clean.putdata(list(src.getdata()))
clean.save("clean.png")   # no EXIF/XMP carried over
Enter fullscreen mode Exit fullscreen mode

Both approaches lean on the same trick as ImageMagick's re-encode: rebuild the file from pixels so nothing rides along. Verify the output with ExifTool regardless of language — libraries change defaults across versions.

Step 5 — When you don't want a toolchain at all

The code above is great for a server-side pipeline. But there are plenty of moments where spinning up ExifTool + ImageMagick + a C2PA CLI is overkill:

  • a designer on your team who doesn't live in a terminal,
  • a one-off image you need cleaned now,
  • a batch of exports you'd rather not push through a script you have to babysit,
  • a case where you don't want to upload private images to some random server to process them.

For those, the pragmatic move is a browser-based tool that will remove AI metadata for you. It does the full stack — EXIF, XMP, and the C2PA manifest — in one drag-and-drop, and the important part for a privacy-minded dev: the processing happens client-side in the browser, so the image never leaves the machine. Drop the file in, download it clean, done. No account, no upload round-trip.

It's what I reach for when the "correct" answer (wire it into the pipeline) isn't worth the setup for a handful of images. You can remove AI metadata from a whole batch and move on.

The one thing metadata stripping can't do

Be honest with yourself about scope: removing metadata is not the same as removing a pixel-level watermark. SynthID-style signals are embedded in the image content, not in a header you can delete. Stripping EXIF/XMP/C2PA removes the declarative provenance — the tags that say "I was made by X" — but a robust in-pixel watermark is a different problem with different (and much harder) tradeoffs. Any tool, CLI or web, that promises to scrub metadata is solving the first problem, not the second. Don't conflate them.

TL;DR

  • AI images ship with up to four layers: EXIF, XMP, C2PA manifests, and sometimes pixel watermarks.
  • exiftool -G1 -a -s file.png to inspect; exiftool -all= for EXIF/XMP.
  • A signed C2PA manifest is sticky — re-encode the pixels (magick -strip, sharp, Pillow) or use c2patool, then verify.
  • In a pipeline, re-encoding without carrying metadata is the cleanest programmatic path.
  • For quick, private, no-setup cleaning — including C2PA — remove AI metadata right in the browser.
  • Metadata ≠ pixel watermark. Know which one you're actually dealing with.

Happy to hear how others handle this in their upload pipelines — do you strip on ingest, on export, or not at all?

Top comments (0)