machine-bridge-mcp 0.17.1 → 0.18.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 (44) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +12 -13
  3. package/SECURITY.md +13 -13
  4. package/browser-extension/manifest.json +2 -2
  5. package/docs/ARCHITECTURE.md +5 -5
  6. package/docs/AUDIT.md +3 -3
  7. package/docs/CLIENTS.md +2 -2
  8. package/docs/GETTING_STARTED.md +13 -9
  9. package/docs/LOGGING.md +2 -2
  10. package/docs/MANAGED_JOBS.md +1 -1
  11. package/docs/MULTI_ACCOUNT.md +75 -358
  12. package/docs/OPERATIONS.md +4 -4
  13. package/docs/POLICY_REFERENCE.md +1 -1
  14. package/docs/PRIVACY.md +1 -1
  15. package/docs/PROJECT_STANDARDS.md +1 -1
  16. package/docs/TESTING.md +4 -4
  17. package/package.json +4 -3
  18. package/src/local/account-access.mjs +37 -0
  19. package/src/local/account-admin.mjs +105 -0
  20. package/src/local/bounded-output.mjs +50 -0
  21. package/src/local/call-registry.mjs +10 -0
  22. package/src/local/cli-account-admin.mjs +79 -0
  23. package/src/local/cli-options.mjs +9 -4
  24. package/src/local/cli-policy.mjs +7 -28
  25. package/src/local/cli.mjs +48 -78
  26. package/src/local/daemon-process.mjs +3 -2
  27. package/src/local/errors.mjs +0 -14
  28. package/src/local/log.mjs +1 -1
  29. package/src/local/managed-jobs.mjs +1 -3
  30. package/src/local/process-execution.mjs +90 -60
  31. package/src/local/relay-connection.mjs +11 -3
  32. package/src/local/runtime.mjs +27 -2
  33. package/src/local/service.mjs +7 -26
  34. package/src/local/shell.mjs +17 -29
  35. package/src/local/state-inventory.mjs +2 -2
  36. package/src/local/state.mjs +30 -85
  37. package/src/local/tool-executor.mjs +8 -2
  38. package/src/shared/access-contract.json +23 -0
  39. package/src/shared/policy-contract.json +30 -7
  40. package/src/worker/access.ts +46 -0
  41. package/src/worker/account-admin.ts +103 -0
  42. package/src/worker/index.ts +75 -48
  43. package/src/worker/oauth-state.ts +184 -2
  44. package/src/worker/tool-catalog.ts +13 -0
@@ -1,406 +1,123 @@
1
- # Multi-client, multi-account, and tenancy architecture
1
+ # Multi-account authorization and tenancy
2
2
 
3
- ## The actual question
3
+ ## What isolation means
4
4
 
5
- The important question is not whether several OAuth clients can obtain tokens. The important question is:
5
+ Machine Bridge remote mode supports several named accounts on one workspace Worker. Each account has an independent password, role, active state, version, OAuth authorization codes, and access tokens. Account changes revoke only that account's outstanding credentials.
6
6
 
7
- > Can independently managed principals share one Machine Bridge deployment without inheriting the same local-machine authority or being able to affect one another?
7
+ This is application-level authorization, not operating-system isolation. All accounts ultimately reach one local daemon running as one OS user. Roles limit which Machine Bridge tools can be listed and invoked; they do not create separate filesystems, browser profiles, process namespaces, keychains, network identities, or kernel security boundaries.
8
8
 
9
- The current release answers **no**. It supports multiple client registrations and multiple access tokens, but it does not model human or service accounts as independent security principals. Anyone who completes authorization with the shared workspace connection password receives the same workspace-level Machine Bridge authority.
9
+ Use separate OS accounts, containers, VMs, state roots, Workers, and workspaces when users are mutually untrusted or require hard isolation.
10
10
 
11
- This distinction must remain explicit because an OAuth `client_id` identifies client software, not the person or service using it.
11
+ ## Roles
12
12
 
13
- ## Terms
14
-
15
- | Term | Meaning in this document |
16
- |---|---|
17
- | OAuth client | A registered MCP host/application with redirect URIs and a `client_id` |
18
- | Principal/account | A human or service identity that may receive authority |
19
- | Grant | A named, versioned set of capabilities assigned to a principal for one bridge |
20
- | Bridge | One canonical workspace, local runtime, Worker, Durable Object, policy ceiling, and credential set |
21
- | Trust domain | Principals that may safely share the same local OS/runtime authority |
22
- | Tenant | An independently administered trust domain requiring isolation from other tenants |
23
-
24
- ## Current behavior
25
-
26
- | Scenario | Current support | Security meaning |
27
- |---|---:|---|
28
- | Several MCP applications or installations connect to one Worker | Yes | Each can register a client and receive separate tokens |
29
- | Several ChatGPT accounts connect using the same connection password | Technically yes | They receive the same workspace authority; no account isolation |
30
- | Several active OAuth tokens for one client | Yes | Tokens are distinct but share the same authorization semantics |
31
- | Per-account roles, capability grants, suspension, or revocation | No | Only workspace-wide policy and token-version revocation exist |
32
- | Revoke one principal while preserving all others | No | The current emergency mechanism rotates the workspace token version and invalidates all access tokens |
33
- | Several independent workspaces | Yes | Each canonical workspace has independent state, policy, Worker name, secrets, resources, jobs, and locks |
34
- | Several mutually untrusted users in one local runtime | No | Process and browser authority remain local-user authority |
35
- | Multi-user tenancy in one Worker deployment | No | The architecture currently has one shared password, one policy, one active daemon, and one default Durable Object route |
36
-
37
- Relevant current implementation:
38
-
39
- - `src/worker/oauth-state.ts` stores OAuth clients, authorization codes, and tokens by `client_id` but has no principal or membership record.
40
- - `src/worker/index.ts` accepts one `MCP_OAUTH_PASSWORD`, validates one `OAUTH_TOKEN_VERSION`, routes the deployment to the Durable Object named `default`, and selects one authenticated daemon.
41
- - `src/local/state.mjs` creates independent credentials and state per canonical workspace.
42
- - [ARCHITECTURE.md](ARCHITECTURE.md) explicitly excludes multi-user tenancy in one Worker deployment.
43
-
44
- ## Safe deployment choices today
45
-
46
- ### Same owner and same trust domain
47
-
48
- Use one bridge and authorize multiple MCP clients. This is appropriate when every client/account is controlled by the same person or team and all may receive the same workspace authority.
49
-
50
- Examples:
51
-
52
- - the same owner connects ChatGPT web and a local Codex client;
53
- - one trusted team uses several MCP host installations with an identical role;
54
- - a user reconnects after replacing a device.
55
-
56
- The separate tokens improve protocol hygiene and cancellation correlation, but they are not separate authorization domains.
57
-
58
- ### Different workspaces with the same owner
59
-
60
- Run one bridge identity per canonical workspace. This preserves independent state, credentials, Workers, policies, resources, and jobs. It is preferable to exposing one broad parent directory solely for convenience.
61
-
62
- ### Different trust domains or mutually untrusted users
63
-
64
- Do not share one `full` runtime. Use a separate bridge instance per trust domain, preferably with:
65
-
66
- 1. a dedicated low-privilege OS account, container, or VM;
67
- 2. a narrow workspace mounted or accessible only to that identity;
68
- 3. an independent state root and Worker credential set;
69
- 4. a separate Worker/deployment identity where administrative separation matters;
70
- 5. a profile no broader than the required workflow.
71
-
72
- This external boundary is necessary because direct processes, package scripts, shells, browser sessions, Accessibility actions, credential stores, and network access inherit the local user's authority. A per-account row in Worker storage cannot turn Node.js process execution into an OS sandbox.
73
-
74
- ## Recommended future architecture
75
-
76
- The least coupled design is **principal-aware authorization inside one bridge, while retaining one bridge per workspace/trust domain**. Do not begin by turning the Worker into a global router for every workspace, machine, and tenant.
77
-
78
- ```text
79
- MCP host / OAuth client
80
- |
81
- | authorization code + PKCE
82
- v
83
- Principal authentication
84
- |
85
- v
86
- Bridge membership ------> named grant + revision
87
- |
88
- v
89
- OAuth code/token bound to principal + grant + resource
90
- |
91
- v
92
- Worker authorization gate
93
- |
94
- | bounded auth context + tool call
95
- v
96
- Local daemon authorization gate
97
- |
98
- v
99
- Workspace / process / browser operation
100
- ```
101
-
102
- The design has five distinct concepts:
103
-
104
- 1. **Client registration** identifies the MCP application and its redirect URIs.
105
- 2. **Principal authentication** establishes the human or service identity.
106
- 3. **Membership** says that a principal may access one bridge.
107
- 4. **Grant** defines the exact capabilities and constraints available to that membership.
108
- 5. **OAuth tokens** carry a revocable reference to the principal, membership, grant revision, and protected resource.
109
-
110
- Keeping these concepts separate prevents `client_id`, email address, connection password, and policy profile from becoming overloaded pseudo-identities.
111
-
112
- ## Data model
113
-
114
- A minimal Worker-side model can remain inside the existing per-bridge Durable Object:
115
-
116
- ```text
117
- principals
118
- principal_id
119
- display_label
120
- status
121
- authentication_method
122
- credential_reference_or_subject
123
- revocation_version
124
- created_at
125
- last_authenticated_at
126
-
127
- memberships
128
- membership_id
129
- principal_id
130
- bridge_id
131
- grant_id
132
- grant_revision
133
- status
134
- expires_at
135
- created_at
136
-
137
- clients
138
- client_id
139
- client_name
140
- redirect_uris
141
- registration_identity
142
- created_at
143
- last_used_at
144
-
145
- authorization_codes
146
- code_hash
147
- client_id
148
- principal_id
149
- membership_id
150
- grant_id
151
- grant_revision
152
- redirect_uri
153
- code_challenge
154
- scope
155
- resource
156
- expires_at
157
-
158
- access_tokens
159
- token_hash
160
- client_id
161
- principal_id
162
- membership_id
163
- grant_id
164
- grant_revision
165
- principal_revocation_version
166
- scope
167
- resource
168
- expires_at
169
- ```
170
-
171
- Use random opaque identifiers. Do not use an email address, OAuth source address, client name, or browser fingerprint as the primary authorization key.
172
-
173
- The current `OAuthClient`, `OAuthCode`, and `OAuthToken` records can be migrated by adding optional principal/grant fields and treating legacy records as the single-owner principal until reauthorization. Avoid a flag day that silently changes existing users' authority.
174
-
175
- ## Grant model
176
-
177
- A grant should reference capabilities rather than duplicate an arbitrary list of tool names. The current shared policy contract already provides the correct vocabulary and compound requirements.
178
-
179
- Example conceptual grants:
180
-
181
- | Grant | Capabilities | Typical use |
13
+ | Role | Effective local profile | Typical use |
182
14
  |---|---|---|
183
- | `reviewer` | read-only, workspace-confined | inspection and review |
184
- | `editor` | read/write, no process execution | deterministic file changes |
185
- | `agent` | workspace-confined writes and direct processes, isolated environment | trusted coding automation |
186
- | `operator` | canonical `full` | single-owner or highly trusted administration |
187
-
188
- The effective authorization must be the intersection of all applicable ceilings:
189
-
190
- ```text
191
- local workspace policy ceiling
192
- AND principal membership grant
193
- AND OAuth scopes/resource binding
194
- AND any MCP-host-side restrictions
195
- ```
196
-
197
- A grant must never widen the locally selected workspace policy. For example, assigning `operator` cannot make a daemon started under `review` execute a process.
198
-
199
- Do not create a second independently maintained permission matrix. Grant resolution should consume the same shared capability contract used by local policy, Worker tool advertisement, and runtime execution.
200
-
201
- ## Dual enforcement without two sources of truth
202
-
203
- Worker-only filtering is insufficient because a protocol bug, stale token, alternate transport, or compromised relay must not bypass local authority. Local-only filtering is also insufficient because unauthorized tools should not be advertised or relayed.
204
-
205
- Use the following contract:
15
+ | `reviewer` | `review` | Read-only workspace, Git, image, resource, and job inspection |
16
+ | `editor` | `edit` | Reviewer access plus deterministic file mutation |
17
+ | `operator` | `agent` | Editor access plus workspace-confined direct process execution and sessions |
18
+ | `owner` | `full` | Complete bridge authority, including shell, unrestricted paths, browser, applications, resources, jobs, and account administration |
206
19
 
207
- 1. The local daemon owns the machine policy ceiling and the canonical grant catalog it is willing to honor.
208
- 2. On authenticated `hello`, the daemon advertises a bounded grant-catalog revision/digest plus the policy ceiling and tool catalog.
209
- 3. The Worker owns principals, memberships, OAuth codes, and access tokens for that bridge.
210
- 4. A membership references only a grant ID/revision present in the daemon-advertised catalog.
211
- 5. The Worker filters `tools/list` and rejects `tools/call` using the membership grant.
212
- 6. Every relayed call includes a bounded authorization context containing opaque principal, membership, grant, and revision identifiers plus scopes.
213
- 7. The local daemon independently resolves the grant and rechecks the operation against the current local ceiling before execution.
214
- 8. A missing grant, stale revision, digest mismatch, suspended membership, or revoked principal fails closed.
20
+ The effective tool set is the intersection of:
215
21
 
216
- This is not duplicated policy: the shared contract defines capability semantics; the Worker and daemon enforce the same resolved grant at separate trust boundaries.
22
+ 1. the account role;
23
+ 2. the policy advertised by the connected local daemon;
24
+ 3. the tools actually available from that daemon.
217
25
 
218
- ## Authentication choices
26
+ The Worker filters `tools/list` and rejects unauthorized calls before relay. Every accepted relay call also carries `account_id`, `account_version`, and `role`; the local runtime validates the role again before dispatch. The shared policy contract remains the single source of capability semantics.
219
27
 
220
- Authentication should be an adapter behind the principal model, not embedded in tool authorization.
28
+ ## Account lifecycle
221
29
 
222
- Reasonable progression:
30
+ List accounts:
223
31
 
224
- 1. **Compatibility mode:** retain the existing shared connection password and map it to one owner principal.
225
- 2. **Local managed accounts:** create separate high-entropy one-time invitations or passkey/WebAuthn credentials for principals.
226
- 3. **External identity provider:** accept OIDC identities from a configured issuer and map stable issuer/subject pairs to principals.
227
- 4. **Service principals:** use separately rotated non-human credentials with narrow grants and explicit expiry.
228
-
229
- Do not issue one static password per person while leaving tokens unbound to a principal. That changes credential distribution but does not create account semantics.
230
-
231
- For external identity, key principals by the pair `(issuer, subject)`, not by mutable email or display name. Keep identity-provider integration optional so a self-hosted single-owner installation remains simple.
232
-
233
- ## OAuth and MCP authorization rules
234
-
235
- A principal-aware implementation should preserve the current authorization-code and PKCE flow and add these rules:
236
-
237
- - the authorization code is bound to the authenticated principal, membership, client, redirect URI, PKCE challenge, scope, and resource;
238
- - access tokens are audience/resource restricted to the exact bridge `/mcp` endpoint;
239
- - tokens contain no raw credentials and are stored only as hashes;
240
- - requested scopes are intersected with the membership grant rather than accepted as authority by themselves;
241
- - dynamic client registration limits remain independent of principal limits;
242
- - token and authorization-code quotas are enforced per principal and client as well as globally;
243
- - cancellation/request correlation includes the token identity but never treats it as the principal database key;
244
- - token validation checks principal status, membership status/expiry, principal revocation version, grant revision, token expiry, and resource binding.
245
-
246
- The MCP authorization specification uses OAuth protected-resource metadata and resource indicators; OAuth security best practice requires exact redirect matching, PKCE, and audience-restricted tokens. Preserve those properties when adding identity rather than inventing a parallel session protocol.
247
-
248
- ## Revocation
249
-
250
- The current `OAUTH_TOKEN_VERSION` is useful as an emergency whole-bridge kill switch but too coarse for ordinary account management.
251
-
252
- Add four independent revocation levels:
253
-
254
- 1. revoke one access token/session;
255
- 2. revoke all tokens issued to one OAuth client for a principal;
256
- 3. increment one principal's revocation version or suspend the principal;
257
- 4. change or revoke one membership/grant revision.
258
-
259
- Keep whole-bridge secret rotation for suspected deployment compromise. Ordinary account removal should not disconnect every other principal.
260
-
261
- Managed jobs require separate treatment: a running job snapshots authority at acceptance and can outlive an MCP token. Revoking an account should block new calls immediately and provide an explicit option to cancel active jobs accepted by that membership. Silent automatic cancellation may be unsafe when cleanup/finally steps are required.
262
-
263
- ## Administration surface
264
-
265
- Account management is an operator function, not an ordinary MCP tool available to every account. A future CLI could expose:
266
-
267
- ```text
268
- machine-mcp account invite
32
+ ```sh
269
33
  machine-mcp account list
270
- machine-mcp account show ACCOUNT_ID
271
- machine-mcp account set-grant ACCOUNT_ID GRANT
272
- machine-mcp account suspend ACCOUNT_ID
273
- machine-mcp account revoke ACCOUNT_ID
274
- machine-mcp account sessions ACCOUNT_ID
275
- machine-mcp account revoke-session SESSION_ID
276
34
  ```
277
35
 
278
- These names are a design proposal, not current commands.
279
-
280
- Administrative mutations should require local operator access or a separately protected admin plane. Do not let a normal `full` MCP session implicitly create additional principals merely because it can execute shell commands; that would make account administration indistinguishable from local-machine takeover.
281
-
282
- ## Concurrency and quotas
283
-
284
- The existing global bounds should remain. Add fair per-principal limits for:
36
+ Create an account. The generated password is displayed once and is not stored locally:
285
37
 
286
- - concurrent relay calls;
287
- - OAuth clients and active tokens;
288
- - authorization failures;
289
- - process sessions;
290
- - accepted managed jobs;
291
- - response bytes and rate windows where needed.
292
-
293
- A principal limit must not replace a global limit. Otherwise many accounts can exhaust one daemon collectively.
294
-
295
- A single local daemon still serializes or coordinates operations that mutate shared workspace state. Account support does not make concurrent Git operations, patches, browser actions, or service changes conflict-free. Existing mutation locks and transactional checks remain authoritative.
296
-
297
- ## Audit and privacy
298
-
299
- Operational logs must continue to omit credentials, tool arguments, file contents, process output, browser content, and personal identifiers.
300
-
301
- For account diagnosis, log only bounded pseudonymous identifiers such as an HMAC-derived principal handle, grant ID/revision, event class, and outcome. Store a separate owner-only administrative audit record only when there is a concrete operator requirement, with explicit retention and access controls.
302
-
303
- Do not expose account lists, emails, identity-provider subjects, or session inventories through ordinary `server_info`. A principal may see its own effective grant and token expiry; only the local administrator should see cross-account inventory.
304
-
305
- ## Cloudflare Durable Object topology
306
-
307
- Retain one Durable Object instance per bridge/workspace. It already provides a serializable state and WebSocket coordination boundary for that bridge.
308
-
309
- Avoid these two extremes:
310
-
311
- - **One Durable Object per access token:** this fragments the one-daemon call registry and complicates global limits, cancellation, and grant updates.
312
- - **One global Durable Object for every workspace and tenant:** this increases blast radius, couples unrelated deployments, creates a central routing/credential registry, and makes local lifecycle operations harder to reason about.
313
-
314
- If a future shared control plane is required, keep it limited to account discovery/invitations and route each request to the bridge-specific Durable Object. Local tool calls should still terminate at the bridge boundary.
38
+ ```sh
39
+ machine-mcp account add alice reviewer
40
+ machine-mcp account add build-bot operator
41
+ ```
315
42
 
316
- ## Rejected designs
43
+ Change role or active state:
317
44
 
318
- ### Treat `client_id` as the account
45
+ ```sh
46
+ machine-mcp account role alice editor
47
+ machine-mcp account disable build-bot
48
+ machine-mcp account enable build-bot
49
+ ```
319
50
 
320
- Rejected because one application may serve many users and one user may use many applications. Client rotation or reinstall would also appear to create a new person.
51
+ Rotate one account's password:
321
52
 
322
- ### Give each user a different connection password only
53
+ ```sh
54
+ machine-mcp account rotate-password alice
55
+ ```
323
56
 
324
- Rejected because credentials, tokens, grants, revocation, quotas, and audit events still lack a stable principal binding.
57
+ Remove an account:
325
58
 
326
- ### Enforce grants only in the Worker
59
+ ```sh
60
+ machine-mcp account remove alice
61
+ machine-mcp account remove alice --yes
62
+ ```
327
63
 
328
- Rejected because the local runtime is the final filesystem/process boundary and must fail closed independently.
64
+ The final active owner cannot be disabled, demoted, or removed. This prevents an accidental administrative lockout.
329
65
 
330
- ### Enforce grants only in the daemon
66
+ ## OAuth model
331
67
 
332
- Rejected because the Worker would advertise and relay tools to unauthorized callers, increasing leakage and attack surface.
68
+ An OAuth `client_id` identifies client software and its registered redirect URIs. It is not an account. One OAuth client may be authorized by several Machine Bridge accounts, and one account may authorize several OAuth clients.
333
69
 
334
- ### Put all machines and workspaces behind one tenant router immediately
70
+ An authorization code records:
335
71
 
336
- Rejected as unnecessary coupling. The existing per-workspace lifecycle is a useful security and operational boundary.
72
+ - OAuth client ID;
73
+ - account ID and account version;
74
+ - role;
75
+ - redirect URI;
76
+ - PKCE S256 challenge;
77
+ - scope and protected resource;
78
+ - expiration.
337
79
 
338
- ### Share `full` among mutually untrusted accounts
80
+ An access-token record contains the same account binding plus the deployment-wide token version. Token values are stored only as SHA-256 lookup keys. Account passwords are CLI-generated 256-bit tokens. The Worker stores independent salted HMAC-SHA-256 verifiers and rejects arbitrary human-chosen passwords; the token entropy, rather than a CPU-intensive dictionary-hardening loop, provides offline-guessing resistance within the Worker CPU budget.
339
81
 
340
- Rejected because `full` exposes local-user process, shell, browser, and filesystem authority. Application-level role checks cannot provide OS isolation.
82
+ At each authenticated request, the Worker verifies that the account still exists, is active, and has the same version and role recorded in the token. A password rotation, role change, suspension, or removal increments or removes that account state and invalidates only its codes and tokens.
341
83
 
342
- ## Incremental delivery plan
84
+ `machine-mcp rotate-secrets` is intentionally broader. It rotates the account-administration secret, daemon secret, and deployment-wide token version, invalidating every account token and requiring all clients to authorize again.
343
85
 
344
- ### Phase 0: make the boundary explicit
86
+ ## Administrative boundary
345
87
 
346
- - document current multi-client behavior and lack of account isolation;
347
- - recommend separate bridge/OS boundaries for different trust domains;
348
- - keep current behavior unchanged.
88
+ Account administration is not exposed as an MCP tool. It uses an owner-only local secret to call private Worker administration endpoints from the CLI. The administration secret is stored only in owner-protected local state and Cloudflare Worker secrets; it is never printed by `status`, sent through MCP, or used as an account password.
349
89
 
350
- ### Phase 1: principal records and targeted revocation
90
+ The first start of a new deployment creates an `owner` account automatically and prints its generated password once. Subsequent starts do not display account passwords.
351
91
 
352
- - add owner principal migration;
353
- - bind codes/tokens to principal and membership IDs;
354
- - add per-principal suspension and revocation version;
355
- - preserve one common grant initially;
356
- - add migration, expiry, quota, and revocation tests.
92
+ ## Concurrency and revocation
357
93
 
358
- ### Phase 2: named grants and dual enforcement
94
+ Pending calls remain bound to the OAuth access token and JSON-RPC request ID. Duplicate in-flight IDs under one token are rejected. Account revocation blocks new requests immediately; calls already relayed remain subject to ordinary cancellation, deadlines, relay disconnect cleanup, and local role validation.
359
95
 
360
- - derive grant capabilities from the shared policy contract;
361
- - advertise grant catalog revision/digest in the daemon handshake;
362
- - filter tool lists/calls in the Worker and recheck locally;
363
- - add stale-revision, reconnect, policy-narrowing, cancellation, and managed-job tests.
96
+ Remote relay loss cancels every relay-owned local call and terminates its child process tree. Process promises settle on cancellation even when a child does not emit `close`; process ownership remains tracked until actual exit.
364
97
 
365
- ### Phase 3: operator account lifecycle
98
+ ## Audit and privacy
366
99
 
367
- - add invitation, list, suspension, grant, and session-revocation CLI commands;
368
- - add owner-only persistence and administrative audit retention;
369
- - add concurrent and fault-injection tests for account mutations.
100
+ Operational metrics identify tools and stable error classes, not account passwords, bearer tokens, arguments, command text, file content, or results. `server_info` may return the authenticated account ID, role, and version to that account so the client can verify its authorization context.
370
101
 
371
- ### Phase 4: optional external identity
102
+ Account names and display names are operator-selected metadata. Do not use secrets, email addresses, customer identifiers, or other unnecessary personal data in account names.
372
103
 
373
- - add passkey/WebAuthn or configured OIDC adapters only after the principal/grant model is stable;
374
- - keep single-owner password compatibility simple;
375
- - do not require a central SaaS account service.
104
+ ## Deployment topology
376
105
 
377
- ## Acceptance criteria for claiming multi-account support
106
+ One workspace normally maps to one Worker name, one Durable Object instance, one local state profile, and one active daemon. Multiple accounts share this topology and its OS trust boundary.
378
107
 
379
- The project should not advertise isolated multi-account support until tests prove all of the following:
108
+ Use a separate deployment when any of these differ:
380
109
 
381
- - two principals can authorize through the same client and receive different grants;
382
- - one principal can use two clients without becoming two accounts;
383
- - revoking one principal leaves another principal's sessions valid;
384
- - narrowing the local policy immediately prevents a broader account grant from executing;
385
- - a stale grant revision fails in both Worker and daemon paths;
386
- - `tools/list` and `tools/call` enforce the same effective grant;
387
- - disconnect, daemon replacement, token expiry, and secret rotation converge safely;
388
- - account quotas cannot bypass global bounds;
389
- - managed jobs are attributed to a membership and have an explicit revocation/cancellation policy;
390
- - logs and public status do not expose personal identifiers or credentials;
391
- - different trust domains remain externally isolated when process/browser authority is available.
110
+ - OS user or machine owner;
111
+ - workspace trust domain;
112
+ - browser/profile trust;
113
+ - network or credential-store authority;
114
+ - billing or incident-response boundary;
115
+ - requirement for hard tenant isolation.
392
116
 
393
- Until those conditions are implemented, the accurate product statement is:
117
+ Do not place several unrelated machines or mutually untrusted teams behind one broad owner deployment merely to reduce administration.
394
118
 
395
- > Machine Bridge supports multiple OAuth clients and tokens for one trusted workspace owner or trust domain. It does not yet provide isolated multi-user tenancy in one deployment.
119
+ ## Security limits
396
120
 
397
- ## Standards and implementation references
121
+ A `reviewer` cannot invoke mutation or process tools through Machine Bridge, but data readable by the local OS user may still be exposed by a bug, compromised dependency, or incorrectly broad workspace. An `operator` can run interpreters, package managers, compilers, and repository scripts; direct argv execution therefore remains powerful even without a shell. An `owner` has effectively the authority of the local OS user and any browser or Accessibility permissions granted to the runtime.
398
122
 
399
- - [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)
400
- - [OAuth 2.0 Security Best Current Practice (RFC 9700)](https://www.rfc-editor.org/rfc/rfc9700)
401
- - [OAuth 2.0 Authorization Server Issuer Identification (RFC 9207)](https://www.rfc-editor.org/rfc/rfc9207)
402
- - [OAuth 2.0 Resource Indicators (RFC 8707)](https://www.rfc-editor.org/rfc/rfc8707)
403
- - [OAuth 2.0 Protected Resource Metadata (RFC 9728)](https://www.rfc-editor.org/rfc/rfc9728)
404
- - [Cloudflare Durable Objects documentation](https://developers.cloudflare.com/durable-objects/)
405
- - [Security policy](../SECURITY.md)
406
- - [Architecture](ARCHITECTURE.md)
123
+ Roles are useful defense in depth and targeted revocation. They are not substitutes for least-privilege OS design, sandboxing, endpoint security, Cloudflare account protection, or careful review of untrusted repositories and instructions.
@@ -67,7 +67,7 @@ Application UI inspection/actions require Accessibility permission for the Node/
67
67
 
68
68
  `machine-mcp` is a foreground command. It remains attached to the terminal, defaults to `info` logging, and stops on `Ctrl+C`. `machine-mcp service start` launches the installed platform service in the background and returns to the shell; that service uses `warn` logging.
69
69
 
70
- A global npm install changes the CLI files on disk but does not replace an already running Node process. Startup and other state-changing CLI operations use a token/process-identity lock and wait up to 30 seconds for a normal concurrent startup to finish; a short launchd/systemd overlap is therefore serialized rather than reported immediately as an error. On a normal foreground start, Machine Bridge unloads the platform service and then independently examines the workspace daemon lock. This second path handles a detached/orphan `--daemon-only` process that launchd/systemd/task scheduling no longer tracks, including legacy locks without mode/version metadata. Before sending `SIGTERM`, Machine Bridge verifies PID and process start time plus the live command line, entrypoint, canonical workspace, canonical state root, and daemon-only flag. It waits up to 15 seconds for both the PID and lock to disappear and does not add a later forced-kill escalation. On POSIX, a daemon that handles and ignores `SIGTERM` therefore reaches the bounded timeout and remains alive. Node maps the supported termination signals to immediate process termination on Windows, so the same verified-daemon stop normally completes rather than exercising the POSIX timeout branch. A foreground or unverifiable process is left untouched; stop a foreground instance with `Ctrl+C`.
70
+ A global npm install changes the CLI files on disk but does not replace an already running Node process. Startup and other state-changing CLI operations use a token/process-identity lock and wait up to 30 seconds for a normal concurrent startup to finish; a short launchd/systemd overlap is therefore serialized rather than reported immediately as an error. On a normal foreground start, Machine Bridge unloads the platform service and then independently examines the workspace daemon lock. This second path handles a detached/orphan `--daemon-only` process that launchd, systemd, or Task Scheduler no longer tracks. Only current lock records containing service mode, version, PID, process start time, entrypoint, workspace, and state root are eligible for takeover. Before sending `SIGTERM`, Machine Bridge verifies PID and process start time plus the live command line, entrypoint, canonical workspace, canonical state root, and daemon-only flag. It waits up to 15 seconds for both the PID and lock to disappear and does not add a later forced-kill escalation. On POSIX, a daemon that handles and ignores `SIGTERM` therefore reaches the bounded timeout and remains alive. Node maps the supported termination signals to immediate process termination on Windows, so the same verified-daemon stop normally completes rather than exercising the POSIX timeout branch. A foreground or unverifiable process is left untouched; stop a foreground instance with `Ctrl+C`.
71
71
 
72
72
  `machine-mcp service status [WORKSPACE]` reports two independent layers: the platform service (`active`) and `workspace_daemon`, plus `effective_active` and `orphaned_workspace_daemon` summary flags. On macOS it is possible for launchd to report inactive while a prior Node process remains alive with parent PID 1; that is an orphan-daemon condition, not proof that the daemon stopped. `service stop` unloads the provider when present and then terminates only a verified service-style workspace daemon. `service uninstall` and full uninstall are ordered fail-closed operations: provider stop → verified daemon stop(s) → definition removal. A failed or ambiguous stop leaves definitions and state intact. If takeover reaches its deadline, run:
73
73
 
@@ -91,7 +91,7 @@ npm --version
91
91
  machine-mcp --verbose
92
92
  ```
93
93
 
94
- `Unknown cli config "--allow-scripts"` proves the package installation ran under npm 11 or older. `Invalid property "node"` or `Invalid property "devEngines.node"` means npm parsed a legacy `devEngines` object; inspect the npm debug log to identify its source rather than assuming it belongs to Machine Bridge. The published package declares both Node.js 26 and npm 12 in `engines`, and `machine-mcp doctor` checks both active versions. Keep `--omit=optional` in the install command. Without it npm may resolve optional `fsevents` and warn that its install script was not included in `allowScripts`; Machine Bridge does not require that development-time watcher at runtime.
94
+ `Unknown cli config "--allow-scripts"` proves the package installation ran under npm 11 or older. `Invalid property "node"` or `Invalid property "devEngines.node"` means npm parsed an outdated `devEngines` object; inspect the npm debug log to identify its source rather than assuming it belongs to Machine Bridge. The published package declares both Node.js 26 and npm 12 in `engines`, and `machine-mcp doctor` checks both active versions. Keep `--omit=optional` in the install command. Without it npm may resolve optional `fsevents` and warn that its install script was not included in `allowScripts`; Machine Bridge does not require that development-time watcher at runtime.
95
95
 
96
96
  ## State-root safety and removal
97
97
 
@@ -108,7 +108,7 @@ Uninstall acquires a state-root `maintenance.lock` that blocks new profile/state
108
108
  Stable errors include `policy_denied`, `invalid_request`, `timeout`, `cancelled`, `network_error`, `unavailable`, `limit_exceeded`, and `integrity_error`, with retryability metadata. Diagnose by code first; free-form messages are guidance, not an API contract.
109
109
 
110
110
 
111
- Windows Scheduled Task command lines use CRT-compatible quoting, including trailing backslashes and drive-root paths. Remote autostart definitions prefer a stable PATH alias that resolves to the currently running Node executable and persist a sanitized absolute-only service `PATH` containing the Node/CLI directories, the installer's absolute PATH entries, and platform defaults. This avoids versioned Homebrew-style paths becoming invalid after upgrades and prevents launchd/systemd from falling back to a minimal system-only PATH. Re-run `machine-mcp service install` after changing Node installation families or PATH layout. A service-style `--daemon-only` start that finds the same workspace daemon already running is an idempotent no-op: it exits successfully without repeating warnings or readiness output; explicit policy/secret/change requests still report that changes were not applied. Autostart logs are stored under the state root in `logs/daemon.out.log` and `logs/daemon.err.log`. Installed services pass `--log-level warn --log-format json`, so each active line is a bounded JSON event suitable for ingestion. Files are owner-only where supported and tail-trimmed before daemon startup. On a logging-schema upgrade, bounded prior content is moved into `daemon.out.legacy.log` and `daemon.err.legacy.log`; use the active filenames when diagnosing current behavior.
111
+ Windows Scheduled Task command lines use CRT-compatible quoting, including trailing backslashes and drive-root paths. Remote autostart definitions prefer a stable PATH alias that resolves to the currently running Node executable and persist a sanitized absolute-only service `PATH` containing the Node/CLI directories, the installer's absolute PATH entries, and platform defaults. This avoids versioned Homebrew-style paths becoming invalid after upgrades and prevents launchd/systemd from falling back to a minimal system-only PATH. Re-run `machine-mcp service install` after changing Node installation families or PATH layout. A service-style `--daemon-only` start that finds the same workspace daemon already running is an idempotent no-op: it exits successfully without repeating warnings or readiness output; explicit policy/secret/change requests still report that changes were not applied. Autostart logs are stored under the state root in `logs/daemon.out.log` and `logs/daemon.err.log`. Installed services pass `--log-level warn --log-format json`, so each active line is a bounded JSON event suitable for ingestion. Files are owner-only where supported and tail-trimmed before daemon startup. If the log schema marker does not match the current format, the active files are cleared before startup and the current marker is written. Runtime code reads and maintains only the active filenames.
112
112
 
113
113
  Logging is level-based:
114
114
 
@@ -214,7 +214,7 @@ Defense-in-depth limits include:
214
214
 
215
215
  ## Upgrade behavior
216
216
 
217
- Policy revision 4 makes named profiles canonical and evaluates compound tool requirements from the shared contract. A state entry labelled `full` is repaired to writes, direct processes, process sessions, shell execution, unrestricted direct filesystem paths, absolute path output, the complete parent environment, and the complete tool catalog. CLI capability overrides are stored as `custom`. The exact pre-0.4 implicit-default shape is still migrated to the current `full` default; explicit restrictive and identified custom profiles remain preserved.
217
+ Policy revision 5 makes named profiles canonical and evaluates compound tool requirements from the shared contract. A state entry labelled `full` means writes, direct processes, process sessions, shell execution, unrestricted direct filesystem paths, absolute path output, the complete parent environment, and the complete tool catalog. CLI capability overrides are stored as `custom`. Persisted policies from another revision are rejected rather than interpreted.
218
218
 
219
219
  `full` removes Machine Bridge's own profile/path/environment/shell denials and makes the complete catalog available to the relay. It does not force a connector host to expose every relayed tool, and the server cannot see the host's final subset. It also does not override operating-system access controls, endpoint security, remote authentication, cloud IAM, `sudo`, or independent MCP-host/platform policy.
220
220
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Generated from `src/shared/policy-contract.json` and `src/shared/tool-catalog.json`. Do not edit this file manually.
4
4
 
5
- Policy revision: **4**. Default profile: **full**.
5
+ Policy revision: **5**. Default profile: **full**.
6
6
 
7
7
  ## Canonical profiles
8
8
 
package/docs/PRIVACY.md CHANGED
@@ -13,7 +13,7 @@ npm run privacy:history
13
13
 
14
14
  `privacy:check` scans tracked and unignored new UTF-8 files and relative names for generic/encrypted/algorithm-specific private-key headers, AWS/GitHub/GitLab/npm/Slack/Google/live-payment/API token forms, JWT-shaped bearer values, embedded-credential URLs, absolute user-home paths, non-example email/`user@host` identifiers, credential-shaped filenames, and locally configured private identifiers. A tracked `.npmrc` is parsed: non-secret repository settings such as `engine-strict=true` are allowed, while authentication/identity keys, environment interpolation, and embedded credentials fail closed. Publication-surface symbolic links are rejected rather than followed. Binary, invalid UTF-8, and files above the bounded scanner limit require explicit manual review instead of being silently skipped. Findings report only file, line, and rule; the matched value is never printed.
15
15
 
16
- `privacy:history` first runs the same current-tree checks, then scans every reachable historical blob path and bounded UTF-8 blob plus every reachable commit message. It catches values that were committed and later deleted. Standard public Dependabot signing trailers are ignored only in that exact commit-message context; the same non-example address remains disallowed in ordinary files. The local `.privacy-denylist` is also applied to history, so a developer may discover legacy identifiers that CI cannot know. Such a finding is real publication history, but ordinary commits cannot erase it.
16
+ `privacy:history` first runs the same current-tree checks, then scans every reachable historical blob path and bounded UTF-8 blob plus every reachable commit message. It catches values that were committed and later deleted. Standard public Dependabot signing trailers are ignored only in that exact commit-message context; the same non-example address remains disallowed in ordinary files. The local `.privacy-denylist` is also applied to history, so a developer may discover older identifiers that CI cannot know. Such a finding is real publication history, but ordinary commits cannot erase it.
17
17
 
18
18
  Git author and committer identity headers are canonical Git metadata rather than blob or commit-message content and are not automatically rejected. Audit them separately with `git log`; changing historical identity metadata requires a coordinated history rewrite and force-update of affected refs.
19
19
 
@@ -36,7 +36,7 @@ Commit and squash-merge subjects follow Conventional Commits:
36
36
  <type>[optional scope][optional !]: <imperative description>
37
37
  ```
38
38
 
39
- Allowed types are `feat`, `fix`, `docs`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `security`, `release`, and `revert`. `feat` and `fix` carry their normal semantic-version meaning. A breaking change uses `!` and explains migration impact in the body or a `BREAKING CHANGE:` footer.
39
+ Allowed types are `feat`, `fix`, `docs`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `security`, `release`, and `revert`. `feat` and `fix` carry their normal semantic-version meaning. A breaking change uses `!` and explains upgrade impact in the body or a `BREAKING CHANGE:` footer.
40
40
 
41
41
  A good change explains both **what changed and why**. Commits should be logically coherent, but intermediate branch history may be amended because the final pull request is squash-merged. Pull-request titles must satisfy the same format because they become the `main` commit subject.
42
42
 
package/docs/TESTING.md CHANGED
@@ -21,7 +21,7 @@ The suite includes:
21
21
  - shared tool-catalog schema, annotation, and profile-inventory checks;
22
22
  - default working-agreement injection without user files, bounded automatic project metadata, script-body non-disclosure, user-global opt-out, repository opt-out rejection, global `model_instructions_file` injection in stdio/remote initialization, hierarchical precedence, `.agents/skills` and `.codex/skills` compatibility discovery, live project/skill rescanning and fingerprints, automatic task ranking/loading with English inflection normalization, bounded Chinese/English intent aliases, capability-name weighting, and selected-skill instruction loading; automatic `package.*` commands with English/Chinese workflow-intent matching and Windows command-shim execution, explicit command override/removal, direct argv handling, timeout ceilings, runtime-keyed routing-telemetry privacy, and execution-profile denial;
23
23
  - concurrent complete-before-visible lock claims, atomic replacement under active readers, malformed-lock grace, snapshot/token-safe reclamation, absolute-age expiry, PID-reuse detection, and bounded startup-lock waiting;
24
- - foreground takeover of active and orphaned background daemons, legacy lock identification from canonicalized process arguments, foreground-process protection, bounded final lock-handoff retry, actual-PID exit waiting, POSIX non-escalating timeout behavior, Windows verified-daemon stop semantics, daemon lock mode/version/process-start metadata, launchd service-target semantics, and silent idempotent duplicate service starts; daemon fixture subprocesses intentionally do not inherit V8 coverage because their purpose is ownership timing rather than code measurement;
24
+ - foreground takeover of active and orphaned background daemons with current service-lock metadata, foreground-process protection, bounded final lock-handoff retry, actual-PID exit waiting, POSIX non-escalating timeout behavior, Windows verified-daemon stop semantics, daemon lock mode/version/process-start metadata, launchd service-target semantics, and silent idempotent duplicate service starts; daemon fixture subprocesses intentionally do not inherit V8 coverage because their purpose is ownership timing rather than code measurement;
25
25
  - fail-closed service lifecycle ordering for provider-stop, all-workspace daemon-stop, and definition removal, including platform/daemon/removal failure injection and normalized macOS/systemd/Windows results;
26
26
  - machine-level browser-broker ownership/client proxying, authenticated extension origin/subprotocol, non-cacheable local pairing, pairing-token non-disclosure, resource-backed upload routing, broker result redaction, pre-registered runtime-handshake listeners, bounded socket/HTTP waits, installed-application discovery caching, and name-based task matching;
27
27
  - relay environment-proxy direct/bypass/agent selection, unsupported proxy rejection, fail-fast invalid configuration, and route observability without endpoint or credential disclosure;
@@ -41,11 +41,11 @@ The suite includes:
41
41
  - real-machine canonical-full sandbox acceptance for outside-workspace I/O, direct/shell execution, full environment inheritance, SSH prerequisites, temporary authorized-key writing, and detached cleanup without external state changes;
42
42
  - deterministic injected atomic-replace failures, sustained transient Windows sharing contention beyond the old retry budget, bounded exponential delay selection, and repeated Windows full-sandbox runs;
43
43
  - canonical named-profile repair and full-only tool exposure parity between local and Worker policy filters;
44
- - managed-job staging/local approval/cancel-before-start, detachment, job-scoped temporary files, resource hash verification/redaction, discard capture, finally execution, descendant-tree escalation, token/snapshot-safe transition locks, runner process identity, plan scrubbing, PID-reuse-safe dead-runner recovery, and legacy PID-file compatibility;
44
+ - managed-job staging/local approval/cancel-before-start, detachment, job-scoped temporary files, resource hash verification/redaction, discard capture, finally execution, descendant-tree escalation, token/snapshot-safe transition locks, runner process identity, plan scrubbing, PID-reuse-safe dead-runner recovery, and rejection of numeric-only runner records;
45
45
  - daemon/startup locking, successfully-read corrupt-JSON recovery, and explicit propagation/preservation of oversized, symbolic-link, permission, and I/O failures;
46
- - guarded state-root removal, unsafe state-root/workspace overlap rejection before creation, all-profile lock/daemon scanning, schema migration, policy-origin persistence, and legacy implicit-default migration;
46
+ - guarded state-root removal, unsafe state-root/workspace overlap rejection before creation, all-profile lock/daemon scanning, strict current-schema validation, corrupt-JSON isolation, and policy-origin persistence;
47
47
  - no filename-based sensitive-file denial under unrestricted policy;
48
- - log redaction, control-character handling, message/field bounds, suppression of both successful and failed per-tool events outside debug, service warning-level configuration, and idempotent bounded migration of legacy log formats;
48
+ - log redaction, control-character handling, message/field bounds, suppression of both successful and failed per-tool events outside debug, service warning-level configuration, current-schema reset, and bounded tail trimming;
49
49
  - deterministic relay connection lifecycle coverage for transport construction/error/deadline, pre-handshake `welcome` validation, authenticated `hello_ack` readiness, identity/version mismatch, retryable Worker handshake errors, fatal protocol errors, autonomous outage-reminder backoff, handshake and heartbeat timeout, brief-outage suppression, sustained-outage escalation, recovery summaries, and supersession;
50
50
  - shared no-follow bounded-file reads for normal files, over-limit data, directories, and symbolic links;
51
51
  - CLI parsing, policy profiles, and client configuration boundaries;