Agent identity & attestation
How raxit binds every decision to an attested agent identity.
If the identity an agent presents doesn't match anything in security.yaml, every call it makes
denies — not because a rule blocked it, but because no rule could ever reference that principal:
security.yaml: deny.principal_mismatch
hint: The attested principal matches no permit rule's agent, so no rule can ever match it; run
raxit without --agent to auto-derive the agent id, or set agent: <presented-principal> in
security.yaml.This is the single most common first-run stall — see Common issues for the fix. The reason it happens at all is the point of this page: raxit never lets an agent assert its own identity as a bare string.
The mental model: identity is attested, not asserted
agent: in security.yaml becomes the Cedar principal (Agent::"<value>"). Every decision
is bound to a verified identity — a token that was cryptographically checked against a
trusted issuer — never a name the agent process simply hands over. An unidentifiable call cannot
be permitted, full stop.
What binds identity in practice
For local development, raxit ships a dev HMAC identity: a symmetric LocalTokenVerifier
(method idp_local) that signs a token as "<id>|<method>|<hex-hmac(id|method)>" using a shared
key (RAXIT_IDENTITY_KEY). raxit run generates a fresh ephemeral dev key per run rather than
reusing a static shared default. This is explicitly dev/demo-grade — infra-free, zero-setup,
and not attested in the cryptographic sense; it's what makes raxit run -- python agent.py work
with no external identity provider on day one.
For production, attested methods bind the principal instead:
- SPIFFE/SPIRE JWT-SVID — workload identity from a SPIRE agent.
- Cloud workload identity — AWS IRSA, GCP workload identity, Azure managed identity.
- OIDC / GitHub OIDC — algorithm-confusion-proof (
alg:nonetokens are rejected outright). - X.509-SVID — mTLS peer authentication, but scoped to the multi-node state server, not agent-principal binding itself.
Whichever method verifies the token, the identity that comes out becomes the Cedar principal
and scopes budgets, rate limits, rule matching, and delegation for every subsequent call in the
session.
Matching methods to policy
A verified identity still needs its method allowlisted, separately from the identity itself
being valid — a verified token using a method not on the allowlist denies with
deny.principal_untrusted_method, distinct from an unverified token (deny.identity_unverified)
and distinct from a verified-but-unreferenced principal (deny.principal_mismatch, above). All
three are identity-stage failures but mean different things:
| Reason code | What actually happened |
|---|---|
deny.identity_unverified | the token is missing, malformed, or fails cryptographic verification |
deny.principal_untrusted_method | the token verifies, but its method isn't allowlisted |
deny.principal_mismatch | the token verifies and its method is trusted, but no rules[] entry references this principal at all |
agent: in security.yaml must equal the identity the agent actually presents at runtime —
either the --agent flag on raxit run, or RAXIT_AGENT_ID in the environment/SDK. This is
the pairing that principal_mismatch exists to catch when it drifts.

