Threat model
Security objective
An attacker controlling the proposing agent must not cause an unauthorized, unscoped, stale, replayed, or silently unaudited production action. A permitted action must remain bounded even when the agent disappears immediately after commit.
Availability is secondary to authority safety: when identity, policy, storage, target state, or audit is ambiguous, writes stop.
Assumed hostile
- agent prompts, memory, model output, and inter-agent messages;
- evidence summaries and any prose returned by tools;
- arbitrary API payloads, replays, races, and malformed JSON;
- a collector attempting to claim another collector’s source root;
- stale policy, mission, evidence, or target observations;
- process interruption between authorization, mutation, and compensation;
- a concurrent human/controller changing the target after quarantine.
Trusted in local V1
- the Node.js process and host account;
- embedded policy and application code;
- SQLite file integrity and filesystem permissions;
- the local Ed25519 private key file;
- development identity headers supplied by the caller;
- configured evidence-authority mappings;
- the simulated executor and clock.
These assumptions make V1 a demonstrator, not a production security boundary. Production must remove the identity-header and single-host trust assumptions.
In the full hardened lab, mutually authenticated TLS and exact SPIFFE URI SAN extraction replace caller-supplied identity headers. OPA must agree with the embedded evaluator, PostgreSQL is authoritative, Temporal carries restoration, OpenBao signs, and a separate mTLS service executes the typed target operation. That service accepts mutation only with a short-lived signed grant for its exact audience and atomically claims the grant ID under a separate PostgreSQL role. The Windows account/DPAPI boundary, manually generated CA, PostgreSQL admin credential, local binaries/configuration and loopback network therefore remain in the trusted computing base. The separate SOTA profile proves workload-to- workload TLS 1.3 between two real SPIRE X.509-SVIDs and one Kubernetes/Cilium path, but does not yet deploy every AEGIS service as a continuously rotated attested workload. It is not equivalent to an HSM, separate administrative domains or an independently assessed cluster matrix. The real adapter is disabled by default in the application profile.
Constitutional invariants and status
| Invariant | V1 enforcement | Production gap |
|---|---|---|
| No secret in agent context | Architecture exposes no credential field; Gitleaks corpus passes | Real credential broker absent |
| Data never becomes authority | Evidence records cannot mint capabilities | End-to-end information-flow labels absent |
| No self-escalation | Separate identities; SPIRE issues client/executor SVIDs and their TLS 1.3 exchange is qualified | Whole-application deployment and external IAM administration absent |
| No unmediated action | Separate mTLS executor; signed audience-bound grant + anti-rejeu; real adapter allowlists/CAS/RBAC and fail-closed proof admission | Version matrix and application-pod API egress remain |
| No mutable constitution | Policy has no API; runtime verifies a signed scoped bundle | Signing/publishing key is local rather than dual-control |
| Provenance everywhere | Evidence/source hashes, authority-derived fault domains, certificate-key signed envelope and nonce flow into admission | Vendor-chain signatures and dependency graph absent |
| Bounded by default | Maximum 300 seconds, durable job and Temporal scheduling | Multi-node/region chaos and escalation absent |
| Memory is not truth | Memory is not an input to authorization | Agent runtime not implemented |
| Monitors reduce power only | No monitor can mint capability | Monitor layer not implemented |
| Independent emergency stop | Durable separate role in hardened lab | Separate process, identity and failure domain still mandatory |
Attack/control matrix
| Attack | Control | Residual risk |
|---|---|---|
| Change duration or target after approval | Exact canonical intent hash over the aegis-jcs-1 profile; a 27-vector specification corpus and an independent Rust implementation agree byte for byte, including on 25 000 generated values |
The second implementation verifies the primitive (canonical bytes, detached Ed25519), not proof semantics; no third party has yet reviewed either |
| L’opérateur s’auto-approuve | Identité d’approbation dédiée, sans chevauchement avec le rôle action ; reçu signé lié à l’intention et aux preuves techniques | En mode local, les en-têtes d’identité restent usurpables ; en production, l’approbateur doit disposer de sa propre identité SPIRE/SSO forte |
| Utiliser le mode observation comme voie d’action | L’endpoint shadow ne consomme jamais la capacité et n’appelle jamais execute; son reçu affirme explicitement targetUnchanged: true |
Il faut encore prouver par revue réseau qu’un déploiement shadow ne possède aucun credential de mutation |
| Reuse a capability | Atomic status transition and one receipt per capability | Multi-region transaction semantics not tested |
| Execute as another agent | Subject binding | Header spoofing in local mode |
| Invent two evidence IDs | Immutable lookup + distinct authority-derived fault domains | Incorrect operator mapping can still hide a shared upstream compromise |
| Launder provenance | Collector-to-root and root-to-fault-domain authority mapping; caller fields are overwritten | Mapping administration is local configuration |
| Wait until evidence/mission expires | Commit-time policy re-evaluation | Clock authority is local |
| Change target after proposal | Prepared-state hash and fresh prepare | Real API consistency/watch semantics untested |
| Race two commits | PostgreSQL serializable atomic consume; live concurrency corpus | Multi-region transaction semantics not tested |
| Call hardened API without a client certificate | TLS handshake requires a lab-CA client certificate | Lab CA/key compromise permits impersonation |
| Present several SPIFFE identities in one certificate | Reject unless exactly one valid URI SAN exists | Manual lab issuance is not workload attestation |
| OPA/AuthZEN outage, malformed/unsigned response, policy drift or direct caller | Fail closed; dual evaluators agree; exact request-bound signed AuthZEN response; TLS 1.3/mTLS and exact SPIFFE ID | Mock PDP signing campaign and local CA are not vendor qualification or dual-control publishing |
| Call executor directly as the agent/operator | Executor requires the exact control-plane client SPIFFE ID and a valid signed grant; smoke proves certificate-only mutation is refused | Local CA compromise alone is insufficient, but combined CA + signer compromise can authorize |
| Capture or replay a valid execution grant | Exact audience/intent/state/policy binding, 15-second maximum TTL and atomic executor-side PostgreSQL claim | Multi-region anti-replay semantics are not qualified |
| Tamper with or advance a restoration job | Signed job/capability/intent/before-state/due binding; retries may move later only inside the signed window | A stolen valid restoration grant can still be replayed idempotently during its window |
| Send a generic patch or command to executor | No such route; schemas reject unknown data; live admission denies arbitrary labels/policies | Only one Kubernetes/Cilium combination is qualified |
| Compromise the legitimate executor workload | Namespace RBAC, fixed label/policy shape, mTLS grant verification, isolated DB role, API egress deny-by-default, fail-closed proof webhook and separately signed read-only observation | One cluster/CNI matrix; the base profile credential can still quarantine arbitrary pods in an allowed namespace |
| Abuse SIEM adapter to choose provenance or replay it | Connector derives collector/source/trust/hash; envelope is certificate-key signed, freshness-bound and nonce-claimed atomically | A compromised legitimate connector key can sign false vendor payloads |
| Commit during emergency stop | Stop checked before issue and before consumption | Same process/database is not an independent failure domain |
| Crash after mutation | Restore job persists and Temporal accepts workflow before mutation | Single-host Temporal dev server and disk can fail together |
| Temporary executor/signer outage during restoration | Retry at most five times with exponential backoff; then signed failure and explicit escalation | Local escalation has no pager or independent operator workflow |
| Roll back over a human change | Simulator requires exact version; real adapter removes only its UID-bound label with JSON Patch CAS | CNI/policy controller convergence and distributed stop race remain |
| Alter a receipt | Content hash + OpenBao Transit Ed25519 signature; legacy-key verification | Local OpenBao/DPAPI is not HSM-backed or independently administered |
| Delete or rewrite all local history | Hash chain, signed checkpoints/export, signed transparency receipts and S3 Object Lock COMPLIANCE versions | Local RustFS shares the host; production external account, independent custody and SCITT interoperability remain absent |
| Forge a successful postcondition | Observer replays proof/receipt, reads current Pod/NetworkPolicy with a no-write identity and signs exact facts | Lab observer shares host/cluster and has no HA or organizational independence |
| Replay a signed external PDP allow | Exact request hash, issuer, policy version, 1–60 s TTL, Ed25519 signature and active-key bundle | Clock/key-distribution compromise and vendor-specific semantics require pilot tests |
| Corrupt the PostgreSQL data directory | Custom-format backup, SHA-256 manifest and isolated restore-test | No PITR, encrypted/off-site copy, DR automation or production RPO/RTO |
| Abuse a legitimate narrow permission | Typed action, scope, risk, state, TTL, compensation | False containment remains possible |
Failure policy
- Missing/invalid identity: deny.
- Unknown or stale evidence: deny.
- Policy disagreement between proposal and commit: deny.
- Target-state disagreement: deny and require a new proposal.
- Capability storage ambiguity: deny; never “best effort.”
- Executor error after consumption: capability remains consumed; emit signed failure receipt and require a fresh authorization.
- Restoration conflict: preserve newer state, emit signed failure, escalate.
- Transient restoration outage: retry five times with bounded backoff, then emit a signed failure and manual-intervention event.
- Audit/signature verification failure: freeze write paths in production.
Before a real pilot
The local gates now include two-party SPIRE mTLS, signed executor grants and PDP decisions, a separately keyed/read-only observer, proof admission, one real Kubernetes/Cilium path, local WORM retention, outage chaos and key-removal campaigns. Before a real pilot: deploy every service under workload attestation; qualify a cluster/CNI matrix; dual-control policy publication; production PITR and external WORM anchoring; hardened Temporal; independent KMS/emergency-stop domains; external audit; then shadow mode before any write permission.