runtime-trust — A runtime is safe to reach from another machine, and joining the mesh trusts nobody by accident
Discovery is not trust, trust is not authorization, and a caller is admitted to change anything only by a key the trust policy names. A host that can reach a runtime reads it and changes nothing it cannot authenticate; every event in the journal was checked against the trust policy that holds now, whether it arrived over a link or from disk; and the limits the documents state are the limits the code enforces.
ACTIVE 6 of 10 issues done
- ready
- 4
- blocked
- 0
- active
- 0
- verify
- 0
- done
- 6
- cancelled
- 0
Problem
The runtime pack's baseline audit (2026-10-09, M01) read the server and the mesh against their documents and found the security model held only on the network hop. A server bound beyond loopback accepted every state-changing request from any host that sent no Origin header: plan transitions, continuity writes, any capability through executions.start, and mesh sessions, claims, handovers and reviews that the node then signed with its own trusted key. Three servers on the owner's machine were bound that way at the time. Beside it: reloading the journal skips the trust check, so a revoked key's events return on restart; Lamport stamps are not bounded, so a trusted key can backdate a claim to win every conflict; an expired claim revives and wins after a partition; hello messages name no recipient; trust is read from the branch's working tree; discovery inputs have unbounded channels and reply reads.
Outcome
Discovery is not trust, trust is not authorization, and a caller is admitted to change anything only by a key the trust policy names. A host that can reach a runtime reads it and changes nothing it cannot authenticate; every event in the journal was checked against the trust policy that holds now, whether it arrived over a link or from disk; and the limits the documents state are the limits the code enforces.
Current state
Remote state-changing requests were admitted by address alone (fixed by I2123). The journal reload, Lamport bounds, claim revival, hello recipient, trust-root and unbounded-input findings stand as recorded in the audit.
Desired state
Every threat in docs/MESH.md's security model names the test that defends it, and every finding of the baseline audit's threat list is either defended with a test or recorded as a limitation the documents state.
Scope
- apps/majordomus-cli/src/http
- apps/majordomus-cli/src/mcp
- apps/majordomus-cli/src/mesh
- apps/majordomus-cli/src/capability
- docs/MESH.md
- docs/MCP.md
- test/cases
Out of scope
- Encrypting the transport. ADR 0032 decided no TLS dependency; confidentiality stays the network's.
- Accounts or a login. A remote writer, when one is needed, is admitted by a key the trust policy names.
Acceptance criteria
- A request from an address that is not loopback runs only a capability that reads or whose input is a signed message, on every transport that knows the caller's address
- Every event the journal holds passed the trust policy in force, whether it arrived over a link or was reloaded from disk
- A trusted key cannot order its claim before claims already admitted, and an expired claim does not return as live
- Every bound the security model names is enforced on the input it names, with a test that sends one more
Validation
- bin/majordomus plan validate
- cargo test --test remote_callers --test mesh_cooperation
Evidence required
- remote_write_refused
- reload_is_trust_checked
Risks
- A remote MCP client loses writes it used to have (announce, mesh sessions). The route that remains is a tunnel that ends on loopback.
Issues
Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.
| issue | status | wave | depends on | title |
|---|---|---|---|---|
| I2123 | DONE | 0 | — | A caller beyond loopback reads, and changes nothing it cannot authenticate |
| I2124 | DONE | 0 | — | An event reloaded from disk meets the trust policy in force, and no stamp outruns the clock |
| I2132 | DONE | 0 | — | A hello names the runtime it is meant for, and no other runtime accepts it |
| I2133 | DONE | 0 | — | Every mesh input and answer has a bound, and a refused registration learns nothing |
| I2134 | DONE | 0 | — | A key removed from the trust list loses its links without a restart, and a node key can be rotated |
| I2135 | DONE | 0 | — | The trust root is read from the default branch, not from whatever the checkout holds |
| I2703 | READY | 1 | I2132 | The mesh cooperation rule lists the hello checks in the order the code runs them, and a test names that order |
| I2704 | READY | 1 | I2133 | A dialer reads no reply beyond the link bound and no more candidates than the bound, and a refusing peer is retried at a measured, jittered rate |
| I2705 | READY | 1 | I2134 | A node key can be rotated, peers follow the new key, and the old key is refused |
| I3175 | READY | 0 | — | Two hosts presenting one node key are named as a duplicate, never folded into one flapping node |
The graph
flowchart LR
I2123["I2123<br/>A caller beyond loopback reads, and changes nothing it cannot authenticate"]:::done
I2124["I2124<br/>An event reloaded from disk meets the trust policy in force, and no stamp outruns the clock"]:::done
I2132["I2132<br/>A hello names the runtime it is meant for, and no other runtime accepts it"]:::done
I2133["I2133<br/>Every mesh input and answer has a bound, and a refused registration learns nothing"]:::done
I2134["I2134<br/>A key removed from the trust list loses its links without a restart, and a node key can be rotated"]:::done
I2135["I2135<br/>The trust root is read from the default branch, not from whatever the checkout holds"]:::done
I2703["I2703<br/>The mesh cooperation rule lists the hello checks in the order the code runs them, and a test names that order"]:::ready
I2704["I2704<br/>A dialer reads no reply beyond the link bound and no more candidates than the bound, and a refusing peer is retried at a measured, jittered rate"]:::ready
I2705["I2705<br/>A node key can be rotated, peers follow the new key, and the old key is refused"]:::ready
I3175["I3175<br/>Two hosts presenting one node key are named as a duplicate, never folded into one flapping node"]:::ready
I2132 --> I2703
I2133 --> I2704
I2134 --> I2705
classDef done stroke:#16a34a,fill:#dcfce7,color:#14532d,stroke-width:2px
classDef active stroke:#2563eb,fill:#eff6ff,color:#1e3a8a,stroke-width:2px
classDef verify stroke:#7c3aed,fill:#f5f3ff,color:#4c1d95,stroke-width:2px
classDef ready stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef planned stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef blocked stroke:#b45309,fill:#fffbeb,color:#78350f,stroke-width:2px
classDef cancelled stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2px
classDef superseded stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2pxLinked from this diagram (10)
- I2123 A caller beyond loopback reads, and changes nothing it cannot authenticate
- I2124 An event reloaded from disk meets the trust policy in force, and no stamp outruns the clock
- I2132 A hello names the runtime it is meant for, and no other runtime accepts it
- I2133 Every mesh input and answer has a bound, and a refused registration learns nothing
- I2134 A key removed from the trust list loses its links without a restart, and a node key can be rotated
- I2135 The trust root is read from the default branch, not from whatever the checkout holds
- I2703 The mesh cooperation rule lists the hello checks in the order the code runs them, and a test names that order
- I2704 A dialer reads no reply beyond the link bound and no more candidates than the bound, and a refusing peer is retried at a measured, jittered rate
- I2705 A node key can be rotated, peers follow the new key, and the old key is refused
- I3175 Two hosts presenting one node key are named as a duplicate, never folded into one flapping node
Canonical record: .ai/repo/project/milestones/runtime-trust.yaml. Read it back with majordomus plan show runtime-trust.