Skip to content

Evidence & Attestation Guide

Every assessment finding produces an immutable evidence record:

EvidenceRecord {
id: UUID
requirement_id: "CRYPTO-01-R1"
evidence_type: Auto | Semi | Doc | Test
source: Scanner | LLM | Manual | CI | Questionnaire
content: "Requirement CRYPTO-01-R1 assessed as pass (confidence: 92%)..."
content_hash: "sha256:a1b2c3..."
signature: (optional HMAC-SHA256)
commit_sha: "abc12345"
llm_provenance: { backend, model, prompt_version, confidence }
status: Draft | Reviewed | Attested
created_at: 2026-04-03T12:00:00Z
}

Evidence records have no updated_at column. They are append-only by design:

  • New scans create new evidence records
  • Old records are never modified or deleted
  • If a finding changes status, a new record is created — the old one persists
  • This satisfies the CRA 10-year retention obligation (Article 23)

Every evidence record is hashed with SHA-256:

content_hash = "sha256:" + hex(SHA256(content))

This allows tamper detection: if the content is modified after creation, the hash won’t match.

SourceCreated ByEvidence Type
ScannerStatic detector findingsAuto
LLMAI-reviewed Semi findingsSemi
ManualHuman-uploaded documents via APIDoc, Test
CICI pipeline attestationAuto
QuestionnaireInterview/questionnaire answersDoc

Every CI scan can produce a signed attestation envelope:

{
"version": "1.0.0",
"subject": {
"evidence_hash": "sha256:a1b2c3...",
"commit_sha": "abc12345",
"scan_id": "uuid",
"product_name": "my-product",
"findings_count": 152,
"summary": { "pass": 55, "fail": 40, "needs_review": 57 }
},
"identity": {
"ci_provider": "github",
"workflow": "CRA Compliance",
"actor": "james",
"repository": "org/repo",
"oidc_issuer": "https://token.actions.githubusercontent.com"
},
"signature": {
"method": "hmac-sha256",
"value": "hex...",
"key_hint": "fleet_te..."
},
"timestamp": "2026-04-03T12:00:00Z"
}
// The signature is verified by recomputing HMAC over the subject JSON
verify_hmac(&subject, &signature, api_key) -> bool

Uses timing-safe comparison (subtle::ConstantTimeEq) to prevent timing attacks.

The attestation module auto-detects CI identity from environment variables:

ProviderDetectionFields Captured
GitHub ActionsGITHUB_ACTIONSworkflow, actor, repository, OIDC issuer
GitLab CIGITLAB_CIpipeline name, user, project path, server URL
JenkinsJENKINS_URLjob name, build user
BitbucketBITBUCKET_PIPELINE_UUIDpipeline UUID, repo name
Azure DevOpsBUILD_BUILDID(build ID)
CircleCICIRCLECI(detected)
DroneDRONE(detected)
WoodpeckerCI_WOODPECKER(detected)
Scanner/LLM creates finding
EvidenceRecord (status: draft)
├─── Manual review ──▶ status: reviewed
├─── Attestation signing ──▶ status: attested
└─── Stored permanently (append-only, 10-year retention)

For Doc and Test evidence types, upload via API:

Terminal window
curl -X POST /api/v1/assessment/products/{id}/evidence/upload \
-H "Authorization: Bearer $KEY" \
-d '{
"requirement_id": "VH-DISC-01-R1",
"evidence_type": "doc",
"content": "Disclosure policy published at...",
"created_by": "james@crabnebula.dev"
}'
StorageRetentionPurpose
PostgreSQL evidence_recordsIndefinite (append-only)Primary evidence store
S3 artifactsIndefiniteDocument attachments, SBOM/CBOM files
CI artifacts10 years (configure per platform)Backup evidence trail
Attestation envelopesIndefiniteCryptographic proof of scan provenance