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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

image_provenance_inspector-0.2.0.tar.gz (104.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

image_provenance_inspector-0.2.0-py3-none-any.whl (26.4 kB view details)

Uploaded Python 3

File details

Details for the file image_provenance_inspector-0.2.0.tar.gz.

File metadata

File hashes

Hashes for image_provenance_inspector-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2dfe6100fdc62ceabf7f9010a0c0ce28f1e53cc01caf2f549398f90976c0aa21
MD5 cd4c2596908e582081ffc259a4db24e8
BLAKE2b-256 50d165c8fffa4ad0928414c04876d38a62be065efe9f2599d3f9db6de12aa523

See more details on using hashes here.

Provenance

The following attestation bundles were made for image_provenance_inspector-0.2.0.tar.gz:

Publisher: publish.yml on KageRyo/image-provenance-inspector

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file image_provenance_inspector-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for image_provenance_inspector-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 45aa950aba5806da727bbcd05769969ef95bffc856003128a8a2c46225046a1f
MD5 db593ab21f1555cd3bb7248dfcc4423b
BLAKE2b-256 86d2531ed9735423514a52abc9f8bd7985eb392e46caa474ed7cb63fe312e501

See more details on using hashes here.

Provenance

The following attestation bundles were made for image_provenance_inspector-0.2.0-py3-none-any.whl:

Publisher: publish.yml on KageRyo/image-provenance-inspector

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page