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.

Source Created By Evidence Type
Scanner Static detector findings Auto
LLM AI-reviewed Semi findings Semi
Manual Human-uploaded documents via API Doc, Test
CI CI pipeline attestation Auto
Questionnaire Interview/questionnaire answers Doc

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:

Provider Detection Fields Captured
GitHub Actions GITHUB_ACTIONS workflow, actor, repository, OIDC issuer
GitLab CI GITLAB_CI pipeline name, user, project path, server URL
Jenkins JENKINS_URL job name, build user
Bitbucket BITBUCKET_PIPELINE_UUID pipeline UUID, repo name
Azure DevOps BUILD_BUILDID (build ID)
CircleCI CIRCLECI (detected)
Drone DRONE (detected)
Woodpecker CI_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"
}'
Storage Retention Purpose
PostgreSQL evidence_records Indefinite (append-only) Primary evidence store
S3 artifacts Indefinite Document attachments, SBOM/CBOM files
CI artifacts 10 years (configure per platform) Backup evidence trail
Attestation envelopes Indefinite Cryptographic proof of scan provenance