Skip to content

Security model

When you evaluate an authorization system, the question that matters is: what happens when something is stolen? This page walks through the TrustPlane Auth security model threat by threat — stolen passports, stolen keys, on-host compromise, stale revocation material — and is careful to say exactly what each configuration proves and, just as importantly, what it does not prove. Overclaiming security is itself a vulnerability.

A passport is engineered to be low-value if stolen. Every acceptance decision layers short expiry, request binding, atomic replay consume, and audience/route/source scoping, so a stolen artifact buys an attacker almost nothing:

flowchart TD
  S["Attacker steals a passport + proof"] --> Q1{"Still within<br/>its short exp?"}
  Q1 -- no --> D1["Denied: expired"]
  Q1 -- yes --> Q2{"Replaying the<br/>exact same request?"}
  Q2 -- "different request" --> D2["Denied: request_binding_mismatch"]
  Q2 -- "same request" --> Q3{"jti already<br/>consumed?"}
  Q3 -- yes --> D3["Denied: jti_replay"]
  Q3 -- no --> Q4{"Right audience<br/>+ route + source?"}
  Q4 -- no --> D4["Denied: source/audience mismatch"]
  Q4 -- yes --> A["A tiny window for the<br/>identical request only"]

The residual risk of a stolen passport is: re-sending the identical request, to the identical audience, within a few seconds, before the legitimate caller’s jti is consumed. That is a dramatically smaller blast radius than a long-lived API key.

Threat coverage: the key-theft and RAT acceptance matrix

Section titled “Threat coverage: the key-theft and RAT acceptance matrix”

TrustPlane Auth uses a precise acceptance matrix. The claim is deliberately exact:

Scenario TrustPlane behavior
Stolen software key Attacker can sign — software only proves possession. Mitigated by short expiry, audience/route scoping, and replay, not prevented.
Stolen hardware_local / attested_workload key Off-host key reuse is prevented — the key is non-exportable / bound to an attested workload.
Stolen passport (replay) Blocked by request binding + atomic replay consume.
On-host RAT invoking the local broker Detected/limited via provenance, local policy, audit, and revocation freshness — not claimed to be impossible while the host is fully compromised and the signer is present.
Stale bundle after revocation High-risk routes fail closed on stale material.
Insufficient signer class Denied with insufficient_key_binding.

The honest version: TrustPlane prevents off-host key reuse only for hardware_local and attested_workload; it blocks stolen-passport replay via binding + atomic replay; and it detects/limits an on-host RAT through provenance, policy, audit, and freshness. It does not claim a fully compromised host can never invoke a present local signer.

Different identity sources prove different things. The frozen source-tier matrix:

Tier Policy shape Proves Does not prove
Software / JWKS issuer key + required_key_binding: software The passport+proof were signed by a configured software key for an allowed issuer/domain/subject/route/audience No export resistance, attestation, hardware binding, or cloud-instance proof
OIDC / JWKS OIDC/JWT issuer key + allowed route source Request came through a configured issuer/key matching route policy No principal record, no non-portability proof, no K8s/hardware attestation
Kubernetes SPIFFE/SPIRE attested_workload SPIFFE allow policy + required_key_binding: attested_workload via broker profile trustplane-spiffe-spire-k8s-v1 Broker verified an X.509-SVID workload identity before issuance; verifier trusts the broker issuer key Verifier does not re-verify the SVID per request; broker signing key is not claimed hardware/enclave-backed
EC2 / Droplet software source non-K8s issuer + subject prefix (e.g. aws:ec2:us-east-1:) + software Non-K8s callers accepted without API keys when policy matches No AWS IID, Nitro, TPM, enclave, IAM, or SPIRE-on-VM attestation — it is a software source
AWS IID / Nitro / hardware / KMS Not currently supported (not in v0.1) (planned) stronger attested key custody Don’t claim these from today’s software/JWKS/EC2 rules
sequenceDiagram
  participant W as Workload pod
  participant SP as SPIRE Workload API
  participant B as Broker (sidecar)
  participant A as Adapter (verifier)
  participant T as Trust bundle

  W->>B: Ask for a passport (over pod-local Unix socket)
  B->>SP: Fetch + verify X.509-SVID (WorkloadIdentitySource)
  SP-->>B: Valid SVID for spiffe://.../sa/orders-caller
  Note over B: Identity attested → issue attested_workload passport
  B-->>W: Passport + transcript-v1 proof
  W->>A: GET /orders + passport + proof
  A->>T: Trust the broker issuer key (from bundle)
  Note over A: Verifier trusts the broker issuer key
  Note over A: The broker is the attestation authority
  A-->>W: allow / deny

The crucial subtlety: the broker is the attestation authority. It verifies the SVID once, at issuance, and the verifier trusts the broker’s issuer key from the bundle. attested_workload means “workload identity was verified via SVID before issuance” — not that the broker’s signing key is hardware-backed or non-exportable.

High-risk routes deny on stale bundle / revocation material. Offline behavior is per-route (freshness_class), never a blanket “fail static.” Multi-replica deployments must use Redis for replay state — the Helm chart literally refuses to render multi-replica without it.

As covered in How it works: all policy checks (route, source, freshness, provenance/context) run before the replay Consume. An attacker therefore cannot burn a victim’s one-time jti by spraying unauthorized requests — replay state is only consumed on an otherwise-valid presentation.

  • Verifiers only ever hold public keys. Private keys stay with the issuer/broker/KMS.
  • Keep credentials, private keys, live bundle contents, and signed request logs out of git, generated artifacts, command logs, and public documentation.
  • Runtime hardening defaults such as non-root images, read-only filesystems, and read-only bundle mounts are covered in the deployment overview.