Bourdon federates the memory of every agent you run. That is its value and its blast radius: a single compromised member that can read everything exfiltrates everything. The model below replaces "everyone who reaches the server is equal" with explicit, server-side trust, and every control on this page is covered by negative tests in CI.
Threat model#
| Threat | Defence |
|---|---|
| Anyone who finds the endpoint reads your federation | No anonymous path on a network-reachable bind: loopback-only default, startup refusal without auth, bearer auth on every request. |
| A compromised member exfiltrates every agent's memory | Trust tiers. Quarantined members read only namespaces they were granted, deny by default. |
| A poisoned member injects content that propagates to every agent | Quarantined writes are staged and invisible to all reads until an operator promotes them. Members write only their own namespace. |
| A member is compromised mid-incident | bourdon revoke <member>: token dead on the next request, no restart. |
| "What did it touch?" | Append-only audit log of every operation, allow and deny, queryable per member, surviving revocation. |
| Token theft from disk or logs | Tokens are stored as SHA-256 hashes only, compared in constant time, shown once at creation, never logged. |
| A request arrives under a hostname you never served | 421 Misdirected Request before authentication unless the name was declared. |
Out of scope today, and said so: multi-tenant federation, encryption at rest, per-memory-item ACLs.
Identities and tokens#
Every federation member is registered in ~/.bourdon/federation.yaml:
bourdon agent add openclaw # quarantined by default
bourdon agent add local-assistant --tier trusted # an agent you control
bourdon agent list # never shows token material
bourdon agent rotate openclaw # new token, old one dead
bourdon revoke openclaw # effective now
agent add prints the bearer token once. Bourdon keeps only the hash. On the HTTP transport the member presents Authorization: Bearer <token> and the server resolves it to an identity: agent id, tier, grants.
Two transports, two identity rules:
- stdio: the caller is your own process, so it gets the implicit trusted
operatoridentity. - HTTP: a bearer token is required.
Tiers#
trusted | quarantined | |
|---|---|---|
| Read | everything, visibility-filtered | only granted namespaces; deny by default |
| Read surface | all tools and resources | query_agent_memory, find_entity, list_recent_work, list_agents, export_agents, plus version history for granted namespaces. Aggregate tools (prepare_recognition_context, get_deeper_context, get_cross_agent_summary, compile_codex_turn) are denied outright. |
Write (commit_to_federation) | direct to the live store | staged under staging/<member>/, own namespace only |
rollback_agent_memory | allowed, snapshotted first | never, by any route |
| Default for new members | yes |
A namespace is one agent's manifest. Grant and remove reads with:
bourdon grant openclaw claude-code # openclaw may now read claude-code's manifest
bourdon ungrant openclaw claude-code
Enforcement is server-side at the request layer. An adapter cannot bypass it, and trusted agents implement no security logic client-side.
Staging#
bourdon staging list
bourdon staging promote openclaw # merge into the live store, same validated path as a trusted write
bourdon staging reject openclaw # delete without promoting
Staged manifests live outside the agents/ directory, so they are invisible to every read tool, every peer, and the desktop tray until promoted. Quarantine follows the content: an operator-run export of a quarantined-class adapter also stages.
Audit#
Every federation operation, allowed or denied, on every transport, appends one line to ~/.bourdon/audit.jsonl:
bourdon audit # recent operations
bourdon audit --agent openclaw # one member's trail, survives revocation
bourdon audit --denials # what was blocked
bourdon audit --export # raw JSONL for your own tooling
No token material is ever written to the audit log or any other log.
Provenance and undo#
Every write archives the manifest it replaced. list_memory_versions shows the history for a namespace, explain_memory_change returns the write that produced a given state (caller identity is recorded and verified; the session and transcript references a caller attaches are not), and rollback_agent_memory restores an archived version. Rollback is the most destructive operation the server exposes: it discards every later contribution, including other agents', so it is trusted-only, it accepts an expected_digest to refuse if anything landed meanwhile, and the restore is itself snapshotted.
Bind and startup contract#
- Default HTTP bind is
127.0.0.1. - A non-loopback bind requires configured auth (a registered member or the legacy shared token). Without it the server exits non-zero at startup.
--allow-unauthenticatedis honoured on loopback only; combined with a non-loopback host the server refuses to start.- Remote peers must be HTTPS; plaintext HTTP is loopback-only.
- Every public name must be declared with
--allowed-host; undeclaredHostheaders get421.
What the manifests themselves leak#
Tokens are one risk; memory content is another. Every participant applies visibility before a row is emitted, private rows never leave the machine, and bourdon audit-leaks --strict --require-files scans the published manifests for credential shapes and escaped private rows. That scan runs against a real on-disk fixture in CI so it can never pass by scanning zero files.
Verification#
bourdon doctor reports federation hygiene: auth posture, members with missing or invalid tiers, revoked-but-present tokens, and staged writes older than seven days. The design rationale behind all of this is spec/SPEC_v0.9.0.md; vulnerability reporting is in SECURITY.md.