machine-bridge-mcp 3.0.0-beta.17 → 3.0.0-beta.20
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 +34 -0
- package/SECURITY.md +6 -4
- 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 +8 -8
- package/docs/AUDIT.md +14 -0
- package/docs/ENGINEERING.md +2 -2
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/LOCAL_AUTHORIZATION.md +1 -1
- package/docs/LOCAL_AUTOMATION.md +1 -1
- package/docs/MULTI_ACCOUNT.md +1 -1
- package/docs/OPERATIONS.md +8 -2
- package/docs/OVERVIEW.md +1 -1
- package/docs/TESTING.md +10 -8
- package/docs/THREAT_MODEL.md +10 -6
- package/docs/UPGRADING.md +12 -0
- 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/resource-operations.mjs +37 -8
- package/src/local/runtime-capabilities.mjs +15 -3
- package/src/local/runtime.mjs +4 -3
- package/src/local/workspace-file-service.mjs +29 -11
- package/src/worker/http.ts +37 -15
- package/src/worker/index.ts +38 -73
- package/src/worker/mcp-resumption.ts +14 -9
- 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/runtime-alarm.ts +22 -2
- package/src/worker/worker-edge-guard.ts +81 -0
- package/src/worker/worker-edge-log.ts +63 -0
- package/src/worker/worker-metadata.ts +39 -0
- package/src/worker/worker-static-routes.ts +38 -50
- package/wrangler.jsonc +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.20 - 2026-07-26
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Rewrite the bounded Worker error-cause traversal with an explicit object type guard and `WeakSet<object>` cycle tracking. This preserves the eight-level/cycle-safe classification behavior while eliminating the CodeQL `js/comparison-between-incompatible-types` finding; the existing cyclic-cause regression test continues to enforce non-duplication.
|
|
8
|
+
|
|
9
|
+
## 3.0.0-beta.19 - 2026-07-26
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Restore the documented `account revoke-client CLIENT_ID` CLI command. The action was implemented end to end, but its positional-argument limit was omitted, so every valid client ID was rejected as an extra positional argument before the signed administration request could be sent. Add direct parser regression coverage for both `account clients` and `account revoke-client`.
|
|
14
|
+
|
|
15
|
+
## 3.0.0-beta.18 - 2026-07-26
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Prevent intermittent hosted-client account loss during refresh-token rotation. A consumed refresh token may now recover at most two same-client, same-resource, same-scope, same-DPoP retries inside a 30-second concurrency window. Both retries reproduce the original deployment-keyed HMAC replacement pair without creating another credential branch or extending expiration; retries beyond that bound return `temporarily_unavailable`, while replay after the window still revokes the complete family. Schema-2 refresh state migrates in place to schema 3.
|
|
20
|
+
- Normalize unexpected outer-Worker failures to a structured retryable `502 worker_gateway_error` instead of allowing `scriptThrewException`/Cloudflare 1101 to surface as a generic account connection failure. Logged error classes contain only error names/codes, never exception messages.
|
|
21
|
+
- Retry an internal terminal WebSocket subscription with bounded delays after transport closure or retryable 429/5xx responses. Normal streamed calls retain the fixed two-request Durable Object path; failure recovery is capped at three subscription attempts.
|
|
22
|
+
- Stop reading request bodies immediately after a declared or observed size violation instead of draining attacker-controlled bytes. Permission and I/O failures during write-path and workspace traversal checks now propagate rather than being misclassified as missing files.
|
|
23
|
+
- Make partial application and skill discovery explicit through bounded path-projected warnings and coarse error classes. Optional `session_bootstrap` failure remains non-fatal but is now visible in Worker observability.
|
|
24
|
+
- Bound account-administration responses to one MiB, cancel oversized bodies, and require successful replies to be JSON objects. Generated SSH key registration now attempts both cleanup targets and reports incomplete rollback instead of silently leaving an unregistered private key.
|
|
25
|
+
- Add a fixed browser-extension error boundary, remove raw debugger details from successful fallback results, and enforce the 32-operation concurrency ceiling independently inside the extension. Error-cause inspection is cycle-aware and capped at eight levels.
|
|
26
|
+
|
|
27
|
+
### Quota and deployment hardening
|
|
28
|
+
|
|
29
|
+
- Serve all public discovery metadata and unknown-path 404 responses in the outer Worker. Only an exact stateful route-and-method allowlist can reach the rate limiter and Durable Object; invalid methods are rejected at the stateless edge.
|
|
30
|
+
- Add a Cloudflare Rate Limiting binding before Durable Object dispatch. Binding failure is fail-open because it is a quota guard rather than an authorization boundary; OAuth, session, and role checks remain inside the Durable Object.
|
|
31
|
+
- Coalesce Durable Object alarms: an already scheduled earlier alarm is reused instead of being rewritten on every daemon heartbeat, and empty alarm state avoids redundant deletes.
|
|
32
|
+
- Report refresh outcomes, estimated resumable-stream row writes, and alarm set/delete/no-op counters in Worker observability. Regression tests hold a normal stream to four storage-row writes before expiry cleanup.
|
|
33
|
+
- Rate-limit repeated edge degradation logs and report suppressed-event counts, while redacting sensitive field names. Remove duplicate `waitUntil` registration for one streamed terminal operation.
|
|
34
|
+
- Add hard critical-coverage thresholds for every new OAuth, stream, metadata, quota, edge-logging, and filesystem-state module rather than relying only on line-count architecture checks.
|
|
35
|
+
- Split OAuth refresh exchange, token issuance, terminal subscription, public metadata, and edge quota guards into focused modules rather than raising architecture limits.
|
|
36
|
+
|
|
3
37
|
## 3.0.0-beta.17 - 2026-07-26
|
|
4
38
|
|
|
5
39
|
### Fixed
|
package/SECURITY.md
CHANGED
|
@@ -132,7 +132,7 @@ Version 3 has no long-lived `ACCOUNT_ADMIN_SECRET`. Account and OAuth-client adm
|
|
|
132
132
|
- timestamp;
|
|
133
133
|
- random nonce.
|
|
134
134
|
|
|
135
|
-
The Worker verifies the root certificate, session signature, bounded timestamp, body hash, and nonce replay state. Nonce capacity fails closed instead of evicting live replay markers.
|
|
135
|
+
The Worker verifies the root certificate, session signature, bounded timestamp, body hash, and nonce replay state. Nonce capacity fails closed instead of evicting live replay markers. The local administration client accepts at most one MiB, cancels oversized responses, and requires every successful non-empty reply to be a JSON object; malformed success cannot be mistaken for an empty valid result.
|
|
136
136
|
|
|
137
137
|
Account passwords are generated 256-bit tokens. The Worker stores independent salted verifiers, not plaintext passwords. A generated password is printed once by the command that creates or rotates it.
|
|
138
138
|
|
|
@@ -174,6 +174,8 @@ The browser broker:
|
|
|
174
174
|
|
|
175
175
|
These controls do not make web content trustworthy. Pages may contain prompt injection, deceptive labels, hidden consequences, changing UI state, inaccessible cross-origin frames, or high-value authenticated sessions. Machine Bridge cannot prove the extension is loaded in an isolated profile.
|
|
176
176
|
|
|
177
|
+
The extension independently caps active operations at 32. Its public error boundary returns only fixed or allowlisted guidance; raw Chrome, DevTools, page, URL, selector, filesystem-path, account-shaped, and credential-shaped exception text is not forwarded to a remote client. Successful trusted-input fallback likewise reports a fixed reason rather than the local debugger failure.
|
|
178
|
+
|
|
177
179
|
After trusted input dispatch begins, an ambiguous failure is reported as unknown outcome and is not automatically replayed.
|
|
178
180
|
|
|
179
181
|
Local resources may be injected without returning their bytes through MCP, but the destination page or application still receives them. Screenshots and page source can themselves contain secrets.
|
|
@@ -192,15 +194,15 @@ Sensitive and persistence targets are owner-only. Generic remote file tools cann
|
|
|
192
194
|
|
|
193
195
|
Direct processes use argv without shell parsing. Shell expansion is available only through the explicit shell tool.
|
|
194
196
|
|
|
195
|
-
Process counts, stdin, output, timeouts, retained sessions, and tool-call concurrency are bounded. Timeout, cancellation,
|
|
197
|
+
Process counts, stdin, output, timeouts, retained sessions, and tool-call concurrency are bounded. Timeout, explicit cancellation, reconnect-grace expiry, runtime shutdown, and non-recoverable daemon replacement use process-tree termination with bounded graceful and forced phases. A transient relay or HTTP/SSE disconnect is not cancellation.
|
|
196
198
|
|
|
197
|
-
Interactive process sessions die
|
|
199
|
+
Interactive process sessions die when their owning runtime stops or is replaced; an ordinary same-process relay reconnect does not itself destroy them. Retained output sessions and process control are bound to account, account version, OAuth client, and refresh family.
|
|
198
200
|
|
|
199
201
|
The daemon does not enforce universal CPU, memory, disk, or network quotas. An authorized owner process can still exhaust host or external resources.
|
|
200
202
|
|
|
201
203
|
## Local resources and managed jobs
|
|
202
204
|
|
|
203
|
-
Registered resources store canonical paths and bounded metadata, not file contents. Private resource files require restrictive permissions on Unix-like systems unless explicitly overridden.
|
|
205
|
+
Registered resources store canonical paths and bounded metadata, not file contents. Private resource files require restrictive permissions on Unix-like systems unless explicitly overridden. Generated SSH private-key bytes are never returned through MCP. If state registration fails after a new key pair is created, both files are removed; failure to complete that rollback is surfaced as a compound error rather than silently leaving an unregistered private key.
|
|
204
206
|
|
|
205
207
|
At job acceptance, referenced resources are reopened, bounded, hashed, and copied into a private runtime area. Changed or unavailable resources fail closed. Environment injection may be visible to same-user process inspection; private file-path substitution or stdin is generally safer.
|
|
206
208
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const SAFE_EXACT = new Set([
|
|
3
|
+
"unsupported browser tab action",
|
|
4
|
+
"no active browser tab",
|
|
5
|
+
"navigate requires url",
|
|
6
|
+
"trusted input is unavailable for this action",
|
|
7
|
+
"trusted input currently requires the top frame; use input_mode=dom for a subframe",
|
|
8
|
+
"this page cannot be scripted by a browser extension",
|
|
9
|
+
"page automation module is unavailable",
|
|
10
|
+
"browser request cancelled",
|
|
11
|
+
"tab closed during navigation wait",
|
|
12
|
+
"element reference is stale; inspect the page again",
|
|
13
|
+
"element did not become geometrically stable before timeout",
|
|
14
|
+
"matched element is not a file input",
|
|
15
|
+
"no form or submit control found",
|
|
16
|
+
"matched element is not associated with a form",
|
|
17
|
+
"select option was not found",
|
|
18
|
+
"trusted input requires a valid tab",
|
|
19
|
+
"trusted input target has no usable viewport point",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
function publicError(error) {
|
|
23
|
+
const message = String(error?.message || error || "");
|
|
24
|
+
if (message.startsWith("unknown browser method:")) return "unknown browser method";
|
|
25
|
+
if (message.startsWith("invalid CSS selector:")) return "invalid CSS selector";
|
|
26
|
+
if (/^selector matched \d+ elements;/.test(message)) return "selector matched multiple elements; use ref or index to disambiguate";
|
|
27
|
+
if (message.startsWith("browser wait timed out")) return "browser wait timed out";
|
|
28
|
+
if (message.startsWith("trusted browser input may have been partially dispatched")) {
|
|
29
|
+
return "trusted browser input may have been partially dispatched; the action outcome is unknown. Inspect the page before retrying.";
|
|
30
|
+
}
|
|
31
|
+
if (message.startsWith("form submission failed after")) {
|
|
32
|
+
return "form submission failed after partial changes; inspect the page before retrying";
|
|
33
|
+
}
|
|
34
|
+
if (message.startsWith("element was not actionable before timeout")) return "element was not actionable before timeout";
|
|
35
|
+
if (message.startsWith("element was not clickable before timeout")) return "element was not clickable before timeout";
|
|
36
|
+
if (message.startsWith("unsupported element action:")) return "unsupported element action";
|
|
37
|
+
if (message.startsWith("unsupported key modifier:") || message.startsWith("unsupported key:")) return "unsupported keyboard input";
|
|
38
|
+
return SAFE_EXACT.has(message) ? message : "browser operation failed";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Object.defineProperty(globalThis, "__machineBridgeBrowserErrorBoundary", {
|
|
42
|
+
value: Object.freeze({ publicError }),
|
|
43
|
+
configurable: false,
|
|
44
|
+
});
|
|
45
|
+
})();
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
"action": {
|
|
31
31
|
"default_title": "Machine Bridge Browser"
|
|
32
32
|
},
|
|
33
|
-
"version_name": "3.0.0-beta.
|
|
33
|
+
"version_name": "3.0.0-beta.20",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
importScripts("devtools-input.js", "browser-operations.js");
|
|
2
|
-
|
|
1
|
+
importScripts("browser-error-boundary.js", "devtools-input.js", "browser-operations.js");
|
|
3
2
|
let socket = null;
|
|
4
3
|
let reconnectTimer = null;
|
|
5
4
|
let reconnectAttempt = 0;
|
|
6
5
|
const MAX_RESULT_BYTES = 7 * 1024 * 1024;
|
|
7
6
|
const BROWSER_EXTENSION_PROTOCOL = 3;
|
|
8
7
|
const HANDSHAKE_TIMEOUT_MS = 3000;
|
|
8
|
+
const MAX_ACTIVE_REQUESTS = 32;
|
|
9
9
|
const activeRequests = new Map();
|
|
10
|
-
|
|
11
10
|
chrome.runtime.onInstalled.addListener(() => { ensureReconnectAlarm(); void connectFromStorage(); });
|
|
12
11
|
chrome.runtime.onStartup.addListener(() => { ensureReconnectAlarm(); void connectFromStorage(); });
|
|
13
12
|
chrome.alarms.onAlarm.addListener((alarm) => {
|
|
@@ -21,7 +20,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
21
20
|
return true;
|
|
22
21
|
});
|
|
23
22
|
chrome.action.onClicked.addListener((tab) => void handleActionClick(tab));
|
|
24
|
-
|
|
25
23
|
async function handleActionClick(tab) {
|
|
26
24
|
if (tab?.id && parsePairingPage(tab.url)) {
|
|
27
25
|
await confirmRepairFromTab(tab);
|
|
@@ -31,12 +29,10 @@ async function handleActionClick(tab) {
|
|
|
31
29
|
const pairUrl = pairingUrlFromEndpoint(current.endpoint) || "http://127.0.0.1:39393/pair";
|
|
32
30
|
await chrome.tabs.create({ url: pairUrl });
|
|
33
31
|
}
|
|
34
|
-
|
|
35
32
|
function pairingUrlFromEndpoint(endpoint) {
|
|
36
33
|
const parsed = parseBrokerEndpoint(endpoint);
|
|
37
34
|
return parsed ? `http://127.0.0.1:${parsed.port}/pair` : "";
|
|
38
35
|
}
|
|
39
|
-
|
|
40
36
|
function parsePairingPage(value) {
|
|
41
37
|
let parsed;
|
|
42
38
|
try { parsed = new URL(String(value || "")); } catch { return null; }
|
|
@@ -299,6 +295,12 @@ async function handleMessage(ws, raw, onReady = () => {}) {
|
|
|
299
295
|
closeSocketQuietly(ws, 1002, "duplicate browser request id");
|
|
300
296
|
return;
|
|
301
297
|
}
|
|
298
|
+
if (activeRequests.size >= MAX_ACTIVE_REQUESTS) {
|
|
299
|
+
if (!sendResponse(ws, message.id, false, null, "too many concurrent browser requests")) {
|
|
300
|
+
closeSocketQuietly(ws, 1011, "browser overload response delivery failed");
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
302
304
|
const state = { cancelled: false, timeoutMs: browserOperations().boundedRequestTimeout(message.timeout_ms), socket: ws };
|
|
303
305
|
activeRequests.set(message.id, state);
|
|
304
306
|
try {
|
|
@@ -309,7 +311,7 @@ async function handleMessage(ws, raw, onReady = () => {}) {
|
|
|
309
311
|
closeSocketQuietly(ws, 1011, "browser response delivery failed");
|
|
310
312
|
}
|
|
311
313
|
} catch (error) {
|
|
312
|
-
if (!state.cancelled && !sendResponse(ws, message.id, false, null,
|
|
314
|
+
if (!state.cancelled && !sendResponse(ws, message.id, false, null, globalThis.__machineBridgeBrowserErrorBoundary.publicError(error))) {
|
|
313
315
|
closeSocketQuietly(ws, 1011, "browser error delivery failed");
|
|
314
316
|
}
|
|
315
317
|
} finally {
|
|
@@ -336,7 +338,6 @@ function sendResponse(ws, id, ok, result, error = "") {
|
|
|
336
338
|
return sendSocketQuietly(ws, payload);
|
|
337
339
|
}
|
|
338
340
|
|
|
339
|
-
|
|
340
341
|
function browserOperations() {
|
|
341
342
|
const api = globalThis.__machineBridgeBrowserOperations;
|
|
342
343
|
if (!api || typeof api.dispatch !== "function") throw new Error("browser operations module is unavailable");
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -64,7 +64,7 @@ See [Session instructions, skills, commands, and capability discovery](AGENT_CON
|
|
|
64
64
|
|
|
65
65
|
`BrowserBridgeManager` owns only connection orchestration for the loopback HTTP/WebSocket broker, owner/client failover, routed requests, cancellation, extension replacement, and start/stop generation control. Every asynchronous startup boundary rechecks the generation so a listener or upstream socket cannot appear after `stop()` has invalidated that start. `browser-operation-service.mjs` owns MCP-facing browser argument normalization, resource-backed values/uploads, form semantics, screenshot conversion, and status presentation. Extension version/capability parsing lives in the strict checked `browser-extension-protocol.mjs`; pairing files and local HTML/Host/Origin helpers live in `browser-pairing-store.mjs`. The first runtime for the machine-level state root becomes broker owner; additional workspaces and stdio runtimes authenticate to `/runtime` and proxy through the same extension socket. This preserves one extension pairing while allowing multiple local MCP runtimes.
|
|
66
66
|
|
|
67
|
-
The packaged Manifest V3 extension runs in the user's existing Chromium profile. Its service worker is limited to pairing, transport, acknowledged protocol readiness, cancellation, and response routing. Fixed `browser-operations.js` owns tab lifecycle, aggregate frame/source budgets, waits, screenshots, and input-backend selection; fixed `page-automation.js` is injected into selected frames for snapshot-version-2 semantics, stable refs, bounded DOM/text traversal, actionability checks, open-Shadow-DOM traversal, structured DOM operations, multi-field forms, and resource-backed file inputs. Fixed `devtools-input.js` exposes only bounded mouse, keyboard, and text sequences through the Chromium debugger API; callers cannot select CDP methods. Trusted sessions attach for one action and detach in `finally`; DOM fallback is allowed only before any DevTools Input dispatch, preventing duplicate side effects after an ambiguous command failure. Protocol 3 requires bidirectional `hello`/`hello_ack` plus exact packaged-version and capability equality; pairing state and replacement are committed only after validation, so an invalid candidate cannot displace or overwrite the working configuration. The broker validates loopback hostnames, canonical extension IDs, matching pairing/broker ports, bearer subprotocols, message sizes, concurrency, and deadlines. Pairing material is owner-only and omitted from MCP/log output.
|
|
67
|
+
The packaged Manifest V3 extension runs in the user's existing Chromium profile. Its service worker is limited to pairing, transport, acknowledged protocol readiness, cancellation, bounded request lifecycle, and response routing. It independently caps active operations at 32 even though the local broker enforces the same ceiling. Fixed `browser-error-boundary.js` maps only an allowlist of actionable messages to the remote protocol; unclassified Chrome, DevTools, page, selector, URL, path, and account-shaped exception text becomes the fixed `browser operation failed` result. Fixed `browser-operations.js` owns tab lifecycle, aggregate frame/source budgets, waits, screenshots, and input-backend selection; successful trusted-input fallback reports a fixed reason rather than the local debugger exception. Fixed `page-automation.js` is injected into selected frames for snapshot-version-2 semantics, stable refs, bounded DOM/text traversal, actionability checks, open-Shadow-DOM traversal, structured DOM operations, multi-field forms, and resource-backed file inputs. Fixed `devtools-input.js` exposes only bounded mouse, keyboard, and text sequences through the Chromium debugger API; callers cannot select CDP methods. Trusted sessions attach for one action and detach in `finally`; DOM fallback is allowed only before any DevTools Input dispatch, preventing duplicate side effects after an ambiguous command failure. Protocol 3 requires bidirectional `hello`/`hello_ack` plus exact packaged-version and capability equality; pairing state and replacement are committed only after validation, so an invalid candidate cannot displace or overwrite the working configuration. The broker validates loopback hostnames, canonical extension IDs, matching pairing/broker ports, bearer subprotocols, message sizes, concurrency, and deadlines. Pairing material is owner-only and omitted from MCP/log output.
|
|
68
68
|
|
|
69
69
|
See [Local application and browser automation](LOCAL_AUTOMATION.md).
|
|
70
70
|
|
|
@@ -106,7 +106,7 @@ The stdio server implements newline-delimited JSON-RPC over stdin/stdout. It neg
|
|
|
106
106
|
|
|
107
107
|
### Cloudflare Worker and Durable Object
|
|
108
108
|
|
|
109
|
-
Public `/healthz`, `/`,
|
|
109
|
+
Public `/healthz`, `/`, discovery metadata, CORS preflight, and unknown-path 404s are answered by the outer Worker without Durable Object requests. Only an exact stateful route allowlist passes a burst guard and reaches one named Durable Object. It owns:
|
|
110
110
|
|
|
111
111
|
- OAuth clients, authorization codes, hashed access-token records, an independently versioned hashed refresh-token store, and throttling metadata;
|
|
112
112
|
- one active end-to-end-verified daemon WebSocket plus bounded candidate and probing sockets;
|
|
@@ -114,7 +114,7 @@ Public `/healthz`, `/`, and CORS preflight are answered by the outer Worker with
|
|
|
114
114
|
- a bounded in-memory map of pending daemon calls, with monotonic operation/reconnect deadlines projected onto Durable Object alarms and rechecked at every event boundary;
|
|
115
115
|
- bounded resumable MCP delivery metadata and terminal responses for recently disconnected SSE clients.
|
|
116
116
|
|
|
117
|
-
`BridgeRoom` owns
|
|
117
|
+
`BridgeRoom` owns stateful routing, MCP authorization/dispatch, daemon WebSocket lifecycle, pending relay-call composition, cancellation, and resumable state. `worker-static-routes.ts` and `worker-metadata.ts` own stateless public routing; `worker-edge-guard.ts` owns the burst guard and structured quota/gateway failures. `mcp-stream-proxy.ts` owns public SSE adaptation, while `mcp-stream-subscription.ts` owns bounded terminal-subscription retries and payload validation. `mcp-stream-channel.ts` owns Durable Object subscriber registration, single-subscriber replacement, and hibernation-safe terminal push. `mcp-access.ts` owns shared Bearer/DPoP authorization for POST and recovery GET. `mcp-resumption-http.ts` owns recovery routing and signed session/protocol binding and returns descriptors rather than a long-lived response. `mcp-resumption.ts` owns stream admission, transaction ordering, immediate pending/terminal polls, expiry, replay, and lifecycle state; `mcp-resumption-records.ts` owns the compact metadata index, terminal-message bounds, serialization, and SHA-256 integrity metadata. `mcp-stream.ts` owns SSE sequence-zero/sequence-one framing and heartbeats. `mcp-jsonrpc.ts` owns JSON-RPC shape validation, result/error framing, MCP tool-result projection, session-instruction bounds, and protocol-header validation. `websocket-protocol.ts` owns record validation plus best-effort send/close/rejection helpers. `OAuthController` owns OAuth-store pruning, registration throttling, authorization submission, account-admin routing, token exchange, access-token verification, and the serialization queue for OAuth mutations. Worker-internal TypeScript imports use explicit `.ts` specifiers and JSON import attributes, so the same modules are directly executable under the pinned Node runtime and bundled by Wrangler.
|
|
118
118
|
|
|
119
119
|
The Worker verifies OAuth, validates MCP envelopes and optional protocol headers, converts `tools/call` into WebSocket messages, correlates explicit cancellation by access-token hash, signed MCP session, and JSON-RPC ID, and formats text/structured/image results. An HTTP/SSE disconnect is transport disposal, not MCP cancellation. For streamed daemon tools, `BridgeRoom` commits a recovery record, registers an event-settled pending call, sends the daemon envelope, and immediately returns an internal descriptor without retaining a terminal Promise. The later WebSocket result, explicit cancellation, timeout, send failure, or reconnect-grace expiry persists the terminal JSON-RPC envelope. JSON-only calls retain the ordinary Promise path. The outer Worker sends `stream:0`, heartbeats, and `stream:1`; the Durable Object accepts one hibernatable subscriber WebSocket per stream and pushes the terminal JSON-RPC envelope once. Authenticated `GET /mcp` with `Last-Event-ID` resumes only the original OAuth-token/MCP-session stream; POST always creates new work. Public requests cannot select internal descriptor/subscribe modes because the outer boundary removes those headers before forwarding. At most 64 records and 1.5 MiB of terminal JSON per record are retained for two minutes. The recovery store tracks active stream identifiers, not live Promises; a transient terminal map is used only when persistence fails. If the Durable Object restarts with a pending record but no active owner, recovery reports that side effects may have occurred and requires reconciliation before retry. It has no local filesystem or process API.
|
|
120
120
|
|
|
@@ -123,7 +123,7 @@ The Worker verifies OAuth, validates MCP envelopes and optional protocol headers
|
|
|
123
123
|
|
|
124
124
|
The local daemon uses a long-term P-256 device root to certify a 24-hour ephemeral session key. Worker deployment receives only the root public JWK. The default provider on every platform is an owner-only portable private JWK. macOS can instead use non-exportable Secure Enclave material only when `MBM_MACOS_TRUST_BROKER` identifies an app-like broker whose Apple signature, Team ID, canonical non-symlink executable path with no group/other write access, provisioning-backed Keychain capability, and actual create/delete key probe all validate. The enrolled state binds that broker identity, Keychain tag, and public key. One root signature at daemon startup certifies the in-memory session key. Every WebSocket attempt then carries a short-lived preflight signed by the session key and bound to Worker origin, package version, nonce, timestamp, and the root certificate. The nonce is consumed once through bounded Durable Object state. After upgrade, the Worker issues a fresh challenge and accepts tools only after a second session signature binds the challenge, origin, package version, daemon instance ID, and timestamp. Reconnects reuse the in-memory key without touching the root. End-to-end readiness still requires an ordinary relay probe before a verified candidate replaces the incumbent.
|
|
125
125
|
|
|
126
|
-
The primary OAuth store separates trusted client registrations and named accounts from authorization codes and access-token records. A separate versioned Durable Object key owns refresh-token families, consumed-token markers, and family revocation. 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, deployment token version, family identity, and expiration where applicable. Only hashes of bearer tokens are persisted. Access tokens last fifteen minutes; refresh tokens rotate
|
|
126
|
+
The primary OAuth store separates trusted client registrations and named accounts from authorization codes and access-token records. A separate versioned Durable Object key owns refresh-token families, consumed-token markers, and family revocation. 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, deployment token version, family identity, and expiration where applicable. Only hashes of bearer tokens are persisted. Access tokens last fifteen minutes; refresh tokens rotate and expire after fourteen idle days or thirty absolute family days. An identity-equivalent retry may reproduce the same deployment-keyed HMAC replacement pair at most twice inside a 30-second concurrency window without extending its expiration; retry-budget exhaustion is throttled, while reuse after that window revokes the complete family, including active access tokens. The Worker carries account, client, and refresh-family identity with every relayed call so long-lived runtime objects cannot cross principals. 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).
|
|
127
127
|
|
|
128
128
|
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.
|
|
129
129
|
|
|
@@ -168,8 +168,8 @@ Remote OAuth binds each code, access token, and refresh token to a named Machine
|
|
|
168
168
|
3. The Worker validates authorization parameters before displaying a password form.
|
|
169
169
|
4. The user verifies client name and redirect URI and enters a Machine Bridge account name and password.
|
|
170
170
|
5. The Worker creates a five-minute code bound to client, redirect, resource, normalized scope, and PKCE challenge.
|
|
171
|
-
6. A valid verifier exchanges the one-time code for an expiring access token and refresh token; only their hashes are stored. A refresh request is bound to the original public client, account, scope, resource,
|
|
172
|
-
7. The MCP client initializes against the sole current protocol version; an obsolete client must upgrade rather than enter a legacy execution path. The Worker returns a stateless HMAC-bound `MCP-Session-Id`, and later request/cancellation correlation is scoped by OAuth token, MCP session, JSON-RPC id type, and id value. Two clients may therefore reuse the same JSON-RPC id concurrently without collision. Sessionless POSTs remain independent and are not inserted into a token-global cancellation index. When the daemon advertises `session_bootstrap`, the Worker requests bounded local instructions and appends them to the initialization result; failure degrades to static instructions.
|
|
171
|
+
6. A valid verifier exchanges the one-time code for an expiring access token and refresh token; only their hashes are stored. A refresh request is bound to the original public client, account, scope, resource, account version/role, deployment token version, and optional DPoP key. Rotation derives one replacement pair from the consumed token and the private deployment token version, then permits at most two identity-equivalent responses with that exact pair during a 30-second concurrency window; over-budget retries are throttled, and replay after the window revokes the family.
|
|
172
|
+
7. The MCP client initializes against the sole current protocol version; an obsolete client must upgrade rather than enter a legacy execution path. The Worker returns a stateless HMAC-bound `MCP-Session-Id`, and later request/cancellation correlation is scoped by OAuth token, MCP session, JSON-RPC id type, and id value. Two clients may therefore reuse the same JSON-RPC id concurrently without collision. Sessionless POSTs remain independent and are not inserted into a token-global cancellation index. When the daemon advertises `session_bootstrap`, the Worker requests bounded local instructions and appends them to the initialization result; failure degrades to static instructions and increments the bounded `session_bootstrap_failed` observability counter.
|
|
173
173
|
8. A new daemon first authenticates as a bounded `probing` socket. The Worker sends a random `relay_probe`; the local runtime returns it through the normal session-bound result-delivery path; only the matching result produces `ready_ack`, promotion to the active daemon, and safe replacement of an incumbent connection.
|
|
174
174
|
9. `tools/list` is derived only from the active end-to-end-verified daemon; without one, only `server_info` is advertised.
|
|
175
175
|
10. `tools/call` receives a random relay call ID and is bound to the current daemon socket, that daemon process's ephemeral instance identifier, and the authenticated client request key. When the client accepts `text/event-stream`, `BridgeRoom` commits recovery state, registers an event-settled pending call, sends the daemon envelope, and returns a bounded descriptor immediately; the outer Worker owns the SSE priming frame, keepalives, and one internal terminal subscription. No unresolved terminal Promise or Durable Object `waitUntil` owns the dispatch. JSON-only clients retain the single terminal response.
|
|
@@ -231,9 +231,9 @@ The default `full` profile passes the complete parent environment. Isolated envi
|
|
|
231
231
|
|
|
232
232
|
Large object results use `structuredContent` as the authoritative representation. The human text mirror is complete only below the shared 16 KiB projection threshold; above it, both local stdio and the Worker return the same compact byte-count/field summary instead of serializing the object a second time. `process-result-projection.mjs`, `process-output-stream.mjs`, and the shared result projector keep lifecycle, byte retention, and MCP presentation as separate boundaries.
|
|
233
233
|
|
|
234
|
-
Startup-lock waits, daemon takeover, process-session reads, managed-job recovery handoff, browser/page waits, application-cache freshness, and in-memory duration metrics use monotonic elapsed time, so wall-clock correction cannot extend or prematurely terminate their configured duration. Persisted timestamps and retention/credential expiry continue to use wall time. Process sessions retain bounded byte buffers with monotonic offsets, accept bounded stdin, support short output/exit waits, and are capped per runtime. Valid UTF-8 is returned as text; byte slices that are not valid UTF-8 also include lossless base64 data. Head/tail previews trim incomplete UTF-8 boundary code points instead of introducing replacement characters. Session IDs are random. Running sessions are killed on runtime stop
|
|
234
|
+
Startup-lock waits, daemon takeover, process-session reads, managed-job recovery handoff, browser/page waits, application-cache freshness, and in-memory duration metrics use monotonic elapsed time, so wall-clock correction cannot extend or prematurely terminate their configured duration. Persisted timestamps and retention/credential expiry continue to use wall time. Process sessions retain bounded byte buffers with monotonic offsets, accept bounded stdin, support short output/exit waits, and are capped per runtime. Valid UTF-8 is returned as text; byte slices that are not valid UTF-8 also include lossless base64 data. Head/tail previews trim incomplete UTF-8 boundary code points instead of introducing replacement characters. Session IDs are random. Running sessions are killed on runtime stop or non-recoverable daemon replacement; a transient same-process relay disconnect enters bounded recovery instead of being treated as cancellation.
|
|
235
235
|
|
|
236
|
-
Child processes run in a separate process group where supported. Timeout, cancellation,
|
|
236
|
+
Child processes run in a separate process group where supported. Timeout, explicit cancellation, reconnect-grace expiry, runtime shutdown, and non-recoverable replacement send termination to process trees, with a referenced forced-escalation timer that remains alive even when the direct child exits before a resistant descendant. Windows uses tree-aware task termination.
|
|
237
237
|
|
|
238
238
|
Managed jobs use the same argv/environment primitives but a different lifecycle. Each job is capped at 16 main and 16 finally steps, 50 retained jobs, 64 registered resources, 8 MiB of referenced resource bytes, 512 KiB of temporary-file content, and bounded per-step output. They are non-interactive. Resource paths/stdin/environment are injected only inside the runner. Exact resource output redaction is defense in depth; discard capture is the strong option when a command may echo credentials.
|
|
239
239
|
|
package/docs/AUDIT.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Security and privacy audit notes
|
|
2
2
|
|
|
3
|
+
## 2026-07-26 version 3.0.0-beta.18 account-continuity and quota audit
|
|
4
|
+
|
|
5
|
+
The repeated hosted-client message `We couldn't connect your account. Please try again.` occurred while the Worker, Durable Object, daemon, Wrangler login, and end-to-end doctor probes were healthy. The remaining credential path had a destructive concurrency assumption: refresh tokens rotated once, and any immediate second use revoked the complete family—including the replacement credentials just returned to a concurrent request. A network retry or two refreshes racing through the same hosted connector could therefore convert a recoverable response race into total account loss. Beta.18 records a bounded source snapshot for 30 seconds and uses a deployment-keyed, domain-separated HMAC to reproduce the original replacement pair for at most two identity-equivalent retry responses. Retries neither create another credential branch nor extend expiration; the Worker returns a retryable 429 after that budget, and retains whole-family revocation for replay after the grace window. Client, resource, scope, account/version/role, deployment token version, and DPoP binding are revalidated on every retry.
|
|
6
|
+
|
|
7
|
+
The broader quota review found that beta.17 removed time-proportional stream polling but still forwarded public discovery and random paths to the Durable Object, rewrote liveness alarms on every heartbeat, exposed Preview URLs, and rethrew outer failures as platform exceptions. Beta.18 moves all stateless metadata and 404s to the outer Worker, adds a stateful-route rate-limit binding, coalesces alarm writes, exposes logical storage-write/alarm counters, retries terminal subscription transport failure within a hard bound, converts unexpected edge failures to a privacy-safe structured 502, and keeps the automatically provisioned `workers.dev` endpoint as the complete default deployment path.
|
|
8
|
+
|
|
9
|
+
A second fault-directed pass found issues outside the original incident scope. Bounded request readers retained at most the configured bytes but continued draining the rest of an oversized stream, allowing attacker-controlled duration work. Invalid methods on known stateful routes reached the rate limiter and Durable Object before returning 405. Several write-path and recursive-walk checks treated every `lstat`/`opendir` error as “missing,” so permission or I/O failure could be evaluated under a false path state. Repeated platform degradation also emitted one log per request, and newly split security modules were absent from the critical-coverage gate. The final implementation cancels body readers as soon as a declared or observed limit is crossed, rejects invalid methods before stateful dispatch, distinguishes only `ENOENT` from real filesystem failure, exposes partial application/skill discovery warnings, suppresses repeated edge logs with a reported duplicate count, removes an unreachable revoked-family branch and duplicate stream `waitUntil`, and establishes coverage floors for every new boundary module.
|
|
10
|
+
|
|
11
|
+
A third pass across local administration, sensitive-resource rollback, and the browser extension found additional boundary defects. The account administration client accepted an unbounded response and silently converted invalid successful JSON into an empty object. Newly generated SSH key files could remain unregistered if state persistence and best-effort cleanup both failed, because cleanup errors were discarded. The browser extension returned arbitrary local/page exception text and debugger fallback details to the remote caller, relied only on the broker for its concurrency ceiling, and the edge error classifier followed cyclic `cause` chains without a depth limit. Skill-symlink warnings also risked embedding absolute paths through native filesystem messages. These paths now use bounded strict JSON, complete two-file key rollback with compound failure, a fixed browser error boundary and fallback reason, an independent 32-request extension ceiling, bounded cycle-aware error traversal, and coarse path-projected discovery warnings.
|
|
12
|
+
|
|
13
|
+
Tests cover schema-2 migration, two idempotent concurrent refresh retries that reproduce the original access/refresh pair, retry-budget exhaustion without family revocation, post-grace whole-family revocation, DPoP proof replay and client/resource/scope isolation, static-route/unknown-path/invalid-method DO bypass, rate-limit denial and fail-open binding outage, nested/cyclic/deep quota classification, sanitized and throttled gateway logs, immediate oversized-body cancellation, bounded strict administration responses, complete and incomplete SSH rollback, fail-closed filesystem state inspection, privacy-safe partial discovery reporting, fixed browser-error projection and independent concurrency, fixed normal stream request/write budgets, single keepalive registration, subscription retry bounds, and alarm set/no-op/delete behavior. This source audit does not claim that an in-Worker rate limiter can enforce the account-wide Workers Free daily limit against distributed traffic; it also records the deployment-wide per-location bucket as a localized availability trade-off.
|
|
14
|
+
|
|
15
|
+
No live Worker deployment, daemon replacement, npm publication, tag, push, or acceptance is implied by this source audit. The exact beta.18 candidate must be owner-activated and observed before acceptance.
|
|
16
|
+
|
|
3
17
|
## 2026-07-25 version 3.0.0-beta.16 pending-call recovery and boundary audit
|
|
4
18
|
|
|
5
19
|
The reported incident exposed two separate failure domains. The host/connector layer returned `No shard mapper found` for a temporary high-replication backfill keyspace and then rejected even minimal MCP calls. The exact text did not exist in Machine Bridge source, deployed Worker events, daemon logs, or local command output; Worker HTTP server-error counters remained unchanged. The original shard-routing failure therefore occurred before or outside the deployed Worker/daemon boundary. The evidence is insufficient to identify the owner of that upstream temporary store, so this audit does not misattribute it to Cloudflare, the local daemon, OAuth, or Git. The host path later recovered without credential rotation or state deletion.
|
package/docs/ENGINEERING.md
CHANGED
|
@@ -63,7 +63,7 @@ Rules:
|
|
|
63
63
|
- Adapters may translate data but should not duplicate policy or schemas.
|
|
64
64
|
- Every protocol control message emitted by one side must be explicitly accepted, rejected, or version-gated by the other side, with an end-to-end contract test covering the message name and semantics.
|
|
65
65
|
- State transitions are explicit; readiness is not inferred from a lower-level event. An open WebSocket is not authenticated until `hello_ack`, and authenticated transport is not service readiness until an end-to-end result probe has returned on the same relay session. Pre-ready work and premature readiness acknowledgements fail closed.
|
|
66
|
-
- Every externally controlled input is bounded before expensive allocation, traversal, parsing, storage, or execution.
|
|
66
|
+
- Every externally controlled input is bounded before expensive allocation, traversal, parsing, storage, or execution. A byte limit constrains bytes actually consumed and duration work, not only the subset retained in memory; once a declared or observed bound is crossed, cancel or close the source immediately.
|
|
67
67
|
- Externally controlled string keys must not use prototype-chain membership or truthiness on ordinary objects. Use `Map`, `Set`, `Object.hasOwn`, or null-prototype records for command dispatch, enums, ACLs, form fields, registries, and other key-addressed contracts.
|
|
68
68
|
- Repository text must not contain invisible ASCII controls other than tab, CR, and LF; architecture tests enforce this even when JavaScript syntax remains valid.
|
|
69
69
|
- Persistent mutations use owner-only files, bounded no-follow reads, flushed atomic replacement, and integrity checks appropriate to the data.
|
|
@@ -72,7 +72,7 @@ Rules:
|
|
|
72
72
|
- Exclusive locks use the shared complete-before-visible hard-link claim. Reclamation requires process identity plus a matching file snapshot/token; do not unlink a path merely because an earlier read looked stale.
|
|
73
73
|
- Service providers normalize success/failure to one result contract. Definition removal follows the shared platform-stop → verified-daemon-stop → remove order.
|
|
74
74
|
- Retry is limited to classified transient failures. Authentication, authorization, validation, integrity, and policy errors fail immediately.
|
|
75
|
-
- Cleanup-only catches may be best effort, but primary failures must not be silently discarded.
|
|
75
|
+
- Cleanup-only catches may be best effort, but primary failures must not be silently discarded. Rollback of newly created credentials, keys, or other sensitive artifacts is part of the primary integrity result: incomplete rollback must be reported explicitly after attempting every cleanup target.
|
|
76
76
|
- New work should not increase an already broad orchestration module when the behavior has an independent lifecycle or test surface. Extract the domain first.
|
|
77
77
|
|
|
78
78
|
`runtime.mjs` owns local tool semantics. `relay-connection.mjs` owns authenticated relay connection lifecycle. The CLI orchestrates them; it must not become the second implementation of either.
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -170,7 +170,7 @@ The first start performs these operations:
|
|
|
170
170
|
|
|
171
171
|
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.
|
|
172
172
|
|
|
173
|
-
The Worker name is a persistent workspace identity, not a retry counter. A successful Wrangler upload is recorded before health verification, so a later timeout does not require a new name and does not make the next start upload again. Supplying a different `--worker-name` for an initialized workspace requires `--force-worker` because it intentionally creates/replaces a separate Cloudflare Worker identity.
|
|
173
|
+
The Worker name is a persistent workspace identity, not a retry counter. A successful Wrangler upload is recorded before health verification, so a later timeout does not require a new name and does not make the next start upload again. Supplying a different `--worker-name` for an initialized workspace requires `--force-worker` because it intentionally creates/replaces a separate Cloudflare Worker identity. The printed `workers.dev` MCP URL is the standard public endpoint and requires no separately owned domain.
|
|
174
174
|
|
|
175
175
|
To run only in the background after setup:
|
|
176
176
|
|
|
@@ -36,7 +36,7 @@ Dynamic OAuth registration creates an untrusted client record. The first success
|
|
|
36
36
|
- the account role;
|
|
37
37
|
- the OAuth client ID.
|
|
38
38
|
|
|
39
|
-
A client cannot silently switch to another account. Account disablement, role changes, password rotation, client revocation, token-version rotation, and refresh-token replay invalidate the relevant credentials.
|
|
39
|
+
A client cannot silently switch to another account. Account disablement, role changes, password rotation, client revocation, token-version rotation, and refresh-token replay outside the bounded concurrent-refresh window invalidate the relevant credentials.
|
|
40
40
|
|
|
41
41
|
Inspect trusted clients locally:
|
|
42
42
|
|
package/docs/LOCAL_AUTOMATION.md
CHANGED
|
@@ -93,7 +93,7 @@ Menu-bar and menu subtrees are not recursively expanded by default. This keeps m
|
|
|
93
93
|
|
|
94
94
|
## Capability discovery and automatic selection
|
|
95
95
|
|
|
96
|
-
`resolve_task_capabilities` rescans instruction files, skills, explicit/automatic package commands, and relevant local automation metadata on every call. It ranks matching skills and commands, optionally loads the best skill, and compares every canonical-full task with cached installed-application names, so a task that directly names an app does not need generic “app/window” wording. Application inventory is refreshed after a bounded cache interval.
|
|
96
|
+
`resolve_task_capabilities` rescans instruction files, skills, explicit/automatic package commands, and relevant local automation metadata on every call. It ranks matching skills and commands, optionally loads the best skill, and compares every canonical-full task with cached installed-application names, so a task that directly names an app does not need generic “app/window” wording. Application inventory is refreshed after a bounded cache interval. Per-root discovery failures are returned as bounded `warnings`, and capability resolution reports `application_discovery.available`, warning count, truncation, and a coarse error class instead of silently treating an unreadable inventory as an empty successful scan.
|
|
97
97
|
|
|
98
98
|
This is the strongest reliable server-side automation boundary available through MCP: discovery, refresh, ranking, and progressive skill loading are automatic. The MCP host still owns the model loop and decides whether a recommended tool is exposed, approved, or invoked. Machine Bridge cannot force ChatGPT web or another host to make a call that the host declines.
|
|
99
99
|
|
package/docs/MULTI_ACCOUNT.md
CHANGED
|
@@ -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,6 +30,12 @@ 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 |
|
|
@@ -39,7 +45,7 @@ Run these commands from the same environment used for startup so `HTTPS_PROXY`,
|
|
|
39
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; no daemon tools are advertised and the candidate will be closed at the readiness deadline |
|
|
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 |
|
|
@@ -165,7 +171,7 @@ Uninstall acquires a state-root `maintenance.lock` that blocks new profile/state
|
|
|
165
171
|
|
|
166
172
|
### Lifecycle and pending-call diagnosis
|
|
167
173
|
|
|
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,
|
|
174
|
+
`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
175
|
|
|
170
176
|
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
177
|
|
package/docs/OVERVIEW.md
CHANGED
|
@@ -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/TESTING.md
CHANGED
|
@@ -38,7 +38,7 @@ 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;
|
|
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
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 retry, and invalid proxy fail-fast behavior without endpoint or credential disclosure;
|
|
43
43
|
- Worker deployment ambiguity/idempotency: a successful Wrangler result followed by health timeout persists the fingerprint, a second start performs no upload, an actual process restart and disk-state reload still performs no upload, definitive stale-version 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;
|
|
@@ -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,13 @@ 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, Worker-restart ambiguity, result-size fallback, SHA-256 tamper detection, transient persistence failure, expiry, capacity,
|
|
162
|
+
`npm run mcp-resumption:test` directly exercises stream cursor parsing, OAuth-token/MCP-session isolation, immediate pending/terminal polls, active and completed replay, Worker-restart ambiguity, result-size fallback, SHA-256 tamper detection, transient persistence failure, expiry, capacity, completed-record eviction, and the four-row normal stream persistence budget.
|
|
161
163
|
|
|
162
|
-
`npm run worker-runtime-infrastructure:test` verifies outer-Worker stream ownership, stripping of caller-supplied internal headers, bounded descriptor/subscribe adaptation, fixed two-request Durable Object budgets, sequence-zero/sequence-one framing, subscription-error closure, subscriber replacement, registration races, non-daemon socket isolation, and the shared two-minute/64-stream/1.5-MiB contract. It also models the production event-lifecycle boundary: event-mode registration must return without a terminal Promise or early settlement, while later success, daemon rejection, explicit cancellation, timeout, send failure, result transformation, persistence failure, and same-instance reconnect each produce one terminal result and remove pending indexes. Deadline tests deliberately use a scheduler that never fires callbacks, advance the monotonic clock, and prove that event-boundary sweeps expire both attached operation deadlines and detached reconnect deadlines without leaking request keys. A direct runtime-alarm coordinator test verifies earliest-pending scheduling, 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 transfers an attached call and preserves its remaining timeout budget. `npm run worker:integration-test` performs the real Wrangler path: keep SSE open while a concurrent `server_info` succeeds and explicit cancellation reaches the matching daemon call; connect a verified same-instance replacement while the incumbent still owns an in-flight call and prove transfer occurs before incumbent close; disconnect after sequence zero; reject another session; recover with GET plus `Last-Event-ID`; and prove a 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`, or Promise-valued recovery state. The parser accumulates complete SSE events and does not assume network chunk boundaries. CORS coverage requires both `DPoP` and `Last-Event-ID`.
|
|
164
|
+
`npm run worker-runtime-infrastructure:test` verifies outer-Worker stream ownership, stripping of caller-supplied internal headers, bounded descriptor/subscribe adaptation, fixed two-request Durable Object 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, registration races, non-daemon socket isolation, and the shared two-minute/64-stream/1.5-MiB contract. It also models the production event-lifecycle boundary: event-mode registration must return without a terminal Promise or early settlement, while later success, daemon rejection, explicit cancellation, timeout, send failure, result transformation, persistence failure, and same-instance reconnect each produce one terminal result and remove pending indexes. Deadline tests deliberately use a scheduler that never fires callbacks, advance the monotonic clock, and prove that event-boundary sweeps expire both attached operation deadlines and detached reconnect deadlines without leaking request keys. A direct runtime-alarm coordinator test verifies earliest-pending scheduling, 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 transfers an attached call and preserves its remaining timeout budget. `npm run worker:integration-test` performs the real Wrangler path: keep SSE open while a concurrent `server_info` succeeds and explicit cancellation reaches the matching daemon call; connect a verified same-instance replacement while the incumbent still owns an in-flight call and prove transfer occurs before incumbent close; disconnect after sequence zero; reject another session; recover with GET plus `Last-Event-ID`; and prove a 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`, or Promise-valued recovery state. The parser accumulates complete SSE events and does not assume network chunk boundaries. CORS coverage requires both `DPoP` and `Last-Event-ID`.
|
package/docs/THREAT_MODEL.md
CHANGED
|
@@ -122,10 +122,10 @@ The implementation aims to preserve these invariants:
|
|
|
122
122
|
- delegated process execution is accepted only when an OS sandbox is behaviorally verified;
|
|
123
123
|
- direct argv execution is used unless the explicit shell tool is authorized;
|
|
124
124
|
- confined paths are canonicalized and symbolic-link write escape is rejected;
|
|
125
|
-
- request bodies, files, messages, output, logs, state, retained results, and
|
|
125
|
+
- 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;
|
|
126
126
|
- cancellation, timeout, disconnect, replacement, and shutdown have explicit process ownership and cleanup semantics;
|
|
127
127
|
- candidate daemons and browser extensions cannot replace healthy incumbents before compatibility and readiness verification;
|
|
128
|
-
- default logs
|
|
128
|
+
- 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
129
|
- multi-stage mutations are atomic or recoverable and do not silently claim partial success;
|
|
130
130
|
- device-root rotation is two-phase and does not promote an undeployed key;
|
|
131
131
|
- supply-chain actions are pinned, minimally permissioned, reviewed, and separately gated.
|
|
@@ -159,9 +159,13 @@ A compromised active owner client can exercise the daemon ceiling without a seco
|
|
|
159
159
|
|
|
160
160
|
Use a narrower profile, separate OS account, container, or VM when prompts, repositories, clients, or workloads are mutually untrusted.
|
|
161
161
|
|
|
162
|
+
### Public Worker endpoint and quota guards
|
|
163
|
+
|
|
164
|
+
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.
|
|
165
|
+
|
|
162
166
|
### Bearer clients
|
|
163
167
|
|
|
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.
|
|
168
|
+
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
169
|
|
|
166
170
|
### Same-user interference
|
|
167
171
|
|
|
@@ -199,11 +203,11 @@ Regression suites cover:
|
|
|
199
203
|
|
|
200
204
|
- account roles, trusted clients, account-version revocation, refresh-family rotation/replay, DPoP proofs, and non-escalatable effective authority;
|
|
201
205
|
- 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;
|
|
206
|
+
- signed account administration, client revocation, bounded strict-JSON administration responses, and removal of the long-lived administration secret;
|
|
203
207
|
- control-plane path denial, path canonicalization, symlink handling, sensitive/persistence targets, and object ownership;
|
|
204
208
|
- 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;
|
|
209
|
+
- process/session cleanup, generated-key rollback including cleanup failure, managed-job lifecycle and recovery, state locks, atomic persistence, and destructive removal;
|
|
210
|
+
- browser pairing, version/capability handshake, broker routing, independent concurrency limits, public-error redaction, sensitive input, and navigation controls;
|
|
207
211
|
- audit-chain integrity, privacy redaction, package contents, installation, release impact, dependency integrity, CodeQL, and Scorecard findings;
|
|
208
212
|
- malformed, over-limit, concurrent, replayed, stale, and fault-injected inputs.
|
|
209
213
|
|