SiMG is an Electron desktop application that cryptographically verifies medical DICOM images before they reach an AI inference model. It catches supply chain attacks and adversarial converter compromises by treating the DICOM-to-image converter as an untrusted process, fingerprinting the raw DICOM independently, and comparing the two outputs before any diagnosis runs.
Medical AI pipelines rely on open-source DICOM-to-image converters (pydicom, SimpleITK, etc.) as an unaudited bridge between scanner output and deep learning inference. A compromised converter can embed imperceptible adversarial perturbations into every image it converts, causing systematic and silent misdiagnosis.
Known CVEs confirm this attack class is real:
- CVE-2024-23912: DICOM parser memory corruption
- CVE-2019-11687: malformed DICOM triggers arbitrary code execution
- CVE-2024-23913: DICOM metadata injection
SiMG addresses this by independently fingerprinting the raw DICOM data and verifying the converter output against that fingerprint before inference.
The pipeline runs in four stages:
-
Fingerprint + Convert (parallel): The C++ anchor engine reads the raw DICOM, computes a perceptual hash (DCT-pHash), radial ring descriptors, and a histogram, then signs the result with ECDSA P-256 and writes a
.simgreference file. In parallel, the Python converter (pydicom) converts the DICOM to PNG. -
Verify: The verifier runs inside a namespace sandbox (user + network + PID isolation). It reads the
.simgfile, validates the ECDSA signature, re-derives all three descriptors from the converted PNG, and computes a weighted similarity score. If the score falls below 0.85, the pipeline halts with a security failure. -
Infer: If verification passes, the verified PNG is sent to a Gemini-backed inference operator that classifies the medical image.
-
Result: The Electron UI displays either the diagnosis result or the specific failure type (security failure vs pipeline error), so security incidents are never silently absorbed as generic errors.
SiMG/
converter/ Python DICOM-to-PNG converters (clean + evil simulator)
desktop/ Electron app (TypeScript + React)
fingerprint/
anchor/ C++ anchor engine (DICOM reader, pHash, rings, SIMG writer)
verifier/ C++ verifier (SIMG reader, comparator, PNG loader)
inference-pipeline/ Python inference operators (Guardian check + Gemini inference)
keys/ ECDSA P-256 key pair (private.pem must be generated locally)
pipelines/ Shell trigger scripts for each pipeline stage
sandbox/ Namespace sandbox wrapper for the verifier
simg.sh Standalone CLI pipeline runner
test_converters.py Converter test suite
Install these before building:
sudo apt update
sudo apt install cmake build-essential libssl-dev libpng-dev libjpeg-dev- Linux: namespace isolation (
unshare) is used for the verification sandbox. Windows users must use WSL2, as native Windows does not support Linux kernel namespaces. - Python 3.10+: required for the converter and inference pipeline
- Node.js 20+: required for the Electron desktop app
- Gemini API key: required for inference (set in
inference-pipeline/.env)
The anchor signs fingerprints with a private key. The verifier checks signatures with the corresponding public key. Generate a fresh key pair before first use:
cd keys/
openssl ecparam -name prime256v1 -genkey -noout -out private.pem
openssl ec -in private.pem -pubout -out public.pemprivate.pem is gitignored and must never be committed. Only public.pem is needed at runtime for verification.
cd converter/
python3 -m venv .venv
.venv/bin/pip install -r requirements.txtcd inference-pipeline/
python3 -m venv .venv
.venv/bin/pip install -r requirements.txtCreate a .env file in inference-pipeline/ with your Gemini API key:
GEMINI_API_KEY=your_key_here
From the repo root:
cd fingerprint/anchor
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cd ../verifier
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build buildOr use the helper script from the desktop directory:
cd desktop/
npm run build:cppcd desktop/
npm install
npm run build
npm startDrop a DICOM file into the UI to run the full pipeline.
You can also run the pipeline without the desktop app:
# Clean converter (should PASS verification)
./simg.sh /path/to/scan.dcm 0
# Evil converter: simulates a supply chain attack (should FAIL)
./simg.sh /path/to/scan.dcm 1The converter test suite validates that the clean converter is deterministic, the evil converter produces imperceptible but detectable perturbations, and histogram divergence is measurable:
python3 test_converters.pyref.simg is a fixed-size 760-byte binary file written by the anchor and verified before inference.
| Field | Offset | Size | Description |
|---|---|---|---|
| Magic | 0 | 4 B | 0x534D4947 ("SIMG") |
| Version | 4 | 2 B | Format version (currently 1) |
| pHash | 8 | 8 B | 64-bit DCT perceptual hash |
| Ring descriptors | 16 | 64 B | 8-zone radial mean + stddev |
| Histogram | 80 | 512 B | 64-bin normalised intensity histogram |
| SHA-256 | 592 | 32 B | Hash of all fields above |
| ECDSA signature | 624 | 72 B | DER-encoded P-256 signature over SHA-256 |
The verifier computes three descriptors from the converted PNG and compares them against the reference in ref.simg:
score = 0.4 * phash_score + 0.3 * ring_score + 0.3 * hist_score
| Component | Metric | Scoring |
|---|---|---|
| pHash | Hamming distance | 1 - (hamming / tolerance) |
| Ring descriptors | Max zone deviation | 1 - (deviation / tolerance) |
| Histogram | Symmetric KL divergence | 1 - (kl / tolerance) |
Threshold is 0.85. A score below this halts the pipeline with SECURITY FAILURE.
| Component | Trust | Reason |
|---|---|---|
| Anchor engine | Trusted | Built from audited C++ source |
| Public key | Trusted | Committed to repo, read-only |
| Converter (pydicom) | Untrusted | Treated as potentially compromised |
| Verifier | Trusted | Runs inside namespace sandbox |
| Inference pipeline | Untrusted runtime | Isolated execution, no network |
The private key never leaves the signing host and is never committed to version control.
| Type | Log prefix | Meaning |
|---|---|---|
| Security failure | [GUARDIAN] SECURITY FAILURE |
Converter output does not match anchor fingerprint |
| Pipeline failure | [MONAI] PIPELINE ERROR |
Runtime or model error during inference |
These are surfaced separately in the UI so security incidents are never absorbed as generic errors.
