Runbook: Attestor - Attestation Verification Failures
Purpose: triage attestation verification failures that block a promotion or release — signature/body mismatches, transparency-log inconsistencies, trust/key-rotation gaps, clock skew, and unreachable logs. Audience: Platform and Security on-call engineers responding to a blocked stella verify release chain step or a failing POST /api/v1/rekor/verify.
Doc-vs-code reconciliation (2026-05-31): This runbook was reconciled against
src/Attestor(esp.StellaOps.Attestor.WebService,StellaOps.Attestor.Core/Verification, andStellaOps.Attestor.Infrastructure/Verification), the Doctor pluginsrc/Doctor/__Plugins/StellaOps.Doctor.Plugin.Attestor, and the CLI command tree undersrc/Cli/StellaOps.Cli/Commands. Several command names and request/response shapes from earlier drafts did not match the code and have been corrected or flagged. Commands tagged (roadmap) are not implemented today; use the documented alternative. Items tagged (not wired) describe a type that exists in the codebase but is not registered in the running Attestor WebService.
Metadata
| Field | Value |
|---|---|
| Component | Attestor |
| Severity | High |
| On-call scope | Platform team, Security team |
| Last updated | 2026-05-31 |
| Live verify API | POST /api/v1/rekor/verify (AttestorWebServiceEndpoints); service AttestorVerificationService (registered in AddAttestorInfrastructure) |
| Doctor check | check.attestation.rekor.verification.job (Doctor plugin StellaOps.Doctor.Plugin.Attestor; skips unless the verification status provider is registered — see note) |
| Verify scope | ASP.NET policy attestor:verify; accepts scope-claim values attestor.verify or attestor.write (AttestorWebServiceComposition) |
Scope catalog gotcha. The Attestor authorization policies (
attestor:write,attestor:verify,attestor:read) and the scope-claim values they accept (attestor.write/attestor.verify/attestor.read) are defined locally inAttestorWebServiceComposition.cs. They are not present in the canonical scope catalogStellaOps.Auth.Abstractions/StellaOpsScopes.cs(which carries the separateattest:create/attest:read/attest:adminconsts).attestor:readis satisfied by any ofattestor.read/attestor.verify/attestor.write;attestor:verifybyattestor.verify/attestor.write;attestor:writebyattestor.write. Trusted-network service bypass also satisfies all three.
Note on health / Doctor surfaces. Two related-but-distinct subsystems exist in the code, and only one is wired into the running service:
- Live verify API —
POST /api/v1/rekor/verifyis backed byIAttestorVerificationService→AttestorVerificationService, which is registered. This is the surface to use for on-demand verification (Diagnosis).- Periodic Rekor verification job + health check + status provider (
RekorVerificationJob,RekorVerificationHealthCheckwithName = "rekor-verification",IRekorVerificationStatusProvider) exist inStellaOps.Attestor.Core.Verificationbut are (not wired) into the Attestor WebService: itsAddHealthChecks()registers only theselfcheck, and the status provider / background job have no production DI registration. The Doctor plugin checkcheck.attestation.rekor.verification.jobtherefore Skips with “Rekor verification service not registered” until those services are wired. Do not expect arekor-verificationentry on the health endpoint today.
Symptoms
- [ ] Attestation verification failing
- [ ] Alert
AttestorVerificationFailedfiring (operator-defined; the platform does not ship this alert rule by default) - [ ]
POST /api/v1/rekor/verifyreturns a result withok=falseand a populatedissues[]list (free-form strings such astime_skew_rejected: ...plus any engine-reported issues), or returns400 Bad Requestwith anAttestorVerificationExceptioncode(e.g.not_found,invalid_query) - [ ] Periodic Rekor verification job reporting failures — only if the job is wired (see Metadata note). Its
RekorVerificationFailureCodevalues are:EntryNotFound,InvalidSignature,InvalidInclusionProof,TimeSkewExceeded,BodyHashMismatch,LogIndexMismatch,NetworkError,Timeout,Unknown. These are distinct from the live verify-APIissues[]strings above. - [ ] Promotions/releases blocked because a
stella verify releasechain step failed
Impact
| Impact Type | Description |
|---|---|
| User-facing | Artifacts cannot be promoted; release blocked |
| Data integrity | May indicate tampered attestation, transparency-log inconsistency, or configuration issue |
| SLA impact | Release pipeline blocked until resolved |
Diagnosis
Quick checks
Check the Attestor service health:
# The Attestor WebService maps two health endpoints (AttestorWebServiceComposition): curl -s https://<attestor-host>/health/ready curl -s https://<attestor-host>/health/liveThese use the default ASP.NET health writer and return a plain status string (
Healthy/Degraded/Unhealthy), not a JSONentries[...]document. Today the only registered check isself; therekor-verificationcheck (not wired) does not appear on these endpoints. TheRekorVerificationHealthChecklogic — were it registered — would reportdatakeyslastRunStatus,entriesVerified,entriesFailed,failureRate,rootConsistent,criticalAlerts, returningHealthywhen verification is disabled,Degradedwhen it has never run or is stale (>48h), andUnhealthyon critical alerts, failure rate over threshold, or a failed root-consistency check. Until it is wired, use the live verify API below.Verify a specific attestation against the transparency log (API):
# ASP.NET policy attestor:verify — accepts scope-claim attestor.verify or attestor.write. curl -s -X POST https://<attestor-host>/api/v1/rekor/verify \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "uuid": "<rekor-uuid>", "refreshProof": true }'The request body is an
AttestorVerificationRequest: supply at least one ofuuid,bundle, orartifactSha256.refreshProof: truere-pulls the proof from the log;offline: trueskips contacting external logs. Returns anAttestorVerificationResult— inspectok(bool),status, andissues[](camelCase). A400carries anAttestorVerificationExceptioncode(e.g.not_found,invalid_query).Verify a DSSE / evidence bundle offline (CLI):
stella attest verify-offline --bundle <bundle.tar.gz> \ --trust-root <trust-root-dir> \ --checkpoint <checkpoint.sig> \ --strict --format jsonstella attest verify-offlinevalidates a bundle’s manifest integrity, DSSE envelope signatures, Rekor inclusion proof (optional unless--strict), and content hash without network access.--trust-root/-rpoints at a directory containing CA certs and the Rekor public key;--checkpoint/-cand--artifact/-aare optional. (There is nostella attest verify --envelope --root --explainform:stella attest verifyis the OCI-image verifier and takes--image/-i; offline bundle verification isverify-offline.)
Deep diagnosis
Inspect attestation details (API):
# ASP.NET policy attestor:read (accepts attestor.read/attestor.verify/attestor.write). curl -s "https://<attestor-host>/api/v1/attestations/<uuid>?refresh=true" \ -H "Authorization: Bearer $TOKEN" | jqThe detail response (
AttestationDetailResponseDto, camelCase) carriesuuid,index,backend,proof(inclusion proof / checkpoint),logURL,status,localTransparencyReceipt,externalMirrorReceipts,mirror, andartifact. Note: this endpoint does not include asignerfield — signer identity (signer.{mode,issuer,subject,keyId}) is returned by the list endpoint (GET /api/v1/attestations), not the per-UUID detail. The equivalent Rekor alias isGET /api/v1/rekor/entries/<uuid>(same handler).List attestations for an artifact (CLI):
# Both subcommands require --image; fetch also requires --predicate-type. stella attest list --image <registry/repo@sha256:...> stella attest fetch --image <registry/repo@sha256:...> --predicate-type <uri>(These CLI subcommands are OCI-registry oriented and are currently placeholder implementations —
attest list/attest verifycarryTODO: Integrate with IOciAttestationAttacherin the source. Treat their output as illustrative until wired; the API endpoints above are the authoritative surface.)Check the active crypto provider / plugin reachability:
stella crypto status stella crypto plugins status stella crypto profile validatestella crypto profile validatereports a plugin-reachable / FIPS-mode disagreement, a common cause of “algorithm not supported” verification failures.Check trust state (TUF):
stella trust status # current trust state + TUF metadata freshness stella trust status --show-keys # include loaded key fingerprints stella trust verify <artifact> # verify an artifact against TUF-loaded anchorsstella trust verifytakes a positional artifact reference (image ref, file path, or attestation) and supports--check-inclusion(default on) and--offline. (There is nostella trust-anchorscommand — see the roadmap note below.)
Resolution
Earlier drafts listed several
stellacommands / flags that do not exist in the CLI (stella trust-anchors ...,stella trust init --repo,stella attest show/create/integrity-check/resign,stella attest verify --envelope --root --explain,stella verify cert-chain,stella keys show,stella issuer trust-status/show/trust,stella issuer keys fetch,stella crypto providers list --algorithms). The real surfaces are: offline bundle verification isstella attest verify-offline(--bundle/--trust-root/--checkpoint); TUF init isstella trust init --tuf-url; provider info isstella crypto provider show/stella crypto profiles list. They are used below, or flagged (roadmap) where no equivalent exists yet.
Immediate mitigation
Refresh trust metadata (TUF) so the verifier sees current roots/keys:
stella trust sync stella trust statusRe-verify after a trust refresh:
stella attest verify-offline --bundle <bundle.tar.gz> --trust-root <trust-root-dir> --strictOr against the transparency log:
curl -s -X POST https://<attestor-host>/api/v1/rekor/verify \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{ "uuid": "<rekor-uuid>", "refreshProof": true }'Bootstrap a TUF trust repository if none is initialized:
# --tuf-url/-u is the repository URL (required); there is no --repo flag. stella trust init --tuf-url <trust-repo-url>
(roadmap) Add an individual issuer certificate / intermediate to a trust anchor list. There is no
stella trust-anchors add/stella trust-anchors add-intermediate/stella trust-anchors add-keycommand. Trust roots are managed via the TUF trust repository (stella trust init|sync|export|import) and the crypto provider configuration. Usestella trust syncto pull the current roots, or update the TUF repo upstream.
Root cause fix
Match the failure to what the verify surface reports. Note the two distinct vocabularies (see Metadata note):
- Live verify API (
POST /api/v1/rekor/verify): a400carries anAttestorVerificationExceptioncode(not_found,invalid_query, plus any engine code), andissues[]carries free-form strings (e.g.time_skew_rejected: ...) produced by the verification engine. - Periodic job (
RekorVerificationFailureCode, only if wired): enum valuesInvalidSignature,BodyHashMismatch,InvalidInclusionProof,LogIndexMismatch,TimeSkewExceeded,EntryNotFound,NetworkError,Timeout,Unknown.
The headings below use the periodic-job enum names as a familiar shorthand for the failure class; map them to the engine issues[] strings you actually see.
InvalidSignature / BodyHashMismatch (signature or body mismatch):
- Confirm the envelope/body was not modified by re-fetching from the log and re-verifying offline:
curl -s "https://<attestor-host>/api/v1/attestations/<uuid>?refresh=true" \ -H "Authorization: Bearer $TOKEN" | jq '.proof, .artifact' stella attest verify-offline --bundle <bundle.tar.gz> --trust-root <trust-root-dir> --strict - If the artifact legitimately changed, re-sign and re-submit a fresh attestation:
(There is no# Sign a new attestation payload (requires attestor:write). curl -s -X POST https://<attestor-host>/api/v1/attestations:sign \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d @sign-request.json # Then submit to the transparency log: curl -s -X POST https://<attestor-host>/api/v1/rekor/entries \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d @submission.jsonstella attest integrity-check,stella attest create, orstella attest resigncommand — use the:signand/rekor/entriesAPIs above.)
Key rotated and old key not trusted:
- Refresh the TUF trust state so rotated keys/roots are visible to the verifier:
stella trust sync stella trust status - Inspect or rotate issuer keys via the issuer key lifecycle commands:
(There is nostella issuer keys list stella issuer keys rotate <key-id>stella issuer keys fetchorstella issuer trustcommand;stella issuer keysprovideslist,create,rotate, andrevoke.)
Certificate expired / chain invalid:
- Validate the active crypto profile and plugin reachability:
stella crypto profile validate stella crypto status - Re-verify offline with the trust-root chain supplied explicitly:
(There is nostella attest verify-offline --bundle <bundle.tar.gz> --trust-root <chain-dir> --strictstella verify cert-chainorstella verify certcommand, andstella attest verifyhas no--explainflag; chain/expiry diagnostics surface in theverify-offlineper-check report and in the API resultissues[].)
InvalidInclusionProof / LogIndexMismatch / root inconsistency:
- Re-fetch the entry with
refresh=trueto pull the latest inclusion proof and checkpoint:curl -s "https://<attestor-host>/api/v1/attestations/<uuid>?refresh=true" \ -H "Authorization: Bearer $TOKEN" | jq '.proof' - A persistently failing root-consistency check (the periodic job’s
RekorVerificationHealthChecklogic reports "Rekor root consistency check failed- possible log tampering", and the Doctor check
check.attestation.rekor. verification.jobfails) is a security event — escalate to the Security team before re-submitting. Note both surfaces are (not wired) today (see Metadata); the live API exposes inclusion-proof issues directly inissues[].
- possible log tampering", and the Doctor check
TimeSkewExceeded:
- The verifier enforces a maximum timestamp skew (
TimeSkewValidator; controlled byAttestoroptionsTimeSkew.*). When the checkpoint/integrated time is outside tolerance, the live API surfaces it as anissues[]entry prefixedtime_skew_rejected:(whenTimeSkew.FailOnRejectis set). Check host/NTP clock sync and the configured skew tolerance, then re-verify:
(There is nocurl -s -X POST https://<attestor-host>/api/v1/rekor/verify \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{ "uuid": "<rekor-uuid>", "refreshProof": true }' | jq '.ok, .issues'--require-timestamp/--max-skewCLI flag onstella attest verify; skew tolerance is server-side configuration.)
EntryNotFound / NetworkError / Timeout (transparency log unreachable):
- The entry may be valid locally but not anchored, or Rekor may be unreachable. See the companion runbook
attestor-rekor-unavailable.md.
Algorithm not supported:
- Confirm the active provider supports the attestation’s algorithm:
(There is nostella crypto status stella crypto plugins statusstella crypto providers(plural) command and no--algorithmsflag. The singularstella crypto provider showreports only the installation provider region setting; the available profiles and their algorithm sets are listed bystella crypto profiles list. Provider/plugin reachability is reported bystella crypto plugins statusandstella crypto profile validate.)
Verification
# Verify the entry against the transparency log (API; policy attestor:verify).
curl -s -X POST https://<attestor-host>/api/v1/rekor/verify \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "uuid": "<rekor-uuid>", "refreshProof": true }' | jq '.ok, .status, .issues'
# Verify an evidence bundle offline against trust roots.
stella attest verify-offline --bundle <bundle.tar.gz> --trust-root <trust-root-dir> --strict
# Verify a full release promotion chain (source, build, signature, transparency).
stella verify release <release-bundle>
# Re-check service health (plain status string; no rekor-verification entry today).
curl -s https://<attestor-host>/health/ready
Prevention
- [ ] Trust metadata: Keep the TUF trust repository current; run
stella trust syncon a schedule - [ ] Key rotation: Plan issuer-key rotation with an overlap period (
stella issuer keys rotate --overlap-days) for verification continuity - [ ] Monitoring: Once the periodic Rekor verification job and its health check are wired (currently not wired — see Metadata), alert on its
failureRate,rootConsistent, andcriticalAlerts. Until then, monitor verify-API outcomes and thecheck.attestation.rekor.verification.jobDoctor check - [ ] Testing: Include
stella verify release/stella attest verify-offlinein the release pipeline - [ ] Crypto profile: Run
stella crypto profile validateafter provider/profile changes
Related Resources
- Architecture:
docs/modules/attestor/rekor-verification-design.md,docs/modules/attestor/transparency.md,docs/modules/attestor/api-reference.md(the previously linkeddocs/modules/attestor/verification.mddoes not exist) - Related runbooks:
attestor-signing-failed.md,attestor-key-expired.md,attestor-rekor-unavailable.md - Trust management:
docs/operations/trust-lattice-runbook.md,docs/operations/trust-lattice-troubleshooting.md,docs/operations/rekor-policy.md(the previously linkeddocs/operations/trust-anchors.mddoes not exist)
