Skip to content

enforcement

Modules


Capability token selection map.

Holds pre-provisioned capability tokens issued by the Authority at pre-flight and selects the best match for each intercepted request based on action class and resource scope (ADR-002).

Tokens are indexed by action class at construction time so that per-request lookups avoid a full linear scan. The agent knows nothing about Firma — the sidecar selects the correct token internally after intent normalization.

Stage 1 — Capability Validation.

First enforcement phase. Selects the best-matching capability token and validates it:

  1. Capability token selection — selects the best-matching pre-provisioned token from the CapabilityMap by action class and resource scope (ADR-002). The agent knows nothing about Firma; the sidecar selects the correct token internally.
  2. Token validation — parse PASETO v4, verify Ed25519 signature, check expiry (with configurable clock skew tolerance), and check revocation via bloom filter + LRU cache.

All operations are fully local — the Authority is never contacted on the hot path. Target latency: < 1 ms p95.

Stage 1 prevents forged, tampered, expired, or revoked capabilities from entering the execution path:

  • Token forgery — cryptographic signature verification rejects tokens not signed by a trusted Authority.
  • Token tampering — any modification to scope, budget, expiry, agent ID, or resource scope invalidates the signature.
  • Expired credential reuse — expiry check rejects tokens whose TTL has elapsed.
  • Revoked token reuse — bloom filter + LRU cache check rejects tokens that have been explicitly invalidated.

Concrete Cedar policy evaluator for Sidecar Stage 2.

Implements [PolicyEvaluation] by evaluating a compiled Cedar policy set against the context produced by [ConstraintEnforcer::build_context][super::constraint_enforcement::ConstraintEnforcer].

Evaluation is fully local — no network calls. [CedarPolicyEvaluator] is constructed from a [PolicyBundle] received from the Authority and tracks freshness against the bundle TTL.

Entity UID conventions (must match Authority’s service.rs)

Section titled “Entity UID conventions (must match Authority’s service.rs)”
Cedar roleFormat
principalFirma::Agent::"<agent_id>"
actionFirma::Action::"<action_class>"
resourceFirma::Resource::"<resource_uri>"

Stage 2 — Constraint Enforcement Engine (CEE).

Second enforcement phase. The semantic layer where Cedar policies and quantitative constraints are evaluated. Operates on a previously normalized ExecutionEnvelope produced by Stage 1 — it must not infer the canonical action class from raw transport-specific input.

Steps:

  1. Scope check — verifies that the requested action_class is within the token’s allowed action_set. Wildcard "*" permits all actions.
  2. Policy bundle freshness — if the bundle TTL has expired without a successful refresh, the Sidecar enters fail-closed mode and denies all new requests.
  3. Context build — assembles the Cedar request context from envelope fields, claims, and runtime signals (budget consumed, risk score).
  4. Cedar eval — evaluates against the current policy bundle. Deterministic: same context + same bundle = same decision. Fully local, no external calls.

Target latency: < 200 µs p95.

Stage 2 prevents valid capabilities from being misused for out-of-policy, over-budget, or contextually disallowed actions:

  • Privilege escalation within token — a valid token does not imply all calls are allowed; Cedar eval checks the specific call against policy.
  • Scope misuse at runtime — a valid capability for action class X cannot be used for action class Y.
  • Budget overrun / quota abuse — pre-computed budget_remaining attribute checked against threshold.
  • Non-deterministic authorization — same context + same bundle always produces the same decision.

Enforcement decision types.

Every enforce() call produces exactly one [EnforcementDecision]: ALLOW or DENY. ALLOW carries the verified claims and normalized envelope for downstream use (credential injection, connector dispatch, audit). DENY carries a structured reason, the originating stage, and a detail message for audit and agent error reporting.

ABORT is an asynchronous in-flight kill signal emitted by the Authority via WatchAborts, not produced by the enforcement pipeline itself.

Internal enforcement errors.

Every variant maps to a [DenyReason] via [EnforcementError::into_deny]. This is the fail-closed boundary: errors become DENY decisions. These types are never exposed to external callers.

Canonical Action Class Registry v0.1.

Contains the 15 canonical action classes defined by FEP v0.1 §2.3.5. Every intent.action_class field in an ExecutionEnvelope MUST be one of these identifiers. Unknown protected actions that cannot be deterministically mapped to a registry entry fail closed with DENY: UNCLASSIFIED_INTENT (FEP [I-N1]).

Identifiers follow the naming rules in FEP §2.3.2: lowercase ASCII, dot-separated, describing semantic meaning only. Transport, provider, and connector names MUST NOT appear in identifiers. See docs/markdown/firma_action_class_registry.md for the implementation notes and default mapping strategy.

The same canonical class is produced regardless of whether the underlying action arrives as a native tool call, a CLI invocation, an HTTP request, or an MCP call. Policies and HITL conditions bind to the canonical action class, not to transport-specific names.

Revocation cache: bloom filter + LRU store.

Implements [firma_core::RevocationStore] for the sidecar. See docs/tasks/006-revocation-cache.md.

  • is_revoked: bloom-miss -> Ok(false) fast path. Bloom-hit -> LRU lookup. LRU-hit -> Ok(true). LRU-miss on bloom-positive (either a bloom false positive or an evicted LRU entry) -> Ok(true), to honor the spec’s “REVOKED is terminal” invariant.
  • add_revocation: bloom insert + LRU insert. Idempotent.
  • V1 never returns Err; the trait’s Result is kept for forward compatibility.

Per-session runtime state for Stage 2 quantitative constraint enforcement.

V1 stores three signals per SessionId: action count (monotonic counter incremented on every admitted request), budget_consumed (cumulative, placeholder 0.0 in V1), risk_score (placeholder 0.0 in V1). Storage is in-memory with LRU eviction — V1 runs single-process.

Eviction resets an evicted session’s counters on next access. Since Cedar policies in V1 are monotone (action_count > N denies as count grows), eviction can only move a denying session back toward allowing — acceptable for V1 scope. Document when this is upgraded to a persistent or cluster-shared store.