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.

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
├── 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.

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.1.0.tar.gz (96.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.1.0-py3-none-any.whl (20.5 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for image_provenance_inspector-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d245fa7e4545e80c9f73e820f75e377c6b299ffa120ade3cc36d892aaf821eca
MD5 311390a9f3b7e72dc92268ad4dd78030
BLAKE2b-256 b88be5a633a45b0e08f0dc5a53107670a307a7c103639d5314f4362818fccf9e

See more details on using hashes here.

Provenance

The following attestation bundles were made for image_provenance_inspector-0.1.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.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for image_provenance_inspector-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 825c66da386dcea491b24c112e7b1a9c24cd6ec233eff2299ef493eb53bdb977
MD5 1841db9a1f2182f448837f15d54386d0
BLAKE2b-256 dfd2e213997b46b339a8480392248b667dbbbc0634e4ed1906b8a0a7ce8de817

See more details on using hashes here.

Provenance

The following attestation bundles were made for image_provenance_inspector-0.1.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

0.2.0

2 files

This release

0.1.0 This release

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