machine-bridge-mcp 3.0.0-beta.17 → 3.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +2 -0
- package/SECURITY.md +9 -7
- package/browser-extension/browser-error-boundary.js +45 -0
- package/browser-extension/browser-operations.js +1 -1
- package/browser-extension/manifest.json +1 -1
- package/browser-extension/service-worker.js +9 -8
- package/docs/ARCHITECTURE.md +17 -15
- package/docs/AUDIT.md +30 -0
- package/docs/ENGINEERING.md +3 -3
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/LOCAL_AUTHORIZATION.md +2 -2
- package/docs/LOCAL_AUTOMATION.md +1 -1
- package/docs/LOGGING.md +3 -1
- package/docs/MULTI_ACCOUNT.md +2 -2
- package/docs/OPERATIONS.md +13 -5
- package/docs/OVERVIEW.md +2 -2
- package/docs/PRIVACY.md +1 -1
- package/docs/TESTING.md +17 -13
- package/docs/THREAT_MODEL.md +13 -8
- package/docs/UPGRADING.md +19 -1
- package/package.json +1 -1
- package/scripts/coverage-check.mjs +12 -0
- package/src/local/account-admin.mjs +68 -1
- package/src/local/agent-skill-discovery.mjs +16 -5
- package/src/local/app-automation.mjs +27 -7
- package/src/local/cli-options.mjs +1 -1
- package/src/local/path-inspection.mjs +23 -0
- package/src/local/process-tree-ownership.mjs +17 -5
- package/src/local/process-tree.mjs +6 -2
- package/src/local/relay-connection.mjs +24 -11
- package/src/local/resource-operations.mjs +37 -8
- package/src/local/runtime-activation.mjs +16 -0
- package/src/local/runtime-capabilities.mjs +15 -3
- package/src/local/runtime.mjs +7 -4
- package/src/local/worker-deployment.mjs +16 -6
- package/src/local/workspace-file-service.mjs +29 -11
- package/src/worker/daemon-socket-attachment.ts +52 -0
- package/src/worker/daemon-sockets.ts +17 -48
- package/src/worker/durable-stream-calls.ts +129 -0
- package/src/worker/durable-stream-result.ts +22 -0
- package/src/worker/http.ts +37 -15
- package/src/worker/index.ts +143 -146
- package/src/worker/mcp-pending-call-expiry.ts +20 -0
- package/src/worker/mcp-pending-call-inspection.ts +33 -0
- package/src/worker/mcp-pending-call-records.ts +58 -0
- package/src/worker/mcp-pending-call-storage.ts +18 -0
- package/src/worker/mcp-pending-call-store.ts +285 -0
- package/src/worker/mcp-resumption-index.ts +41 -0
- package/src/worker/mcp-resumption-records.ts +8 -2
- package/src/worker/mcp-resumption.ts +84 -86
- package/src/worker/mcp-stream-dispatch.ts +42 -54
- package/src/worker/mcp-stream-proxy.ts +7 -69
- package/src/worker/mcp-stream-subscription.ts +98 -0
- package/src/worker/oauth-controller.ts +11 -2
- package/src/worker/oauth-refresh-exchange.ts +147 -0
- package/src/worker/oauth-refresh-families.ts +59 -17
- package/src/worker/oauth-state.ts +13 -2
- package/src/worker/oauth-token-derivation.ts +33 -0
- package/src/worker/oauth-token-issuance.ts +106 -0
- package/src/worker/oauth-tokens.ts +12 -184
- package/src/worker/observability.ts +19 -0
- package/src/worker/pending-admission.ts +15 -0
- package/src/worker/pending-call-contract.ts +4 -7
- package/src/worker/pending-calls.ts +4 -10
- package/src/worker/runtime-alarm-storage.ts +35 -0
- package/src/worker/runtime-alarm.ts +30 -20
- package/src/worker/websocket-protocol.ts +4 -0
- package/src/worker/worker-edge-guard.ts +81 -0
- package/src/worker/worker-edge-log.ts +63 -0
- package/src/worker/worker-entry.ts +50 -0
- package/src/worker/worker-metadata.ts +39 -0
- package/src/worker/worker-static-routes.ts +38 -50
- package/wrangler.jsonc +8 -0
package/docs/MULTI_ACCOUNT.md
CHANGED
|
@@ -27,7 +27,7 @@ Effective authority is the intersection of:
|
|
|
27
27
|
|
|
28
28
|
There is no temporary elevation path. A `reviewer`, `editor`, or `operator` cannot acquire `owner` capability through a local lease, approval ID, token refresh, or reconnect.
|
|
29
29
|
|
|
30
|
-
The Worker filters `tools/list`
|
|
30
|
+
The Worker filters the stable `tools/list` discovery catalog by account role. It separately rejects calls that are outside the current account role or the live end-to-end-ready daemon ceiling before relay. Every accepted call carries account ID, account version, OAuth client ID, refresh-family ID, and role. The local runtime validates those values again before dispatch.
|
|
31
31
|
|
|
32
32
|
Authenticated `server_info.authorization.effective_policy` and `effective_tools` describe the current account. `daemon.policy` and `daemon.tools` describe only the local capability ceiling; a `full` daemon does not make an `editor` account full.
|
|
33
33
|
|
|
@@ -107,7 +107,7 @@ Authorization codes bind:
|
|
|
107
107
|
|
|
108
108
|
Access and refresh tokens additionally bind to the deployment token version and refresh-family ID. Token values are stored as SHA-256 lookup keys.
|
|
109
109
|
|
|
110
|
-
Access tokens last fifteen minutes. Refresh tokens rotate on every use, have a fourteen-day idle limit and thirty-day family limit, and leave bounded replay markers.
|
|
110
|
+
Access tokens last fifteen minutes. Refresh tokens rotate on every use, have a fourteen-day idle limit and thirty-day family limit, and leave bounded replay markers. To tolerate a lost response or concurrent hosted-client refresh, the same consumed token may return the same HMAC-derived replacement pair at most twice during a 30-second window. These retries do not create new credential branches or extend expiration. Further in-window attempts are rate-limited; reuse after that window revokes the complete family, including active access tokens.
|
|
111
111
|
|
|
112
112
|
A refresh request also verifies that the client remains bound to the current account version and role.
|
|
113
113
|
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -30,16 +30,22 @@ machine-mcp --verbose
|
|
|
30
30
|
|
|
31
31
|
Run these commands from the same environment used for startup so `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` are identical. After the foreground connection succeeds, run `machine-mcp service install` from that same PowerShell session. Installation stores only an allowlisted proxy/custom-CA environment snapshot in private local state so the logon task does not lose session-only `$env:` settings after reboot. A later start with no proxy variables does not erase the saved snapshot; set a variable explicitly to an empty value and reinstall to clear it. `machine-mcp service status` reports only the saved key names. Debug logs expose only the selected route and classified error; they never print a proxy endpoint or credentials.
|
|
32
32
|
|
|
33
|
+
### Worker quota controls
|
|
34
|
+
|
|
35
|
+
The standard public endpoint remains the automatically provisioned `workers.dev` URL printed by Machine Bridge; no personal domain is required. The outer Worker serves health, discovery metadata, CORS preflight, and unknown 404s without Durable Object state. Stateful routes pass a 120-per-minute Cloudflare Rate Limiting binding before DO dispatch. This guard is per Cloudflare location and executes after the Worker starts; it protects the Durable Object from ordinary bursts but is not an exact account-wide daily Workers quota. Configure Cloudflare account usage alerts when available. A binding outage fails open so quota protection cannot become an authentication outage.
|
|
36
|
+
|
|
37
|
+
`server_info.worker.observability.oauth_refresh` distinguishes normal rotation, bounded retry issuance, exhausted retry budget, post-grace family revocation, and rejected grants. `durable_budget` reports estimated stream-row writes plus alarm sets, deletes, and no-ops. These are process-lifetime logical counters for diagnosis, not Cloudflare billing records.
|
|
38
|
+
|
|
33
39
|
### Blocking-layer decision table
|
|
34
40
|
|
|
35
41
|
| Result | Interpretation |
|
|
36
42
|
|---|---|
|
|
37
43
|
| `authorization.effective_policy.profile` is `full` and the tool is in `authorization.effective_tools`, but the current session UI exposes fewer tools | Host/connector post-relay filtering; Machine Bridge cannot enumerate or override that subset |
|
|
38
44
|
| `daemon.policy.profile` is `full` but `authorization.effective_policy.profile` is `review`, `edit`, or `agent` | Expected account-role narrowing; the daemon field is only a capability ceiling and must not be reported as the account permission |
|
|
39
|
-
| `worker.sockets_live.authenticated` is nonzero but `worker.sockets_live.ready` is zero | Transport authentication exists, but the end-to-end result probe has not completed
|
|
45
|
+
| `worker.sockets_live.authenticated` is nonzero but `worker.sockets_live.ready` is zero | Transport authentication exists, but the end-to-end result probe has not completed. The stable account catalog remains discoverable, while `authorization.effective_tools` contains no executable daemon tool and calls fail retryably until readiness |
|
|
40
46
|
| `capability_routing.bootstrap_observed` is false | The current local runtime has not received `session_bootstrap`; reconnect or inspect host initialization handling |
|
|
41
47
|
| `task_resolution_observed` is false after a substantive task | The host/model did not call `resolve_task_capabilities`; server-side discovery cannot force that host decision |
|
|
42
|
-
| Task resolution ran but all match counts are zero |
|
|
48
|
+
| Task resolution ran but all match counts are zero | Check `application_discovery`: `available=false` or a nonzero warning count means application inventory was partial or unavailable; otherwise the resolver ran successfully but found no sufficiently relevant local skill, command, or application |
|
|
43
49
|
| No structured result because the host rejects the call | Host/connector approval or safety layer, or transport before daemon delivery |
|
|
44
50
|
| `mcp-host-to-daemon` passes but `local-filesystem` fails | Local state/runtime permissions, disk policy, sandbox, or endpoint security |
|
|
45
51
|
| Filesystem passes but `local-process-spawn` fails | Local executable policy, endpoint security, OS permissions, or damaged Node runtime |
|
|
@@ -53,9 +59,11 @@ A successful diagnostic result applies only to that probe. An MCP host can still
|
|
|
53
59
|
|
|
54
60
|
Machine Bridge supports concurrent calls: the Worker admits up to 32 pending daemon calls and the local runtime admits up to 16 active tool calls. These are capacity limits, not a single global execution queue. Each successful MCP initialization receives a signed `MCP-Session-Id`; JSON-RPC ids and cancellation are scoped to that session, so separate chat windows may reuse the same numeric ids safely even when they share one OAuth account and token.
|
|
55
61
|
|
|
56
|
-
`server_info.worker.pending_calls` reports `active`, `detached`, `request_keys`, `maximum`, `oldest_ms`, and `
|
|
62
|
+
`server_info.worker.pending_calls` reports `active`, `detached`, `request_keys`, `maximum`, `oldest_ms`, `by_tool`, `transient`, and `durable_streams`. `worker.sockets_live` separately reports `authenticated`, `probing`, `ready`, and `candidates`; only `ready` sockets contribute to `daemon.connected` and `authorization.effective_tools`. The stable role-filtered `tools/list` catalog does not disappear during a brief outage. A nonzero `active` count means work is in flight, not that the bridge is locked. `detached > 0` means a daemon socket was lost and those requests are inside the bounded two-minute same-instance reconnect window. Calls for simple reads and probes should continue while another independent process call runs. Only explicit session-scoped MCP cancellation, timeout, or reconnect-grace expiry removes the pending record and its request key; an HTTP response disconnect is not cancellation. A daemon-socket closure detaches only calls assigned to its opaque connection generation; the same daemon process can reclaim them after completing readiness, while another process cannot. A verified same-instance replacement transfers both detached and still-attached calls before the incumbent closes, and delayed old-socket results are rejected. JSON-only and streamed calls pass through one FIFO admission gate and share the reported 32-call ceiling. JSON-only calls use an in-event timer plus the shared alarm/sweep backstop. Streamed calls persist their operation or reconnect deadline and use the Durable Object alarm plus event-entry sweep; no JavaScript timer is their durable owner. Each detach/rebind monotonically extends active-record expiry over the new reconnect and remaining-operation budgets. Therefore `detached > 0` with `oldest_ms` materially beyond the two-minute grace is a lifecycle defect rather than normal recovery. Grace expiry rejects the request and cancels the local ordinary operation. Refreshing a chat page is not the recovery mechanism and should not be required.
|
|
63
|
+
|
|
64
|
+
A Worker-requested `daemon_transport_error` or `daemon_liveness_timeout` is a retryable connection invalidation. The local daemon must close only the affected socket, preserve pending-call detach semantics, and reconnect; it must not enter the fatal `relay_protocol_error` path or exit for launchd to restart. The Worker uses close code 1012 for these transient cases. The daemon also recognizes the bounded close reasons `daemon pong failed`, `daemon send failed`, and `daemon liveness timeout` if the preceding error frame is not delivered. Unknown error codes, authentication rejection, and server identity/version mismatch remain permanent failures and require operator action.
|
|
57
65
|
|
|
58
|
-
For Streamable HTTP clients such as ChatGPT that advertise `text/event-stream`, the outer Worker returns an immediate sequence-zero SSE event identifier and a keepalive comment every ten seconds until the terminal sequence-one JSON-RPC result. `BridgeRoom` never owns the long-lived public stream or an unresolved terminal Promise. Stream initiation commits recovery state
|
|
66
|
+
For Streamable HTTP clients such as ChatGPT that advertise `text/event-stream`, the outer Worker returns an immediate sequence-zero SSE event identifier and a keepalive comment every ten seconds until the terminal sequence-one JSON-RPC result. `BridgeRoom` never owns the long-lived public stream or an unresolved terminal Promise. Stream initiation commits recovery state and then durably attaches the opaque call ID, daemon instance, WebSocket generation, request key, deadlines, and bounded transform metadata before sending work and returning a descriptor. A later WebSocket result, explicit cancellation, operation timeout, send failure, or reconnect-grace expiry writes the terminal result through one generation-checked transaction. One internal hibernatable WebSocket subscription therefore coexists with concurrent `server_info`, recovery, and session-scoped `notifications/cancelled` requests while SSE remains open, without creating a request per poll interval. Caller-supplied internal stream headers are removed at the public boundary. If the client or an intermediary closes the stream, Machine Bridge keeps the bounded operation alive; only `notifications/cancelled` carries cancellation semantics. A compatible host resumes the original stream with authenticated `GET /mcp`, the original `MCP-Session-Id`, and `Last-Event-ID`; it must not repeat the POST. Recovery records are token/session-bound, retained for at most two minutes, limited to 64 streams, and persist at most 1.5 MiB of terminal JSON. Error `-32002` means the online result exceeded the replay budget. Error `-32003` is reserved for a pending stream record that has no durable call owner after restart; a valid persisted call remains pending and recoverable. Error `-32005` means stored replay data failed integrity validation.
|
|
59
67
|
|
|
60
68
|
`server_info.worker.observability.calls.unmatched_results` counts results that reached the Worker after their pending record was already removed. A small increase can accompany cancellation or timeout races, especially during mixed-version upgrade convergence; sustained growth together with old pending calls indicates incompatible components or a lifecycle defect. The counter contains no tool arguments or result data.
|
|
61
69
|
|
|
@@ -165,7 +173,7 @@ Uninstall acquires a state-root `maintenance.lock` that blocks new profile/state
|
|
|
165
173
|
|
|
166
174
|
### Lifecycle and pending-call diagnosis
|
|
167
175
|
|
|
168
|
-
`server_info.runtime.lifecycle` reports `ready`, `starting`, `running`, `failed`, `stopping`, or `stopped`. `server_info.observability.in_flight_calls` and `server_info.runtime.processes` distinguish a blocked call from a surviving process. `server_info.runtime.execution_guardrails` reports the enforced local concurrency/timeout/stdin/output/session limits and explicitly states that CPU quota, memory quota, and network isolation are not enforced in process. Worker `server_info.worker.pending_calls` reports the internal-call index, client request-key index, and detached-call count. All three must return to zero after a terminal result, explicit cancellation,
|
|
176
|
+
`server_info.runtime.lifecycle` reports `ready`, `starting`, `running`, `failed`, `stopping`, or `stopped`. `server_info.observability.in_flight_calls` and `server_info.runtime.processes` distinguish a blocked call from a surviving process. `server_info.runtime.execution_guardrails` reports the enforced local concurrency/timeout/stdin/output/session limits and explicitly states that CPU quota, memory quota, and network isolation are not enforced in process. Worker `server_info.worker.pending_calls` reports the internal-call index, client request-key index, and detached-call count. All three must return to zero after a terminal result, explicit cancellation, timeout, or reconnect-grace expiry. An HTTP/SSE client disconnect alone is transport disposal and may leave a recoverable call active until terminal delivery or the normal lifecycle boundary. During a brief daemon interruption, `active` and `request_keys` may remain nonzero while `detached` identifies the recoverable subset; after same-instance readiness, `detached` returns to zero without losing those requests. Nonzero request-key counts after active calls reach zero indicate a lifecycle defect rather than normal load. `worker.observability.calls.unmatched_results` is the bounded counter for late results that no longer have a receiver.
|
|
169
177
|
|
|
170
178
|
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.
|
|
171
179
|
|
package/docs/OVERVIEW.md
CHANGED
|
@@ -41,7 +41,7 @@ flowchart LR
|
|
|
41
41
|
### Remote transport
|
|
42
42
|
|
|
43
43
|
1. The Worker validates OAuth client, token, account state, role, resource binding, and MCP session state.
|
|
44
|
-
2. The Worker
|
|
44
|
+
2. The Worker exposes a stable account-role discovery catalog, then independently intersects each execution with the current end-to-end-ready daemon capability ceiling.
|
|
45
45
|
3. The Durable Object relays a bounded tool envelope carrying account, account-version, OAuth-client, refresh-family, and role identity over the root-certified ephemeral daemon socket.
|
|
46
46
|
4. The local runtime revalidates account authorization, policy, call lifecycle, timeout, and cancellation.
|
|
47
47
|
5. The local authority gate permits work only inside the immutable role ceiling. Consequential effects are classified for hard enforcement and audit; they never create a temporary elevation path.
|
|
@@ -84,7 +84,7 @@ Low-level responsibilities remain in focused modules. Architecture tests enforce
|
|
|
84
84
|
|
|
85
85
|
Each canonical workspace has independent profile state, Worker identity, trusted clients, account/token state, legacy-lease cleanup state, audit-chain state, locks, service metadata, and managed jobs. State mutations use owner-only files where supported, bounded reads, atomic replacement, and process-identity-aware locks.
|
|
86
86
|
|
|
87
|
-
The relay distinguishes a root-certified ephemeral session, signed preflight, challenge authentication, readiness probing, and active service. A candidate daemon must prove possession of the certified session key and complete an end-to-end probe before replacing an incumbent.
|
|
87
|
+
The relay distinguishes a root-certified ephemeral session, signed preflight, challenge authentication, readiness probing, and active service. A candidate daemon must prove possession of the certified session key and complete an end-to-end probe before replacing an incumbent. A transient socket loss detaches ordinary calls for the bounded same-daemon reconnect window; only explicit cancellation, timeout, reconnect-grace expiry, runtime shutdown, or a non-recoverable daemon replacement terminates their local process trees.
|
|
88
88
|
|
|
89
89
|
Managed jobs use a separate durable lifecycle. Their plans are integrity-bound, runners are process-identity checked, transitions are lock-protected, terminal plans are scrubbed, and recovery is bounded.
|
|
90
90
|
|
package/docs/PRIVACY.md
CHANGED
|
@@ -61,4 +61,4 @@ For an accidental publication, remove the value from the current tree and releas
|
|
|
61
61
|
|
|
62
62
|
For Streamable HTTP recovery, the workspace Durable Object may temporarily persist the terminal JSON-RPC response of a remote tool call. This response can contain source text, command output, file metadata, images encoded by the protocol, or other user data returned by the requested tool. It is operational delivery state, not anonymized telemetry and not publication-safe evidence.
|
|
63
63
|
|
|
64
|
-
Persistence is bounded to 64 streams, at most 1.5 MiB per terminal response, and a two-minute retention window. Records are bound to the OAuth access-token identity and signed MCP session, carry a SHA-256 integrity value, and are removed on expiry or completed-record eviction. The digest detects accidental corruption; it is not a signature against an attacker who controls the Durable Object. Normal logs continue to omit tool arguments and results.
|
|
64
|
+
Persistence is bounded to 64 streams, at most 1.5 MiB per terminal response, and a two-minute terminal-retention window. While a streamed call is active, the record also contains the tool name, opaque call and WebSocket-generation identifiers, daemon-process identifier, client request correlation, operation/reconnect deadlines, and bounded account metadata needed only to project `project_overview`. It does not persist tool arguments or an in-progress result. Records are bound to the OAuth access-token identity and signed MCP session, carry a SHA-256 integrity value after terminal serialization, and are removed on expiry or completed-record eviction. Opaque call, connection, stream, and event identifiers are correlation values rather than bearer credentials. The digest detects accidental corruption; it is not a signature against an attacker who controls the Durable Object. Normal logs continue to omit tool arguments and results.
|
package/docs/TESTING.md
CHANGED
|
@@ -24,7 +24,7 @@ The suite includes:
|
|
|
24
24
|
- GitHub backlog enforcement that paginates all open issues and pull requests, permits only the current branch PR, and requires standard closing keywords for every open issue before a guarded push;
|
|
25
25
|
- release-impact enforcement requiring a new package version and CHANGELOG section for release-relevant changes;
|
|
26
26
|
- strict dev/beta/rc/stable channel parsing and npm dist-tag enforcement;
|
|
27
|
-
- persistent candidate relay/service handoff ordering and
|
|
27
|
+
- persistent candidate relay/service handoff ordering, early-failure provider restoration after lock cleanup, and aggregated restoration failures;
|
|
28
28
|
- owner-only local and registry prerelease activation records;
|
|
29
29
|
- npm/GitHub prerelease metadata validation, minimum soak timing, blocking-issue rejection, and stable-promotion content/file-mode identity;
|
|
30
30
|
- release-state diagnostics distinguishing missing local/remote version tags from tags that point to the wrong commit, plus a release-CI gate that rejects missing, pending, failed, pull-request-only, stale, or wrong-commit runs;
|
|
@@ -38,9 +38,9 @@ The suite includes:
|
|
|
38
38
|
- 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;
|
|
39
39
|
- 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; service-PATH coverage reproduces two nested npm run-script prefixes plus a stale prior candidate runtime and proves the current Node/runtime and inherited user tools remain while npm-private and inactive-runtime entries are removed; Windows coverage reproduces an inline `/TR` command above 262 characters, proves the short launcher action remains bounded, verifies least-privilege logon registration, restart/log routing, language-independent `Ready`/`Running` observation, and state-observed stop/removal despite localized nonzero command output;
|
|
40
40
|
- private service-environment capture/load coverage for exact allowlisting, value bounds and control-character rejection, non-proxy secret exclusion, runtime-value precedence, Windows case-insensitive replacement, explicit empty-value clearing, and preservation across a later environment-free startup;
|
|
41
|
-
- 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, direct loopback health despite hostile environment-proxy settings, stop-during-start generation invalidation, frozen-wall-clock browser deadline coverage, installed-application discovery caching, and name-based task matching;
|
|
42
|
-
- Worker health direct/proxy routing through a real local HTTP CONNECT proxy, `NO_PROXY` bypass, exact `workers.dev` origin/name validation, redirect rejection, body/deadline bounds, error classification, bounded propagation
|
|
43
|
-
- Worker deployment ambiguity/idempotency: a successful Wrangler result followed by health timeout persists the fingerprint, a
|
|
41
|
+
- 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, direct loopback health despite hostile environment-proxy settings, stop-during-start generation invalidation, frozen-wall-clock browser deadline coverage, installed-application discovery caching, per-root warning projection, capability-resolution degradation reporting, and name-based task matching;
|
|
42
|
+
- Worker health direct/proxy routing through a real local HTTP CONNECT proxy, `NO_PROXY` bypass, exact `workers.dev` origin/name validation, redirect rejection, body/deadline bounds, error classification, an extended bounded post-deployment propagation window, and invalid proxy fail-fast behavior without endpoint or credential disclosure;
|
|
43
|
+
- Worker deployment ambiguity/idempotency: a successful Wrangler result followed by health timeout persists the fingerprint, a matching current-version fingerprint receives the full propagation budget and never uploads again without explicit `--force-worker`, an actual process restart and disk-state reload still performs no upload, definitive legacy or unversioned stale evidence redeploys under the same name, accidental name changes are rejected, forced replacements clear current endpoint state, and prior names remain in uninstall inventory;
|
|
44
44
|
- relay environment-proxy direct/bypass/agent selection, unsupported proxy rejection, fail-fast invalid configuration, and route observability without endpoint or credential disclosure;
|
|
45
45
|
- canonical path and symbolic-link escape tests;
|
|
46
46
|
- relative-path privacy and error-path redaction tests;
|
|
@@ -53,7 +53,7 @@ The suite includes:
|
|
|
53
53
|
- fixed internal command execution with validated argv, no shell, isolated HOME/temp/cache, bounded output/deadlines, and ordinary cancellation/process tracking while arbitrary delegated process tools remain sandbox-gated;
|
|
54
54
|
- author-email privacy in `git_log`;
|
|
55
55
|
- isolated command HOME/temp/cache behavior;
|
|
56
|
-
- one-shot timeout, descendant process-group/tree termination including descendants that ignore graceful shutdown after the direct child has already exited, cancellation, and process-session interaction;
|
|
56
|
+
- one-shot timeout, descendant process-group/tree termination including descendants that ignore graceful shutdown after the direct child has already exited, post-`SIGTERM` ownership refresh, targeted PID identity fallback when full process-table inspection is unavailable, PID-reuse denial, repeated leak checks, cancellation, and process-session interaction;
|
|
57
57
|
- layered fixed runtime diagnostics for filesystem, direct process, shell, managed-job storage, and resource availability; machine-readable execution guardrails that must keep CPU, memory, and network isolation marked unenforced unless an actual OS boundary is added;
|
|
58
58
|
- local resource CLI registration, permission checks, dynamic reload, state-path redaction, content non-disclosure, and the extracted runtime-resource boundary for bounded binary/UTF-8 reads plus generation authorization;
|
|
59
59
|
- real Ed25519 and RSA generation, idempotent reuse, public/private correspondence, mode enforcement, incomplete/mismatched/symlink rejection, and private-content non-disclosure;
|
|
@@ -65,7 +65,7 @@ The suite includes:
|
|
|
65
65
|
- 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;
|
|
66
66
|
- no filename-based sensitive-file denial under unrestricted policy;
|
|
67
67
|
- shared local/Worker free-form log redaction, sensitive content under non-sensitive Worker keys, immutable local/Worker structured metadata, control-character handling, message/field bounds, suppression of both successful and failed per-tool events outside debug, service warning-level configuration, JSON-mode parity across event and direct logger methods with timestamp/stream/redaction assertions, current-schema reset, and bounded tail trimming;
|
|
68
|
-
- deterministic relay connection lifecycle coverage for transport construction/error/deadline, pre-handshake `welcome` validation, separate `hello_ack` authentication and `ready_ack` end-to-end readiness, session-bound probe return, pre-ready tool rejection, premature-ready rejection, identity/version mismatch, retryable Worker hello/readiness errors, fatal protocol errors, autonomous outage-reminder backoff, handshake/readiness/heartbeat timeout, brief-outage suppression, sustained-outage escalation, recovery summaries, and supersession;
|
|
68
|
+
- deterministic relay connection lifecycle coverage for transport construction/error/deadline, failed `hello` delivery, pre-handshake `welcome` validation, separate `hello_ack` authentication and `ready_ack` end-to-end readiness, session-bound probe return and probe-delivery races, pre-ready tool rejection, premature-ready rejection, identity/version mismatch, retryable Worker hello/readiness/transport/liveness errors, retryable close-only transport/liveness delivery, fatal unknown protocol errors, autonomous outage-reminder backoff, handshake/readiness/heartbeat timeout, brief-outage suppression, sustained-outage escalation, recovery summaries, and supersession;
|
|
69
69
|
- shared no-follow bounded-file reads for normal files, over-limit data, directories, and symbolic links;
|
|
70
70
|
- owner-only directory enforcement rejecting final symlinks, failing closed on POSIX chmod errors, verifying `0700`, and retaining Windows portability; Worker temporary-secret lifecycle coverage for process-start-bound names, valid stale-owner reclamation, ambiguous-owner retention, `0600` mode, deletion failures, and simultaneous deployment/cleanup failures;
|
|
71
71
|
- SARIF security-gate behavior for unknown findings, exact accepted rule/path matches, path mismatch rejection, rationale quality, and exception expiry;
|
|
@@ -79,14 +79,16 @@ The suite includes:
|
|
|
79
79
|
- live stdio MCP initialization with session instructions, capability resolution, discovery, calls, rich content, sessions, cancellation, managed-job acceptance, and a detached job/finally phase that survives stdio shutdown;
|
|
80
80
|
- P-256 root generation, root-certified ephemeral session issuance, macOS trust-broker build/signature checks, signed WebSocket preflight, one-time transactional nonce consumption, challenge transcript binding, wrong-root/session/tamper/expiry/replay rejection, and prevention of unauthenticated candidate churn;
|
|
81
81
|
- request-scoped effective authority and catalog-wide risk review; non-escalatable reviewer/editor/operator ceilings; authenticated-owner direct execution; control-plane root denial; external and sensitive path composition; persistence-target rejection; symbolic-link ancestor and patch-move canonicalization; owner-only browser/application/data-export and persistent-plan effects; account/client/refresh-family ownership of processes, output sessions, and jobs; delegated sandbox fail-closed behavior; legacy-lease non-consumption; and malformed-record rejection;
|
|
82
|
-
- root-certified ephemeral P-256 account-administration requests with origin/method/path/body/key/time/nonce binding, transactional one-time nonce consumption, removal of the long-lived administration secret, certificate/signature/body tamper rejection, nonce replay rejection,
|
|
83
|
-
- live local Worker OAuth registration, the unauthenticated `resource_metadata` challenge, protected-resource and authorization-server discovery, Streamable transport metadata, consent, URL-constructed `303` callbacks including the ChatGPT and hosted Claude redirect URIs with encoded state, PKCE, `offline_access`, form-encoded authorization-code and refresh-token exchanges, fifteen-minute access tokens, trusted single-account client binding, optional DPoP proof and token-family binding, unsupported critical-header rejection, proof-verification non-consumption, post-authorization replay consumption, invalid-grant cache-exhaustion resistance, independent client revocation, refresh-family idle/absolute limits, bounded consumed-token/revoked-family replay state, record-level schema validation, access/refresh rotation,
|
|
82
|
+
- root-certified ephemeral P-256 account-administration requests with origin/method/path/body/key/time/nonce binding, transactional one-time nonce consumption, removal of the long-lived administration secret, certificate/signature/body tamper rejection, nonce replay rejection, malformed nonce-state fail-closed behavior, one-megabyte response bounds, immediate oversized-response cancellation, and strict successful JSON-object validation;
|
|
83
|
+
- live local Worker OAuth registration, the unauthenticated `resource_metadata` challenge, protected-resource and authorization-server discovery, Streamable transport metadata, consent, URL-constructed `303` callbacks including the ChatGPT and hosted Claude redirect URIs with encoded state, PKCE, `offline_access`, form-encoded authorization-code and refresh-token exchanges, fifteen-minute access tokens, trusted single-account client binding, optional DPoP proof and token-family binding, unsupported critical-header rejection, proof-verification non-consumption, post-authorization replay consumption, invalid-grant cache-exhaustion resistance, independent client revocation, refresh-family idle/absolute limits, bounded consumed-token/revoked-family replay state, record-level schema validation, access/refresh rotation, idempotent identity-equivalent concurrent refresh recovery with identical replacement credentials and expiration, retry-budget throttling, post-grace replay with whole-family access/refresh revocation, account-version refresh revocation, authorization-code replay rejection, pending-registration throttling that excludes already authorized DCR clients, exact built-in ChatGPT/Grok browser origins, additive custom origins, unrelated-origin preflight rejection, no CORS response sharing for unrelated or opaque origins, opaque-origin authorization-form routing, exact per-request redirect-origin CSP with narrowly scoped Microsoft regional-consent and final Copilot Studio handoff exceptions, accessible credential-error rendering, protocol negotiation, HMAC-bound MCP session issuance, SSE content negotiation including `q=0`, immediate stream priming, keepalive and terminal-event framing, HTTP abort without implicit cancellation, explicit cancellation after response disconnect, shared Worker/local timeout ceilings, two-session same-id concurrency, sessionless same-id independence, session-scoped cancellation isolation, same-session duplicate rejection, daemon-backed session bootstrap, dynamic tool advertisement, rich content, candidate/probing/ready transitions, invalid readiness-result rejection, incumbent preservation until verified handover, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure. The metadata/refresh contract is the path used by Claude DCR and Copilot Studio Dynamic discovery. The same integration runs an `editor` account against a canonical `full` daemon and proves that `server_info` and remote `project_overview` report effective `edit` authority while retaining the full daemon ceiling only in explicitly scoped fields.
|
|
84
84
|
- local runtime proof that one blocked tool handler does not serialize an independent handler, plus relay fault injection proving an undeliverable terminal result interrupts the ambiguous socket and enters reconnect backoff.
|
|
85
85
|
- a real headless-Chrome OAuth navigation regression with four cases: `form-action 'self'` blocks the first cross-origin callback, allowing only the registered callback blocks the regional redirect, allowing the registered and regional callbacks blocks the final Copilot Studio redirect, and the complete policy preserves `code` and `state` through all three cross-origin hops. Linux CI fails if Chrome is unavailable; other environments skip only this browser executable check while retaining the Worker CSP assertions.
|
|
86
86
|
|
|
87
|
+
`npm run ssh-key:test` exercises Ed25519/RSA generation, private-file permissions, public/private matching, existing-pair reuse, incomplete-pair rejection, symbolic-link denial, atomic registration under the startup lock, registry conflicts/capacity, complete two-file rollback after state-write failure, and explicit failure when sensitive-key cleanup remains incomplete.
|
|
88
|
+
|
|
87
89
|
## Opt-in live desktop and browser validation
|
|
88
90
|
|
|
89
|
-
The normal suite uses deterministic mocks for macOS Accessibility and an authenticated in-process extension peer for browser routing. Browser contract tests cover acknowledged protocol readiness, provisional pairing commit/rollback boundaries, strict extension IDs and matching loopback ports, stale-extension rejection, compatible replacement without premature displacement, clean rejection of in-flight direct and proxied requests, keepalive handling, trusted-input replay prevention after partial dispatch, focus-safe screenshots, aggregate 64-frame/source/element budgets, bounded hostile DOM/text processing, contenteditable-secret suppression, partial-form failure reporting, stable refs, combined waits, and actionability failures. Before a release that changes local UI automation, run the macOS live smoke test on a machine where the invoking Node/terminal process has Accessibility permission:
|
|
91
|
+
The normal suite uses deterministic mocks for macOS Accessibility and an authenticated in-process extension peer for browser routing. Browser contract tests cover acknowledged protocol readiness, provisional pairing commit/rollback boundaries, strict extension IDs and matching loopback ports, stale-extension rejection, compatible replacement without premature displacement, clean rejection of in-flight direct and proxied requests, keepalive handling, trusted-input replay prevention after partial dispatch, fixed non-sensitive fallback reasons, privacy-safe public browser errors, an independent extension-side 32-operation ceiling, focus-safe screenshots, aggregate 64-frame/source/element budgets, bounded hostile DOM/text processing, contenteditable-secret suppression, partial-form failure reporting, stable refs, combined waits, and actionability failures. Before a release that changes local UI automation, run the macOS live smoke test on a machine where the invoking Node/terminal process has Accessibility permission:
|
|
90
92
|
|
|
91
93
|
```sh
|
|
92
94
|
npm run app-automation:live-test
|
|
@@ -150,13 +152,15 @@ Run `npm run privacy:check` before committing and before packaging. Run and revi
|
|
|
150
152
|
|
|
151
153
|
`npm run lint` uses ESLint as a semantic JavaScript correctness gate rather than a style formatter. It covers the Node CLI/runtime, repository scripts, tests, and packaged browser extension and rejects undefined identifiers in function bodies that `node --check` cannot detect. A dedicated lint-gate self-test proves that both Node and browser configurations reject a synthetic undefined binding while accepting the service-worker `importScripts` global. A focused `shell:test` requires Wrangler to run through the current Node executable and its package JavaScript entrypoint rather than a `.cmd` or shell shim. Architecture tests require `shell:test`, `lint:test`, `lint`, and `install:test` to remain in the complete check pipeline and reject non-exact direct dependency ranges.
|
|
152
154
|
|
|
153
|
-
The stdio integration test also sends an oversized line, verifies bounded rejection, and confirms that the next valid request is still processed.
|
|
155
|
+
The stdio integration test also sends an oversized line, verifies bounded rejection, and confirms that the next valid request is still processed. Worker request-body tests use pull-counted streams and require immediate `ReadableStream.cancel()` after a declared or observed limit violation, proving that byte retention and execution work are both bounded.
|
|
154
156
|
|
|
155
157
|
## Architecture and documentation regression checks
|
|
156
158
|
|
|
157
|
-
`npm run architecture:test` runs independent module-boundary, repository-hygiene, browser/security-structure, and release/documentation-contract checks. It validates the explicit fast/full check plans, local import graph, domain/adapter direction, module headroom budgets, immutable workflow references, package-script targets, documentation links, publication inventory, and selected security-shape invariants. These source-shape checks are deliberately supplementary: behavior, denial, race, and fault-injection tests remain authoritative for semantic guarantees. Tests must not depend on a fixed CI job count when the actual invariant is that every npm job uses the same verified bootstrap.
|
|
159
|
+
`npm run architecture:test` runs independent module-boundary, repository-hygiene, browser/security-structure, and release/documentation-contract checks. It validates the explicit fast/full check plans, local import graph, domain/adapter direction, module headroom budgets, immutable workflow references, package-script targets, documentation links, publication inventory, and selected security-shape invariants. Critical coverage thresholds include every extracted OAuth refresh, token issuance, stream subscription, static metadata/routing, quota guard, edge logger, and filesystem-state module. These source-shape checks are deliberately supplementary: behavior, denial, race, and fault-injection tests remain authoritative for semantic guarantees. Tests must not depend on a fixed CI job count when the actual invariant is that every npm job uses the same verified bootstrap.
|
|
158
160
|
## Resumable MCP delivery coverage
|
|
159
161
|
|
|
160
|
-
`npm run mcp-resumption:test` directly exercises stream cursor parsing, OAuth-token/MCP-session isolation, immediate pending/terminal polls, active and completed replay,
|
|
162
|
+
`npm run mcp-resumption:test` directly exercises stream cursor parsing, OAuth-token/MCP-session isolation, immediate pending/terminal polls, active and completed replay, orphaned-stream restart ambiguity, persisted-call restart recovery, strict call-record validation, request-key uniqueness, operation/reconnect deadlines, repeated detach/rebind retention extension, stale-generation rejection, prototype-safe aggregation, exactly-once completion, result-size fallback, SHA-256 tamper detection, transient persistence failure, expiry, capacity, completed-record eviction, the four-row plain-stream budget, and the fixed six-row durable-call lifecycle budget.
|
|
163
|
+
|
|
164
|
+
`npm run worker-runtime-infrastructure:test` verifies outer-Worker stream ownership, stripping of caller-supplied internal headers, bounded descriptor/subscribe adaptation, fixed request budgets, bounded transient subscription retries, outer metadata/404/invalid-method bypass, stateful burst limiting, structured and duplicate-suppressed gateway failures, coalesced alarm writes, sequence-zero/sequence-one framing, subscription-error closure, subscriber replacement, non-daemon socket isolation, and the shared two-minute/64-stream/1.5-MiB contract. It models the production durable-call boundary: initiation persists ownership without retaining a terminal Promise, later success or daemon rejection settles once, send failure cleans the record, capacity fails closed, and same-instance reconnect moves ownership to a new generation while rejecting the stale one. Deadline tests prove both JSON-only timer/sweep behavior and persisted-call alarm expiry without leaking request keys. A direct runtime-alarm coordinator test verifies the earliest transient-or-durable deadline, alarm removal when no deadline remains, event-entry expiry before rescheduling, and bounded reporting when Durable Object alarm storage fails. The same suite also proves that direct same-instance handover preserves the remaining timeout budget. `npm run worker:integration-test` performs the real Wrangler path: the role-filtered catalog remains stable before, during, and after daemon availability changes; execution still fails closed without a ready daemon; an open SSE stream coexists with concurrent `server_info` and explicit cancellation; verified same-instance replacement transfers an in-flight call before incumbent close; disconnect/recovery remains token/session isolated; and sequence-one acknowledgement is not delivered twice. Managed-job integration treats `__proto__`, `constructor`, `toString`, and `valueOf` environment/resource-map keys as ordinary own data while retaining duplicate-key rejection. Static architecture checks forbid a stream-initiation `dispatchJsonRpc` Promise, `resumption.attach`, Durable Object `waitUntil`, Promise-valued recovery state, or return of the obsolete transient `registerEvent` branch. The parser accumulates complete SSE events and does not assume network chunk boundaries. CORS coverage requires both `DPoP` and `Last-Event-ID`.
|
|
161
165
|
|
|
162
|
-
|
|
166
|
+
Beta.21 closeout requires a fresh pass of the complete 63-task fast plan and 91-task full plan after the final relay error-classification changes; an earlier green run cannot be reused as terminal evidence.
|
package/docs/THREAT_MODEL.md
CHANGED
|
@@ -113,6 +113,7 @@ Machine Bridge considers:
|
|
|
113
113
|
The implementation aims to preserve these invariants:
|
|
114
114
|
|
|
115
115
|
- unknown, malformed, stale, replayed, duplicated, unauthorized, and over-limit input is rejected;
|
|
116
|
+
- the stable account-role discovery catalog is not treated as execution authority; every call is intersected with the current end-to-end-ready daemon policy and fails closed when that authority is absent;
|
|
116
117
|
- remote authority is the intersection of daemon policy and account role, never the union;
|
|
117
118
|
- no approval record, token refresh, or local migration state can elevate a delegated role;
|
|
118
119
|
- OAuth clients are bound to one account/version/role and can be revoked independently;
|
|
@@ -122,10 +123,10 @@ The implementation aims to preserve these invariants:
|
|
|
122
123
|
- delegated process execution is accepted only when an OS sandbox is behaviorally verified;
|
|
123
124
|
- direct argv execution is used unless the explicit shell tool is authorized;
|
|
124
125
|
- confined paths are canonicalized and symbolic-link write escape is rejected;
|
|
125
|
-
- request bodies, files, messages, output, logs, state, retained results, and
|
|
126
|
-
- cancellation, timeout, disconnect, replacement, and shutdown have explicit process ownership and cleanup semantics;
|
|
126
|
+
- request bodies, files, messages, output, logs, state, retained results, concurrency, and error-cause traversal are bounded; byte limits stop further source consumption rather than merely truncating retained data;
|
|
127
|
+
- cancellation, timeout, disconnect, replacement, and shutdown have explicit process ownership and cleanup semantics; streamed calls persist ownership and deadlines across Durable Object hibernation, and per-WebSocket generations reject delayed results or close events from an obsolete connection;
|
|
127
128
|
- candidate daemons and browser extensions cannot replace healthy incumbents before compatibility and readiness verification;
|
|
128
|
-
- default logs
|
|
129
|
+
- default logs, audit records, discovery warnings, browser responses, and administration errors omit secrets, arguments, contents, raw paths, form values, raw local exception text, and output;
|
|
129
130
|
- multi-stage mutations are atomic or recoverable and do not silently claim partial success;
|
|
130
131
|
- device-root rotation is two-phase and does not promote an undeployed key;
|
|
131
132
|
- supply-chain actions are pinned, minimally permissioned, reviewed, and separately gated.
|
|
@@ -159,9 +160,13 @@ A compromised active owner client can exercise the daemon ceiling without a seco
|
|
|
159
160
|
|
|
160
161
|
Use a narrower profile, separate OS account, container, or VM when prompts, repositories, clients, or workloads are mutually untrusted.
|
|
161
162
|
|
|
163
|
+
### Public Worker endpoint and quota guards
|
|
164
|
+
|
|
165
|
+
The default public endpoint remains the automatically provisioned `workers.dev` origin so ordinary users need no DNS zone or custom domain. The in-Worker Rate Limiting binding is deliberately a Durable Object burst guard, not an authentication boundary or an exact global quota accountant. It runs after a Worker invocation begins, is scoped by Cloudflare location, and currently uses one deployment-wide stateful bucket per location. A concentrated source can therefore cause a temporary localized denial for other clients sharing that location, while distributed traffic can still consume the account-wide Workers request allowance. Binding failure is fail-open to avoid turning a quota helper into an account outage. Operators who already control an external edge may add pre-Worker filtering independently, but the public package does not require or assume a private domain.
|
|
166
|
+
|
|
162
167
|
### Bearer clients
|
|
163
168
|
|
|
164
|
-
DPoP is optional for interoperability. A Bearer token can be used by whoever possesses it until expiry or revocation. Short access-token lifetime, rotating refresh families, client binding, account versioning, and replay-family revocation reduce but do not eliminate token theft risk.
|
|
169
|
+
DPoP is optional for interoperability. A Bearer token can be used by whoever possesses it until expiry or revocation. Short access-token lifetime, rotating refresh families, client binding, account versioning, a bounded identity-equivalent concurrency window, and post-window replay-family revocation reduce but do not eliminate token theft risk.
|
|
165
170
|
|
|
166
171
|
### Same-user interference
|
|
167
172
|
|
|
@@ -185,7 +190,7 @@ Application-level limits bound many requests and outputs. An authorized owner pr
|
|
|
185
190
|
|
|
186
191
|
### System VPN/TUN and distributed activation
|
|
187
192
|
|
|
188
|
-
A system VPN/TUN can remain administratively connected while its selected upstream route, synthetic DNS mapping, or transport path is temporarily unusable. Machine Bridge can detect missing inbound relay traffic, classify the socket close, bound retry delay, and report outage history, but it cannot select or repair a third-party VPN node. `system-network-stack` is therefore not a claim of direct routing.
|
|
193
|
+
A system VPN/TUN can remain administratively connected while its selected upstream route, synthetic DNS mapping, or transport path is temporarily unusable. Machine Bridge can detect missing inbound relay traffic, classify the socket close, bound retry delay, and report outage history, but it cannot select or repair a third-party VPN node. `system-network-stack` is therefore not a claim of direct routing. A Worker transport/liveness invalidation is treated as a retryable socket-generation failure; elevating it to a permanent protocol error would let an ordinary network fault restart the daemon and amplify the outage. Unknown protocol messages and authentication or version mismatch remain fail-closed.
|
|
189
194
|
|
|
190
195
|
Candidate activation verifies the foreground candidate before service handoff and records exact package/deployment evidence. It does not provide an atomic transaction spanning Cloudflare deployment and every local service manager. If the Worker changes and the local handoff later fails, the operator may need the recorded previous runtime/deployment evidence to complete rollback. Local cleanup errors are aggregated rather than hidden, but remote rollback is not fabricated.
|
|
191
196
|
|
|
@@ -199,11 +204,11 @@ Regression suites cover:
|
|
|
199
204
|
|
|
200
205
|
- account roles, trusted clients, account-version revocation, refresh-family rotation/replay, DPoP proofs, and non-escalatable effective authority;
|
|
201
206
|
- root-certified ephemeral sessions, preflight nonce replay, daemon challenge binding, readiness, reconnect, and candidate replacement;
|
|
202
|
-
- signed account administration, client revocation, and removal of the long-lived administration secret;
|
|
207
|
+
- signed account administration, client revocation, bounded strict-JSON administration responses, and removal of the long-lived administration secret;
|
|
203
208
|
- control-plane path denial, path canonicalization, symlink handling, sensitive/persistence targets, and object ownership;
|
|
204
209
|
- delegated sandbox behavior and fail-closed platform detection;
|
|
205
|
-
- process/session cleanup, managed-job lifecycle and recovery, state locks, atomic persistence, and destructive removal;
|
|
206
|
-
- browser pairing, version/capability handshake, broker routing, sensitive input, and navigation controls;
|
|
210
|
+
- process/session cleanup, generated-key rollback including cleanup failure, managed-job lifecycle and recovery, state locks, atomic persistence, and destructive removal;
|
|
211
|
+
- browser pairing, version/capability handshake, broker routing, independent concurrency limits, public-error redaction, sensitive input, and navigation controls;
|
|
207
212
|
- audit-chain integrity, privacy redaction, package contents, installation, release impact, dependency integrity, CodeQL, and Scorecard findings;
|
|
208
213
|
- malformed, over-limit, concurrent, replayed, stale, and fault-injected inputs.
|
|
209
214
|
|
package/docs/UPGRADING.md
CHANGED
|
@@ -67,6 +67,12 @@ Legacy version 2 lease state may be listed, revoked, or cleared for cleanup, but
|
|
|
67
67
|
|
|
68
68
|
`ACCOUNT_ADMIN_SECRET` is deleted from local state and is no longer deployed to the Worker. Account and OAuth-client administration uses the same root-certified ephemeral session established for daemon startup or an independently authorized local administration command.
|
|
69
69
|
|
|
70
|
+
## Version 3 beta.21 relay-continuity change
|
|
71
|
+
|
|
72
|
+
Beta.21 changes the Worker-side stream-call record and MCP discovery contract. `tools/list` is stable for an authenticated account role; `server_info.authorization.effective_tools` remains the live execution authority. Streamed calls persist their daemon instance, WebSocket generation, request correlation, and deadlines so Durable Object hibernation or restart does not itself orphan an active call. JSON-only requests retain the prior bounded in-event path.
|
|
73
|
+
|
|
74
|
+
Treat beta.21 as a coordinated Worker and daemon candidate. A beta.20 Worker or daemon does not implement the same generation and persistence contract, so exact-version convergence is required before continuity claims are accepted. The change preserves calls only across a relay interruption while the same local daemon process and machine remain alive. A daemon-process restart, machine shutdown, or lost local execution state is still not durable execution; use managed jobs for that requirement.
|
|
75
|
+
|
|
70
76
|
## Normal upgrade
|
|
71
77
|
|
|
72
78
|
1. Inspect or cancel interactive processes and managed jobs that should not survive daemon replacement.
|
|
@@ -101,7 +107,7 @@ A full daemon policy is not proof that a delegated account has full authority. U
|
|
|
101
107
|
|
|
102
108
|
Machine Bridge rejects unreadable, malformed, foreign-schema, or ambiguous state rather than silently initializing replacement state.
|
|
103
109
|
|
|
104
|
-
Worker deployment records upload success separately from health convergence.
|
|
110
|
+
Worker deployment records upload success separately from health convergence. Post-deployment verification allows a longer bounded edge-propagation window. Once the current package fingerprint and version are recorded, ordinary retries verify that deployment without uploading again; only explicit `--force-worker` authorizes a duplicate deployment after diagnosis. A failed activation that stopped an active service restores the provider after releasing candidate and workflow locks, and a pending root is not promoted merely because Wrangler returned success.
|
|
105
111
|
|
|
106
112
|
The packaged Swift broker source is a development and protocol-conformance fixture only. The local build is ad-hoc signed and is deliberately rejected by the production validator because it cannot obtain a provisioning-profile-validated data-protection Keychain access group. A production Secure Enclave broker must be shipped as an app-like, correctly signed and provisioned component outside the npm runtime build.
|
|
107
113
|
|
|
@@ -120,6 +126,18 @@ A rollback must restore together:
|
|
|
120
126
|
- the prior browser extension.
|
|
121
127
|
|
|
122
128
|
Do not roll back by editing version or schema fields, copying selected credential files, or restoring only the Worker. Prefer fixing forward when a complete backup is unavailable.
|
|
129
|
+
## Version 3.0.0-beta.18
|
|
130
|
+
|
|
131
|
+
Beta.18 replaces beta.17 for hosted clients that experienced intermittent account connection loss during refresh rotation. Upgrade Worker, daemon/CLI, and browser-extension metadata together. Existing refresh state migrates from schema 2 to schema 3 without credential deletion.
|
|
132
|
+
|
|
133
|
+
Activate the candidate through the standard owner command:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
npm run release:candidate:activate -- --allow-worker-deploy
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The existing same-name `workers.dev` endpoint remains the public MCP URL, so users do not need to own a domain or update the hosted client endpoint solely for this upgrade.
|
|
140
|
+
|
|
123
141
|
## Version 3.0.0-beta.15
|
|
124
142
|
|
|
125
143
|
Beta.15 replaces blocked beta.14. Upgrade Worker, daemon/CLI, and browser-extension metadata together through the normal candidate activation flow. Streamed daemon calls now use event-driven settlement: the initiating Durable Object request returns after registration and send, while later WebSocket, cancellation, timeout, send-failure, or reconnect-expiry events persist the terminal result. Clients that support standard resumption should reconnect and reinitialize so they send `MCP-Session-Id` and use `GET /mcp` with `Last-Event-ID`; older JSON-only clients retain single-response behavior but cannot recover a disposed response stream.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "machine-bridge-mcp",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.21",
|
|
4
4
|
"description": "Cross-client MCP bridge for local agent context, structured browser and application automation, files, Git, processes, resources, and durable jobs over stdio or OAuth relay.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,6 +39,7 @@ const tests = [
|
|
|
39
39
|
"tests/delegated-process-sandbox-test.mjs",
|
|
40
40
|
"tests/dpop-test.mjs",
|
|
41
41
|
"tests/worker-security-boundaries-test.mjs",
|
|
42
|
+
"tests/ssh-key-test.mjs",
|
|
42
43
|
];
|
|
43
44
|
|
|
44
45
|
try {
|
|
@@ -76,6 +77,8 @@ try {
|
|
|
76
77
|
"src/local/log.mjs": [60, 40],
|
|
77
78
|
"src/local/runtime.mjs": [75, 55],
|
|
78
79
|
"src/local/runtime-paths.mjs": [90, 50],
|
|
80
|
+
"src/local/resource-operations.mjs": [80, 50],
|
|
81
|
+
"src/local/account-admin.mjs": [90, 60],
|
|
79
82
|
"src/local/cli.mjs": [48, 21.9],
|
|
80
83
|
"src/local/cli-service.mjs": [100, 75],
|
|
81
84
|
"src/local/service-convergence.mjs": [100, 75],
|
|
@@ -103,6 +106,7 @@ try {
|
|
|
103
106
|
"src/local/runtime-diagnostics.mjs": [75, 65],
|
|
104
107
|
"src/local/runtime-capabilities.mjs": [75, 45],
|
|
105
108
|
"src/local/monotonic-deadline.mjs": [100, 100],
|
|
109
|
+
"src/local/path-inspection.mjs": [100, 60],
|
|
106
110
|
"src/local/state.mjs": [85, 45],
|
|
107
111
|
"src/local/relay-connection.mjs": [90, 55],
|
|
108
112
|
"src/local/managed-jobs.mjs": [85, 50],
|
|
@@ -129,6 +133,14 @@ try {
|
|
|
129
133
|
"src/worker/mcp-resumption-config.ts": [100, 80],
|
|
130
134
|
"src/worker/mcp-resumption-records.ts": [90, 65],
|
|
131
135
|
"src/worker/mcp-stream-proxy.ts": [85, 55],
|
|
136
|
+
"src/worker/mcp-stream-subscription.ts": [85, 60],
|
|
137
|
+
"src/worker/worker-static-routes.ts": [100, 90],
|
|
138
|
+
"src/worker/worker-metadata.ts": [100, null],
|
|
139
|
+
"src/worker/worker-edge-guard.ts": [100, 55],
|
|
140
|
+
"src/worker/worker-edge-log.ts": [100, 80],
|
|
141
|
+
"src/worker/oauth-token-issuance.ts": [100, 80],
|
|
142
|
+
"src/worker/oauth-token-derivation.ts": [100, 75],
|
|
143
|
+
"src/worker/oauth-refresh-exchange.ts": [95, 60],
|
|
132
144
|
"src/worker/mcp-stream-dispatch.ts": [90, 60],
|
|
133
145
|
"src/worker/mcp-resumption.ts": [90, 70],
|
|
134
146
|
"src/worker/mcp-stream.ts": [90, 65],
|
|
@@ -5,6 +5,7 @@ import { encodeDeviceSessionCertificate, signWithDeviceSessionIdentity, validate
|
|
|
5
5
|
import { BridgeError } from "./errors.mjs";
|
|
6
6
|
|
|
7
7
|
const REQUEST_TIMEOUT_MS = 15_000;
|
|
8
|
+
const MAX_ADMIN_RESPONSE_BYTES = 1024 * 1024;
|
|
8
9
|
|
|
9
10
|
export function generateAccountPassword() {
|
|
10
11
|
return `account_password_${randomBytes(32).toString("base64url")}`;
|
|
@@ -84,7 +85,7 @@ export class AccountAdminClient {
|
|
|
84
85
|
throw new BridgeError("network_error", "account administration request failed", { cause: error, retryable: true });
|
|
85
86
|
});
|
|
86
87
|
if (response.status === 204) return { removed: true };
|
|
87
|
-
const payload = await response
|
|
88
|
+
const payload = await readAdminJsonResponse(response);
|
|
88
89
|
if (!response.ok) {
|
|
89
90
|
const message = typeof payload.message === "string" ? payload.message : typeof payload.error === "string" ? payload.error : `account administration failed (${response.status})`;
|
|
90
91
|
throw new BridgeError(response.status === 404 ? "not_found" : response.status === 409 ? "conflict" : response.status === 401 ? "authentication_failed" : "invalid_request", message);
|
|
@@ -93,6 +94,72 @@ export class AccountAdminClient {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
|
|
98
|
+
async function readAdminJsonResponse(response) {
|
|
99
|
+
const declared = Number(response.headers.get("content-length") || "0");
|
|
100
|
+
if (Number.isFinite(declared) && declared > MAX_ADMIN_RESPONSE_BYTES) {
|
|
101
|
+
await cancelResponseBody(response.body);
|
|
102
|
+
throw new BridgeError("invalid_response", "account administration response exceeded the size limit");
|
|
103
|
+
}
|
|
104
|
+
if (!response.body) {
|
|
105
|
+
if (response.ok) throw new BridgeError("invalid_response", "account administration response was empty");
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
const reader = response.body.getReader();
|
|
109
|
+
const chunks = [];
|
|
110
|
+
let bytes = 0;
|
|
111
|
+
try {
|
|
112
|
+
for (;;) {
|
|
113
|
+
const { done, value } = await reader.read();
|
|
114
|
+
if (done) break;
|
|
115
|
+
if (!value) continue;
|
|
116
|
+
bytes += value.byteLength;
|
|
117
|
+
if (bytes > MAX_ADMIN_RESPONSE_BYTES) {
|
|
118
|
+
await cancelReader(reader);
|
|
119
|
+
throw new BridgeError("invalid_response", "account administration response exceeded the size limit");
|
|
120
|
+
}
|
|
121
|
+
chunks.push(value);
|
|
122
|
+
}
|
|
123
|
+
} finally {
|
|
124
|
+
reader.releaseLock();
|
|
125
|
+
}
|
|
126
|
+
let payload;
|
|
127
|
+
try {
|
|
128
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(concatBytes(chunks, bytes));
|
|
129
|
+
payload = JSON.parse(text);
|
|
130
|
+
} catch (cause) {
|
|
131
|
+
if (response.ok) {
|
|
132
|
+
throw new BridgeError("invalid_response", "account administration response was not valid JSON", { cause });
|
|
133
|
+
}
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
137
|
+
if (response.ok) throw new BridgeError("invalid_response", "account administration response was not a JSON object");
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
140
|
+
return payload;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function cancelResponseBody(body) {
|
|
144
|
+
if (!body) return;
|
|
145
|
+
const reader = body.getReader();
|
|
146
|
+
try { await cancelReader(reader); } finally { reader.releaseLock(); }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function cancelReader(reader) {
|
|
150
|
+
try { await reader.cancel("response size limit reached"); } catch { /* cleanup only */ }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function concatBytes(chunks, bytes) {
|
|
154
|
+
const output = new Uint8Array(bytes);
|
|
155
|
+
let offset = 0;
|
|
156
|
+
for (const chunk of chunks) {
|
|
157
|
+
output.set(chunk, offset);
|
|
158
|
+
offset += chunk.byteLength;
|
|
159
|
+
}
|
|
160
|
+
return output;
|
|
161
|
+
}
|
|
162
|
+
|
|
96
163
|
export function accountAdminRequestHeaders({
|
|
97
164
|
sessionIdentity,
|
|
98
165
|
origin,
|
|
@@ -4,6 +4,7 @@ import { dirname, join, relative, sep } from "node:path";
|
|
|
4
4
|
import { assertAllowedPath, MAX_SKILL_ROOTS } from "./agent-contract.mjs";
|
|
5
5
|
import { sha256 } from "./agent-context-projection.mjs";
|
|
6
6
|
import { readRegularUtf8 } from "./agent-text-file.mjs";
|
|
7
|
+
import { classifyOperationalError } from "./log.mjs";
|
|
7
8
|
|
|
8
9
|
export const MAX_SKILL_ENTRY_BYTES = 512 * 1024;
|
|
9
10
|
export const MAX_SKILL_RESULTS = 500;
|
|
@@ -99,10 +100,17 @@ export async function discoverLocalSkills(options) {
|
|
|
99
100
|
if (entry.isDirectory()) {
|
|
100
101
|
stack.push({ directory: child, depth: current.depth + 1 });
|
|
101
102
|
} else if (entry.isSymbolicLink()) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
let target;
|
|
104
|
+
try { target = await realpath(child); } catch (error) {
|
|
105
|
+
if (warnings.length < 100) warnings.push({ entrypoint: child, message: boundedMessage(error) });
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
let targetInfo;
|
|
109
|
+
try { targetInfo = await stat(target); } catch (error) {
|
|
110
|
+
if (warnings.length < 100) warnings.push({ entrypoint: child, message: boundedMessage(error) });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (!targetInfo.isDirectory()) continue;
|
|
106
114
|
assertAllowedPath(target, options.workspace, options.unrestricted, "skill symlink target");
|
|
107
115
|
stack.push({ directory: target, depth: current.depth + 1 });
|
|
108
116
|
}
|
|
@@ -214,8 +222,11 @@ async function summarizeSkill(entrypoint, sourceRoot) {
|
|
|
214
222
|
|
|
215
223
|
/** @param {unknown} error */
|
|
216
224
|
function boundedMessage(error) {
|
|
225
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code) {
|
|
226
|
+
return `local skill access failed (${classifyOperationalError(error)})`;
|
|
227
|
+
}
|
|
217
228
|
const message = error instanceof Error ? error.message : String(error || "invalid local skill");
|
|
218
|
-
return message.replace(/[\r\n]+/g, " ").slice(0, 1000);
|
|
229
|
+
return message.replace(/[\r\n\u0000-\u001f\u007f]+/g, " ").slice(0, 1000);
|
|
219
230
|
}
|
|
220
231
|
|
|
221
232
|
/** @param {string} value */
|