AID-H-002.007: External Media & Document Provenance Verification
Coverage includes:
- Verifying C2PA / Content Credentials where available.
- Classifying inputs as
verified,unsigned,invalid, orstripped. - Preserving provenance state through OCR, chunking, embedding, and downstream retrieval metadata.
- Requiring step-up review when high-impact workflows rely on unsigned or invalid media.
Pillars: data, app
Lifecycle: building, operation
Scope boundary
Owns inbound cryptographic provenance verification for externally produced media and documents before OCR, chunking, embedding, or sanitization, and propagates the verified, unsigned, invalid, or stripped state into downstream metadata.
AID-H-002.003: AID-H-002.007 verifies source attestation before destructive normalization; AID-H-002.003 strips unsafe metadata, decodes, rebuilds, and admits the artifact. The provenance verdict must be preserved before sanitization changes the original bytes or metadata.
AID-D-001.002: AID-H-002.007 verifies an external publisher or capture-device attestation chain; AID-D-001.002 uses signal-level forensics to detect synthetic or manipulated media. Cryptographic provenance and forensic authenticity are independent verdicts and either may be absent or contradictory.
AID-M-002.002: AID-H-002.007 verifies third-party media provenance at inbound use; AID-M-002.002 verifies whole-artifact integrity against organization-governed digests and signatures. External publisher trust and internal artifact-lifecycle trust use different authorities and populations.
AID-H-020.001: AID-H-002.007 establishes the source-media provenance verdict before OCR and chunking; AID-H-020.001 protects the integrity of the resulting chunks from ingestion through retrieval. Source provenance precedes chunk integrity and neither substitutes for the other.
Implementation guidance
- AID-H-002.007-G001: Verify C2PA or equivalent provenance metadata at ingestion time and classify each asset as verified, unsigned, invalid, or stripped before it enters OCR, chunking, or embedding pipelines.
- AID-H-002.007-G002: Propagate provenance state into downstream chunk, embedding, and retrieval metadata and require step-up review when high-impact decisions rely on unsigned or invalid media.
AID-H-002.007-G001
Concept:
Verify C2PA / Content Credentials before metadata stripping, media rebuild, OCR, chunking, or transcoding. The verifier must use the supported c2pa-python object lifecycle, a pinned trust-anchor configuration, and the manifest validation state rather than guessing fields in the raw JSON.
Step 1: Verify at the file edge with the supported c2pa-python API
# File: provenance/verify_asset.py
from __future__ import annotations
import json
from dataclasses import dataclass
from enum import Enum
from hashlib import sha256
from pathlib import Path
from c2pa import C2paError, Context, Reader, Settings
TRUST_ANCHORS_PATH = Path("/etc/c2pa/trust_anchors.pem")
class ProvenanceStatus(str, Enum):
VERIFIED = "verified"
UNSIGNED = "unsigned"
INVALID = "invalid"
STRIPPED = "stripped"
@dataclass(frozen=True)
class ProvenanceVerdict:
asset_sha256: str
status: ProvenanceStatus
issuer: str | None
assertion_summary: list[str]
validation_errors: list[str]
def sha256_file(path: str | Path) -> str:
digest = sha256()
with Path(path).open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def build_c2pa_context() -> Context:
# The trust-anchor file is mounted read-only from the reviewed runtime image.
anchors = TRUST_ANCHORS_PATH.read_text(encoding="utf-8")
settings = Settings.from_dict({
"verify": {"verify_cert_anchors": True},
"trust": {"trust_anchors": anchors},
})
return Context(settings=settings)
def _manifest_metadata(reader: Reader) -> tuple[str | None, list[str]]:
manifest = reader.get_active_manifest() or {}
claim_info = manifest.get("claim_generator_info") or []
issuer = None
if isinstance(claim_info, list) and claim_info and isinstance(claim_info[0], dict):
issuer = claim_info[0].get("name")
issuer = issuer or manifest.get("claim_generator")
assertions = [
item.get("label")
for item in manifest.get("assertions", [])
if isinstance(item, dict) and item.get("label")
]
return issuer, assertions
def verify_asset(asset_path: str, *, expected_provenance: bool) -> ProvenanceVerdict:
asset_sha256 = sha256_file(asset_path)
try:
with build_c2pa_context() as context:
reader = Reader.try_create(asset_path, context=context)
if reader is None:
status = (
ProvenanceStatus.STRIPPED
if expected_provenance
else ProvenanceStatus.UNSIGNED
)
return ProvenanceVerdict(
asset_sha256=asset_sha256,
status=status,
issuer=None,
assertion_summary=[],
validation_errors=[],
)
with reader:
validation_state = (reader.get_validation_state() or "").lower()
issuer, assertions = _manifest_metadata(reader)
if validation_state != "valid" or not issuer:
results = reader.get_validation_results() or {}
return ProvenanceVerdict(
asset_sha256=asset_sha256,
status=ProvenanceStatus.INVALID,
issuer=issuer,
assertion_summary=assertions,
validation_errors=[json.dumps(results, sort_keys=True)],
)
return ProvenanceVerdict(
asset_sha256=asset_sha256,
status=ProvenanceStatus.VERIFIED,
issuer=issuer,
assertion_summary=assertions,
validation_errors=[],
)
except (C2paError, OSError, ValueError) as exc:
return ProvenanceVerdict(
asset_sha256=asset_sha256,
status=ProvenanceStatus.INVALID,
issuer=None,
assertion_summary=[],
validation_errors=[str(exc)],
)
Step 2: Admit or downgrade at ingestion
Use the verdict before OCR or embedding. Workflows that require authenticated media must fail closed on invalid or stripped. Lower-assurance workflows may admit unsigned assets but must preserve that trust state.
# File: provenance/intake_gate.py
from provenance.verify_asset import ProvenanceStatus, verify_asset
PROVENANCE_REQUIRED_WORKFLOWS = {
"evidence_review",
"newsroom_intake",
"regulated_kyc",
}
class AssetRejected(Exception):
pass
def admit_media_asset(asset_path: str, *, workflow: str) -> dict:
verdict = verify_asset(
asset_path,
expected_provenance=workflow in PROVENANCE_REQUIRED_WORKFLOWS,
)
if verdict.status in {ProvenanceStatus.INVALID, ProvenanceStatus.STRIPPED}:
raise AssetRejected(f"asset rejected with provenance status={verdict.status}")
return {
"asset_sha256": verdict.asset_sha256,
"provenance_status": verdict.status.value,
"provenance_issuer": verdict.issuer,
"provenance_assertions": verdict.assertion_summary,
}
Step 3: Preserve the verdict, not the full manifest
Once the verdict is recorded, downstream sanitization may strip non-essential metadata or rebuild the media bytes as required by AID-H-002.003. Do not reverse the order, or provenance evidence will be destroyed before verification completes.
Action: Make provenance status part of the input admission contract. Verify first, classify every asset as verified, unsigned, invalid, or stripped, and use that verdict to block or downgrade trust before OCR, chunking, or embedding begins.
AID-H-002.007-G002
Production implementation: provenance-preserving derivation
Derive chunk and embedding metadata only from a verified source record. Never upgrade unsigned, invalid, or stripped to verified during parsing or retrieval.
# File: provenance/derive_records.py
from __future__ import annotations
import hashlib
import json
ALLOWED_STATES = {"verified", "unsigned", "invalid", "stripped"}
def derive_chunk(source: dict, *, page: int, start: int, end: int, text: str, embedding_model_sha256: str) -> dict:
required = {"asset_id", "source_media_sha256", "provenance_state", "provenance_receipt_sha256", "policy_version"}
if set(source) != required or source["provenance_state"] not in ALLOWED_STATES:
raise ValueError("source provenance record differs")
for field in ("source_media_sha256", "provenance_receipt_sha256", "embedding_model_sha256"):
value = embedding_model_sha256 if field == "embedding_model_sha256" else source[field]
if len(value) != 64 or any(c not in "0123456789abcdef" for c in value):
raise ValueError(field + " is not lower-case SHA-256")
if page < 1 or start < 0 or end <= start or not text:
raise ValueError("chunk boundary is invalid")
text_sha256 = hashlib.sha256(text.encode("utf-8")).hexdigest()
identity = json.dumps(
{"source_media_sha256": source["source_media_sha256"], "page": page, "start": start, "end": end, "text_sha256": text_sha256},
sort_keys=True, separators=(",", ":"),
).encode()
return {
"chunk_id": hashlib.sha256(identity).hexdigest(),
"asset_id": source["asset_id"],
"source_media_sha256": source["source_media_sha256"],
"text_sha256": text_sha256,
"page": page,
"start": start,
"end": end,
"provenance_state": source["provenance_state"],
"provenance_receipt_sha256": source["provenance_receipt_sha256"],
"embedding_model_sha256": embedding_model_sha256,
"policy_version": source["policy_version"],
}
Action: Reconcile every produced page/chunk/embedding to the signed derivation manifest. The retrieval or decision service requires fresh step-up approval when policy classifies the propagated state and intended use as consequential; missing lineage denies use rather than defaulting to unsigned.
Open-source tools
- c2pa-rs
- c2patool
- c2pa-python
Commercial / hosted tools
- Adobe Content Authenticity Inspect
- Truepic Vision
Threat mappings
MITRE ATLAS
- AML.T0052.001 Phishing: Deepfake-Assisted Phishing (provenance checks downgrade unverified external media)
MAESTRO
- Data Tampering (L2)
OWASP LLM Top 10 2026
- LLM05:2026 Data and Model Poisoning (verified media provenance is an admission trust signal for poisoning review but does not establish that authenticated content is benign)
- LLM07:2026 Misinformation (signed provenance and step-up review reduce reliance on forged evidence but do not independently validate every claim)
OWASP ML Top 10 2023
- N/A
OWASP Top 10 for Agentic Applications 2026
- N/A
NIST Adversarial Machine Learning 2025
- N/A
Cisco Integrated AI Security and Safety Framework
- AITech-7.3 Data Source Abuse and Manipulation (Rejects external content lacking required verified provenance; does not establish factual truth)
Google Secure AI Framework 2.0 - Risks
- N/A
Databricks AI Security Framework 3.0
- Raw Data 1.7: Lack of data trustworthiness
- Raw Data 1.11: Compromised 3rd-party datasets