Strip Image AI Metadata
Platforms like LinkedIn read C2PA metadata embedded by AI image generators (ChatGPT, DALL-E, Midjourney, etc.) and display an attribution label. This skill removes that metadata without re-encoding, so image quality is preserved.
Supported formats
- PNG: removes
caBXchunks, generator text chunks (parameters,prompt,workflowand friends) and AI-relatedtEXt/iTXt/zTXtchunks, and erases AI attribution tags insideeXIf - JPEG: removes APP11 C2PA segments and AI-related APP1 (XMP) segments, erases AI attribution tags inside structured EXIF, and cleans the trailer after the
FFD9end-of-image marker - WebP: removes
C2PAchunks and AI-relatedXMPchunks, and erases AI attribution tags insideEXIF - Sidecars:
.xmpand.jsonfiles next to the image, or passed directly
Quick use
Run the bundled script on one or more images:
python3 scripts/strip_c2pa.py image.png # strips metadata + renames to IMG_YYYYMMDD_HHMMSS.png
python3 scripts/strip_c2pa.py image.jpg -o cleaned.jpg
python3 scripts/strip_c2pa.py *.png
python3 scripts/strip_c2pa.py photo.xmp # a sidecar on its own
By default the script writes a cleaned copy with a neutral camera-style filename (IMG_YYYYMMDD_HHMMSS.<ext>) in the same directory and leaves the original untouched. Use -i/--in-place to overwrite the original, or -o for an explicit output path.
Check the exit code. 0 means the output is clean. 1 means something is still wrong and the file must not be published. Every failure is also printed.
Workflow
Inspect (optional): If the user is unsure whether an image has C2PA metadata, run:
strings -a image.png | grep -i c2paor read PNG chunks with Python to look for
caBX.strings -amatters: without-asome builds skip the trailer that follows the end-of-image marker, which is exactly where a hidden manifest lives.List the sidecars before touching the image, so nothing is missed:
ls -1 image.jpg.xmp image.xmp image.jpg.json image.json 2>/dev/nullThe script finds these itself, but seeing them first tells you what the run should report.
Strip: Run
scripts/strip_c2pa.py <path>. The script writes a neutral-named copy by default and reports what was removed, image and sidecars together.Verify: The script scans every file it wrote for remaining C2PA/AI signatures. A leftover is reported as
FAILEDand exits1, not as a note to skim past. If that happens, do not publish the file. Inspect it withexiftool -a -G1 -u <file>andstrings -a <file>and deal with whatever is left by hand before it ships.
Sidecars
Lightroom, Capture One, Bridge and most generation UIs write provenance into a file beside the image: photo.jpg plus photo.xmp, or photo.png plus photo.png.json. A spotless JPEG shipped next to a sidecar naming the generator is not a clean result.
The script handles both naming conventions (name.ext.xmp and name.xmp, either case) automatically whenever it processes an image, and it accepts a sidecar as a direct argument.
- XMP: AI provenance is removed property by property. Anything in a
c2pa/contentauthnamespace goes, as do properties and attributes whose value names a generator (xmp:CreatorTool="ChatGPT",Iptc4xmpExt:DigitalSourceTypeset totrainedAlgorithmicMedia, adc:descriptionthat says "Generated with DALL-E 3"). A property left empty by that removal is dropped too. Everything else survives, including Lightroomcrs:develop settings, ratings, keywords anddc:creator. - JSON: generator keys (
parameters,prompt,workflow,sd_model,c2paand friends) are dropped, as is any key or string value naming a generator. The rest of the file keeps its structure. - Output naming: the sidecar follows the image.
photo.jpgplusphoto.xmpbecomeIMG_20260915_120000.jpgplusIMG_20260915_120000.xmp, so the pair stays matched. With-iboth are rewritten in place. - Unparseable sidecar: if the XML or JSON does not parse, the script refuses. It leaves that file untouched, prints
FAILED sidecar, names any AI signatures it can still see in the raw bytes, and exits1. Delete the sidecar or fix it by hand; do not ship the pair.
If the image itself is already clean but a sidecar is not, the script still writes the cleaned pair and says so.
The JPEG trailer after FFD9
FFD9 ends the JPEG codestream. Bytes after it are invisible to every decoder, which makes them a convenient hiding place, and a C2PA manifest parked there is still read by the platforms this skill exists to defeat. exiftool -all= does not remove it either.
The script cleans that region:
- A C2PA JUMBF manifest box is removed.
- An appended image (MPO/MPF multi-picture, common on phones) carries real pixels, so it is cleaned recursively rather than dropped.
- Any other trailer data carrying an AI signature is dropped whole, with its byte count and the signatures found reported.
- A trailer with no AI signature is left alone.
To confirm by hand that nothing is hiding back there. rfind is used because an EXIF thumbnail contains its own FFD9, so the first one in the file is usually not the real end of image:
python3 - <<'PY'
from pathlib import Path
d = Path('image.jpg').read_bytes()
i = d.rfind(b'\xFF\xD9')
print('bytes after the final EOI:', len(d) - (i + 2))
print(d[i+2:i+200])
PY
EXIF attribution
Structured EXIF is not deleted wholesale, because it carries orientation, colour and the embedded thumbnail. Instead the attribution tags are erased in place: ImageDescription, DocumentName, Make, Model, Software, Artist, Copyright, UserComment, the Windows XP* tags, CameraOwnerName, LensMake and LensModel, in IFD0, the Exif sub-IFD and the thumbnail IFD1.
A tag is only erased when its value names a generator, so Artist: Jane Doe and a real camera's Make/Model survive while Software: DALL-E 3 does not. The value bytes are overwritten with zeros rather than merely unreferenced, so nothing is left for strings to find, and no bytes move, so orientation and the thumbnail stay valid. The EXIF block comes out exactly the same length.
To audit what is left:
exiftool -a -G1 -u -Software -Artist -ImageDescription -Copyright -UserComment -XP:all image.jpg
What this does NOT do
- It does not remove visible watermarks baked into pixels.
- It does not touch EXIF tags outside the attribution list above, so a MakerNote, an ICC profile description or an APP13/APP14 record naming a generator survives. These affect rendering or are vendor-opaque, so they are reported rather than guessed at: the final scan flags them and exits
1. - It does not re-encode, so file size and image quality are unchanged (except for the removed metadata).
- It does not upload anything or call any network service.
When to re-encode instead
If the platform still shows an AI label after stripping, the attribution might be baked into pixels or stored in a non-C2PA format. In that case, use ImageMagick to decode and re-encode:
convert input.png -strip output.png
This is lossless for PNG but may cause slight quality loss for JPEG. Note that -strip drops orientation too, so check that the result is not rotated.