Security

Identity is proven.
Everything else is denied.

Kirra treats its own fleet as a hostile network: every node proves who it is cryptographically, every carrier is untrusted, every privileged route demands a scope, and every decision lands in a ledger that can't be quietly edited.

Node attestation

Challenge–response, rooted toward hardware

Trust is never asserted by an admin — it's proven per node: an Ed25519 signature over a fresh 30-second nonce, checked against the node's registered key. Nodes under a require_tpm_quote policy must additionally present a TPM2 quote whose PCR16 digest matches their registered measured-boot value, bound to the same nonce.

Attestation challenge–response The verifier issues a random nonce with a 30-second TTL. The node signs node id plus nonce with its attestation key; optionally a TPM quote binds PCR16 to the same nonce. The verifier checks the signature against the registered public key and the PCR digest, then marks the node trusted. Any failure rejects. Verifier nonce · 30 s TTL · volatile never persisted Node Ed25519(node_id ‖ nonce) + TPM2 quote over PCR16 (policy) 1 · challenge nonce 2 · signed proof (+ quote) 3 · verify against registered AK · PCR16 digest must match ⇒ Trusted — anything else ⇒ reject, nonce preserved for retry

crates/kirra-safety-authority/src/attestation.rssrc/tpm_quote.rsSECURITY.md


Authorization

Scoped roles, constant-time everywhere

Fail-closed RBAC

Four roles — admin, integrator, auditor, operator — with per-scope gates on every privileged route. The authorization core is a pure function: no env reads, no store access, no way to accidentally default-allow. A missing admin token means 503, never 200.

src/authz.rsdocs/safety/PRINCIPAL_TOKENS.md

No timing oracles

Every security-critical byte comparison goes through constant_time_compare — standard equality on secrets is forbidden by invariant. Token plaintexts are never stored; only SHA-256 hashes.

src/security.rs

mTLS with expiry teeth

Client certificates are CA-verified at the TLS layer and pinned by SHA-256 fingerprint to a role. A cert at or past its expiry fail-closes exactly like a revocation; a half-configured TLS listener aborts startup rather than falling back to plaintext.

docs/safety/TRANSPORT_SECURITY.md


Audit

A memory that can't be edited

Every trust decision, config digest, campaign mutation, and denied command is a SHA-256 hash-chained ledger entry. The chain survives SIGKILL mid-append as a valid prefix — proven by a power-loss drill in CI — ships off-box to WORM storage, and re-verifies independently with no access to the source database.

Explainable denials

A denied actuator command mints a verdict id and becomes a signed, human-readable artifact: machine deny-code, operator explanation, the recorded inputs, and the chain fields — served to auditors at GET /verdicts/{id}.

src/verdicts.rs

Federation without trust

Cross-controller trust reports are Ed25519-signed, freshness-windowed (5 s), and replay-proof — nonces burn atomically with the report commit. Generation ordering reconciles conflicting reports deterministically.

src/federation.rssrc/federation_reconciliation.rs

Release tokens

The governor signs the digest of the exact validated contract bytes; the actuator verifies “the governor approved exactly these bytes” before anything moves. Key provisioning is fail-closed: no configured source, no key — a non-production dev key never loads by default.

crates/kirra-release-token/src/lib.rsdocs/safety/GOVERNOR_KEY_PROVISIONING.md


Supply chain

Pinned, audited, signed

Supply chain controls
ControlMechanismGate
Dependency policycargo-deny: license allowlist, wildcard ban, crates.io-only sourcesblocking CI lane
Vulnerability auditcargo-audit on every pushblocking (pipefail)
Actions pinningevery GitHub Action pinned to a 40-hex commit SHA, checked by scriptblocking CI lane
Container provenancedigest-pinned base images; keyless cosign signatures via OIDCdocker workflow
Release integritytag ↔ version ↔ changelog preflight; safety-case evidence bundle attached to releasesrelease workflow

deny.tomlscripts/pin-actions.sh.github/workflows/docker.yml.github/workflows/release.yml

Coordinated disclosure: see SECURITY.md — 48-hour first response.

READ THE SOURCE

Attack the design, not the marketing.

The invariants, the threat analysis, and the audit chain implementation are all public — we prefer adversarial readers.