A non-invasive, sidecar-friendly marking, detection, and provenance layer for EU AI Act Article 50(2) provider workflows.
AIMark is designed to bolt onto an existing model stack after generation:
model output (unchanged)
├─ signed AIMark provenance receipt (Ed25519 + trusted JWKS)
├─ C2PA manifest/signing path (c2patool for supported assets)
├─ fingerprint registry (exact/excerpt/light-edit matching)
├─ public detection endpoint (/detect)
└─ interoperability signpost (/.well-known/ai-content-provenance)
This is a minimal compliance candidate, not legal certification.
It is shipping-oriented only when you configure the operational pieces:
- Stable provider signing key in a KMS/HSM.
- Published public JWKS/trust-registry entry.
- Official
c2patoolinstalled and configured with production C2PA signing credentials or signer-path. - Public/free detection endpoint where required.
- Calibrated thresholds and documented false-positive/false-negative behavior.
- Non-removal terms adopted into the provider/deployer terms.
- Written equivalence memo if you omit intrinsic text watermarking.
The design is intentionally honest: current common c2patool write support is still media-focused, even though the C2PA 2.3/2.4 specification added text and structured-text embedding. Raw .txt is therefore not claimed as embedded C2PA unless the provider supplies a text-capable C2PA signer. For raw text, AIMark exports a non-invasive fallback package:
output.txt
output.txt.aimark.json
output.txt.c2pa-manifest.json
C2PA_TEXT_FALLBACK_NOTES.txt
For true embedded C2PA, export to a supported asset/container and run
aimark c2pa-sign.
pip install -e .# Generate AIMark receipt signing keys.
aimark keygen --out aimark-ed25519.pem --pub-out aimark.jwks.json --key-id acme-key-1
# Mark text without changing output.
aimark mark output.txt --private-key aimark-ed25519.pem --key-id acme-key-1 \
--provider Acme --model-id acme-1 --out output.txt.aimark.json
# Verify with trusted JWKS. This is the production-trust path.
aimark verify output.txt --receipt output.txt.aimark.json \
--trusted-jwks aimark.jwks.json --require-trusted
# Detect from raw content alone after metadata is stripped.
cat output.txt | aimark detect --registry aimark_registry.sqlite3
# Export raw-text fallback package.
aimark export-package output.txt --private-key aimark-ed25519.pem \
--key-id acme-key-1 --provider Acme --model-id acme-1 --out-dir packageCreate a C2PA manifest definition:
aimark c2pa-manifest --receipt output.txt.aimark.json --asset image.png --out c2pa-manifest.jsonCheck whether a file can be written with C2PA by current tooling:
aimark c2pa-supports image.pngSign/embed using the official c2patool:
aimark c2pa-sign image.png \
--receipt output.txt.aimark.json \
--out signed-image.png \
--manifest-out c2pa-manifest.json \
--signer-path ./production-c2pa-signerVerify the signed asset, enforcing trust against the provider's published JWKS (same trust root as the receipt layer). For production embedded C2PA verification, let c2patool inspect the asset; a separately supplied manifest definition is only a non-embedded fallback check.
aimark c2pa-verify signed-image.png \
--trusted-jwks aimark.jwks.json --require-trustedVerification returns aimark_signature_valid, aimark_signature_trusted, asset_binding_checked, binding_mode, and accepted_for_production. A self-signed or forged manifest is valid but not trusted, so it is not accepted. A standalone manifest-definition JSON may be accepted as a non-embedded fallback, but it is not accepted as production embedded C2PA until c2patool validates the asset binding.
c2pa-sign fails closed without a production signer (--signer-path,
--remote, or --settings). For non-production testing only, pass
--allow-dev-signer; the result is marked production: false. Never ship a
dev-signed asset.
AIMark fails closed for formats not in the current common c2patool write set. That includes .txt, .json, and current read-only .pdf; .md remains fallback-only unless the provider deploys a C2PA 2.4 structured-text signer.
from aimark_sidecar import AIMarkSidecar, Signer, Registry
signer = Signer.from_pem(open("aimark-ed25519.pem", "rb").read(), key_id="acme-key-1")
sidecar = AIMarkSidecar(signer=signer, registry=Registry("registry.sqlite3"), provider_default="Acme")
result = sidecar.mark(text, model_id="acme-1")
assert result["text"] == text
receipt = result["ai_provenance"]
verification = sidecar.verify(text, receipt, require_trusted=True)
detection = sidecar.detect(text)GET /health
GET /pubkey
GET /.well-known/ai-content-provenance
GET /compliance
POST /mark {text, provider, model_id}
POST /verify {text, receipt}
POST /detect {text}
POST /c2pa/manifest {receipt, asset_filename}
Put auth in front of /mark. /detect is meant to be public where required.
AIMark does not change generated text. The second layer is a registry/fingerprint and detection layer. That is a deliberate alternative route for text; if a customer or regulator requires strict intrinsic watermarking, add the optional watermark adapter rather than altering the default output path.
- It is not legal certification.
- It is not embedded C2PA for raw text unless the provider supplies a text-capable C2PA 2.3/2.4 signer.
- It does not use C2PA test certificates for production.
- It does not prove authorship after heavy paraphrase.
- It does not replace visible UI labelling duties for deployers.
The fastest way to adopt the sidecar with minimal effort: configure by environment, run the container.
cp .env.example .env # fill in provider, key path, registry path, base URL
docker compose up --build # gateway on :8765, data persisted in a volumePOST /mark after generation (auth in front of it), and expose POST /detect
publicly and free of charge. All configuration is via environment variables (see
.env.example and aimark_sidecar/config.py); no code edits required.
This package ships the documents a provider needs to adopt and defend the posture — not just the code:
EQUIVALENCE_JUSTIFICATION.md— the reasoned basis for the no-watermark text approach under Article 50(2)'s alternative-approach allowance. The artifact a gap analysis starts from. Complete it and have counsel review it.INTEGRATION.md— how a provider bolts the sidecar on (inline or gateway), and the registration-coverage control that detection depends on.COMPLIANCE_MAP.md— measure-by-measure mapping (also machine-readable viaGET /compliance).DATA_PROTECTION.md— registry/detection GDPR considerations and DPIA prompt.TERMS_NONREMOVAL.md— the Measure 1.2 terms clause to adopt.SHIPPING_CHECKLIST.md— the go-live gate. The package is a minimal- compliance candidate, not a certification: real signing credentials, registration coverage, and counsel sign-off sit outside the code.