agent-embassy 1.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.
- package/CHANGELOG.md +61 -0
- package/CONTRIBUTING.md +122 -0
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/README.zh-CN.md +185 -0
- package/SECURITY.md +224 -0
- package/assets/live-dashboard/app.css +1610 -0
- package/assets/mark-seal.svg +7 -0
- package/assets/mark.svg +7 -0
- package/assets/social-preview.png +0 -0
- package/assets/vendor/react/LICENSE +21 -0
- package/assets/vendor/react/react-dom.production.min.js +267 -0
- package/assets/vendor/react/react.production.min.js +31 -0
- package/dist/src/errors.d.ts +5 -0
- package/dist/src/errors.js +11 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/gateway/claude-compatibility-scratch.d.ts +18 -0
- package/dist/src/gateway/claude-compatibility-scratch.js +158 -0
- package/dist/src/gateway/claude-compatibility-scratch.js.map +1 -0
- package/dist/src/gateway/claude-helper-client.d.ts +43 -0
- package/dist/src/gateway/claude-helper-client.js +212 -0
- package/dist/src/gateway/claude-helper-client.js.map +1 -0
- package/dist/src/gateway/claude-helper-protocol.d.ts +172 -0
- package/dist/src/gateway/claude-helper-protocol.js +373 -0
- package/dist/src/gateway/claude-helper-protocol.js.map +1 -0
- package/dist/src/gateway/claude-helper-supervisor.d.ts +74 -0
- package/dist/src/gateway/claude-helper-supervisor.js +497 -0
- package/dist/src/gateway/claude-helper-supervisor.js.map +1 -0
- package/dist/src/gateway/claude-helper.d.ts +1 -0
- package/dist/src/gateway/claude-helper.js +269 -0
- package/dist/src/gateway/claude-helper.js.map +1 -0
- package/dist/src/gateway/claude-peer.d.ts +273 -0
- package/dist/src/gateway/claude-peer.js +2359 -0
- package/dist/src/gateway/claude-peer.js.map +1 -0
- package/dist/src/gateway/claude-runtime.d.ts +40 -0
- package/dist/src/gateway/claude-runtime.js +302 -0
- package/dist/src/gateway/claude-runtime.js.map +1 -0
- package/dist/src/gateway/cli-copy.d.ts +8 -0
- package/dist/src/gateway/cli-copy.en.d.ts +13 -0
- package/dist/src/gateway/cli-copy.en.js +43 -0
- package/dist/src/gateway/cli-copy.en.js.map +1 -0
- package/dist/src/gateway/cli-copy.js +19 -0
- package/dist/src/gateway/cli-copy.js.map +1 -0
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +13 -0
- package/dist/src/gateway/cli-copy.zh-CN.js +43 -0
- package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -0
- package/dist/src/gateway/cli.d.ts +44 -0
- package/dist/src/gateway/cli.js +969 -0
- package/dist/src/gateway/cli.js.map +1 -0
- package/dist/src/gateway/codex-app-server.d.ts +310 -0
- package/dist/src/gateway/codex-app-server.js +1746 -0
- package/dist/src/gateway/codex-app-server.js.map +1 -0
- package/dist/src/gateway/codex-local-transport.d.ts +81 -0
- package/dist/src/gateway/codex-local-transport.js +617 -0
- package/dist/src/gateway/codex-local-transport.js.map +1 -0
- package/dist/src/gateway/codex-registration-generation.d.ts +5 -0
- package/dist/src/gateway/codex-registration-generation.js +19 -0
- package/dist/src/gateway/codex-registration-generation.js.map +1 -0
- package/dist/src/gateway/codex-registration-succession.d.ts +209 -0
- package/dist/src/gateway/codex-registration-succession.js +588 -0
- package/dist/src/gateway/codex-registration-succession.js.map +1 -0
- package/dist/src/gateway/compatibility.d.ts +70 -0
- package/dist/src/gateway/compatibility.js +252 -0
- package/dist/src/gateway/compatibility.js.map +1 -0
- package/dist/src/gateway/config.d.ts +24 -0
- package/dist/src/gateway/config.js +130 -0
- package/dist/src/gateway/config.js.map +1 -0
- package/dist/src/gateway/control.d.ts +308 -0
- package/dist/src/gateway/control.js +1573 -0
- package/dist/src/gateway/control.js.map +1 -0
- package/dist/src/gateway/dashboard-copy.d.ts +7 -0
- package/dist/src/gateway/dashboard-copy.en.d.ts +506 -0
- package/dist/src/gateway/dashboard-copy.en.js +507 -0
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -0
- package/dist/src/gateway/dashboard-copy.js +516 -0
- package/dist/src/gateway/dashboard-copy.js.map +1 -0
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +506 -0
- package/dist/src/gateway/dashboard-copy.zh-CN.js +507 -0
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -0
- package/dist/src/gateway/dashboard-model.d.ts +207 -0
- package/dist/src/gateway/dashboard-model.js +720 -0
- package/dist/src/gateway/dashboard-model.js.map +1 -0
- package/dist/src/gateway/dashboard.d.ts +26 -0
- package/dist/src/gateway/dashboard.js +941 -0
- package/dist/src/gateway/dashboard.js.map +1 -0
- package/dist/src/gateway/delivery-machine.d.ts +235 -0
- package/dist/src/gateway/delivery-machine.js +540 -0
- package/dist/src/gateway/delivery-machine.js.map +1 -0
- package/dist/src/gateway/instance-lease.d.ts +21 -0
- package/dist/src/gateway/instance-lease.js +549 -0
- package/dist/src/gateway/instance-lease.js.map +1 -0
- package/dist/src/gateway/live-dashboard-app/app.js +2914 -0
- package/dist/src/gateway/live-dashboard-assets.d.ts +10 -0
- package/dist/src/gateway/live-dashboard-assets.js +73 -0
- package/dist/src/gateway/live-dashboard-assets.js.map +1 -0
- package/dist/src/gateway/live-dashboard-bootstrap.d.ts +36 -0
- package/dist/src/gateway/live-dashboard-bootstrap.js +176 -0
- package/dist/src/gateway/live-dashboard-bootstrap.js.map +1 -0
- package/dist/src/gateway/live-dashboard-command.d.ts +54 -0
- package/dist/src/gateway/live-dashboard-command.js +324 -0
- package/dist/src/gateway/live-dashboard-command.js.map +1 -0
- package/dist/src/gateway/live-dashboard-http.d.ts +41 -0
- package/dist/src/gateway/live-dashboard-http.js +445 -0
- package/dist/src/gateway/live-dashboard-http.js.map +1 -0
- package/dist/src/gateway/live-dashboard-protocol.d.ts +38 -0
- package/dist/src/gateway/live-dashboard-protocol.js +156 -0
- package/dist/src/gateway/live-dashboard-protocol.js.map +1 -0
- package/dist/src/gateway/live-dashboard-server.d.ts +33 -0
- package/dist/src/gateway/live-dashboard-server.js +130 -0
- package/dist/src/gateway/live-dashboard-server.js.map +1 -0
- package/dist/src/gateway/live-dashboard-stream.d.ts +46 -0
- package/dist/src/gateway/live-dashboard-stream.js +233 -0
- package/dist/src/gateway/live-dashboard-stream.js.map +1 -0
- package/dist/src/gateway/live-dashboard.d.ts +31 -0
- package/dist/src/gateway/live-dashboard.js +190 -0
- package/dist/src/gateway/live-dashboard.js.map +1 -0
- package/dist/src/gateway/locale.d.ts +4 -0
- package/dist/src/gateway/locale.js +10 -0
- package/dist/src/gateway/locale.js.map +1 -0
- package/dist/src/gateway/progress-watch-machine.d.ts +97 -0
- package/dist/src/gateway/progress-watch-machine.js +172 -0
- package/dist/src/gateway/progress-watch-machine.js.map +1 -0
- package/dist/src/gateway/providers.d.ts +287 -0
- package/dist/src/gateway/providers.js +2451 -0
- package/dist/src/gateway/providers.js.map +1 -0
- package/dist/src/gateway/server.d.ts +57 -0
- package/dist/src/gateway/server.js +264 -0
- package/dist/src/gateway/server.js.map +1 -0
- package/dist/src/gateway/service.d.ts +449 -0
- package/dist/src/gateway/service.js +4831 -0
- package/dist/src/gateway/service.js.map +1 -0
- package/dist/src/gateway/store.d.ts +340 -0
- package/dist/src/gateway/store.js +4047 -0
- package/dist/src/gateway/store.js.map +1 -0
- package/dist/src/gateway/types.d.ts +514 -0
- package/dist/src/gateway/types.js +414 -0
- package/dist/src/gateway/types.js.map +1 -0
- package/dist/src/mutex.d.ts +4 -0
- package/dist/src/mutex.js +23 -0
- package/dist/src/mutex.js.map +1 -0
- package/docs/DESIGN.md +134 -0
- package/docs/GATEWAY-ARCHITECTURE.md +858 -0
- package/package.json +80 -0
- package/skills/embassy-peer/SKILL.md +198 -0
- package/skills/embassy-peer/agents/openai.yaml +4 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Security fixes are applied to the latest Embassy release and the `main`
|
|
6
|
+
branch. Pre-release prototype versions are not supported.
|
|
7
|
+
|
|
8
|
+
## Reporting a vulnerability
|
|
9
|
+
|
|
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.
|
|
17
|
+
|
|
18
|
+
## Deployment boundary
|
|
19
|
+
|
|
20
|
+
Embassy is personal, same-machine software for one macOS user. Run it only
|
|
21
|
+
under an OS account that is yours alone and where you trust every process
|
|
22
|
+
already running as that user. Do not expose its sockets or state directory on a
|
|
23
|
+
network, host it as a service, or use it to share a Claude or Codex subscription
|
|
24
|
+
between users.
|
|
25
|
+
|
|
26
|
+
The broker is local; the agents are not. Embassy does not call a provider API,
|
|
27
|
+
but a delivered body becomes model input in the receiving product and may be
|
|
28
|
+
sent to and retained by Anthropic or OpenAI under that product's normal terms
|
|
29
|
+
and settings.
|
|
30
|
+
|
|
31
|
+
## Trust model
|
|
32
|
+
|
|
33
|
+
Embassy provides same-UID containment and route attribution, not authentication
|
|
34
|
+
against other processes running as the same OS user.
|
|
35
|
+
|
|
36
|
+
- A Codex route is attributed to the exact inherited `CODEX_THREAD_ID` of the
|
|
37
|
+
task that self-registers it.
|
|
38
|
+
- A Claude route is attributed to a validated live peer generation and native
|
|
39
|
+
session UUID. An inherited `CLAUDE_CODE_MESSAGING_SOCKET` is a transient
|
|
40
|
+
reply capability, not a credential.
|
|
41
|
+
- Aliases are labels. They do not grant authority and are re-resolved against
|
|
42
|
+
the exact private route binding before delivery.
|
|
43
|
+
- Any process already running as the same OS user may be able to present local
|
|
44
|
+
environment or socket capabilities. Embassy is not a sandbox for untrusted
|
|
45
|
+
same-user code.
|
|
46
|
+
|
|
47
|
+
## Routing and consent
|
|
48
|
+
|
|
49
|
+
- A Codex task must explicitly self-register with a `codex-*` alias before it
|
|
50
|
+
can participate.
|
|
51
|
+
- Codex-to-Claude delivery requires an explicit operator-created pair with a
|
|
52
|
+
compatible live Claude session. Discovery alone is never permission to send.
|
|
53
|
+
- Every registered `codex-*` peer is visible to every compatible live Claude
|
|
54
|
+
session running as the same OS user, but paired mode accepts only sessions
|
|
55
|
+
holding an explicit pair edge with that exact task. Other senders settle
|
|
56
|
+
terminally with `SENDER_NOT_PAIRED`. `embassy serve --inbound open` is the
|
|
57
|
+
explicit opt-out from pairing.
|
|
58
|
+
- Pairs are additive, bounded, and per-edge: adding an edge never retires
|
|
59
|
+
another, and removing one invalidates its active conversation capabilities
|
|
60
|
+
before the change is published. Explicitly requested endpoint replacement
|
|
61
|
+
(registration succession) atomically settles the outgoing endpoint's
|
|
62
|
+
accepted work before the replacement is exposed; a half-replaced
|
|
63
|
+
intermediate state is never published.
|
|
64
|
+
- Embassy never mutates a Codex task's approval or sandbox policy and never
|
|
65
|
+
answers an approval request. An inbound turn uses the task's existing native
|
|
66
|
+
policy. With `approvalPolicy: never`, no human confirmation occurs on that
|
|
67
|
+
path; with an approval-requiring policy, the turn may wait for the user.
|
|
68
|
+
- Claude's native `crossSessionInbound` setting controls messages entering a
|
|
69
|
+
Claude session. Embassy cannot override an accept, hold, or refuse decision.
|
|
70
|
+
|
|
71
|
+
Every routed message is untrusted input capable of steering the receiving
|
|
72
|
+
agent. Registration and selection control reachability; they do not make the
|
|
73
|
+
message content trustworthy.
|
|
74
|
+
|
|
75
|
+
The literal leading `STEER:` prefix is a protocol instruction, not proof of a
|
|
76
|
+
trusted author or safe intent. Any exact same-UID Claude sender already allowed
|
|
77
|
+
to reach the registered Codex task can use it. The receiver's existing Codex
|
|
78
|
+
policy still governs tools and approvals, and an operator who does not want
|
|
79
|
+
this timing behavior must set `EMBASSY_STEERING_ENABLED=0` before starting the
|
|
80
|
+
broker.
|
|
81
|
+
|
|
82
|
+
## Process and protocol boundary
|
|
83
|
+
|
|
84
|
+
- The v1 launcher is foreground, macOS-only, same-machine, and local-host-only.
|
|
85
|
+
- Before provider setup, the launcher acquires one host-wide macOS advisory
|
|
86
|
+
lease. If its lease helper exits or the lease is otherwise lost
|
|
87
|
+
unexpectedly, Embassy shuts down rather than continuing without singleton
|
|
88
|
+
ownership.
|
|
89
|
+
- The control plane is a private Unix-domain socket in a controller-owned
|
|
90
|
+
mode-0700 state directory. `embassy serve` has no TCP or HTTP listener; its
|
|
91
|
+
only listeners are private Unix-domain sockets. The opt-in
|
|
92
|
+
`embassy dashboard --live` companion is a separate process with its own
|
|
93
|
+
loopback HTTP listener — see "Live companion boundary" below.
|
|
94
|
+
- Provider protocols are version-pinned. Unknown Claude Code peer or Codex App
|
|
95
|
+
Server versions, schemas, and endpoint generations fail closed.
|
|
96
|
+
- Embassy publishes at most one visibly prefixed, process-owned `codex-*`
|
|
97
|
+
record in Claude's registry. It creates one callback socket and removes only
|
|
98
|
+
exact-owned artifacts whose generation still matches during graceful
|
|
99
|
+
shutdown.
|
|
100
|
+
- App Server methods are allowlisted. Embassy exposes no archive, deletion,
|
|
101
|
+
shell, configuration, authentication, plugin, history, approval-response, or
|
|
102
|
+
generic RPC method.
|
|
103
|
+
- `turn/steer` is reachable only for an exact leading `STEER:` body in the
|
|
104
|
+
Claude-to-Codex direction, with an exact observed active-turn ID. App Server
|
|
105
|
+
admits it at the next tool-call boundary; Embassy never interrupts or injects
|
|
106
|
+
mid-generation. Clean boundary refusal falls back to the normal queue, which
|
|
107
|
+
retains at most three steers per route. The environment kill switch defaults
|
|
108
|
+
on and can disable this classification globally. Interrupt remains limited to
|
|
109
|
+
an exact turn started and positively observed by the same connector.
|
|
110
|
+
- Exact App Server 0.147.0 initialization enables `experimentalApi: true`
|
|
111
|
+
solely for `thread/resume.excludeTurns: true`. It adds no general
|
|
112
|
+
experimental method or authority. Missing, malformed, or nonempty returned
|
|
113
|
+
turns fail closed and are never retained.
|
|
114
|
+
- Queues, frames, bodies, callbacks, deadlines, hop counts, deduplication,
|
|
115
|
+
rate limits, and transient conversations are bounded. Ambiguous writes are
|
|
116
|
+
never retried automatically.
|
|
117
|
+
- The only network listener Embassy can create belongs to the opt-in
|
|
118
|
+
`embassy dashboard --live` companion, described under "Live companion
|
|
119
|
+
boundary". Everything enumerated above concerns `embassy serve`.
|
|
120
|
+
|
|
121
|
+
## Filesystem boundary
|
|
122
|
+
|
|
123
|
+
Controller-owned state is a dedicated mode-0700 directory. Its files and
|
|
124
|
+
control socket are mode 0600 and validated against replacement, symlinks, and
|
|
125
|
+
unexpected ownership or permissions.
|
|
126
|
+
|
|
127
|
+
The host-wide singleton has one fixed surface under the verified login home:
|
|
128
|
+
the private mode-0700 `~/.local/state/agent-embassy` directory and its mode-0600
|
|
129
|
+
`.gateway-host.lock`. Neither `EMBASSY_STATE_DIR` nor `XDG_STATE_HOME` relocates
|
|
130
|
+
that lease. Embassy executes the exact `/usr/bin/lockf` and `/bin/cat` helpers,
|
|
131
|
+
without a shell, to hold the kernel lease for the foreground process lifetime.
|
|
132
|
+
The lock file is retained and reused across restarts; process exit releases the
|
|
133
|
+
kernel lock.
|
|
134
|
+
|
|
135
|
+
Embassy's provider-facing access is intentionally enumerable:
|
|
136
|
+
|
|
137
|
+
- read and execute the configured Claude launcher only for bounded exact-version
|
|
138
|
+
attestation;
|
|
139
|
+
- read the live Claude session registry and connect validated peer sockets;
|
|
140
|
+
- for an explicit `compat-certify`, start one bounded no-stdin Claude print
|
|
141
|
+
session in a scrubbed environment, route one marked diagnostic frame only to
|
|
142
|
+
that scratch session, and terminate it after the receipt deadline;
|
|
143
|
+
- create and later remove its one callback socket and one registry record;
|
|
144
|
+
- resolve the managed Codex installation and attach to the already-running
|
|
145
|
+
local App Server; and
|
|
146
|
+
- inspect canonical filesystem metadata needed to validate provider-advertised
|
|
147
|
+
endpoints and generations.
|
|
148
|
+
|
|
149
|
+
Claude-owned registry files and peer sockets are accepted according to actual
|
|
150
|
+
filesystem accessibility plus bounded schema, type, PID/path correlation,
|
|
151
|
+
liveness, and generation checks. Embassy does not treat provider-owned Unix
|
|
152
|
+
owner or mode bits as an additional routing policy.
|
|
153
|
+
|
|
154
|
+
Embassy does not need or intentionally read credentials, Keychain items,
|
|
155
|
+
Claude project history, Codex or Claude transcripts, shell history, or provider
|
|
156
|
+
configuration contents. Report a bug if any normal code path attempts to do so.
|
|
157
|
+
|
|
158
|
+
## Persistence and disclosure
|
|
159
|
+
|
|
160
|
+
Message bodies, prompts, replies, raw provider frames, tool data, stderr,
|
|
161
|
+
callback addresses, and socket paths remain memory-only. They are discarded on
|
|
162
|
+
restart and never replayed.
|
|
163
|
+
|
|
164
|
+
The closed private binding store may retain the exact Codex thread ID and Claude
|
|
165
|
+
session UUID required for ownership and endpoint re-observation. Native IDs are
|
|
166
|
+
forbidden from public snapshots, normalized events, the dashboard, aliases,
|
|
167
|
+
logs, errors, and CLI output. A Claude UUID may enter only as a user-supplied
|
|
168
|
+
explicit CLI selector; Embassy never discovers or prints it publicly.
|
|
169
|
+
|
|
170
|
+
The static dashboard is two atomically rewritten mode-0600 files, not a web
|
|
171
|
+
application: `gateway-dashboard.html` and `gateway-dashboard.zh-CN.html`, both
|
|
172
|
+
written on every publish and cross-linked in the page. They contain allowlisted
|
|
173
|
+
metadata only and have no JavaScript, external assets, storage, telemetry,
|
|
174
|
+
mutation endpoint, or self-refresh. Any process already running as the same OS
|
|
175
|
+
user can read them.
|
|
176
|
+
|
|
177
|
+
### Live companion boundary
|
|
178
|
+
|
|
179
|
+
`embassy dashboard --live` binds an authenticated HTTP listener on `127.0.0.1` with
|
|
180
|
+
an ephemeral port. It is a separate foreground process from `embassy serve`.
|
|
181
|
+
Access requires a one-use 256-bit URL-fragment token exchanged for a
|
|
182
|
+
path-scoped `HttpOnly` `SameSite=Strict` session cookie. The exact Host header
|
|
183
|
+
is checked on every request; navigation GETs permit a missing Origin and carry
|
|
184
|
+
no sentinel, while non-navigation POSTs require the exact Origin plus the
|
|
185
|
+
`X-Embassy-Request` sentinel. There are no CORS headers, generic control or
|
|
186
|
+
provider routes, storage, telemetry, or external assets. The sole mutation
|
|
187
|
+
route accepts only select-Claude, unselect-Claude, and refresh-discovery JSON
|
|
188
|
+
bodies, capped at 1 KiB and six confirmed actions per minute. The browser
|
|
189
|
+
cannot register tasks, send, reply, approve, interrupt, change settings, or
|
|
190
|
+
invoke arbitrary broker/provider methods. Any process
|
|
191
|
+
running as your OS user — including root and browser extensions with local
|
|
192
|
+
filesystem access — can read what the browser can read.
|
|
193
|
+
|
|
194
|
+
The bootstrap URL, including its one-use capability, is written to a mode-0600
|
|
195
|
+
`bootstrap.html` inside a fresh mode-0700 run directory under the private state
|
|
196
|
+
root and removed when the companion exits; treat that directory with the same
|
|
197
|
+
care as the rest of the state root.
|
|
198
|
+
|
|
199
|
+
Reports involving the live companion are in scope if they demonstrate a path by
|
|
200
|
+
which a remote origin, a cross-site request, or a process running as a
|
|
201
|
+
different OS user can read the authenticated stream or bypass the token-to-cookie
|
|
202
|
+
exchange. Same-UID local reads are within the documented containment model and
|
|
203
|
+
are not treated as vulnerabilities unless they bypass an explicit control such
|
|
204
|
+
as accessing a session without presenting the cookie.
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
## Validation boundary
|
|
208
|
+
|
|
209
|
+
Routine tests use temporary directories, fake peers, and fake App Server
|
|
210
|
+
transports. They do not inspect live provider state or contact a model.
|
|
211
|
+
|
|
212
|
+
The default `observed` compatibility policy may admit an unknown same-major
|
|
213
|
+
provider version only after the bounded schema probe passes. That evidence
|
|
214
|
+
cannot detect a semantic change that preserves every validated shape. Runtime
|
|
215
|
+
record, frame, response, identity, and deadline checks remain mandatory, and
|
|
216
|
+
operators can use `compat-certify` for explicit on-machine wire evidence after
|
|
217
|
+
an upstream update. `compat-check` creates no message or turn;
|
|
218
|
+
`compat-certify --with-turn` is the only certification form that explicitly
|
|
219
|
+
starts a Codex model turn.
|
|
220
|
+
|
|
221
|
+
Passive live discovery, a live provider connection, a native message, and an
|
|
222
|
+
App Server turn are distinct authorization gates. Each requires an explicit
|
|
223
|
+
user request for that operation. Never infer permission for a live send from a
|
|
224
|
+
previous smoke test, and never enable live provider traffic in CI.
|