Skip to main content

Image Provenance Inspector

CI Python 3.12 License: MIT

Image Provenance Inspector is an open-source tool for inspecting image metadata, C2PA Content Credentials, and editing provenance. It turns the evidence available inside an image into a small, human-readable report without collapsing uncertain origin and editing signals into one overconfident label.

It is designed to answer:

What AI-related or editing information did this image actually retain?

It is not an AI detector and it does not try to defeat platform labels. A missing manifest or metadata field is not proof that an image is original.

What it checks

  • EXIF, XMP, and IPTC presence
  • Camera model, capture time, and editing software when available
  • C2PA Content Credentials, claim generator, validation state, and actions
  • SHA-256, dimensions, format, and file size
  • Optional original-versus-edited comparison using SSIM, perceptual hash, and a difference mask

The rule-based assessment reports two independent dimensions:

Dimension Values Meaning
origin CAMERA_CAPTURED, SYNTHETIC, UNKNOWN Evidence about where the asset may have originated.
editing NONE_DETECTED, TRADITIONAL, GENERATIVE, UNKNOWN Evidence about editing actions retained in the asset.

Camera metadata is reported as medium-confidence CAMERA_CAPTURED evidence, not as proof that pixels are untouched. A generative action without a known generator remains origin=UNKNOWN, because a real source image may have lost its camera metadata. These values describe evidence, not certainty; provenance can be stripped, lost during export, or incomplete.

Quick start

The project targets Python 3.12 and uses uv for reproducible environments.

uv sync --extra dev --extra c2pa --extra ui
uv run uvicorn app.main:app --reload

For the optional native acceleration used by larger comparison workloads, add the analysis extra. The API also has a Pillow/standard-library fallback:

uv sync --extra dev --extra c2pa --extra ui --extra analysis

The API is available at http://127.0.0.1:8000. Interactive OpenAPI documentation is at http://127.0.0.1:8000/docs.

For the Streamlit interface, start the API dependencies in the same environment and run:

uv run streamlit run ui/streamlit_app.py

ExifTool is used when installed for broader metadata coverage. Pillow is the built-in fallback, so local development and tests do not require the system executable. On Debian/Ubuntu:

sudo apt-get install libimage-exiftool-perl

API

Analyze one image

curl -X POST http://127.0.0.1:8000/api/v1/analyze \
  -F "image=@photo.jpg"

The response is a fixed Pydantic-validated JSON contract:

{
  "file": {
    "format": "JPEG",
    "width": 4032,
    "height": 3024,
    "size_bytes": 1842930,
    "sha256": "..."
  },
  "capture": {
    "camera": "Apple iPhone",
    "captured_at": "2026-08-20T15:32:11"
  },
  "metadata": {
    "exif": true,
    "xmp": true,
    "iptc": false,
    "software": "Adobe Photoshop"
  },
  "c2pa": {
    "present": true,
    "valid": true,
    "claim_generator": "Adobe Photoshop Generative Fill",
    "actions": ["c2pa.edited"],
    "generative_edit": true
  },
  "assessment": {
    "origin": {
      "type": "CAMERA_CAPTURED",
      "confidence": "medium",
      "reason": [
        "Camera capture metadata detected",
        "Metadata can be copied, rewritten, or retained after editing"
      ]
    },
    "editing": {
      "type": "GENERATIVE",
      "confidence": "high",
      "reason": [
        "Generative editing or AI provenance evidence detected",
        "C2PA generative action detected"
      ]
    }
  }
}

c2pa.valid is null when no validation state is available. The API does not convert absent evidence into a positive or negative origin claim.

Compare an original and edited image

curl -X POST http://127.0.0.1:8000/api/v1/compare \
  -F "original=@original.jpg" \
  -F "edited=@edited.jpg"

The response includes ssim (0–1), phash_distance, changed_area_ratio (0–1), and a base64-encoded PNG difference mask.

Privacy-sanitized copy

Create a local-use copy with selected privacy-sensitive EXIF fields removed:

curl -X POST http://127.0.0.1:8000/api/v1/sanitize \
  -F "image=@photo.jpg" \
  -F "remove=gps" \
  -F "remove=device-identifiers" \
  -F "remove=owner" \
  -F "remove=comments" \
  -F "remove=private-thumbnails" \
  -F "remove=timestamps"

If remove is omitted, all supported privacy groups are selected. The endpoint returns the sanitized image as base64 together with a report of removed fields, preserved metadata, C2PA state, and pixel comparison metrics.

The sanitizer is intentionally provenance-preserving: it does not remove or rewrite C2PA, XMP, IPTC, software fields, or other provenance-bearing blocks. When C2PA Content Credentials are detected, the input is returned unchanged so that a signed asset is not silently invalidated. This is a privacy metadata utility, not an AI-label remover.

The same operation is available from the CLI:

uv run image-provenance-inspector sanitize photo.jpg \
  --remove gps \
  --remove device-identifiers \
  --output photo.sanitized.jpg

All validation and expected upload errors use the same Pydantic ErrorResponse shape:

{
  "code": "invalid_image",
  "message": "The uploaded file is not a supported image.",
  "details": []
}

The default upload limit is 20 MiB. Set IPI_MAX_UPLOAD_BYTES to change it for a deployment.

Validation fixtures

The repository includes a pinned official C2PA JPEG fixture under fixtures/. Its test expects a signed c2pa.created manifest but keeps both origin and editing classification as UNKNOWN; a credential is evidence of provenance data, not automatic proof of camera capture or AI generation. See fixtures/README.md before adding real-world vendor samples.

Docker

Build and run the API container:

docker build -t image-provenance-inspector .
docker run --rm -p 8000:8000 image-provenance-inspector

The image includes ExifTool and the API runtime. It does not store uploaded images after a request completes.

Development

Install development and UI dependencies, then run the checks locally:

uv sync --extra dev --extra c2pa --extra ui --extra analysis
uv run ruff check .
uv run ruff format --check .
uv run pytest --cov=app --cov-report=term-missing --cov-fail-under=80

The GitHub Actions workflow runs the same lint, formatting, coverage, lock-file, wheel-build, and Docker-build gates on Python 3.12. uv.lock is committed so CI and deployments resolve the same dependency graph.

The project also exposes a CLI using the same service and Pydantic result model:

uv run image-provenance-inspector photo.jpg

For contribution and maintainer workflows, see CONTRIBUTING.md.

Project layout

app/
├── api/             API route helpers
├── analyzers/       image, metadata, C2PA, and comparison readers
├── models/          public Pydantic schemas
├── provenance/      explainable classification rules
├── cli.py           command-line entry point and privacy sanitizer
├── main.py          FastAPI application
└── service.py       shared analysis orchestration
tests/               unit and API contract tests
fixtures/            traceable C2PA and real-world validation assets
ui/                  Streamlit interface

Scope and limitations

This project inspects, validates, explains, and compares evidence. It deliberately does not:

  • remove C2PA credentials or invisible watermarks;
  • bypass Facebook, Instagram, or other platform detection;
  • guarantee that an image is AI-generated or camera-original;
  • infer provenance from pixels alone.

Uploaded content may contain sensitive metadata such as GPS coordinates. Run the service locally or review and sanitize metadata before sending images to a hosted deployment. The v0.2 sanitizer removes selected privacy EXIF fields while preserving provenance-bearing blocks.

Responsible use

⚠️ Responsible use

Use this project only with images and metadata that you are authorized to inspect. Do not use it to facilitate unlawful activity, privacy violations, fraud, copyright infringement, or attempts to evade platform safeguards.

If an image is AI-generated or AI-edited, follow the applicable laws, license terms, disclosure requirements, and content policies of the platform where it is published. Do not remove, falsify, or bypass C2PA credentials, labels, watermarks, or other provenance signals.

This project provides technical evidence, not a legal determination. You are responsible for obtaining the rights and permissions needed for image use and for complying with applicable laws and platform policies.

License

Released under the MIT License. See LICENSE.

Metadata

Release files for image-provenance-inspector 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for image-provenance-inspector 0.2.0
File Size Uploaded
image_provenance_inspector-0.2.0.tar.gz 104.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for image-provenance-inspector 0.2.0
File Interpreter ABI Platform
image_provenance_inspector-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 130.7 kB

Release files / image_provenance_inspector-0.2.0.tar.gz

Download URL image_provenance_inspector-0.2.0.tar.gz
Size 104.4 kB
Tags Source
SHA-256 checksum
How to use checksums
2dfe6100fdc62ceabf7f9010a0c0ce28f1e53cc01caf2f549398f90976c0aa21
BLAKE2b-256 checksum
How to use checksums
50d165c8fffa4ad0928414c04876d38a62be065efe9f2599d3f9db6de12aa523
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release files / image_provenance_inspector-0.2.0-py3-none-any.whl

Download URL image_provenance_inspector-0.2.0-py3-none-any.whl
Size 26.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
45aa950aba5806da727bbcd05769969ef95bffc856003128a8a2c46225046a1f
BLAKE2b-256 checksum
How to use checksums
86d2531ed9735423514a52abc9f8bd7985eb392e46caa474ed7cb63fe312e501
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page