Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@
</div>

```bash
pip install cognis-deepcheck
pip install "git+https://github.com/cognis-digital/deepcheck.git"
deepcheck scan . # → prioritized findings in seconds
```

<!-- cognis:layman:start -->
## What is this?

Deepcheck is a command-line tool that examines images (JPEG and PNG) to determine whether they were taken by a real camera or generated by AI software like Midjourney, Stable Diffusion, or DALL-E. It reads the hidden technical data embedded in image files — such as camera make, compression patterns, and content-authenticity certificates (C2PA) — and gives you a plain verdict: likely authentic, suspicious, or likely synthetic. It also shows exactly which signals drove that conclusion, so you can quickly judge whether an image is trustworthy. It is useful for journalists, researchers, content moderators, and anyone who needs to verify whether a photo is genuine before publishing or acting on it.
<!-- cognis:layman:end -->

## Contents

- [Why deepcheck?](#why) · [Features](#features) · [Quick start](#quick-start) · [Example](#example) · [Architecture](#architecture) · [AI stack](#ai-stack) · [How it compares](#how-it-compares) · [Integrations](#integrations) · [Install anywhere](#install-anywhere) · [Related](#related) · [Contributing](#contributing)
Expand All @@ -46,10 +52,56 @@ Lightweight synthetic-media detector with C2PA validation — without standing u
<div align="right"><a href="#top">↑ back to top</a></div>

<a name="quick-start"></a>
<!-- cognis:domains:start -->
## Domains

**Primary domain:** Intelligence & OSINT · **JTF MERIDIAN division:** NULLBYTE · BLACK CELL

**Topics:** `cognis` `osint` `intelligence` `recon`

Part of the **Cognis Neural Suite** — 300+ source-available tools organized across 12 domains under the JTF MERIDIAN command structure. See the [suite on GitHub](https://github.com/cognis-digital) and [jtf-meridian](https://github.com/cognis-digital/jtf-meridian) for how the pieces fit together.
<!-- cognis:domains:end -->

<!-- cognis:install:start -->
## Install

`deepcheck` is source-available (not published to PyPI) — every method below installs
straight from GitHub. Pick whichever you prefer; the one-line scripts auto-detect
the best tool available on your machine.

**One-liner (Linux / macOS):**
```sh
curl -fsSL https://raw.githubusercontent.com/cognis-digital/deepcheck/HEAD/install.sh | sh
```

**One-liner (Windows PowerShell):**
```powershell
irm https://raw.githubusercontent.com/cognis-digital/deepcheck/HEAD/install.ps1 | iex
```

**Or install manually — any one of:**
```sh
pipx install "git+https://github.com/cognis-digital/deepcheck.git" # isolated (recommended)
uv tool install "git+https://github.com/cognis-digital/deepcheck.git" # uv
pip install "git+https://github.com/cognis-digital/deepcheck.git" # pip
```

**From source:**
```sh
git clone https://github.com/cognis-digital/deepcheck.git
cd deepcheck && pip install .
```

Then run:
```sh
deepcheck --help
```
<!-- cognis:install:end -->

## Quick start

```bash
pip install cognis-deepcheck
pip install "git+https://github.com/cognis-digital/deepcheck.git"
deepcheck --version
deepcheck scan . # scan current project
deepcheck scan . --format json # machine-readable
Expand Down
11 changes: 5 additions & 6 deletions deepcheck/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import argparse
import json
import os
import struct
import sys

from . import TOOL_NAME, TOOL_VERSION
Expand Down Expand Up @@ -89,9 +90,12 @@ def main(argv=None) -> int:

try:
result = analyze_image(args.image)
except (OSError, struct_error_t()) as exc: # type: ignore[misc]
except (OSError, struct.error, ValueError) as exc:
print(f"{TOOL_NAME}: error: {exc}", file=sys.stderr)
return 2
except Exception as exc: # noqa: BLE001
print(f"{TOOL_NAME}: unexpected error: {type(exc).__name__}: {exc}", file=sys.stderr)
return 2

if args.format == "json":
print(json.dumps(result.to_dict(), indent=2))
Expand All @@ -101,10 +105,5 @@ def main(argv=None) -> int:
return 1 if _is_finding(result) else 0


def struct_error_t():
import struct
return struct.error


if __name__ == "__main__":
raise SystemExit(main())
56 changes: 37 additions & 19 deletions deepcheck/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,19 +283,19 @@ def validate_c2pa(blob: bytes) -> C2PAResult:
types = [b["type"] for b in boxes]

# A valid C2PA store carries a manifest superbox and a claim.
has_store = any(l and l.startswith("c2pa") for l in labels) or b"c2pa" in blob[:64].lower()
has_claim = any(l and "claim" in l for l in labels)
has_assertions = any(l and "assertions" in l for l in labels)
has_store = any(lbl and lbl.startswith("c2pa") for lbl in labels) or b"c2pa" in blob[:64].lower()
has_claim = any(lbl and "claim" in lbl for lbl in labels)
has_assertions = any(lbl and "assertions" in lbl for lbl in labels)

# Assertions are labelled child boxes under the assertion store.
res.assertions = sorted(
{l for l in labels if l and ("." in l or l.startswith("c2pa.") or l.startswith("cai."))}
{lbl for lbl in labels if lbl and ("." in lbl or lbl.startswith("c2pa.") or lbl.startswith("cai."))}
)

# Hard binding: a data-hash / box-hash assertion must exist for the manifest
# to actually bind to the asset bytes.
res.has_hard_binding = any(
l and ("hash.data" in l or "hash.boxes" in l or l.endswith(".hash")) for l in labels
lbl and ("hash.data" in lbl or "hash.boxes" in lbl or lbl.endswith(".hash")) for lbl in labels
)

# Claim generator string, if present in a CBOR-ish text blob.
Expand Down Expand Up @@ -347,7 +347,7 @@ def _dqt_signals(dqt_tables: list[bytes]) -> list[Signal]:
else:
# 16-bit entries
for k in range(count):
if p + 1 < len(tbl):
if p + 2 <= len(tbl):
values.append(struct.unpack(">H", tbl[p : p + 2])[0])
p += 2
if not values:
Expand Down Expand Up @@ -412,27 +412,45 @@ def _score_to_verdict(score: float, c2pa: C2PAResult) -> Verdict:


def analyze_image(path: str) -> AnalysisResult:
if not path:
raise ValueError("path must be a non-empty string")
with open(path, "rb") as fh:
data = fh.read()
if not data:
return AnalysisResult(
path=path,
format="unknown",
width=None,
height=None,
verdict=Verdict.UNKNOWN.value,
synthetic_score=0.0,
signals=[{"name": "empty_file", "weight": 0.0, "detail": "file contains no data"}],
metadata={"metadata_bytes": 0, "has_ai_tag": False, "has_camera_hint": False},
c2pa=C2PAResult(note="no data to analyse"),
)
fmt = _sniff_format(data)
width = height = None
signals: list[Signal] = []
raw_meta = b""

if fmt == "jpeg":
parsed = _parse_jpeg(data)
width, height = parsed["width"], parsed["height"]
raw_meta = b"".join(p for _, p in parsed["app_segments"])
signals += _dqt_signals(parsed["dqt_tables"])
jumbf = parsed["jumbf"]
elif fmt == "png":
parsed = _parse_png(data)
width, height = parsed["width"], parsed["height"]
raw_meta = b"".join(parsed["text_chunks"])
jumbf = parsed["jumbf"]
else:
try:
if fmt == "jpeg":
parsed = _parse_jpeg(data)
width, height = parsed["width"], parsed["height"]
raw_meta = b"".join(p for _, p in parsed["app_segments"])
signals += _dqt_signals(parsed["dqt_tables"])
jumbf = parsed["jumbf"]
elif fmt == "png":
parsed = _parse_png(data)
width, height = parsed["width"], parsed["height"]
raw_meta = b"".join(parsed["text_chunks"])
jumbf = parsed["jumbf"]
else:
jumbf = b""
signals.append(Signal("unknown_format", 0.0, "unrecognized container; limited analysis"))
except (struct.error, ValueError) as exc:
signals.append(Signal("parse_error", 0.0, f"format parser raised {type(exc).__name__}: {exc}"))
jumbf = b""
signals.append(Signal("unknown_format", 0.0, "unrecognized container; limited analysis"))

meta = {"_raw_metadata": raw_meta}
signals += _metadata_signals(meta)
Expand Down
Binary file not shown.
29 changes: 29 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Comprehensive installer for cognis-digital/deepcheck (Windows PowerShell).
# Tries: pipx -> uv -> pip (git+https) -> from source.
# deepcheck is source-available and not on PyPI; all paths install from GitHub.
$ErrorActionPreference = "Stop"
$Repo = "deepcheck"
$Url = "git+https://github.com/cognis-digital/deepcheck.git"
$Git = "https://github.com/cognis-digital/deepcheck.git"
function Say($m) { Write-Host "[$Repo] $m" -ForegroundColor Magenta }
function Have($c) { [bool](Get-Command $c -ErrorAction SilentlyContinue) }

if (-not (Have python) -and -not (Have py)) {
Say "Python 3.9+ is required but was not found. Install Python first."; exit 1
}
if (Have pipx) {
Say "Installing with pipx (isolated, recommended)..."
pipx install $Url; if ($LASTEXITCODE -eq 0) { Say "Done. Run: deepcheck"; exit 0 }
}
if (Have uv) {
Say "Installing with uv..."
uv tool install $Url; if ($LASTEXITCODE -eq 0) { Say "Done. Run: deepcheck"; exit 0 }
}
if (Have pip) {
Say "Installing with pip (user site)..."
pip install --user $Url; if ($LASTEXITCODE -eq 0) { Say "Done. Run: deepcheck"; exit 0 }
}
Say "No packaging tool worked; falling back to a source clone."
$Tmp = Join-Path $env:TEMP "$Repo-src"
git clone --depth 1 $Git $Tmp
Say "Cloned to $Tmp - run: cd $Tmp; python -m pip install ."
44 changes: 34 additions & 10 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
#!/usr/bin/env sh
# Universal installer for deepcheck. Prefers uv > pipx > pip; installs from the repo.
set -e
SRC="git+https://github.com/cognis-digital/deepcheck.git"
echo "Installing deepcheck ..."
if command -v uv >/dev/null 2>&1; then uv tool install "$SRC"
elif command -v pipx >/dev/null 2>&1; then pipx install "$SRC"
elif command -v python3 >/dev/null 2>&1; then python3 -m pip install --user "$SRC"
else echo "Need uv, pipx, or python3+pip"; exit 1; fi
echo "Done. Run: deepcheck --help"
#!/usr/bin/env sh
# Comprehensive installer for cognis-digital/deepcheck (Linux / macOS).
# Tries the best available method: pipx -> uv -> pip (git+https) -> from source.
# deepcheck is source-available and not on PyPI; all paths install from GitHub.
set -eu

REPO="deepcheck"
URL="git+https://github.com/cognis-digital/deepcheck.git"
GITURL="https://github.com/cognis-digital/deepcheck.git"

say() { printf '\033[1;35m[%s]\033[0m %s\n' "$REPO" "$1"; }
have() { command -v "$1" >/dev/null 2>&1; }

if ! have python3 && ! have python; then
say "Python 3.9+ is required but was not found. Install Python first."; exit 1
fi

if have pipx; then
say "Installing with pipx (isolated, recommended)..."
pipx install "$URL" && { say "Done. Run: deepcheck"; exit 0; }
fi
if have uv; then
say "Installing with uv..."
uv tool install "$URL" && { say "Done. Run: deepcheck"; exit 0; }
fi
if have pip3 || have pip; then
PIP="$(command -v pip3 || command -v pip)"
say "Installing with pip (user site)..."
"$PIP" install --user "$URL" && { say "Done. Run: deepcheck"; exit 0; }
fi

say "No packaging tool worked; falling back to a source clone."
TMP="$(mktemp -d)"; git clone --depth 1 "$GITURL" "$TMP/$REPO"
say "Cloned to $TMP/$REPO — run: cd $TMP/$REPO && python3 -m pip install ."
2 changes: 1 addition & 1 deletion integrations/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Usage: <tool> scan . --format json | python integrations/webhook.py --url URL
"""
from __future__ import annotations
import argparse, json, sys, urllib.request
import argparse, sys, urllib.request

def main() -> int:
ap = argparse.ArgumentParser()
Expand Down
1 change: 1 addition & 0 deletions layman.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deepcheck is a command-line tool that examines images (JPEG and PNG) to determine whether they were taken by a real camera or generated by AI software like Midjourney, Stable Diffusion, or DALL-E. It reads the hidden technical data embedded in image files — such as camera make, compression patterns, and content-authenticity certificates (C2PA) — and gives you a plain verdict: likely authentic, suspicious, or likely synthetic. It also shows exactly which signals drove that conclusion, so you can quickly judge whether an image is trustworthy. It is useful for journalists, researchers, content moderators, and anyone who needs to verify whether a photo is genuine before publishing or acting on it.
Loading
Loading