The contract

The threat model.

This is what adamance defends against, who it is written against, and what it deliberately does not cover. It is not a description of the code. It is the standard the code has to meet before any of it ships.

How this works. Every feature starts here. Before I decide how something works, I work out how it fails, who would abuse it, what they would get, and what stops them. That document is the brief the code is written against, and it is a requirement rather than a report: a row stays on it until the threat stops being technically possible, not until the code catches up. Eight of them cover adamance, and all eight ship with the source at v1 so you can hold the code to every line of this page.

How it binds the code

The threat model is the contract the code is held to. Tests and the proof harness are what hold it there.

Ask for a login system and you get whatever pattern is most common on the internet. Ask for this one, with these failure modes already closed, and you get something that can be checked. The threat model is what turns the second question into a brief.

It is also what a test is written against. A test that has only ever passed proves nothing, so the guard gets pulled out on purpose and the suite has to go red. If it stays green with the protection deleted, the test was decoration.

A promise you cannot check is marketing. This page exists so that when the source lands, you can check every one of them.

Publishing it before the code is deliberate. It means the standard was set first and cannot be quietly rewritten afterwards to match whatever got built.

Who this is written against

Four attackers, and two more the moment an AI agent holds an account.

The four

  • Someone outside with no account. Port scans, public exploits, credential stuffing, phishing. Assumed to have no zero-days in the upstream components and no way to break the cryptography.
  • A machine you manage that has been taken over. Root on one host, holding that host's real credentials. The question this answers is what it reaches next, and the answer is nothing it was not already scoped to.
  • A real user doing something they should not. A valid login and an interest in the gaps between rules. Every rule is deny by default, so a gap denies rather than allows.
  • An insider on the control plane. The hardest one, because they hold the keys the rest of the design rests on. Partly answered, and I am not going to round that up. Separation, second approval on the actions that matter and a record that leaves the building all hold against an insider working through the console. They do not hold against one who edits FreeIPA, Keycloak or OPA directly: the change never reaches the gateway, so the chain stays perfectly valid while being incomplete. That is TM-41, it is open, and it is the first thing being fixed. Behind it sits TM-42, because the actor on an event is a field the producer fills in, so a compromised gateway can prove the audit system said Alice did it and never that Alice did.

And two more, once you hand an agent an account

  • Whoever wrote the text your agent is about to read. They never log in. They put words in a ticket, a log line, a README, a web page, and wait for your agent's task to take it there. An agent that has been talked into something asks politely and means it.
  • A careless sponsor. Every agent account names a human who answers for it. Someone who grants too much is the shortest route to an agent with too much reach.

Nothing an agent reads can widen what it is allowed to do. The boundaries are not instructions in a prompt, they are the same checks that decide whether a person may run sudo.

What it has to hold

The properties the code is held to.

Each of these traces back to a threat above. If a control does not answer a documented threat it is ceremony, and if a threat has no control it is a gap and gets tracked until it is not.

Identity and access

  • Deny by default, everywhere. An access rule that nobody wrote denies, and a gap in the rules is a refusal rather than an opening.
  • Nothing standing. SSH is short-lived certificates issued per person, not keys sitting in a file on a box you forgot about.
  • Sensitive actions ask again. Signing in once is not a licence for the rest of the day, and the list of what counts is in the policy where you can read it.
  • Admin is borrowed, not held. Elevation is requested for a window and expires on its own.
  • Scope is real. An operator holds part of the estate, an auditor holds read-only with no SSH and no secrets, and neither can widen their own grant.

Enforcement

  • The decision is made on the machine, by the machine. Every host holds a signed copy of the policy and decides locally, so a server that is rebooting or wedged does not change what your hosts allow.
  • It breaks safe. A component that cannot reach what it needs refuses rather than waving things through.
  • Policy is signed, and a host will not apply a bundle it cannot verify.
  • An agent principal is fenced by type, in the policy and again in the code, so a mistake in one does not carry. Editing policy, touching accounts, enrolling hosts, opening the vault and approving anything stay closed to it whatever a rule says.

Transport and record

  • Mutual TLS between the agent and the server, both directions, with a post-quantum key exchange on it. Traffic captured today is not worth storing for later.
  • Every privileged action lands in an append-only, hash-chained record. Alter or reorder one entry in the middle and every hash after it stops matching. Cut the tail off cleanly and it does not: what survives is a shorter chain that still verifies, and only the off-box anchor holds a head number higher than the one left behind. Without that anchor a truncation is indistinguishable from a quiet week, which is why the anchor is the control and the chain on its own is not.
  • A signed copy leaves the box on a timer, to a destination you own, so the record of an attack is somewhere the attacker is not.
  • Privileged sessions are recorded, signed and replayable, and where you require it a session that cannot be captured does not start.
What is worth stealing

Assets, ranked by how much damage losing one does.

Ranking these first is what stops a threat model turning into a list of everything that could theoretically go wrong. The controls further down exist in this order.

1. Kerberos KDC master key
Forge tickets for anyone, anywhere in the fleet, silently. Total identity compromise.
2. The certificate authority key
Issue trusted certificates impersonating any host or service.
3. The SSH CA signing key
Issue SSH certificates impersonating any user to any host. Kept on a separate trust chain from the X.509 CA for exactly this reason.
4. Directory admin credentials
Create accounts, escalate them, change group membership, disable policy.
5. The policy bundle signing key
Push arbitrary policy to every managed host. Allow any SSH, anywhere.
6. The session signing key
Forge admin sessions without ever touching the directory.
7. The transactional database
Holds second-factor evidence, refresh tokens and enrollment tokens. Forge fresh-factor state and every step-up gate opens.
8. The SIEM enrollment key
Spoof logs, suppress alerts, blind the monitoring.
9. Operator recovery codes
Bypass the second factor on an operator account.
10. The audit log
Destroying or altering it hides any of the above. Which is why it is chained, and why a copy leaves the box.
Where trust changes hands

Every boundary, and what authenticates across it.

A boundary is any place where something on one side has to prove itself to something on the other. If a line here has no authentication, it is a design error rather than a detail.

Browser to the console
TLS 1.3 with a server certificate.
Console to the API
An OIDC bearer token plus a CSRF token, over TLS 1.3.
API to the directory
A service principal over Kerberos and LDAPS.
API to the policy engine
Mutual TLS with short-lived service identities.
API to the database
A credential issued by the vault, over TLS.
Managed host to the directory
The host's own keytab, over Kerberos and LDAPS. It proves that host and nothing else.
Managed host to the policy server
Mutual TLS with the host certificate, and the bundle itself is signed so the host verifies it before applying it.
Managed host to the SIEM
A pre-shared agent key with mutual authentication, over the SIEM's own encrypted protocol.
Operator to break-glass
A hardware token, through an audited bastion, over a private tunnel.
Vector, and the control that answers it

What has to stop each attack.

This is the part that makes it a threat model rather than a list of features. Every control here exists because of a threat above it. If a control answers no threat it is ceremony and it comes out.

Getting in at all

Console exposed to the internet
Version 1 binds to a private network or a VPN and is not published to the open internet. A public path with a web firewall and an address allowlist is later work, and it is not how version 1 runs.
Credential stuffing
A second factor on admin accounts, on by default. Rate limiting on the token endpoint. Lockout after repeated failures, with a backoff that grows.
Stealing a session cookie
Sessions live fifteen minutes. Refresh tokens are bound to the address and client they were issued to, and are single use. Sensitive operations demand a fresh second factor regardless of how you got here.
A vulnerable upstream image
Every image pinned by digest rather than tag. A scheduled vulnerability scan in CI that fails the build on anything high or critical. A documented patch cadence with an emergency channel for the worst ones.
Directory ports reachable from outside
Only the reverse proxy sits at the edge. Directory and Kerberos ports bind to the private network.

Joining a machine that should not join

An unauthorized host enrolls
Enrollment needs a one-time token issued by an authenticated admin, bound to the hostname it is for, expiring within a day. There is no anonymous enrollment path.
The install is intercepted
The artifact is served over TLS from a pinned hostname with a published checksum, and the agent binary is signature-verified before it is served at all. A failure there returns nothing rather than something unverified.
An enrollment token is replayed
Tokens are single use and consumed atomically. Each use is logged with the source address and the host principal it produced.
A host lies about its name
The joining host presents a signing request for the hostname in the token. The authority refuses to sign any other name.

Moving sideways once inside

A host reuses its keytab as another host
A host keytab proves that host and grants nothing transitively. User SSH runs on ephemeral certificates from a separate authority, and host credentials are never accepted for a user session.
The SSH signing key is stolen
It lives on its own trust chain, separate from the X.509 authority, so compromising one does not compromise the other. Certificates are short-lived, and revocation is a documented path rather than an afterthought.
A host edits its own logs
The record does not live on the host it describes. Privileged actions are written to the hash-chained store on the control plane, and a signed copy of the chain head leaves the box on a timer.
A host reads policy meant for others
Bundles are scoped to the host that asks for them. Authenticating the caller and scoping the response are two controls, not one, and this row needs both.
A host abuses its sudo rights
Sudo rules name specific commands rather than everything. Invocations are logged centrally and anomalies raise an alert.
A stolen ticket used elsewhere
Tickets are short-lived, eight hours by default and shorter for admin principals, and renewal past the maximum requires authenticating again.

Getting around the rules

A gap in a policy that was not deny-first
Every decision document and every host access rule is deny by default with explicit allows only, and policy unit tests in CI check that it stayed that way.
Racing a policy update
Decisions are made locally on the host. Security-critical policy carries a short freshness window, and the highest-impact rules are pushed on change rather than waited for.
A bad policy change ships
Policy goes through git, then CI running rule unit tests and evaluation against fixtures, then human review, then a signed bundle build. Anything touching admin groups or root access needs a second reviewer.

When the operator is the problem

An operator quietly escalates someone
Every write to the directory and to policy is logged against that operator's identity, into a record they cannot edit from where they are standing.
A compromised operator changes policy
Sensitive operations need a second factor proven in the last few minutes, not at login. Anything affecting admin groups or root access waits for an approver who is not the requester.
Backups are tampered with
Backups are signed and encrypted with a key no operator account holds. Restoring one means presenting the offline key.
Pivoting between control plane hosts
Components run separately, with no shared sockets, and the API service account holds no SSH access to the other control plane hosts.
How you check it holds

A proof is not a test.

A test asserts that a function returns an error. That is worth something, and it is not what the page above is claiming. Everything above claims that a running deployment refuses.

So adamance ships a proof harness alongside the code. A proof breaks a real dependency on a live system, watches what happens, and puts it back. Pull the certificate, revoke the credential, stop the thing the control depends on, then try the operation that should now be refused. Restore, and confirm the system came back.

It runs against your own deployment, and it writes down what it proved rather than printing a number of passing tests.

Four verdicts, and two of them exist so that "I could not tell" can never be dressed up as a pass.

Proven is the strict one. The same probe had to succeed before the fault was injected, the fault had to be independently confirmed as live, the probe had to be refused, and the service had to recover once everything was put back. Anything less is not a pass.

Failed means the fault was live and the operation went through anyway. Not proven means the check could not run. Inconclusive means it ran and the evidence does not support a verdict, which happens when a probe fails at the transport level and a refusal becomes indistinguishable from something simply being broken.

Neither of the last two is ever counted as a pass, because a harness that quietly rounds uncertainty up to green is worse than not having one.

The rule that shapes the whole thing. A check never reports its own verdict. It supplies mechanism only: how to probe, how to inject the fault, how to tell a refusal apart from an error. The runner does the comparison and writes the result. An earlier design let each check hand back its own outcome, which meant a check could report a confident pass having done nothing at all, and a tool that reports a forged pass is worse than no tool.

The cryptographic floor

Non-negotiable defaults.

Going below any of these takes a written reason. There is no configuration flag that quietly drops one.

TLS
1.3 only. No falling back to 1.2. Authenticated cipher suites only.
Agent key exchange
Hybrid post-quantum, X25519 with ML-KEM-768. If the post-quantum half turns out to be flawed you still have the exchange the internet has trusted for a decade, and if the classical half falls to a quantum computer the other one holds.
Signatures
Ed25519, which is classical. Moving identities to post-quantum signatures is a later transition and this page does not claim it. Traffic recorded today is still protected, because breaking a recording means breaking the exchange.
Kerberos
AES-256 only. The older weak ciphers are disabled rather than deprioritised.
Tokens
Edwards-curve signatures only. Shared-secret algorithms are refused at validation time, so the classic algorithm-confusion attack has nothing to land on.
Password hashing
Argon2id anywhere adamance stores one itself, with parameters reviewed yearly.
Long-lived signing keys
Hardware-backed storage where the deployment has it, and sealed offline files with documented rotation where it does not. The X.509 and SSH authorities stay on separate chains.
The limits

What adamance does not defend against.

A threat model that claims everything is worth nothing. These are stated so you can plan around them, and so I cannot quietly drift into pretending otherwise later.

Out of scope, on purpose

  • A nation-state actor with physical access to your control plane.
  • A compromised CPU or a firmware-level supply chain attack.
  • Two operator accounts compromised at once, where one of them holds the approver role.
  • Coercion. If somebody is standing over you, no policy engine helps.
  • A malicious change inside an upstream project that ships legitimately. Every image and dependency is pinned to an exact digest and scanned in CI, where a high or critical finding fails the build, so nothing gets swapped underneath you between one release and the next and a known hole does not go out quietly. What pinning cannot do is vouch for the release itself. adamance pins and tests what it is given, and it does not audit FreeIPA's source.
  • The security of wherever you send your off-box copy. That account is yours to run. adamance signs what it sends and tells you when the copy stops arriving. It does not defend your mailbox.
  • Making an AI agent competent. Give one a broad grant and it will do broad damage inside that grant, quickly, and the record will show you exactly how. Scope makes mistakes small, not impossible.
  • Anything on a host after root. Tampering with a local log is detected because the record does not live there, but somebody who owns the machine owns what is on the machine.
What ships with the source

What each one covers.

Eight threat models, and the harness that checks them

All eight are published in full at github.com/kevwillow/adamance-threat-model, and they are public before the source they describe is. Adamance is built threat-first, and the history says exactly how threat-first, which is not the same as perfectly. The control-plane model has been in revision since May 2026. The seven subsystem models were added on 1 September and several of them describe controls that were built before anyone wrote them down. The history is not squashed, so you can read every correction they have taken to the day, including the rows that said a control was confirmed when nothing implemented it. A model that only ever gained rows would tell you nothing about whether anybody was checking it.

  • The control plane. Identity, enrollment, policy distribution, lateral movement, and the operators themselves.
  • Agent accounts. Written on the assumption that the agent can be talked into anything, and bounding what happens when it is.
  • The installer. Including the part nobody can engineer away: a script piped into a shell can verify everything it fetches afterwards, and it cannot verify itself. So the install is pinned, and what it fetches is signed.
  • The audit trail and its off-box copy. What a hash chain proves on its own, which is less than people assume, and what an external witness adds.
  • Session recording. Where the guarantee holds, and the one deliberate exception, which is a root-owned break-glass file that pages when somebody uses it.
  • The Samba domain module. One root binary does the irreversible work, and it never trusts what it is handed. The Windows domain surface it adopts is thirty years old and well understood by the people who attack it.
  • VPN, RADIUS and DNS. The modules that let something adamance does not manage authenticate against it.
  • Active Directory integration. Your directory stays authoritative and read-only, and the credential that reaches it is bound to the one host it may be sent to.

Alongside those eight, the proof harness ships with its output, so the documents and the thing that checks them arrive together.

What I am not claiming. Nobody outside this project has audited it yet. No external reviewer has been through the code and come back with a verdict. That review happens before v1 ships, and when it does I will say what they found and what I changed. Until then you have this page, the documents behind it, and shortly the source they both describe.