Article
What WordPress image plugins do to Content Credentials
I put signed images into WordPress, with and without the most-used image plugins, and checked what a visitor gets. Mostly, it's an image without Content Credentials. Some plugins also replace the signed original on the server, and in two cases a correct image came out looking tampered with.
Content Credentials (C2PA) are a signed record inside the file: who made it, with what, and whether AI was involved. It's one of the ways the marking that Article 50 of the AI Act asks for can travel with an AI-generated image. The signature covers the bytes of the file, so anything that rewrites those bytes either removes the record or breaks it.
WordPress rewrites images all the time. It makes smaller sizes, and image plugins compress and convert on top of that. I wanted to know what's left at the end.
How I measured
A fresh WordPress 7.1.2 on PHP 8.3, with Imagick. Three signed images: a small JPEG, a large
JPEG from Lightroom (3280×2451, so WordPress also makes a -scaled copy), and a
PNG generated and signed by OpenAI. Each was uploaded the way the browser does it, the background
jobs were allowed to finish, and then I checked two things:
- every file WordPress and the plugin wrote;
- every image URL in the page (
srcandsrcset), fetched the way Chrome asks for it, withAccept: image/avif,image/webp.
Each plugin ran on its own, with its defaults. The one exception is WP-Optimize, which doesn't
compress until you turn that on, so I turned that one setting on. The files were checked with
c2pa-verifier, which gives the same
verdicts as c2patool.
Most of it ran on an arm64 Mac. EWWW Image Optimizer only ships its compression tools for x86, so I ran EWWW again on GitHub's x86 runners, with plain WordPress next to it. Plain WordPress gave the same results on both.
What a visitor gets
| Setup | Signed original on the server | Visitor gets valid credentials |
|---|---|---|
| WordPress only | kept | only when the page uses the original file itself (the small JPEG, and the PNG, whose original is in the srcset) |
| Modern Image Formats | kept | no: AVIF is served |
| Converter for Media | kept | no: WebP is served, also at the original's URL |
| WebP Express | kept | as with WordPress only |
| EWWW Image Optimizer | large JPEG replaced by a 2560 px copy, no backup; PNG recompressed with the C2PA chunk left in | only the small JPEG; the PNG comes out broken, see below |
| Smush | JPEGs replaced, signed copy kept as .bak | not for the JPEGs |
| WP-Optimize | small JPEG and PNG replaced, signed copy kept as a backup that's deleted after 50 days by default | no |
| reSmush.it | small JPEG and PNG replaced, signed copy kept as -unsmushed | no |
| Jetpack image CDN | not touched, it serves its own copy | no for JPEG; one PNG came back broken, see below |
None of the generated sizes kept valid credentials, with or without a plugin. That isn't a bug. A smaller image is a different file, and a manifest signed for the original doesn't cover it. Each size would need its own manifest that points back to the original, which is what I proposed in WordPress/ai#1058.
Plugins that replace the original
What I didn't expect was the original itself. Smush, WP-Optimize and reSmush.it compress the uploaded file and strip its metadata, and the signed version is only kept as a backup under another name. WP-Optimize deletes those backups after 50 days unless you change that. EWWW shrinks large uploads to 2560 pixels in place and doesn't keep the signed file at all.
On a site like that the signed file can be gone. Then there's nothing to show a visitor, and nothing to sign the smaller sizes from later.
A correct image that looks tampered with
The OpenAI PNG went wrong in a different way, twice. EWWW on the server and Jetpack's CDN on
the way to the visitor both returned the same pixels, all 1.5 million of them, packed
differently, and left the C2PA chunk in. The signature covers the bytes, not the picture, so the
check now says the image was changed after signing. For Jetpack's copy that's what
c2patool 0.27 and 0.28 say as well as c2pa-verifier. EWWW's copy has the same size
as what optipng -o2 makes of the file, and c2patool says the same about
that.
I think that's worse than losing the credentials. Anyone who checks is told the image was edited, while nothing in it changed.
The cause looks the same in both: a lossless PNG optimiser that keeps chunks it doesn't know.
With optipng -strip all the chunk is removed instead. In EWWW's case it was one line:
it only passed -strip all when optipng's version matched 0.7, and the optipng it
ships calls itself 7.9.1. On a server that has optipng 0.7 installed itself (Debian ships
0.7.8), EWWW does pass -strip all, and the PNG simply loses its credentials. I
reported it
(nosilver4u/ewww-image-optimizer#389),
it was fixed in the development version the same afternoon, and I confirmed the fix on x86.
Jetpack's CDN did this with one of five signed PNGs I tried. The other four lost the chunk, and the two of those I compared pixel by pixel had been recompressed with some loss. That one is reported too (Automattic/jetpack#53217).
Limits, up front
- October 2026. Measured twice, with the plugin versions of that week; the results were the same. An update can change any of it.
- Defaults. Other settings give other results. A setting that keeps "metadata" usually means EXIF and XMP, not C2PA.
- Not measured: plugins that need an account (Imagify, ShortPixel, Optimole, TinyPNG, LiteSpeed, Image Optimizer), and the image processing Gutenberg now does in the browser.
- Three test images. Enough to see what happens, not to give percentages.
If you run a site
- Find out whether your image plugin compresses the uploaded original, and if so where the backups go and how long they're kept.
- If an image's provenance matters, offer the original file for download. Check that it really is the original: a plugin that converts to WebP can serve WebP at the original's URL too.
- Look at what your visitors get. Save an image from your live page and open it in the browser demo, which runs the same verifier and keeps the file on your computer.
The scripts and the results, file by file, are on GitHub, so you can run them against your own plugins. I did the measuring with Claude Code; every result here came from running those scripts. If you maintain one of these plugins and I got something wrong or missed a setting, I'd like to hear it.
c2patool's verdicts. It's what
checked every file in this article.