agent-embassy 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/CONTRIBUTING.md +92 -200
  3. package/README.md +159 -138
  4. package/SECURITY.md +191 -335
  5. package/dist/src/gateway/broker-check.d.ts +26 -0
  6. package/dist/src/gateway/broker-check.js +89 -0
  7. package/dist/src/gateway/broker-check.js.map +1 -0
  8. package/dist/src/gateway/broker-control.d.ts +72 -0
  9. package/dist/src/gateway/broker-control.js +164 -0
  10. package/dist/src/gateway/broker-control.js.map +1 -0
  11. package/dist/src/gateway/broker.d.ts +117 -0
  12. package/dist/src/gateway/broker.js +186 -0
  13. package/dist/src/gateway/broker.js.map +1 -0
  14. package/dist/src/gateway/claude-peer.d.ts +1 -124
  15. package/dist/src/gateway/claude-peer.js +62 -1053
  16. package/dist/src/gateway/claude-peer.js.map +1 -1
  17. package/dist/src/gateway/codex-stateless-transport.d.ts +0 -12
  18. package/dist/src/gateway/codex-stateless-transport.js +8 -142
  19. package/dist/src/gateway/codex-stateless-transport.js.map +1 -1
  20. package/dist/src/gateway/config.d.ts +2 -6
  21. package/dist/src/gateway/config.js +17 -27
  22. package/dist/src/gateway/config.js.map +1 -1
  23. package/dist/src/gateway/coordinator.d.ts +57 -0
  24. package/dist/src/gateway/coordinator.js +217 -0
  25. package/dist/src/gateway/coordinator.js.map +1 -0
  26. package/dist/src/gateway/core-cli.d.ts +15 -0
  27. package/dist/src/gateway/core-cli.js +282 -0
  28. package/dist/src/gateway/core-cli.js.map +1 -0
  29. package/dist/src/gateway/core-service-command.d.ts +16 -0
  30. package/dist/src/gateway/core-service-command.js +71 -0
  31. package/dist/src/gateway/core-service-command.js.map +1 -0
  32. package/dist/src/gateway/endpoint-directory.d.ts +33 -0
  33. package/dist/src/gateway/endpoint-directory.js +223 -0
  34. package/dist/src/gateway/endpoint-directory.js.map +1 -0
  35. package/dist/src/gateway/federation.d.ts +89 -0
  36. package/dist/src/gateway/federation.js +632 -0
  37. package/dist/src/gateway/federation.js.map +1 -0
  38. package/dist/src/gateway/ledger-codec.d.ts +3 -0
  39. package/dist/src/gateway/ledger-codec.js +159 -0
  40. package/dist/src/gateway/ledger-codec.js.map +1 -0
  41. package/dist/src/gateway/ledger.d.ts +138 -0
  42. package/dist/src/gateway/ledger.js +319 -0
  43. package/dist/src/gateway/ledger.js.map +1 -0
  44. package/dist/src/gateway/local-control.d.ts +23 -0
  45. package/dist/src/gateway/local-control.js +489 -0
  46. package/dist/src/gateway/local-control.js.map +1 -0
  47. package/dist/src/gateway/native-destinations.d.ts +34 -0
  48. package/dist/src/gateway/native-destinations.js +272 -0
  49. package/dist/src/gateway/native-destinations.js.map +1 -0
  50. package/dist/src/gateway/owned-state.d.ts +57 -0
  51. package/dist/src/gateway/owned-state.js +398 -0
  52. package/dist/src/gateway/owned-state.js.map +1 -0
  53. package/dist/src/gateway/provenance-envelope.d.ts +3 -3
  54. package/dist/src/gateway/provenance-envelope.js +3 -23
  55. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  56. package/dist/src/gateway/runtime.d.ts +41 -0
  57. package/dist/src/gateway/runtime.js +175 -0
  58. package/dist/src/gateway/runtime.js.map +1 -0
  59. package/docs/CONFIGURATION.md +178 -266
  60. package/docs/DELIVERY.md +133 -82
  61. package/docs/GATEWAY-ARCHITECTURE.md +276 -943
  62. package/package.json +5 -5
  63. package/skills/embassy-peer/SKILL.md +38 -194
  64. package/skills/embassy-peer/agents/openai.yaml +2 -2
  65. package/dist/src/gateway/claude-helper-client.d.ts +0 -2
  66. package/dist/src/gateway/claude-helper-client.js +0 -2
  67. package/dist/src/gateway/claude-helper-client.js.map +0 -1
  68. package/dist/src/gateway/claude-helper-protocol.d.ts +0 -117
  69. package/dist/src/gateway/claude-helper-protocol.js +0 -122
  70. package/dist/src/gateway/claude-helper-protocol.js.map +0 -1
  71. package/dist/src/gateway/claude-helper-supervisor.d.ts +0 -87
  72. package/dist/src/gateway/claude-helper-supervisor.js +0 -347
  73. package/dist/src/gateway/claude-helper-supervisor.js.map +0 -1
  74. package/dist/src/gateway/claude-helper.d.ts +0 -1
  75. package/dist/src/gateway/claude-helper.js +0 -252
  76. package/dist/src/gateway/claude-helper.js.map +0 -1
  77. package/dist/src/gateway/cli.d.ts +0 -57
  78. package/dist/src/gateway/cli.js +0 -1503
  79. package/dist/src/gateway/cli.js.map +0 -1
  80. package/dist/src/gateway/codex-socket-holder.d.ts +0 -26
  81. package/dist/src/gateway/codex-socket-holder.js +0 -76
  82. package/dist/src/gateway/codex-socket-holder.js.map +0 -1
  83. package/dist/src/gateway/control.d.ts +0 -261
  84. package/dist/src/gateway/control.js +0 -903
  85. package/dist/src/gateway/control.js.map +0 -1
  86. package/dist/src/gateway/peer-client.d.ts +0 -58
  87. package/dist/src/gateway/peer-client.js +0 -217
  88. package/dist/src/gateway/peer-client.js.map +0 -1
  89. package/dist/src/gateway/peer-mailbox.d.ts +0 -78
  90. package/dist/src/gateway/peer-mailbox.js +0 -168
  91. package/dist/src/gateway/peer-mailbox.js.map +0 -1
  92. package/dist/src/gateway/peer-protocol.d.ts +0 -105
  93. package/dist/src/gateway/peer-protocol.js +0 -70
  94. package/dist/src/gateway/peer-protocol.js.map +0 -1
  95. package/dist/src/gateway/peer-stdio.d.ts +0 -23
  96. package/dist/src/gateway/peer-stdio.js +0 -100
  97. package/dist/src/gateway/peer-stdio.js.map +0 -1
  98. package/dist/src/gateway/providers.d.ts +0 -163
  99. package/dist/src/gateway/providers.js +0 -972
  100. package/dist/src/gateway/providers.js.map +0 -1
  101. package/dist/src/gateway/server.d.ts +0 -47
  102. package/dist/src/gateway/server.js +0 -219
  103. package/dist/src/gateway/server.js.map +0 -1
  104. package/dist/src/gateway/service.d.ts +0 -373
  105. package/dist/src/gateway/service.js +0 -2274
  106. package/dist/src/gateway/service.js.map +0 -1
  107. package/dist/src/gateway/status-view.d.ts +0 -167
  108. package/dist/src/gateway/status-view.js +0 -488
  109. package/dist/src/gateway/status-view.js.map +0 -1
  110. package/dist/src/gateway/store.d.ts +0 -185
  111. package/dist/src/gateway/store.js +0 -2359
  112. package/dist/src/gateway/store.js.map +0 -1
  113. package/dist/src/gateway/types.d.ts +0 -528
  114. package/dist/src/gateway/types.js +0 -341
  115. package/dist/src/gateway/types.js.map +0 -1
package/SECURITY.md CHANGED
@@ -2,352 +2,208 @@
2
2
 
3
3
  ## Supported versions
4
4
 
5
- Security fixes are applied to the latest Embassy release and the `main`
6
- branch. Pre-release prototype versions are not supported.
5
+ Security fixes are provided for the current release line. Version 4 uses a
6
+ reset-only state boundary and does not run compatibility code for older private
7
+ state or control protocols.
7
8
 
8
9
  ## Reporting a vulnerability
9
10
 
10
- Please use this repository's private GitHub Security Advisory interface. Do
11
- not open a public issue for a suspected vulnerability.
12
-
13
- Reports should contain the smallest synthetic reproduction possible. Do not
14
- include credentials, OAuth material, Keychain data, message bodies, raw model
15
- output, provider histories, local Embassy state, socket addresses, native
16
- session or thread identifiers, or unredacted personal paths.
11
+ Open a private security report with the repository owner. Do not put message
12
+ bodies, credentials, native identifiers, socket paths, provider histories, or
13
+ raw diagnostic output in a public issue.
17
14
 
18
15
  ## Deployment boundary
19
16
 
20
- Embassy is personal software for one macOS user. Each broker remains local to
21
- one machine; an explicit private `nodes.json` may connect the user's own
22
- SSH-reachable machines through the user's existing OpenSSH configuration.
23
- Broker identity always comes from that owned mode-0600 `nodes.json`; when it
24
- is absent, the broker creates it once from this machine's hostname, and that
25
- recorded identity stays as written when the machine is later renamed. Run
26
- every node only under an OS account that is yours alone and where you trust
27
- every process already running as that user. Do not expose Embassy sockets or
28
- state on a network, host it as a service, or use it to share a provider
29
- subscription between users.
30
-
31
- The broker is local; the agents are not. Embassy does not call a provider API,
32
- but a delivered body becomes model input in the receiving product and may be
33
- sent to and retained by Anthropic or OpenAI under that product's normal terms
34
- and settings.
35
-
36
- ## Trust model
37
-
38
- Embassy provides same-UID containment and route attribution, not authentication
39
- against other processes running as the same OS user.
40
-
41
- - A Codex route is attributed to the exact inherited `CODEX_THREAD_ID` of the
42
- task that self-registers it. App Server attachment and endpoint generations
43
- are current transport facts, never durable route authority.
44
- - A Claude route is attributed to a validated live peer generation and native
45
- session UUID. An inherited `CLAUDE_CODE_MESSAGING_SOCKET` is a transient
46
- reply capability, not a credential.
47
- - Aliases are labels. They do not grant authority and are re-resolved against
48
- the exact private route binding before delivery.
49
- - Immediately before provider delivery, Embassy places the body inside one
50
- broker-owned `cross-session-message` textual frame. Its sender attribution
51
- and first-child reply hint come from validated broker metadata, not from the
52
- message body. This is a provenance boundary for the receiving model, not a
53
- cryptographic signature or authentication against same-UID code.
54
- - Any process already running as the same OS user may be able to present local
55
- environment or socket capabilities. Embassy is not a sandbox for untrusted
56
- same-user code.
17
+ Embassy is a personal, same-user gateway for user-owned Macs. The broker is a
18
+ foreground process or a per-user launchd agent. It has one private Unix-domain
19
+ control socket and no TCP or HTTP listener. Cross-machine delivery is an
20
+ outbound `/usr/bin/ssh` process to another explicitly configured Embassy
21
+ gateway.
57
22
 
58
- ## What Embassy defends, and what it deliberately does not
23
+ The design assumes the login account and machines are trusted. It does not turn
24
+ one Unix user into multiple security principals, sandbox an untrusted agent, or
25
+ provide hostile multi-user isolation.
59
26
 
60
- Embassy's security boundary is intentionally narrower than “protect this user
61
- from every process this user runs.” The boundary below governs implementation,
62
- review, and audit work.
27
+ ## What Embassy defends, and what it deliberately does not
63
28
 
64
29
  ### What Embassy defends
65
30
 
66
- - **The same-UID OS and artifact boundary for anything Embassy executes or
67
- treats as identity evidence.** Embassy validates canonical paths, ownership,
68
- symlink policy, modes, approved version-directory containment, its own state
69
- and sockets, and the generation of artifacts it owns. Before acting on an
70
- identity-bearing input—such as an inherited task identity, provider record,
71
- route, provider record, or reply request—it validates the input's bounded
72
- shape and its current ownership and correlation. Immediately before an
73
- effect, the owning transport re-attests every changing path, socket,
74
- process, interface, target, and generation fact it actually uses. Unsafe
75
- controller-wide evidence is fatal; this class comprises Embassy-owned or
76
- executed artifacts and Embassy callback, control, and state paths. The
77
- Claude-owned external sessions registry root is instead a read-side identity
78
- source: unsafe UID or mode evidence quarantines and write-fences only Claude,
79
- with a loud observation, while the broker and other provider stay available.
80
- A bad provider record, endpoint, or acted-on input is rejected or fenced at
81
- that artifact rather than accepted on a best-effort basis.
82
- - **Honest provenance at the message boundary.** The broker creates the outer
83
- `cross-session-message` frame and first-child reply hint from validated route
84
- metadata, and neutralizes body text shaped like its reserved framing tags
85
- before composition. Those marks tell the receiver which transport path and
86
- sender alias Embassy observed. They are not a signature, and every delivered
87
- body remains untrusted input whose claims and requested actions require the
88
- receiver's normal judgment, sandbox, and approval policy.
89
- - **Anti-runaway containment.** Queue counts and bytes, message and frame sizes,
90
- callbacks, conversations, retained bodies, deduplication records, rate
91
- windows, and deadlines are bounded. Exhaustion rejects, expires, or fences
92
- work with an explicit result; a bound never creates permission or justifies
93
- replaying an ambiguous write.
31
+ Embassy defends the boundary between model-authored content and authority to
32
+ address or write to a live session. A body, alias, provider response, catalog
33
+ row, or persisted field cannot grant that authority by itself. Authority comes
34
+ from the current same-user process boundary, an exact endpoint identity,
35
+ owned private state, and fresh per-operation transport attestation.
36
+
37
+ Specifically, Embassy defends:
38
+
39
+ - exact Claude session and Codex task identity across alias changes;
40
+ - refusal rather than guessed routing when a name is absent or ambiguous;
41
+ - one bounded, private state document with atomic replacement and strict schema
42
+ validation;
43
+ - per-operation ownership, path, protocol, and artifact checks before every
44
+ native write;
45
+ - bounded bodies, queues, batches, deadlines, rates, in-flight operations, and
46
+ retained evidence;
47
+ - a durable `queued` `reserved` `armed` `accepted` `terminal` phase law
48
+ that never replays an uncertain write;
49
+ - structural provenance framing and identity-bound replies;
50
+ - authenticated, direct SSH handoff with a destination-owned queue and no
51
+ post-commit retry;
52
+ - redaction of native IDs, addresses, credentials, histories, raw frames, and
53
+ bodies from public status and errors;
54
+ - retirement and replacement that settle incident work without moving it to a
55
+ different endpoint.
56
+
57
+ Every proposed audit check or hardening rule must enforce one of these stated
58
+ boundaries. A new boundary requires an explicit product decision; it must not
59
+ arrive disguised as a regression test.
94
60
 
95
61
  ### What Embassy deliberately does not defend
96
62
 
97
- - **Other local software already running as the same user.** Embassy provides
98
- no local-process authentication and no capability or local-user consent
99
- boundary against software already operating under that UID. Addressing is
100
- routing, not authorization; aliases, conversation tokens,
101
- inherited environment values, private sockets, and same-user file modes do
102
- not authenticate one same-user process from another.
103
- - **Predictions based on version strings.** A version string is diagnostic
104
- metadata, never routing authority, security evidence, or attack detection.
105
- Current path, ownership, protocol, interface, used-artifact generation, and
106
- correlated operation facts decide what Embassy can safely do. Codex
107
- registration performs no provider I/O; every delivery proves the current
108
- boundary independently. Boot refusal is reserved
109
- for an unsafe or lost singleton lease, corrupt controller state, or an unsafe
110
- OS boundary. Interface drift or one unavailable optional provider degrades
111
- that surface; it does not take down the broker or the other providers.
112
-
113
- ### Audit rule
114
-
115
- Every new audit check must cite the sentence in this doctrine that it enforces.
116
- If the proposed check has no supporting sentence here, raise it explicitly as
117
- a doctrine-change proposal, including the product and threat-model consequence,
118
- before adding the check. A test, review finding, or “hardening” patch must not
119
- silently expand Embassy's claimed boundary.
120
-
121
- ## Routing and the permission model
122
-
123
- - A universal shell peer explicitly registers one `peer-*` alias. Its principal
124
- is that alias plus a `peer_` token minted and printed exactly once. The token
125
- is supplied on stdin (or inherited only by a harness with a stable shell),
126
- compared in constant time, and rechecked immediately before effects. This is
127
- same-UID attribution, not authentication against other same-user software;
128
- there is deliberately no PID binding, token file, Keychain entry, or daemon.
129
- - A Codex task must explicitly self-register with a `codex-*` alias before it
130
- can participate.
131
- - The permission to message is the OS boundary Embassy already sits inside:
132
- the same UID, the same host or a host the operator configured in the
133
- private `nodes.json` and an exact alias. Embassy holds no separate,
134
- revocable grant between two endpoints, and never claimed one could defend
135
- against software already running as that UID.
136
- - A discovered Claude session's logical route is installed by the broker on
137
- the session's first use the first message sent to it, or its first native
138
- message out. Installation records identity; it grants nothing that
139
- addressing did not already imply.
140
- - A name is not an identity. A route's identity is its (host, native session
141
- UUID) pair: a session that re-anchors or renames itself keeps its
142
- registration and its in-flight conversations, while a name that currently
143
- belongs to more than one live session is refused with
144
- `PEER_ALIAS_COLLISION` at send time and never resolved by picking first.
145
- That fence is a fence on names: the session UUID stays addressable, and a
146
- sender is never fenced by its own display name, because its identity was
147
- attested rather than typed.
148
- - Every routed body carries the broker-composed provenance envelope naming its
149
- sender, and what "naming" is worth depends on the hop. On a local hop the
150
- sender is verified: the broker read it from the inherited socket capability,
151
- the inherited Codex thread id, or a shell peer's minted token, none of which
152
- the sender chose. Across a federated hop the sender is named by the sending
153
- node, and what the destination authorizes is that node's membership in its
154
- own `nodes.json` the destination trusts the peer broker to have verified
155
- its own local sender, exactly as much as it trusts that operator. In both
156
- cases the envelope is attribution, not authentication of the body's claims,
157
- and the boundary that stops anyone else is the operating system's.
158
- - Explicitly requested endpoint replacement
159
- (`register-codex --succeeds`) is one atomic logical-route transaction: it
160
- settles the outgoing route's work by recorded write phase, removes its
161
- capabilities, and publishes only the replacement. There is no prepared,
162
- activated, re-anchored, or recovery generation and no half-replaced state.
163
- - Embassy never mutates a Codex task's approval or sandbox policy and never
164
- answers an approval request. An inbound turn uses the task's existing native
165
- policy. With `approvalPolicy: never`, no human confirmation occurs on that
166
- path; with an approval-requiring policy, the turn may wait for the user.
167
- - Claude's native `crossSessionInbound` setting controls messages entering a
168
- Claude session. Embassy cannot override an accept, hold, or refuse decision.
169
- - Delivery scheduling is asymmetric. After routing and pre-write validation,
170
- Claude-bound bodies are written immediately to Claude's native mailbox
171
- regardless of an observed busy or idle state; `transport_written` proves the
172
- mailbox write and settles that direction as `delivered`, not read or
173
- consumed. Codex-bound ordinary bodies remain idle-gated, and only the exact
174
- `STEER:` path below can use the active turn's next tool-call boundary.
175
- - A CLI initiator receives the full conversation token in the accepted control
176
- result, and every routed recipient receives it in the broker-owned first
177
- `embassy-reply-hint`. The token is a transient participant-scoped locator,
178
- not sufficient authority: `send --conversation` rechecks inherited caller identity,
179
- conversation membership, and current routing policy.
180
-
181
- Every routed message is untrusted input capable of steering the receiving
182
- agent. The OS boundary and routing control reachability; the provenance marker
183
- does not make the message content or asserted intent trustworthy.
184
-
185
- The literal leading `STEER:` prefix is a protocol instruction, not proof of a
186
- trusted author or safe intent. Any exact same-UID Claude sender already allowed
187
- to reach the registered Codex task can use it. The receiver's existing Codex
188
- policy still governs tools and approvals, and an operator who does not want
189
- this timing behavior must set `EMBASSY_STEERING_ENABLED=0` before starting the
190
- broker.
191
-
192
- ## Process and protocol boundary
193
-
194
- - The launcher is foreground and macOS-only. Provider attestation and the
195
- control plane remain machine-local. Configured federation owns only a fixed
196
- outbound `ssh ... embassy peer-stdio` subprocess; SSH supplies transport
197
- authentication, encryption, and liveness, and Embassy opens no federation
198
- listener.
199
- - Before provider setup, the launcher acquires one host-wide macOS advisory
200
- lease. If its lease helper exits or the lease is otherwise lost
201
- unexpectedly, Embassy shuts down rather than continuing without singleton
202
- ownership.
203
- - The control plane is a private Unix-domain socket in a controller-owned
204
- mode-0700 state directory. `embassy serve` has no TCP or HTTP listener; its
205
- only listeners are private Unix-domain sockets.
206
- - Provider startup validates exact OS ownership, path, symlink, lease, state,
207
- and generation evidence. Unsafe evidence for Embassy-owned or executed
208
- artifacts and Embassy callback, control, or state paths refuses broker
209
- startup; unsafe UID or mode evidence for Claude's external sessions registry
210
- root quarantines only Claude. A provider version is best-effort diagnostic
211
- metadata and carries no routing authority. Runtime authority comes from the
212
- OS boundary, exact owned route and session identity, current
213
- per-operation transport facts, strict protocol handling, and correlated
214
- operation results.
215
- A Claude record whose peer protocol is not 1 is rejected in isolation and
216
- included in bounded rejection evidence. Every Codex endpoint used by a
217
- delivery must negotiate its current interface and resume the exact task
218
- before that operation receives final write authorization.
219
- - Embassy publishes at most one process-owned `codex-*` record in Claude's
220
- registry with the supported explicit versioned Embassy-advertisement marker.
221
- The prefix is a visible alias convention, not the discriminator: an unmarked
222
- genuine Claude session named `codex-*` remains discoverable. Embassy creates
223
- one callback socket and removes only exact-owned artifacts whose generation
224
- still matches during graceful shutdown.
225
- - App Server methods are allowlisted. Connectors expose no archive, deletion,
226
- shell, configuration, authentication, plugin, history, approval-response, or
227
- generic RPC method.
228
- - `turn/steer` is reachable only for an exact leading `STEER:` body in the
229
- Claude-to-Codex direction, with an exact observed active-turn ID. App Server
230
- admits it at the next tool-call boundary; Embassy never interrupts or injects
231
- mid-generation. Clean boundary refusal falls back to the normal queue, which
232
- retains at most three steers per route. The environment kill switch defaults
233
- on and can disable this classification globally. Embassy never issues
234
- `turn/interrupt`.
235
- - The App Server 0.147.0 initialization this adapter targets enables `experimentalApi: true`
236
- solely for `thread/resume.excludeTurns: true`. It adds no general
237
- experimental method or authority. Missing, malformed, or nonempty returned
238
- turns fail closed and are never retained.
239
- - Queues, frames, bodies, callbacks, deadlines, deduplication,
240
- rate limits, and transient conversations are bounded. Ambiguous writes are
241
- never retried automatically.
242
- - A peer catalog contains only bounded, body-free local metadata. It never
243
- exports imported rows, message or conversation tokens, native identifiers,
244
- provider frames, sockets, paths, credentials, or raw diagnostics. Each
245
- destination broker owns its durable queue; loss after a federated write is
246
- UNKNOWN and is never replayed.
247
- - Raw-body classification and accounting happen before framing. In the
248
- untrusted body only, Embassy case-insensitively neutralizes boundary-shaped
249
- opening or closing copies of its reserved framing tags before composing the
250
- real outer frame. Framing or size failure occurs before provider write and is
251
- never an ambiguous write.
252
- - Embassy creates no network listener at all. Everything enumerated above
253
- concerns `embassy serve`.
254
-
255
- ## Filesystem boundary
256
-
257
- Controller-owned state is a dedicated mode-0700 directory. Its files and
258
- control socket are mode 0600 and validated against replacement, symlinks, and
259
- unexpected ownership or permissions. Those files include message content:
260
- the durable queue and the bounded recent-delivery ledger both retain message
261
- bodies, so the state file holds mail at rest and not metadata alone. Anything
262
- already running as the same OS user can read it.
263
-
264
- The host-wide singleton has one fixed surface under the verified login home:
265
- the private mode-0700 `~/.local/state/agent-embassy` directory and its mode-0600
266
- `.gateway-host.lock`. Neither `EMBASSY_STATE_DIR` nor `XDG_STATE_HOME` relocates
267
- that lease. Embassy executes the exact `/usr/bin/lockf` and `/bin/cat` helpers,
268
- without a shell, to hold the kernel lease for the foreground process lifetime.
269
- The lock file is retained and reused across restarts; process exit releases the
270
- kernel lock.
271
-
272
- Embassy's provider-facing access is intentionally enumerable:
273
-
274
- - derive the fixed Claude registry and callback roots from the verified current
275
- OS user, without reading a Claude launcher or configuration file;
276
- - read the live Claude session registry and validate only the registry record,
277
- peer socket, PID, workspace, state-root, and generation evidence used by the
278
- current operation;
279
- - create and later remove its one callback socket and one registry record;
280
- - resolve the managed Codex installation and open one attested local App Server
281
- connection per operation; and
282
- - inspect canonical filesystem metadata needed to validate provider-advertised
283
- endpoints and generations.
284
-
285
- The Claude-owned external sessions registry root must be owned by the current
286
- UID with exact mode 0700 before Embassy enumerates it; failure quarantines and
287
- write-fences only Claude, including when that registry root is absent. Within
288
- an admitted root, individual registry records
289
- and peer sockets retain their bounded schema, file/socket type, PID/path and
290
- allowed-root correlation, accessibility, liveness, and generation checks.
291
- Embassy invents no additional owner or mode rule for those individual
292
- provider-owned artifacts.
293
-
294
- Embassy does not need or intentionally read credentials, Keychain items,
295
- Claude project history, Codex or Claude transcripts, shell history, or provider
296
- configuration contents. Report a bug if any normal code path attempts to do so.
297
-
298
- ## Persistence and disclosure
299
-
300
- Raw provider frames, tool data, stderr, callback addresses, and socket paths
301
- remain memory-only and are discarded on restart. Message bodies are the
302
- exception: queued and recently delivered bodies are retained under bounded caps
303
- in the mode-0600 state file. A queued or reserved message may resume once after
304
- a broker restart against its still-exact logical route. An
305
- armed or accepted message at crash settles ambiguous or unconfirmed and is
306
- never replayed.
307
-
308
- For a shell peer, durable route ownership stores only
309
- `peer:<sha256(uid NUL alias NUL token)>`; the raw peer token and private mailbox
310
- receipts never enter state, logs, snapshots, or routed frames.
311
- Pending waiters, acknowledgements, and the bounded exact-duplicate receipt
312
- tombstone are memory-only. A restart therefore cannot falsely confirm a
313
- stdout write whose acknowledgement was not observed.
314
-
315
- The full `conv_` token exposed to a CLI initiator or routed recipient travels
316
- only inside the accepted CLI result or transient provider payload. It is never
317
- persisted, journaled, logged, placed in a receipt, or projected through public
318
- events or snapshots; public metadata may retain only an existing
319
- non-reconstructable suffix. Broker-owned marker fields
320
- introduce no socket paths, Codex thread IDs, Claude session UUIDs, endpoint
321
- generations, or private route handles. The untrusted body remains opaque text
322
- and may itself contain sender-provided strings.
323
-
324
- The closed private binding store may retain the exact Codex thread ID and Claude
325
- session UUID required for logical ownership. Native IDs are
326
- forbidden from public snapshots, normalized events, aliases,
327
- logs, errors, and CLI output. A Claude UUID may enter only as a user-supplied
328
- explicit CLI selector (`embassy send --to <uuid>`); Embassy never discovers or
329
- prints it publicly.
330
-
331
- ## Validation boundary
332
-
333
- Routine tests use temporary directories, fake peers, and fake App Server
334
- transports. They do not inspect live provider state or contact a model.
335
-
336
- Broker/provider startup owns bounded validation of configured installations and
337
- exact OS boundaries. Unsafe Embassy-owned or executed artifacts, callback,
338
- control, or state paths remain startup-fatal; unsafe UID or mode evidence on
339
- Claude's external sessions registry root quarantines only that provider.
340
- Runtime derives no authority from version metadata. It reports best-effort connector health, observation
341
- freshness, and last safe codes while strict record, frame, response, identity,
342
- current used-artifact generation, correlation, and deadline checks decide each
343
- operation. Claude registry parsing remains strict for every required and
344
- consumed field while ignoring unknown top-level fields; bounded rejected-record
345
- counts and an observed-empty registry are surfaced instead of hidden. Each
346
- Codex delivery independently attests, connects, initializes, and resumes the
347
- exact registered task before its final write authorization. No observation
348
- traffic routes a user message or starts a model turn.
349
-
350
- Passive live discovery, a live provider connection, a native message, and an
351
- App Server turn are distinct authorization gates. Each requires an explicit
352
- user request for that operation. Never infer permission for a live send from a
353
- previous smoke test, and never enable live provider traffic in CI.
63
+ Embassy does not defend against the login user, root, a compromised user-owned
64
+ machine, the user's SSH configuration, or the provider applications
65
+ themselves. It does not cryptographically authenticate text inside a provider
66
+ conversation, prove a model read or understood a message, or make aliases
67
+ permanent identifiers.
68
+
69
+ Configured gateways belong to one same-user trust domain. A plain SSH login
70
+ is sufficient for federation; Embassy does not require a forced command or
71
+ dedicated per-node key and does not independently authenticate a logical host
72
+ label. A copied `nodes.json` with the wrong allowed `host` can therefore
73
+ misattribute a message's machine of origin. Correct host labels are trusted
74
+ operator configuration, not a separately attested property.
75
+
76
+ Provider availability and version metadata are observations, not authority.
77
+ `embassy health` proves the local broker control path. `embassy check` proves
78
+ the broker's ledger/coordinator/receipt loop without a live agent. Neither is a
79
+ provider readiness, model comprehension, or cross-machine proof.
80
+
81
+ The textual provenance envelope is a structural marker, not a signature. A
82
+ recipient must treat user-supplied text inside it as untrusted content. Embassy
83
+ neutralizes reserved envelope tags but does not sanitize general prompts.
84
+
85
+ ## Identity and routing
86
+
87
+ An endpoint's opaque `(ID, host, provider)` tuple is routing authority. Its
88
+ `name@host` alias is a current lookup index. A name is resolved once before
89
+ admission; writes, replies, restart recovery, and settlement use the tuple.
90
+ Historical names never resolve, and queued work is never silently rebound.
91
+
92
+ Codex tasks self-register from inherited `CODEX_THREAD_ID`. Embassy never
93
+ accepts, prints, or guesses the value. Registration performs no provider I/O.
94
+ Each operation resumes and attests that exact task immediately before write.
95
+
96
+ Claude callers are resolved from inherited `CLAUDE_CODE_MESSAGING_SOCKET`,
97
+ which must be an absolute path. The path may become an in-memory `uds:`
98
+ capability only; it is never a CLI argument, public output, or persisted field.
99
+ Claude native session UUIDs are stored only in closed private route state.
100
+ Discovery accepts only compatible interactive/background same-user records and
101
+ checks the exact record and socket again before use.
102
+
103
+ Aliases may collide in discovered Claude state. In that case name lookup
104
+ refuses; user-supplied exact UUID selection can identify a Claude target, but
105
+ Embassy never publishes a UUID. A retired or replaced endpoint remains fenced
106
+ while its bounded retirement evidence is retained. Re-enrollment after that
107
+ evidence expires receives a fresh opaque ID, never the retired ID. Public
108
+ endpoint IDs can select exact local operator retirement without exposing a
109
+ native ID or authorizing remote mutation.
110
+
111
+ A remote peer's claimed host must be named in `nodes.json`; the claim and its
112
+ source tuple and alias are trusted within the SSH login boundary. The message's
113
+ source host must match the claim, so first contact does not rely on a
114
+ previously polled catalog. Catalogs are bounded memory-only
115
+ caches and are never write authority. `refresh` may replace a successful
116
+ node's rows or retain its last timestamped rows with
117
+ `PEER_TUNNEL_UNAVAILABLE`; `status` reads that observation without network I/O.
118
+ Named and exact routing still queries the owner.
119
+
120
+ ## Delivery and uncertainty
121
+
122
+ One transaction admits a message. One coordinator reserves a bounded FIFO
123
+ batch for one exact destination and delivery class. Provider I/O occurs outside
124
+ the state transaction; authorization then revalidates the exact prepared bytes
125
+ and every locally owned endpoint ID, alias, and native handle under the
126
+ transaction immediately before the write.
127
+
128
+ Only a positive no-write result may return work to the queue. Reserved work may
129
+ recover after a process restart. Armed work becomes `ambiguous`; accepted work
130
+ becomes its recorded `ambiguous` or `unconfirmed` loss result. Neither is
131
+ replayed. A late callback cannot overwrite a terminal result.
132
+
133
+ The broker persists message bodies and opaque delivery/conversation values only
134
+ inside the bounded private ledger. It does not persist provider histories,
135
+ provider output, raw frames, tool data, callback sockets, or credentials.
136
+ Recent terminal delivery and retirement evidence is bounded by count, bytes,
137
+ and time; it is not a general analytics journal.
138
+
139
+ `STEER:` is recognized only as an exact leading prefix from Claude to Codex.
140
+ It targets the exact already-accepted operation at a safe tool-call boundary,
141
+ never interrupts a generation, and falls back to the ordinary bounded queue
142
+ when cleanly unavailable. Embassy never calls `turn/interrupt`, answers an
143
+ approval, or changes a task's approval or sandbox policy.
144
+
145
+ ## Filesystem and process boundary
146
+
147
+ The state directory is a current-user-owned mode-0700 real directory. State and
148
+ configuration files are mode-0600 regular files. Reads and writes reject
149
+ symbolic links, ownership changes, mode changes, inode swaps, oversized data,
150
+ and unsupported schema. Atomic persistence uses an exclusive private temporary
151
+ file, file sync, rename, and directory sync. An unknown commit outcome poisons
152
+ the running store rather than guessing.
153
+
154
+ One fixed host-wide kernel lease is acquired before provider setup. Changing
155
+ `EMBASSY_STATE_DIR` does not create permission to run a second broker. The
156
+ control socket is accepted only at the expected private path with current-user
157
+ ownership and exact socket type. Mutating control requests that lose a reply
158
+ after write report an ambiguous outcome and are not retried.
159
+
160
+ The launchd agent records absolute executable paths, nonempty `EMBASSY_*`
161
+ values, and `XDG_STATE_HOME`. It copies no other shell state or arbitrary
162
+ `PATH`; operators must not place secrets in an `EMBASSY_*` variable.
163
+ `embassy serve` stays foreground and does not daemonize.
164
+
165
+ Claude registry failures quarantine Claude operations rather than authorizing a
166
+ guess. Embassy validates each consumed peer-protocol-1 field while tolerating
167
+ unknown top-level registry fields. Unsafe controller-owned state may refuse the
168
+ whole broker because its ownership is the broker's authority boundary.
169
+
170
+ ## SSH boundary
171
+
172
+ Federation is direct and configured statically. Embassy runs the exact system
173
+ SSH client with batch mode, no TTY, no forwarding, no agent forwarding, no
174
+ local command, no tunnel, and no shell interpolation. Only the current user's
175
+ `HOME`, `USER`, `LOGNAME`, and `SSH_AUTH_SOCK` are forwarded to the process.
176
+ SSH establishes the trusted login using the user's configuration. Embassy's
177
+ correlated protocol checks protocol version, configured membership of the
178
+ peer's claimed host, and matching source hosts in handoffs. It does not inspect
179
+ how SSH authentication was performed or prove that the host label identifies
180
+ the physical machine that opened the connection.
181
+
182
+ The destination validates and durably enqueues a handoff before returning
183
+ acceptance. Only a protocol-proven pre-enqueue refusal is definite. Process
184
+ death, malformed data, wrong correlation, transport loss, and failure after the
185
+ commit boundary remain uncertain and are never replayed automatically.
186
+
187
+ ## Public disclosure boundary
188
+
189
+ Public JSON is a closed projection. It may contain opaque Embassy endpoint IDs,
190
+ aliases, providers, hosts, queue depths, safe codes, phases/outcomes, ages,
191
+ recent retirement times, and bounded remote catalog rows and observation times.
192
+ It must never contain native IDs or handles, socket paths, message bodies,
193
+ delivery/conversation secrets, credentials, exceptions, raw diagnostics, or
194
+ provider histories. Human output is derived from the same validated shape.
195
+
196
+ Never write protocol diagnostics to stdout: stdout may itself be a framed
197
+ protocol channel. Operational hints use bounded safe codes and stderr.
198
+
199
+ ## State reset and rollback
200
+
201
+ Private state schema 6 and control protocol 5 are the only v4 formats. Older or
202
+ unknown state refuses before mutation. There is no converter, compatibility
203
+ reader, or alias for removed commands. The operator must inspect and settle old
204
+ work with the old binary, stop the broker, preserve the old state, and start v4
205
+ with a fresh `gateway-state.json` while keeping `nodes.json`.
206
+
207
+ Reset invalidates all old routes, receipts, and conversation references. The
208
+ only rollback is the preserved old binary with its untouched old state. Embassy
209
+ does not merge schemas or promise conversation continuity across reset.
@@ -0,0 +1,26 @@
1
+ import { type Coordinator, type Destination, type WakeInput, type WakeResult } from "./coordinator.js";
2
+ import { type LedgerLimits, type LedgerState } from "./ledger.js";
3
+ import type { OwnedStateFile } from "./owned-state.js";
4
+ export type BrokerCheckOptions = Readonly<{
5
+ host: string;
6
+ limits: LedgerLimits;
7
+ store: OwnedStateFile<LedgerState>;
8
+ coordinator: Coordinator;
9
+ }>;
10
+ export type BrokerCheckResult = Readonly<{
11
+ status: "ok";
12
+ scope: "broker-loopback";
13
+ }>;
14
+ /** Adds a broker-only destination around the real Codex adapter. It intercepts only
15
+ * identities minted for the duration of check(); every production write delegates. */
16
+ export declare class LoopbackDestination implements Destination {
17
+ private readonly innerCodex;
18
+ private readonly expected;
19
+ private checking;
20
+ constructor(innerCodex: Destination);
21
+ deliver(input: WakeInput): Promise<WakeResult>;
22
+ cleanup({ host, limits, store }: Omit<BrokerCheckOptions, "coordinator">): Promise<void>;
23
+ check(options: BrokerCheckOptions): Promise<BrokerCheckResult>;
24
+ private runCheck;
25
+ close(): Promise<void>;
26
+ }