machine-bridge-mcp 0.17.1 → 0.18.1

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 (45) hide show
  1. package/CHANGELOG.md +27 -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-convergence.mjs +20 -0
  34. package/src/local/service.mjs +12 -29
  35. package/src/local/shell.mjs +17 -29
  36. package/src/local/state-inventory.mjs +2 -2
  37. package/src/local/state.mjs +30 -85
  38. package/src/local/tool-executor.mjs +8 -2
  39. package/src/shared/access-contract.json +23 -0
  40. package/src/shared/policy-contract.json +30 -7
  41. package/src/worker/access.ts +46 -0
  42. package/src/worker/account-admin.ts +103 -0
  43. package/src/worker/index.ts +75 -48
  44. package/src/worker/oauth-state.ts +184 -2
  45. package/src/worker/tool-catalog.ts +13 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.1 - 2026-07-14
4
+
5
+ ### Fixed
6
+
7
+ - Wait for launchd service state to converge after a successful `bootout` before deciding that stop or restart failed. The bounded poll handles macOS's asynchronous unload window while still failing closed when the service remains active.
8
+ - Add deterministic coverage for delayed launchd inactivity and for the bounded failure path.
9
+
10
+ ## 0.18.0 - 2026-07-14
11
+
12
+ ### Isolated multi-account authorization
13
+
14
+ - Replace the workspace-wide shared OAuth password with named accounts and four roles: reviewer, editor, operator, and owner. OAuth codes and tokens are bound to one account id, account version, and role; account changes revoke only that account's credentials.
15
+ - Intersect each account role with the connected daemon policy when listing or invoking tools. The Worker rejects unauthorized tools before relay, and every relayed call carries immutable account authorization metadata for a second local enforcement check.
16
+ - Add owner-only account administration for listing, creating, enabling, disabling, changing roles, rotating passwords, and removing accounts. Passwords are generated 256-bit tokens stored as independent salted HMAC-SHA-256 verifiers; arbitrary human-chosen passwords are rejected, plaintext tokens are shown once, and they are not retained in local state.
17
+ - Upgrade the existing Cloudflare Worker and Durable Object in place. A one-time release operation converts the current shared credential into the initial owner account and annotates existing OAuth codes and tokens without changing their keys or the global token version, preserving the existing ChatGPT connector authorization.
18
+
19
+ ### Runtime reliability and diagnostics
20
+
21
+ - Cancel all relay-owned local calls immediately when the relay disconnects. Process execution now rejects cancellation before a child emits `close`, while process ownership remains tracked until actual exit, preventing permanently active calls without losing process cleanup.
22
+ - Replace prefix-only command capture with bounded beginning-and-end retention. Long stdout and stderr report exact omitted byte counts and preserve the diagnostic tail where test and compiler failures normally appear.
23
+ - Consume rejected authenticated MCP request bodies before returning 401, preventing workerd request-stream exceptions after per-account token revocation.
24
+
25
+ ### Breaking state and maintenance model
26
+
27
+ - Advance local state to schema 6, the state-root marker to schema 2, and policy to revision 5. Final runtime code accepts only the current formats; valid obsolete state is rejected instead of migrated, while syntactically corrupt JSON is isolated and rebuilt as current empty state.
28
+ - Remove shared-password CLI flags, policy migration branches, old daemon-lock interpretation, numeric managed-job PID compatibility, and old log-format archival. The one-time in-place release operation updates live state and deletes obsolete artifacts before the final runtime is installed.
29
+
3
30
  ## 0.17.1 - 2026-07-14
4
31
 
5
32
  ### Installation and first use
package/README.md CHANGED
@@ -34,7 +34,7 @@ A newly selected workspace starts with the maximum-permission `full` profile for
34
34
  - tool results may return absolute paths;
35
35
  - child processes inherit the complete parent environment.
36
36
 
37
- Policy state records whether it came from the default, an explicit named profile, or custom overrides. Named profiles are canonical contracts: a stored `full` profile is repaired on load to enable writes, shell execution, unrestricted paths, full parent environment, absolute path output, and the complete tool catalog. Any explicit per-capability narrowing is stored as `custom`. Policy revision 4 normalizes named profiles from the shared contract, refreshes default/migrated state, preserves explicit restrictive/custom profiles, and applies compound tool requirements consistently in the local daemon and Worker.
37
+ Policy state records whether it came from the default, an explicit named profile, or custom overrides. Named profiles are canonical contracts: a stored `full` profile always enables writes, shell execution, unrestricted paths, the full parent environment, absolute path output, and the complete tool catalog. Any explicit per-capability narrowing is stored as `custom`. Policy revision 5 is the only accepted persisted policy format and applies compound tool requirements consistently in the local daemon and Worker.
38
38
 
39
39
  | Profile | File edits | Direct argv processes | Shell commands | Filesystem scope | Process environment |
40
40
  |---|---:|---:|---:|---|---|
@@ -51,7 +51,7 @@ For prerequisites, first remote deployment, ChatGPT connection, stdio configurat
51
51
 
52
52
  Node.js 26 or newer and npm 12 or newer are required. The repository pins the active development versions in `.node-version`, `.nvmrc`, and `packageManager`; project installs fail on older Node runtimes.
53
53
 
54
- Use a new temporary directory for the bootstrap. The existing `npm`/`npx` front-end may inspect nearby project metadata before it launches the requested npm version; starting from an empty directory prevents an unrelated legacy `devEngines` declaration from blocking the bootstrap. The actual installation is then executed by the pinned npm 12 CLI rather than whichever npm happens to be first on `PATH`.
54
+ Use a new temporary directory for the bootstrap. The existing `npm`/`npx` front-end may inspect nearby project metadata before it launches the requested npm version; starting from an empty directory prevents an unrelated outdated `devEngines` declaration from blocking the bootstrap. The actual installation is then executed by the pinned npm 12 CLI rather than whichever npm happens to be first on `PATH`.
55
55
 
56
56
  macOS/Linux:
57
57
 
@@ -81,7 +81,7 @@ npm --version
81
81
  machine-mcp doctor
82
82
  ```
83
83
 
84
- `Unknown cli config "--allow-scripts"` proves the Machine Bridge install was executed by npm 11 or older rather than the required npm 12. `Invalid property "node"` or `Invalid property "devEngines.node"` means that npm parsed a legacy `devEngines` object; the npm debug log is required to identify which package supplied it. The empty-directory, pinned-npm procedure avoids relying on that old parser. If `npm --version` still reports an older version after the bootstrap, reopen the terminal and rerun `machine-mcp doctor`.
84
+ `Unknown cli config "--allow-scripts"` proves the Machine Bridge install was executed by npm 11 or older rather than the required npm 12. `Invalid property "node"` or `Invalid property "devEngines.node"` means that npm parsed an outdated `devEngines` object; the npm debug log is required to identify which package supplied it. The empty-directory, pinned-npm procedure avoids relying on that old parser. If `npm --version` still reports an older version after the bootstrap, reopen the terminal and rerun `machine-mcp doctor`.
85
85
 
86
86
  Recent npm releases may otherwise warn that Wrangler's native dependencies (`esbuild`, `workerd`, and `sharp`) have install scripts awaiting approval. The scoped command approves the reviewed native script names that npm 12 evaluates during global resolution while `--omit=optional` keeps optional `fsevents` out of the installed runtime. `fsevents` is used for development-time filesystem watching rather than Machine Bridge runtime or deployment. Omitting `--omit=optional` can therefore produce a harmless blocked-script warning for `fsevents@2.3.3`; use the documented command rather than changing global npm policy. `machine-mcp doctor` remains the authoritative runtime check.
87
87
 
@@ -109,7 +109,8 @@ On first remote start, the CLI:
109
109
  4. deploys a per-workspace Worker;
110
110
  5. installs a platform-native login service unless `--no-autostart` is used;
111
111
  6. starts an outbound-only daemon connection;
112
- 7. prints the Remote MCP URL and connection password.
112
+ 7. creates the initial `owner` account when needed and prints its password once;
113
+ 8. prints the Remote MCP URL.
113
114
 
114
115
  A normal `machine-mcp` invocation is a foreground start: it remains attached to the terminal and prints `info` logs. Startup and other state-changing operations use an owner-token/process-identity lock and wait up to 30 seconds for an ordinary concurrent startup to finish instead of failing on a brief launchd/systemd race. After a global package upgrade, the CLI unloads the platform service and also checks the workspace lock for a detached/orphan `--daemon-only` process that the service manager no longer tracks. It terminates only a process whose PID, process start time, entrypoint, canonical workspace, canonical state root, and daemon-only arguments all match, waits up to 15 seconds for the PID and lock to disappear, and then starts the installed version in the foreground. A genuine foreground or unverifiable conflict is left untouched and reported with actionable guidance. To run only in the background, use `machine-mcp service start`; inspect its owner-only logs under `~/.local/state/machine-bridge-mcp/logs/`.
115
116
 
@@ -121,20 +122,19 @@ machine-mcp --verbose
121
122
 
122
123
  The global install replaces files on disk but cannot hot-reload an already running Node process; the foreground command performs the bounded service and orphan-daemon takeover. Keep `--omit=optional` in the installation step to prevent the development-only optional `fsevents` package from producing a blocked-install-script warning. If takeover fails, run `machine-mcp service status` first; it reports the platform service and workspace daemon separately. Then run `machine-mcp service stop` and retry.
123
124
 
124
- Use the printed values in the MCP client:
125
+ Use the printed URL in the MCP client:
125
126
 
126
127
  ```text
127
128
  MCP Server URL: https://<worker>.<account>.workers.dev/mcp
128
- MCP connection password: mcp_password_...
129
129
  ```
130
130
 
131
- The remote authorization flow uses an authorization code, PKCE S256, exact redirect/resource binding, expiring access tokens stored as hashes, and a token-version value for bulk revocation. After password approval, the Worker constructs the registered callback with the URL API and returns `303 See Other`, so OAuth response parameters are encoded and the browser performs a GET to the callback.
131
+ During OAuth authorization, enter the name and password of a Machine Bridge account. The first start creates `owner` and prints its generated password once. Additional accounts are managed with `machine-mcp account`. The authorization flow uses PKCE S256, exact redirect/resource binding, expiring hashed access tokens, per-account version checks, and a deployment-wide token version for emergency bulk revocation.
132
132
 
133
133
  ### Multiple clients and accounts
134
134
 
135
- One Worker can hold several OAuth client registrations and access tokens, so the same trusted owner or team can connect more than one MCP application or ChatGPT account. This is **multi-client access, not isolated multi-account tenancy**: the current release uses one workspace connection password, one workspace policy, and one active daemon authority. It has no principal records, per-account roles, targeted account revocation, or account-specific audit boundary.
135
+ One Worker supports several named accounts and OAuth clients. Accounts have independent passwords, roles, active state, versions, codes, and tokens. The roles are `reviewer`, `editor`, `operator`, and `owner`; their effective authority is intersected with the connected daemon policy and enforced in both the Worker and local runtime. Role changes, suspension, password rotation, and removal revoke only the affected account.
136
136
 
137
- Do not share a `full` bridge with mutually untrusted users. Use a separate bridge and preferably a separate low-privilege OS account, container, or VM for each trust domain. See [Multi-client, multi-account, and tenancy architecture](docs/MULTI_ACCOUNT.md) for the current support matrix and the proposed principal/grant design.
137
+ This is application-level authorization, not kernel or browser-profile isolation. All accounts reach one daemon running as one OS user. Use separate Workers and preferably separate low-privilege OS accounts, containers, or VMs for mutually untrusted users or hard tenant isolation. See [Multi-account authorization and tenancy](docs/MULTI_ACCOUNT.md).
138
138
 
139
139
  ## Optional local stdio MCP
140
140
 
@@ -218,7 +218,7 @@ Foreground logging defaults to human-readable text. Installed background service
218
218
 
219
219
  ## Policy controls
220
220
 
221
- Policy revision 4 is defined once in `src/shared/policy-contract.json`. The local runtime, Worker advertisement filter, manager-level defense-in-depth checks, tests, and generated [policy reference](docs/POLICY_REFERENCE.md) consume that same contract. A custom policy is authorized by capabilities rather than its display name; compound classes such as `write+direct-exec` require every capability.
221
+ Policy revision 5 is defined once in `src/shared/policy-contract.json`. The local runtime, Worker advertisement filter, manager-level defense-in-depth checks, tests, and generated [policy reference](docs/POLICY_REFERENCE.md) consume that same contract. A custom policy is authorized by capabilities rather than its display name; compound classes such as `write+direct-exec` require every capability.
222
222
 
223
223
  The default is `full`. Narrow or customize it with explicit flags:
224
224
 
@@ -436,9 +436,9 @@ machine-mcp status
436
436
  machine-mcp doctor
437
437
  machine-mcp rotate-secrets
438
438
  machine-mcp resource add|list|check|remove
439
+ machine-mcp account list|add|role|enable|disable|rotate-password|remove
439
440
  machine-mcp browser status|setup|pair|path
440
441
  machine-mcp job submit|inspect|approve|list|read|cancel
441
- machine-mcp --print-mcp-credentials
442
442
  machine-mcp uninstall [--keep-worker] [--yes]
443
443
  ```
444
444
 
@@ -458,10 +458,9 @@ The service definition contains neither credentials nor a duplicate policy. It l
458
458
 
459
459
  ```sh
460
460
  machine-mcp rotate-secrets
461
- machine-mcp --print-mcp-credentials
462
461
  ```
463
462
 
464
- Rotation stops the installed service, refuses to proceed while another foreground daemon owns the workspace lock, rotates the MCP password, daemon secret, and OAuth token version, and requires redeployment. Rotated values are redacted by default; only the client connection password can be printed through the explicit reconnect flag. Previously issued access tokens then fail validation.
463
+ Rotation stops the installed service, refuses to proceed while another foreground daemon owns the workspace lock, and rotates the account-administration secret, daemon secret, and deployment-wide OAuth token version. It invalidates every account access token and requires all clients to authorize again. Use `machine-mcp account rotate-password NAME` for targeted password rotation without affecting other accounts.
465
464
 
466
465
  ## State and observability
467
466
 
package/SECURITY.md CHANGED
@@ -15,7 +15,7 @@ Do not open a public issue for an undisclosed vulnerability. Use GitHub private
15
15
  - expected and observed impact;
16
16
  - whether credentials, filesystem data, process authority, or network access were exposed.
17
17
 
18
- Do not include live MCP passwords, daemon secrets, OAuth tokens, Cloudflare credentials, private keys, or unrelated local files. Rotate credentials used in a reproduction.
18
+ Do not include live account passwords, account-administration secrets, daemon secrets, OAuth tokens, Cloudflare credentials, private keys, or unrelated local files. Rotate credentials used in a reproduction.
19
19
 
20
20
  ## Repository and documentation privacy
21
21
 
@@ -36,13 +36,13 @@ An authorized client can invoke every tool exposed by the selected profile and r
36
36
 
37
37
  The Worker is a remote authentication and relay boundary. The local runtime is the filesystem and process boundary. Stdio bypasses the Worker and relies on local process/configuration trust.
38
38
 
39
- ### Multiple clients are not isolated accounts
39
+ ### Accounts are authorization boundaries, not OS sandboxes
40
40
 
41
- The current remote design accepts multiple OAuth client registrations and can issue multiple access tokens, but it has one per-workspace connection password and one workspace policy. An OAuth `client_id` identifies client software and redirect URIs; it is not a human or service principal. Different ChatGPT accounts or MCP applications that authorize with the same password therefore enter the same trust domain and receive the same Machine Bridge authority.
41
+ Remote mode supports named accounts with independent passwords, roles, active state, versions, OAuth codes, and access tokens. The roles `reviewer`, `editor`, `operator`, and `owner` map to the local review, edit, agent, and full policy profiles. The Worker intersects the account role with the connected daemon policy, and the local runtime validates the account role again before dispatch. Account suspension, role changes, password rotation, and removal revoke only that account.
42
42
 
43
- The current release has no per-account role, suspension, membership expiry, targeted principal revocation, or account-specific audit boundary. Secret rotation invalidates all outstanding access tokens for the workspace. Do not present this behavior as multi-user tenancy.
43
+ An OAuth `client_id` still identifies client software and redirect URIs; it is not an account. One account can authorize several clients, and one client can be authorized by several accounts.
44
44
 
45
- Mutually untrusted users must use separate bridge instances and external isolation appropriate to the authority exposed: a dedicated low-privilege OS account, container, or VM, a narrow workspace, an independent state root and Worker credential set, and the narrowest useful profile. Worker-side account records alone cannot isolate direct processes, shells, browser sessions, Accessibility actions, credential stores, or network authority inherited from the local OS user. See [Multi-client, multi-account, and tenancy architecture](docs/MULTI_ACCOUNT.md).
45
+ All accounts ultimately reach one daemon running as one OS user. Application roles cannot isolate direct processes, shells, browser sessions, Accessibility actions, credential stores, or network authority inherited from that user. Mutually untrusted users or hard tenant boundaries require separate bridge instances and external isolation: dedicated low-privilege OS accounts, containers, or VMs, narrow workspaces, independent state roots, and separate Workers. See [Multi-account authorization and tenancy](docs/MULTI_ACCOUNT.md).
46
46
 
47
47
  ## Profiles are capability sets, not sandboxes
48
48
 
@@ -90,7 +90,7 @@ Text and file resources can be injected locally so their contents do not appear
90
90
 
91
91
  ## Shared authorization contract
92
92
 
93
- Policy revision 4 is a single source of truth shared by the local daemon and Worker. The catalog availability class controls both advertisement and execution; manager-level checks invoke the same gate as defense in depth. Custom policies are evaluated by capabilities, so a label cannot impersonate `full`. Persistent job start requires both write and direct-execution authority; read-only job/resource inventory does not imply mutation authority.
93
+ Policy revision 5 is a single source of truth shared by the local daemon and Worker. The catalog availability class controls both advertisement and execution; manager-level checks invoke the same gate as defense in depth. Custom policies are evaluated by capabilities, so a label cannot impersonate `full`. Persistent job start requires both write and direct-execution authority; read-only job/resource inventory does not imply mutation authority.
94
94
 
95
95
  Transport-visible failures use stable error codes and retryability. Unexpected implementation errors are not downgraded to ordinary resource state, and raw internal exceptions are not returned by the Worker. This reduces both authorization drift and accidental disclosure through error strings.
96
96
 
@@ -124,11 +124,11 @@ A custom state root is rejected if it overlaps the selected workspace in either
124
124
 
125
125
  ## Credential exposure
126
126
 
127
- Local state contains the MCP connection password and daemon secret. State, lock, temporary secret, runtime, and service-log files use owner-only permissions where supported. State writes are flushed and atomically replaced. A read/type/permission/size/symbolic-link failure is not treated as empty or corrupt state; only successfully read invalid JSON is moved to a bounded corrupt backup.
127
+ Local state contains the account-administration secret, daemon secret, and deployment-wide token version. Account passwords are generated 256-bit tokens and are not stored locally; the Worker stores only independent salted HMAC-SHA-256 verifiers. The fixed high-entropy token format is mandatory, so the verifier does not rely on a CPU-intensive human-password KDF inside the Worker request budget. State, lock, temporary secret, runtime, and service-log files use owner-only permissions where supported. State writes are flushed and atomically replaced. A read/type/permission/size/symbolic-link failure is not treated as empty or corrupt state; only successfully read invalid JSON is moved to a bounded corrupt backup.
128
128
 
129
129
  Logs recursively redact known credential fields, private-key headers, npm/GitHub/GitLab/Slack/Google/AWS/live-payment/API token forms, JWT-shaped bearer values, embedded-credential URLs, email addresses, user-home paths, and control characters. These patterns are defense in depth; unknown, transformed, split, encrypted, or application-specific secret forms can still pass through an explicitly requested tool result.
130
130
 
131
- First-run or explicit reconnect output can intentionally display the MCP connection password. JSON output and standalone secret rotation redact credentials by default; printing requires the explicit reconnect flag. The daemon secret is never printed in full. Avoid shared terminal logs, shell recordings, screenshots, CI output, or support tickets.
131
+ A new deployment prints the generated initial owner password once. Account creation and targeted password rotation also print the generated password once. JSON output includes a generated password only for the command that created it; status, diagnostics, and secret rotation never reveal stored secrets. The account-administration and daemon secrets are never printed. Avoid shared terminal logs, shell recordings, screenshots, CI output, or support tickets.
132
132
 
133
133
  The default `full` profile passes the complete parent environment. Narrower profiles replace HOME, temp, and common cache paths and do not pass arbitrary parent variables. The isolated mode reduces accidental environment-secret leakage; it does not prevent code from explicitly accessing known resources.
134
134
 
@@ -160,9 +160,9 @@ Runner diagnostic logs are owner-only and do not receive child stdout/stderr. St
160
160
 
161
161
  ## OAuth and public endpoints
162
162
 
163
- Remote mode uses authorization code flow with PKCE S256, exact redirect/resource/client binding, expiring authorization codes and access tokens, hashed token storage, token-version revocation, and bounded dynamic client registration. Client registrations and tokens may coexist, but all authorizations currently share one workspace credential and authority; there is no principal-aware account model. Successful consent constructs the registered callback through the URL API and returns `303 See Other`; response parameters are encoded rather than concatenated into an unchecked header string.
163
+ Remote mode uses authorization code flow with PKCE S256, exact redirect/resource/client binding, expiring authorization codes and access tokens, hashed token storage, per-account version checks, deployment-wide token-version revocation, and bounded dynamic client registration. Authorization codes and tokens are bound to one account ID, account version, and role. Successful consent constructs the registered callback through the URL API and returns `303 See Other`; response parameters are encoded rather than concatenated into an unchecked header string.
164
164
 
165
- The authorization page displays the validated client name and redirect URI. Enter the connection password only after initiating the connection and recognizing both values.
165
+ The authorization page displays the validated client name and redirect URI. Enter an account name and password only after initiating the connection and recognizing both values.
166
166
 
167
167
  Password failures and registrations are limited by deployment-keyed HMAC source identity. Browser requests are same-origin unless an exact origin is listed in `MBM_ALLOWED_ORIGINS`; loopback OAuth redirect permission does not grant browser-origin access.
168
168
 
@@ -199,13 +199,13 @@ No logging policy can prevent data from being returned to a client that explicit
199
199
  For sensitive review:
200
200
 
201
201
  ```sh
202
- machine-mcp --workspace /narrow/project --profile review --no-print-credentials
202
+ machine-mcp --workspace /narrow/project --profile review
203
203
  ```
204
204
 
205
205
  For controlled editing without execution:
206
206
 
207
207
  ```sh
208
- machine-mcp --workspace /narrow/project --profile edit --no-print-credentials
208
+ machine-mcp --workspace /narrow/project --profile edit
209
209
  ```
210
210
 
211
211
  Also:
@@ -217,7 +217,7 @@ Also:
217
217
  - treat an unreadable or malformed live lock as an incident to inspect, not a file to delete blindly;
218
218
  - select `agent`, `edit`, or `review` instead of the default `full` when broad authority is unnecessary;
219
219
  - inspect client names and OAuth redirect URIs;
220
- - rotate secrets after suspected disclosure;
220
+ - rotate one account password after a targeted disclosure, or rotate deployment secrets after a bridge-wide incident;
221
221
  - inspect `status`, `doctor`, and service status;
222
222
  - register credential files as local resources instead of reading them into a model conversation;
223
223
  - use `capture_output: "discard"` for credential-consuming steps and idempotent finally steps for cleanup;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Machine Bridge Browser",
4
- "version": "0.17.1",
4
+ "version": "0.18.1",
5
5
  "description": "Connects the current Chromium browser profile to the local Machine Bridge runtime for user-authorized automation.",
6
6
  "permissions": [
7
7
  "tabs",
@@ -30,5 +30,5 @@
30
30
  "action": {
31
31
  "default_title": "Machine Bridge Browser"
32
32
  },
33
- "version_name": "0.17.1"
33
+ "version_name": "0.18.1"
34
34
  }
@@ -86,9 +86,9 @@ Local resource registrations remain in owner-only state and are reloaded for eve
86
86
 
87
87
  ### Canonical policy contracts
88
88
 
89
- Policy revision 4 is loaded from one shared JSON contract by both local and Worker policy evaluators. Tool advertisement and execution authorization use catalog availability classes; managers receive the same authorizer instead of reimplementing profile conditionals. Read-only job/resource inspection is `always`, job cancellation is write-gated, and starting a persistent job requires `write+direct-exec`.
89
+ Policy revision 5 is loaded from one shared JSON contract by both local and Worker policy evaluators. Tool advertisement and execution authorization use catalog availability classes; managers receive the same authorizer instead of reimplementing profile conditionals. Read-only job/resource inspection is `always`, job cancellation is write-gated, and starting a persistent job requires `write+direct-exec`.
90
90
 
91
- Named profiles are normalized to their complete capability sets. In particular, `full` always means write enabled, shell execution, unrestricted paths, complete parent environment, absolute-path display, and every catalog tool. CLI flags that alter an individual capability deliberately change the profile identity to `custom`. Policy revision 4 repairs stale or manually edited named-profile fields rather than allowing a misleading partially restricted `full` label, and applies compound availability requirements from the shared contract.
91
+ Named profiles are normalized to their complete capability sets. In particular, `full` always means write enabled, shell execution, unrestricted paths, complete parent environment, absolute-path display, and every catalog tool. CLI flags that alter an individual capability deliberately change the profile identity to `custom`. Policy revision 5 defines the only accepted persisted policy shape. Named profiles are normalized to their canonical capability sets, and persisted data from another revision is rejected rather than interpreted. Compound availability requirements come from the shared contract.
92
92
 
93
93
  The full-only `generate_ssh_key_resource` operation is implemented locally. The Worker only filters and relays its shared catalog definition. Local generation uses `ssh-keygen`, verifies public/private correspondence, registers the private file through the same owner-only state transaction as the CLI, and rolls back a newly created pair if state persistence fails.
94
94
 
@@ -109,7 +109,7 @@ All requests for a deployed Worker route to one named Durable Object. It owns:
109
109
 
110
110
  The Worker verifies OAuth, validates MCP envelopes and optional protocol headers, converts `tools/call` into WebSocket messages, correlates cancellation by access-token hash and JSON-RPC ID, and formats text/structured/image results. It has no local filesystem or process API.
111
111
 
112
- The current OAuth store is multi-client but not principal-aware. `client_id` identifies an MCP application/installation; it does not identify a human or service account. Every successful authorization uses the same per-workspace connection password and receives the same workspace policy ceiling. Isolated account support therefore requires explicit principals, memberships, named grants, targeted revocation, and dual Worker/local enforcement rather than treating client registrations as users. The recommended design retains one bridge-specific Durable Object and one local runtime per workspace/trust domain; see [MULTI_ACCOUNT.md](MULTI_ACCOUNT.md).
112
+ The OAuth store separates client registrations from named accounts. A `client_id` identifies an MCP application and redirect URIs; account records identify the authorized human or service identity. Codes and tokens bind client ID, account ID, account version, role, scope, resource, and expiration. The Worker intersects the role with the active daemon policy before advertising or relaying tools, and the local runtime validates the relayed role again. One bridge-specific Durable Object and one local runtime remain the normal topology for a workspace/trust domain; see [MULTI_ACCOUNT.md](MULTI_ACCOUNT.md).
113
113
 
114
114
  The daemon attachment deliberately omits workspace path/name/hash and process ID. Explicit authenticated tools may return workspace metadata according to local path-display policy.
115
115
 
@@ -147,7 +147,7 @@ Remote OAuth currently determines which registered client token may call tools;
147
147
  1. The MCP client discovers protected-resource and authorization-server metadata.
148
148
  2. It dynamically registers bounded redirect metadata.
149
149
  3. The Worker validates authorization parameters before displaying a password form.
150
- 4. The user verifies client name and redirect URI and enters the connection password.
150
+ 4. The user verifies client name and redirect URI and enters a Machine Bridge account name and password.
151
151
  5. The Worker creates a five-minute code bound to client, redirect, resource, scope, and PKCE challenge.
152
152
  6. A valid verifier exchanges the one-time code for an expiring bearer token; only its hash is stored.
153
153
  7. The MCP client initializes and negotiates a supported protocol version. When the daemon advertises `session_bootstrap`, the Worker requests bounded local instructions and appends them to the initialization result; failure degrades to static instructions.
@@ -226,7 +226,7 @@ Local state and global config are owner-only, versioned, and size-bounded. Share
226
226
 
227
227
  Process locks contain purpose, workspace, ownership token, lock time, and process start time. Stale removal rechecks device/inode/size/mtime and token so an old observer cannot delete a replacement lock. Recent malformed claims receive a grace period. Startup/state operations wait a bounded interval; daemon and runner identity remain process-lifetime locks. Managed-job transition and recovery locks use the same ownership/snapshot principles and support an atomic runner handoff.
228
228
 
229
- Only successfully read but syntactically invalid JSON is moved to a bounded `.corrupt-*` backup. Permission, type, symbolic-link, size, encoding, and I/O failures propagate. A state root must be disjoint from its selected workspace. Resource paths are omitted from redacted status output. Custom roots are adopted only when empty or recognizable as legacy Machine Bridge state.
229
+ Only successfully read but syntactically invalid JSON is moved to a bounded `.corrupt-*` backup. Permission, type, symbolic-link, size, encoding, and I/O failures propagate. A state root must be disjoint from its selected workspace. Resource paths are omitted from redacted status output. A custom root is initialized only when empty and must contain the current state marker on subsequent use. Valid state from another schema is rejected; syntactically invalid JSON is isolated as a bounded corrupt backup and rebuilt as current empty state.
230
230
 
231
231
  Active managed jobs persist an owner-only plan, status, runner process identity, and bounded runner diagnostics. Terminal jobs delete the full plan and retain only bounded status/redacted results for up to seven days. This balances crash cleanup with minimization of scripts, stdin, argv, environment overrides, and resource source paths.
232
232
 
package/docs/AUDIT.md CHANGED
@@ -7,7 +7,7 @@ This document records the cross-cutting audit initiated for version 0.12.0, the
7
7
 
8
8
  This review confirmed that earlier safety controls existed but were distributed across duplicated policy checks, transport-specific pending maps, message-regex errors, and oversized composition files. It reproduced a production-visible Worker defect where a completed client JSON-RPC request key could remain indexed and cause unrelated later calls to fail as duplicate in-flight IDs. Pending calls now use one atomic double-index registry whose terminal `take` operation clears the internal ID, request key, and timer before resolution or rejection; success, daemon error, cancellation, timeout, send failure, and socket loss have behavior coverage.
9
9
 
10
- Policy revision 4 replaces local/Worker/manager interpretations with one contract. The audit found a concrete compound-ACL error: `start_job` was advertised by direct-exec alone even though it creates persistent files. It now requires write plus direct execution; read-only job/resource inspection no longer inherits mutation requirements. Typed errors, shared middleware, explicit runtime lifecycle, process ownership, structured JSON logs, and local/Worker metrics replace ad hoc message parsing and invisible pending state.
10
+ Policy revision 5 supplies one authorization contract to the local runtime, Worker role intersection, and manager-level checks. The audit found a concrete compound-ACL error: `start_job` was advertised by direct-exec alone even though it creates persistent files. It now requires write plus direct execution; read-only job/resource inspection no longer inherits mutation requirements. Typed errors, shared middleware, explicit runtime lifecycle, process ownership, structured JSON logs, and local/Worker metrics replace ad hoc message parsing and invisible pending state.
11
11
 
12
12
  The composition roots were reduced by extracting filesystem, process, Git, CLI parsing/admin, ranking, managed-job validation, browser protocol/pairing, Worker HTTP, OAuth, policy, errors, pending calls, and observability. Executable architecture limits prevent these responsibilities from returning. During extraction, a missing local ESM binding was initially hidden by a broad catch and surfaced only as `resource_unavailable`; the catch now rethrows unknown faults, and the stdio generated-resource test exercises the complete state reload path.
13
13
 
@@ -47,7 +47,7 @@ The review also found three governance gaps not represented by the original fail
47
47
  - Workflow actions used movable major-version tags. They are now pinned to immutable official commit SHAs, with an executable invariant.
48
48
  - Repository privacy checks covered only the current tree. Package audit now scans reachable historical paths, bounded UTF-8 blobs, and commit messages; deleted credential fixtures fail without echoing their values.
49
49
 
50
- No generic active credential pattern was found in the current tree or reachable history after excluding standard public automation trailers. The developer-local denylist does match legacy historical identifiers, and the Git metadata audit found a legacy non-noreply author/committer identity. These are identity/privacy metadata rather than active credentials. Removing them would require a coordinated history rewrite and force-update of affected refs, which is intentionally not performed as an incidental code fix.
50
+ No generic active credential pattern was found in the current tree or reachable history after excluding standard public automation trailers. The developer-local denylist does match historical identifiers, and the Git metadata audit found an older non-noreply author/committer identity. These are identity/privacy metadata rather than active credentials. Removing them would require a coordinated history rewrite and force-update of affected refs, which is intentionally not performed as an incidental code fix.
51
51
 
52
52
  ## Scope
53
53
 
@@ -77,7 +77,7 @@ The runtime now:
77
77
  - gives a recent malformed lock a grace period rather than deleting a possibly in-progress claim;
78
78
  - waits a bounded interval for ordinary startup/state operations instead of failing immediately.
79
79
 
80
- Managed-job transition, recovery, and runner identity follow the same principles while retaining compatibility with legacy numeric PID files.
80
+ Managed-job transition, recovery, and runner identity follow the same ownership and process-start-time rules. Numeric-only runner records are rejected.
81
81
 
82
82
  ### Service lifecycle
83
83
 
package/docs/CLIENTS.md CHANGED
@@ -122,9 +122,9 @@ Run:
122
122
  machine-mcp --workspace /path/to/project
123
123
  ```
124
124
 
125
- Enter the printed `/mcp` URL in the remote MCP connector. During OAuth authorization, verify the displayed client name and redirect URI before entering the connection password.
125
+ Enter the printed `/mcp` URL in the remote MCP connector. During OAuth authorization, verify the displayed client name and redirect URI before entering a Machine Bridge account name and password.
126
126
 
127
- Several OAuth clients and access tokens can coexist, but the current release does not model separate human/service principals. Every authorization that uses the shared workspace password receives the same workspace authority. Do not treat separate ChatGPT accounts as isolated Machine Bridge accounts; see [MULTI_ACCOUNT.md](MULTI_ACCOUNT.md).
127
+ Several OAuth clients and named accounts can coexist. Accounts have independent passwords, roles, active state, versions, and targeted revocation. Their effective tool sets are intersected with the connected daemon policy. All accounts still share one daemon and OS user, so hard tenant isolation requires separate deployments; see [MULTI_ACCOUNT.md](MULTI_ACCOUNT.md).
128
128
 
129
129
  ## Profile guidance
130
130
 
@@ -163,7 +163,8 @@ The first start performs these operations:
163
163
  4. deploys a Worker and Durable Object for that workspace;
164
164
  5. installs a platform-native login service unless `--no-autostart` is supplied;
165
165
  6. starts an outbound authenticated WebSocket from the local daemon to the Worker;
166
- 7. prints the remote `/mcp` URL and a connection password.
166
+ 7. creates the initial `owner` account when no account exists and prints its generated password once;
167
+ 8. prints the remote `/mcp` URL.
167
168
 
168
169
  The foreground command remains attached to the terminal. Keep it running while testing. The remote Worker cannot execute local tools when no authenticated daemon is connected.
169
170
 
@@ -182,14 +183,15 @@ machine-mcp --workspace /path/to/project --no-autostart
182
183
 
183
184
  ## 7. Connect ChatGPT
184
185
 
185
- Machine Bridge prints values in this form:
186
+ Machine Bridge prints the remote endpoint and, only when creating the first account, a one-time owner password:
186
187
 
187
188
  ```text
188
189
  MCP Server URL: https://<worker>.<account>.workers.dev/mcp
189
- MCP connection password: mcp_password_...
190
+ Account: owner
191
+ Password: account_password_...
190
192
  ```
191
193
 
192
- Treat the connection password as a credential. Do not place it in a repository, issue, screenshot, chat message, shell history note, or shared document.
194
+ Save the generated password in a password manager. It is not stored locally or shown again. Do not place it in a repository, issue, screenshot, chat message, shell history note, or shared document.
193
195
 
194
196
  Current ChatGPT developer-mode flow, as documented in OpenAI's [Connect from ChatGPT](https://developers.openai.com/apps-sdk/deploy/connect-chatgpt) guide:
195
197
 
@@ -199,7 +201,7 @@ Current ChatGPT developer-mode flow, as documented in OpenAI's [Connect from Cha
199
201
  4. Enter a descriptive name and the exact printed `/mcp` URL.
200
202
  5. Start the connection.
201
203
  6. On the Machine Bridge authorization page, verify the displayed client name, redirect URI, and resource.
202
- 7. Enter the printed connection password only after those values are recognized.
204
+ 7. Enter the Machine Bridge account name and password only after those values are recognized.
203
205
  8. Create a new chat and enable the app for that conversation.
204
206
 
205
207
  ChatGPT navigation labels can change. The invariant is that the client must connect to the public `/mcp` endpoint and complete the OAuth authorization page served by the Worker.
@@ -273,7 +275,7 @@ machine-mcp doctor
273
275
  machine-mcp workspace show
274
276
  machine-mcp service status
275
277
  machine-mcp browser status
276
- machine-mcp --print-mcp-credentials
278
+ machine-mcp account list
277
279
  ```
278
280
 
279
281
  Useful operational actions:
@@ -282,11 +284,13 @@ Useful operational actions:
282
284
  machine-mcp service stop
283
285
  machine-mcp service start
284
286
  machine-mcp rotate-secrets
287
+ machine-mcp account add alice reviewer
288
+ machine-mcp account rotate-password alice
285
289
  machine-mcp resource list
286
290
  machine-mcp job list
287
291
  ```
288
292
 
289
- `rotate-secrets` invalidates previously issued OAuth access tokens and requires clients to reconnect. It is a whole-workspace revocation mechanism in the current release.
293
+ `rotate-secrets` invalidates every account access token and requires all clients to authorize again. For targeted revocation, disable an account, change its role, rotate its password, or remove it.
290
294
 
291
295
  ## 12. Work with more than one workspace
292
296
 
@@ -315,7 +319,7 @@ rm -rf "$install_dir"
315
319
  machine-mcp --workspace /path/to/project --verbose
316
320
  ```
317
321
 
318
- The foreground start performs bounded takeover of an old verified daemon when safe. If it refuses:
322
+ The foreground start performs bounded takeover of an earlier verified daemon when safe. If it refuses:
319
323
 
320
324
  ```sh
321
325
  machine-mcp service status
@@ -397,4 +401,4 @@ Uninstall is fail-closed. If a verified daemon, active managed job, unreadable l
397
401
 
398
402
  ## 16. Before sharing access
399
403
 
400
- The current release supports multiple OAuth client registrations but not isolated multi-user tenancy. Different ChatGPT accounts can technically authorize against the same Worker if they know the shared connection password, but they receive the same workspace authority and cannot be managed as independent principals. Read [MULTI_ACCOUNT.md](MULTI_ACCOUNT.md) before sharing a deployment.
404
+ The current release supports named accounts with targeted revocation and four roles: `reviewer`, `editor`, `operator`, and `owner`. Roles are enforced in the Worker and local runtime, but all accounts still share one daemon and OS user. Use separate deployments and external isolation for mutually untrusted users or hard tenant boundaries. Read [MULTI_ACCOUNT.md](MULTI_ACCOUNT.md) before sharing a deployment.
package/docs/LOGGING.md CHANGED
@@ -79,7 +79,7 @@ The implementation omits:
79
79
  - stdin, stdout, and stderr;
80
80
  - file, patch, image, and temporary-file content;
81
81
  - OAuth request bodies;
82
- - connection passwords, daemon secrets, authorization codes, and access tokens;
82
+ - account passwords, account-administration secrets, daemon secrets, authorization codes, and access tokens;
83
83
  - registered resource values and source paths;
84
84
  - browser pairing tokens, page URLs/source, DOM metadata, form values, uploaded file bytes, and screenshots;
85
85
  - application names, Accessibility trees, selectors, and entered values;
@@ -118,7 +118,7 @@ logs/daemon.err.log
118
118
 
119
119
  Existing files are opened without following symbolic links where supported and tail-trimmed on UTF-8/line boundaries before startup. Background services use `warn`, so ordinary tool traffic and brief relay interruptions do not cause sustained growth.
120
120
 
121
- The log format has an explicit schema marker. When the schema changes, the daemon copies a bounded tail of each prior active log to an owner-only `daemon.out.legacy.log` or `daemon.err.legacy.log` snapshot, clears the active file, and advances the marker. The migration is idempotent and keeps historical transport-format lines visibly separate from current behavior.
121
+ The log format has an explicit schema marker. If the marker differs from the current format, the daemon clears the active files before startup and writes the current marker. Runtime code recognizes only `daemon.out.log` and `daemon.err.log`; it does not parse or archive other log formats.
122
122
 
123
123
  Each managed job has owner-only runner diagnostic logs. Child-step output is retained only in bounded, redacted job results according to `capture_output`; it is not copied into daemon or runner operational logs.
124
124
 
@@ -277,7 +277,7 @@ Per-workspace jobs are stored below the owner-only profile directory. Active job
277
277
 
278
278
  Retained public job data contains bounded status and redacted results. Up to 50 jobs are retained for up to seven days. Private runtime copies are removed after the finally phase. Runner stdout/stderr log files contain only runner-level diagnostics; step output is not written to those operational logs.
279
279
 
280
- The detached runner records both PID and process start time. Recovery therefore rejects a reused PID instead of treating an unrelated process as the active runner; legacy numeric PID files remain readable during migration. Recovery-lock handoff preserves a random ownership token, and the runner removes only a lock whose PID, token, and file snapshot still match. Timeout and cancellation terminate the process group/tree, retain a referenced forced-escalation timer, and clean descendants that ignore graceful termination before the runner exits.
280
+ The detached runner records a structured owner record containing PID and process start time. Recovery rejects a reused PID instead of treating an unrelated process as the active runner. Numeric-only runner records are invalid. Recovery-lock handoff preserves a random ownership token, and the runner removes only a lock whose PID, token, and file snapshot still match. Timeout and cancellation terminate the process group/tree, retain a referenced forced-escalation timer, and clean descendants that ignore graceful termination before the runner exits.
281
281
 
282
282
  Missing job JSON is distinct from unreadable or invalid job JSON. Permission, type, symbolic-link, size, UTF-8, I/O, and parse failures retain the job directory, produce an `unreadable` status for listing, skip automatic recovery/pruning, and block uninstall until an operator inspects the state.
283
283