# Strip Image AI Metadata

> Strip C2PA and AI-generation metadata from images (PNG, JPEG, WebP) and their .xmp/.json sidecars, to remove "Generated by AI" / "ChatGPT" / "DALL-E" labels that platforms like LinkedIn, Instagram, and X display. Use when the user wants to clean AI-generated images before posting, remove AI attribution from photos, strip C2PA manifests, sanitize image metadata for professional use, or verify whether an image contains C2PA data.

- Skill: `getedgehq/strip-image-ai-metadata` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add getedgehq/strip-image-ai-metadata`
- Raw SKILL.md: https://api.skillmd.com/api/skills/getedgehq/strip-image-ai-metadata/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: getedgehq (https://skillmd.com/u/getedgehq)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/getedgehq/strip-image-ai-metadata

---


# 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 `caBX` chunks, generator text chunks (`parameters`, `prompt`, `workflow` and friends) and AI-related `tEXt`/`iTXt`/`zTXt` chunks, and erases AI attribution tags inside `eXIf`
- **JPEG**: removes APP11 C2PA segments and AI-related APP1 (XMP) segments, erases AI attribution tags inside structured EXIF, and cleans the trailer after the `FFD9` end-of-image marker
- **WebP**: removes `C2PA` chunks and AI-related `XMP ` chunks, and erases AI attribution tags inside `EXIF`
- **Sidecars**: `.xmp` and `.json` files next to the image, or passed directly

## Quick use

Run the bundled script on one or more images:

```bash
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

1. **Inspect** (optional): If the user is unsure whether an image has C2PA metadata, run:
   ```bash
   strings -a image.png | grep -i c2pa
   ```
   or read PNG chunks with Python to look for `caBX`. `strings -a` matters: without `-a` some builds skip the trailer that follows the end-of-image marker, which is exactly where a hidden manifest lives.

2. **List the sidecars** before touching the image, so nothing is missed:
   ```bash
   ls -1 image.jpg.xmp image.xmp image.jpg.json image.json 2>/dev/null
   ```
   The script finds these itself, but seeing them first tells you what the run should report.

3. **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.

4. **Verify**: The script scans every file it wrote for remaining C2PA/AI signatures. A leftover is reported as `FAILED` and exits `1`, not as a note to skim past. If that happens, do not publish the file. Inspect it with `exiftool -a -G1 -u <file>` and `strings -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`/`contentauth` namespace goes, as do properties and attributes whose value names a generator (`xmp:CreatorTool="ChatGPT"`, `Iptc4xmpExt:DigitalSourceType` set to `trainedAlgorithmicMedia`, a `dc:description` that says "Generated with DALL-E 3"). A property left empty by that removal is dropped too. Everything else survives, including Lightroom `crs:` develop settings, ratings, keywords and `dc:creator`.
- **JSON**: generator keys (`parameters`, `prompt`, `workflow`, `sd_model`, `c2pa` and 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.jpg` plus `photo.xmp` become `IMG_20260915_120000.jpg` plus `IMG_20260915_120000.xmp`, so the pair stays matched. With `-i` both 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 exits `1`. 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:

```bash
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:

```bash
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:
```bash
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.

