# Security review: delegated 0.2 evaluation path

Status: internal focused review (July 2026). Not a third-party audit.

## Scope

This review covers the `delegated` 0.2 crate evaluation pipeline:

- wire parsing and contract validation (`contracts.rs`)
- Ed25519 signing and verification (`crypto.rs`)
- issuance builder (`issuance.rs`)
- evaluator, operation binding, and audit emission (`engine.rs`)
- in-memory and Redis trust-state backends (`revocation.rs`, `delegated-redis`)
- reference vectors and the reference issuer/verifier stack

Out of scope: host key custody, TLS, identity providers, policy languages, and
application-specific action/resource vocabularies.

## Methodology

1. Trace the evaluator pipeline against [SPEC.md](../SPEC.md) and
   [THREAT_MODEL.md](THREAT_MODEL.md).
2. Review fail-closed behavior on malformed input, backend errors, and audit
   persistence failures.
3. Examine canonical signing serialization and replay-state ordering.
4. Run the expanded unit, integration, property, vector, and reference-stack
   test suites.
5. Inspect Redis nonce atomicity (`SET NX EX`) and issuer-scoped key layout.

## Verified security properties

| Property | Mechanism | Result |
|---|---|---|
| Untrusted request cannot select verification keys | `IssuerKeyResolver` is host-only | Pass |
| Tampered claims fail verification | Ed25519 over canonical unsigned token | Pass |
| Bearer cannot choose authorized operation | `OperationContext` is host-only | Pass |
| Constrained resources/depth fail closed | `bind_operation` requires host facts | Pass |
| Replay resistance | Atomic `consume_nonce` after other checks | Pass |
| Unauthorized attempts do not burn nonces | Nonce consumed only after binding | Pass (tested) |
| Revocation and emergency deny | Issuer-scoped mutable state | Pass |
| Backend errors deny | `trust_state` stage maps errors to denial | Pass |
| Audit failure prevents allow return | `evaluate_and_audit` propagates I/O error | Pass |
| Oversized input rejected early | `max_envelope_bytes` pre-parse limit | Pass |
| Strict JSON decoding | `deny_unknown_fields` on models | Pass |

## Findings

### F-1: Bearer credentials (informational)

Tokens are bearer credentials with no proof of possession. This matches the
documented threat model. Hosts must use TLS, short lifetimes, and least-privilege
scopes.

### F-2: Audit failure after nonce consumption (low, accepted)

`evaluate_and_audit` consumes a nonce before persisting audit events. A storage
outage can therefore reject a retry of an otherwise valid token. This is an
intentional security-over-availability trade documented in SPEC.md and
OPERATIONS.md.

**Recommendation:** keep default behavior; operators should monitor audit sink
health and treat audit failures as incidents.

### F-3: Redis revocation keys are not TTL-bound (low)

`delegated-redis` stores revocation and deny flags without expiry. This is
correct semantically but requires operational cleanup in long-lived clusters.

**Recommendation:** document key lifecycle procedures; consider optional TTL for
non-emergency revocations in a future adapter.

### F-4: Canonical JSON is project-specific (informational)

Signing uses lexicographically sorted object keys via `serde_json` primitives.
This is the Delegated 0.2 profile documented in SPEC.md, not full RFC 8785.

**Recommendation:** freeze the profile at 1.0 and keep reference vectors as the
interop contract.

### F-5: Clock trust remains with the host (informational)

Activation and expiry rely on evaluator-supplied time with bounded leeway.
Compromised host clocks can widen or narrow effective lifetimes within the
configured leeway cap.

**Recommendation:** require NTP/chrony in production deployments (already noted in
OPERATIONS.md).

No exploitable defects were identified in the reviewed code paths.

## Residual risks (accepted non-goals)

- Malicious or compromised configured issuers
- Race between revocation check and operation execution
- Incorrect host mapping from routes to `OperationContext`
- Stolen bearer tokens until expiry, revocation, or nonce consumption

## Test evidence

- `tests/core.rs`: integration scenarios including concurrency and fail-closed paths
- `tests/contracts.rs`: structural validation edge cases
- `tests/issuance_proptest.rs`: builder and contract invariants
- `tests/vectors.rs`: 15 reference vectors for cross-implementation conformance
- `crates/delegated-redis`: Redis atomicity tests (when `REDIS_URL` is set)
- `examples/reference-stack`: end-to-end issuer/verifier smoke test

## Path to 1.0

Before declaring 1.0:

1. Freeze the 0.2 wire format and reference vectors.
2. Obtain an independent third-party audit or formal verification engagement.
3. ~~Publish a supported `delegated-redis` operations guide for production Redis
   topologies (sentinel/cluster).~~ Done: [delegated-redis operations guide](../crates/delegated-redis/OPERATIONS.md).
4. Keep the public API stable for at least one downstream integration cycle.

## Conclusion

The 0.2 evaluation core implements the documented fail-closed model consistently.
With durable trust state, audit persistence, and correct host operation mapping,
the library is suitable for controlled production integrations. It should not be
marketed as a turnkey authorization platform without those host responsibilities.
