machine-bridge-mcp 0.18.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +3 -1
  4. package/SECURITY.md +8 -6
  5. package/browser-extension/manifest.json +2 -2
  6. package/browser-extension/page-automation.js +0 -1
  7. package/docs/AGENT_CONTEXT.md +2 -1
  8. package/docs/ARCHITECTURE.md +3 -3
  9. package/docs/AUDIT.md +33 -0
  10. package/docs/ENGINEERING.md +5 -1
  11. package/docs/GETTING_STARTED.md +3 -0
  12. package/docs/LOCAL_AUTOMATION.md +1 -1
  13. package/docs/OPERATIONS.md +21 -1
  14. package/docs/PROJECT_STANDARDS.md +1 -1
  15. package/docs/TESTING.md +9 -3
  16. package/package.json +11 -3
  17. package/scripts/privacy-check.mjs +13 -13
  18. package/scripts/sarif-security-gate.mjs +146 -0
  19. package/src/local/account-access.mjs +0 -1
  20. package/src/local/atomic-fs.mjs +1 -1
  21. package/src/local/browser-bridge.mjs +2 -2
  22. package/src/local/browser-extension-protocol.mjs +1 -1
  23. package/src/local/browser-pairing-store.mjs +3 -4
  24. package/src/local/cli.mjs +22 -55
  25. package/src/local/default-instructions.mjs +4 -3
  26. package/src/local/errors.mjs +1 -5
  27. package/src/local/exclusive-file.mjs +9 -3
  28. package/src/local/full-access-test.mjs +5 -5
  29. package/src/local/job-runner.mjs +1 -1
  30. package/src/local/managed-jobs.mjs +27 -51
  31. package/src/local/relay-connection.mjs +7 -0
  32. package/src/local/runtime.mjs +20 -6
  33. package/src/local/secure-file.mjs +89 -10
  34. package/src/local/service.mjs +25 -35
  35. package/src/local/shell.mjs +32 -10
  36. package/src/local/ssh-key.mjs +71 -45
  37. package/src/local/state.mjs +46 -56
  38. package/src/local/worker-secret-file.mjs +130 -0
  39. package/src/local/workspace-file-service.mjs +10 -2
  40. package/src/shared/server-metadata.json +2 -3
  41. package/src/worker/http.ts +7 -8
  42. package/src/worker/index.ts +31 -41
  43. package/src/worker/mcp-session.ts +72 -0
  44. package/src/worker/oauth-state.ts +1 -1
  45. package/src/worker/pending-calls.ts +36 -5
  46. package/src/worker/tool-timeout.ts +18 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.1 - 2026-07-14
4
+
5
+ ### Startup and release-gate reliability
6
+
7
+ - Fix two production CLI references that were used without imports: `readdirSync` in the default Worker deployment fingerprint path and `inspectProcessInstance` in secret rotation. The former made a freshly installed 1.0.0 package fail immediately on zero-argument startup even though syntax, package, and cross-platform CI checks were green.
8
+ - Fix the Windows Wrangler execution boundary uncovered by the new startup probe. Runtime code no longer passes `wrangler.cmd` to `spawn(..., { shell: false })`, which fails with `EINVAL` on Node 26; it invokes the dependency's declared JavaScript entrypoint through the current Node executable on every platform. A focused regression and the installed startup probe prevent a return to shell shims.
9
+ - Add an ESLint correctness gate over Node production code, tests, scripts, and the packaged browser extension. Undefined identifiers, redeclarations, duplicate keys, unreachable statements, and non-loop constant conditions now fail `npm run check` before packaging or publication. A configuration self-test injects synthetic undefined Node/browser bindings so retaining a no-op or mis-scoped lint script cannot satisfy the gate.
10
+ - Strengthen `install:test` to install the real `npm pack` tarball into an isolated global prefix and execute the installed CLI with zero arguments from a package-free workspace and isolated state root. A cross-platform fake Wrangler shim provides a controlled external boundary; the test requires state initialization, rejects `ReferenceError`/`is not defined`, and proves that default startup reaches Worker orchestration rather than only supporting `--version`.
11
+ - Make both static lint and the installed zero-argument startup probe mandatory parts of the complete local, prepublish, Linux, macOS, and Windows release gate. Architecture checks prevent either gate from being removed silently and now enforce the existing exact-semver dependency policy instead of leaving it as documentation only.
12
+
13
+ ## 1.0.0 - 2026-07-14
14
+
15
+ ### Current-only protocol and runtime contract
16
+
17
+ - Declare only MCP protocol `2025-11-25` across shared metadata, stdio, the local runtime, daemon handshakes, and the Cloudflare Worker. Older protocol dates are no longer advertised as supported; clients must negotiate the current protocol before invoking tools.
18
+ - Add stateless, HMAC-bound `MCP-Session-Id` issuance. Concurrent chat windows using the same OAuth token now have independent JSON-RPC request-id and cancellation domains; sessionless independent POST requests no longer share a token-wide request-id lock.
19
+ - Make terminal relay delivery fail closed: when a completed daemon call cannot return its `tool_result`, the daemon interrupts the ambiguous socket so Worker socket cleanup releases pending calls immediately instead of retaining a phantom active call until its long timeout.
20
+ - Remove numeric-only managed-job lock interpretation and the remaining dead compatibility branches, unused imports, unreachable conditions, and stale orchestration variables. Current JSON ownership records with process start identity and random tokens are the only accepted lock contract.
21
+ - Keep the current local state schema intact so an existing 0.18.x state root upgrades in place. The source upgrade remains operationally bounded: install the new package, run one normal `machine-mcp` startup to converge Worker and daemon versions, then reload the unpacked browser extension.
22
+
23
+ ### Descriptor-first file and process security
24
+
25
+ - Centralize regular-file opens behind a no-follow descriptor primitive that validates the opened object, applies permissions through the descriptor, and performs bounded reads without a separate path check. State, privacy scanning, service logs, managed-job diagnostics, and owner-only files now use this boundary.
26
+ - Inspect SSH key pairs from private, bounded snapshots copied into a private temporary directory. Fingerprinting and public/private correspondence no longer operate on mutable caller paths, and the original files are revalidated by identity and constant-time byte comparison before success is returned.
27
+ - Fix managed-job recovery handoff so the recovery runner must remove `recovery.lock` with the exact handed-off PID and ownership token. It can no longer delete a replacement claim by path alone.
28
+ - Separate the internal `runExecutable` argv boundary from explicit shell execution, bound executable/argv sizes, reject NUL bytes, and retain `shell: false` for direct execution. Worker deployment consistency now uses an HMAC fingerprint keyed by deployment secrets rather than an ordinary digest over secret values.
29
+
30
+ ### Enforced security evidence
31
+
32
+ - Extend classified Windows atomic-replacement retries from 16 to 32 bounded attempts after hosted Windows runners reproduced an `EPERM` sharing window beyond the previous budget. The algorithm still uses one same-directory atomic rename, exponential backoff with jitter, and no delete-destination fallback.
33
+ - Move the repository's local GitHub control-plane prohibition into shared MCP initialization and built-in working agreements so it is visible before project-specific task execution. If local Machine Bridge `git`/`gh` access is unavailable, automation must stop rather than fall back to a hosted GitHub connector or ChatGPT plugin. Align `ENGINEERING.md` and `CONTRIBUTING.md` with the source-release ownership contract.
34
+ - Fail closed when owner-only directories are symlinks, non-directories, cannot be restricted to `0700` on POSIX, or remain group/other-accessible. Extract Worker deployment secret-file lifecycle from the CLI, bind temporary names to process-start identity, delete only positively reclaimable stale files, and surface cleanup failures instead of silently retaining management secrets.
35
+ - Apply the same private-directory boundary to browser pairing, fail managed-job launch when existing diagnostic logs cannot be safely trimmed, and roll back POSIX file/patch commits when exact mode application fails. State-root removal now blocks on unreadable, malformed, symbolic-link, oversized, or otherwise unverifiable config/profile/daemon records instead of treating them as absent.
36
+ - Remove the unused local error factory and default-role constant and make internal-only protocol, pairing, instruction, file, and OAuth helpers non-exported, reducing dead and misleading module surface.
37
+
38
+ - Add a SARIF security gate to the required CodeQL workflow. Any new security-tagged result fails the required check; intentional high-authority process boundaries require an exact rule/path exception with a substantive rationale and expiry date.
39
+ - Add deterministic property tests over hostile browser-protocol bytes, policy combinations, argv values, and shell-metacharacter arguments. Add production-path service command coverage and remove test-only filesystem check/use patterns that obscured CodeQL results.
40
+ - Preserve the complete cross-platform, Worker, stdio, browser, lifecycle, atomic-file, process-tree, package, privacy-history, dependency, and release-integrity suite as the release gate.
41
+
3
42
  ## 0.18.1 - 2026-07-14
4
43
 
5
44
  ### Fixed
package/CONTRIBUTING.md CHANGED
@@ -26,7 +26,7 @@ Before the reviewed code is pushed to `main`:
26
26
  4. inspect the complete diff and the npm package manifest;
27
27
  5. commit and push the reviewed change to GitHub.
28
28
 
29
- The release operator then creates the matching Git tag and GitHub Release and publishes the same version to npm. These release actions are not implicit coding-agent tasks. Unless the user explicitly requests them in the current task, automation must not publish or alter npm versions, create tags or releases, install the CLI globally, deploy the Worker, rotate credentials, mutate live deployment state, or start/stop/install/remove the daemon or autostart service. See [AGENTS.md](AGENTS.md) for the repository automation contract.
29
+ After all required pull-request checks pass, repository automation completes the source release under [AGENTS.md](AGENTS.md): squash-merge through local `gh`, verify the exact `main` push CI, create the annotated version tag, and create or update the matching final GitHub Release. The release operator separately publishes the same version to npm and performs any live machine update. Automation must not publish, deprecate, or unpublish npm packages; install the CLI globally; deploy the Worker; rotate credentials; mutate live deployment state; or start/stop/install/remove the daemon or autostart service without explicit user authorization.
30
30
 
31
31
  After npm publication, the standard machine update is:
32
32
 
package/README.md CHANGED
@@ -49,6 +49,8 @@ This default prioritizes usability, not least privilege. `run_process` and proce
49
49
 
50
50
  For prerequisites, first remote deployment, ChatGPT connection, stdio configuration, browser pairing, upgrades, troubleshooting, and removal, follow [Installation and first use](docs/GETTING_STARTED.md). The commands below are the compact installation reference.
51
51
 
52
+ Version 1 supports only MCP protocol `2025-11-25`. Existing current-schema local state upgrades in place, but clients must reconnect and the unpacked browser extension must be reloaded after package upgrade.
53
+
52
54
  Node.js 26 or newer and npm 12 or newer are required. The repository pins the active development versions in `.node-version`, `.nvmrc`, and `packageManager`; project installs fail on older Node runtimes.
53
55
 
54
56
  Use a new temporary directory for the bootstrap. The existing `npm`/`npx` front-end may inspect nearby project metadata before it launches the requested npm version; starting from an empty directory prevents an unrelated outdated `devEngines` declaration from blocking the bootstrap. The actual installation is then executed by the pinned npm 12 CLI rather than whichever npm happens to be first on `PATH`.
@@ -487,7 +489,7 @@ npm audit --omit=dev --audit-level=high
487
489
  npm pack --dry-run
488
490
  ```
489
491
 
490
- `npm run check` covers privacy and release-impact gates, architecture/link invariants, generated Worker types, TypeScript, recursively discovered JavaScript syntax, catalog-to-runtime handler parity, deterministic relay lifecycle and secure-file tests, concurrent lock/atomic-replacement/PID-reuse fixtures, fail-closed service lifecycle tests, descendant process-tree termination, local path/write/state/log/service invariants, Ed25519/RSA generation and key-pair validation, real-machine `full` sandbox acceptance, a clean npm package-manifest/mode/sensitive-artifact check, managed-job integrity/redaction/finally/cancellation/recovery, a live stdio MCP flow, and a live local OAuth/Worker/WebSocket/MCP flow. GitHub Actions runs the suite on Linux, macOS, and Windows with the pinned Node 26/npm 12 baseline; macOS and package-audit jobs also exercise the documented isolated global installation.
492
+ `npm run check` covers privacy and release-impact gates, architecture/link invariants, generated Worker types, TypeScript, recursively discovered JavaScript syntax, semantic undefined-identifier checks, catalog-to-runtime handler parity, deterministic relay lifecycle and secure-file tests, concurrent lock/atomic-replacement/PID-reuse fixtures, fail-closed service lifecycle tests, descendant process-tree termination, local path/write/state/log/service invariants, Ed25519/RSA generation and key-pair validation, real-machine `full` sandbox acceptance, a clean npm package-manifest/mode/sensitive-artifact check, an isolated installation of the actual packed tarball whose zero-argument CLI startup must reach a controlled Worker-deployment boundary, managed-job integrity/redaction/finally/cancellation/recovery, a live stdio MCP flow, and a live local OAuth/Worker/WebSocket/MCP flow. GitHub Actions runs the complete suite on Linux, macOS, and Windows with the pinned Node 26/npm 12 baseline.
491
493
 
492
494
  The cross-cutting 0.12.0 review, corrected failure modes, and residual OS-level limits are recorded in [docs/AUDIT.md](docs/AUDIT.md). See also [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md), [docs/MULTI_ACCOUNT.md](docs/MULTI_ACCOUNT.md), [docs/AGENT_CONTEXT.md](docs/AGENT_CONTEXT.md), [docs/LOCAL_AUTOMATION.md](docs/LOCAL_AUTOMATION.md), [docs/MANAGED_JOBS.md](docs/MANAGED_JOBS.md), [docs/TESTING.md](docs/TESTING.md), [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [docs/ENGINEERING.md](docs/ENGINEERING.md), [docs/PROJECT_STANDARDS.md](docs/PROJECT_STANDARDS.md), the generated [MCP tool reference](docs/TOOL_REFERENCE.md), and [SECURITY.md](SECURITY.md).
493
495
 
package/SECURITY.md CHANGED
@@ -6,7 +6,7 @@ Security fixes are applied to the latest released version. Upgrade before report
6
6
 
7
7
  ## Reporting a vulnerability
8
8
 
9
- Do not open a public issue for an undisclosed vulnerability. Use GitHub private vulnerability reporting for this repository. Include:
9
+ Do not open a public issue for an undisclosed vulnerability. Use [GitHub private vulnerability reporting](https://github.com/YuLeiFuYun/machine-bridge-mcp/security/advisories/new) for this repository. Include:
10
10
 
11
11
  - affected version and operating system;
12
12
  - remote or stdio transport;
@@ -23,6 +23,8 @@ Tests, examples, documentation, release notes, and package metadata are publicat
23
23
 
24
24
  Removing a value from the current branch does not remove it from Git history, caches, forks, or an already published npm package. Rotate any exposed credential immediately and coordinate destructive history rewriting separately when its risk is justified.
25
25
 
26
+ CodeQL is enforced as a security gate, not merely uploaded as advisory output. The workflow parses generated SARIF and rejects every security-tagged result unless `.github/codeql-accepted-findings.json` contains the exact rule and path, a substantive authority-boundary rationale, and a non-expired review date. Rule-wide suppression is not an acceptable substitute.
27
+
26
28
  ## Core trust model
27
29
 
28
30
  Trusted components are:
@@ -83,7 +85,7 @@ Canonical `full` exposes structured browser and desktop UI authority. The browse
83
85
 
84
86
  The browser loopback broker validates loopback `Host`, requires a random owner-only bearer subprotocol, accepts extension sockets only from a canonical 32-character Chromium extension ID, requires the pairing page and WebSocket endpoint to use the same loopback port, and authenticates additional local runtimes separately. Initial extension pairing is trust-on-first-use; after pairing, a different localhost page cannot replace the stored broker unless the user clicks the extension action while the genuine pairing page is active. The pairing token is never returned by MCP or written to operational logs. These controls defend against casual cross-origin and DNS-rebinding access; they do not protect against malicious code already running as the same OS user or a compromised browser extension profile.
85
87
 
86
- Caller-supplied JavaScript, AppleScript, JXA source, and arbitrary DevTools methods are deliberately unsupported. Actions use fixed implementation code and structured selectors. The debugger adapter is restricted to fixed `Input` commands, attaches only around one trusted action, and detaches in `finally`. `auto` falls back only before any Input dispatch; after dispatch begins, failure is reported as an unknown outcome and is never replayed through DOM. The broker and extension require a protocol-3 `hello`/`hello_ack` exchange plus exact packaged-version and capability equality before either reports readiness. Pairing material is persisted only after acknowledgement, and an invalid replacement candidate cannot displace the current connection or overwrite its stored endpoint/token. This removes an avoidable arbitrary-evaluation surface but does not make DOM or Accessibility actions safe. Pages can contain prompt injection, misleading labels, hidden consequences, cross-origin frames unavailable to inspection, or state that changes between inspection and submission. High-impact account, financial, legal, medical, publishing, deletion, and purchase actions require contextual review and any host/user confirmation the client provides.
88
+ Caller-supplied JavaScript, AppleScript, JXA source, and arbitrary DevTools methods are deliberately unsupported. Actions use fixed implementation code and structured selectors. The debugger adapter is restricted to fixed `Input` commands, attaches only around one trusted action, and detaches in `finally`. `auto` falls back only before any Input dispatch; after dispatch begins, failure is reported as an unknown outcome and is never replayed through DOM. The broker and extension require the current protocol-3 `hello`/`hello_ack` exchange plus exact packaged-version and capability equality before either reports readiness. Pairing material is persisted only after acknowledgement, and an invalid replacement candidate cannot displace the current connection or overwrite its stored endpoint/token. This removes an avoidable arbitrary-evaluation surface but does not make DOM or Accessibility actions safe. Pages can contain prompt injection, misleading labels, hidden consequences, cross-origin frames unavailable to inspection, or state that changes between inspection and submission. High-impact account, financial, legal, medical, publishing, deletion, and purchase actions require contextual review and any host/user confirmation the client provides.
87
89
 
88
90
  Text and file resources can be injected locally so their contents do not appear in MCP arguments or results. The destination page/application still receives the value, and remote tool metadata/results still traverse the Worker and host. Page source and screenshots can themselves contain secrets. Source/inspection budgets are aggregate across frames, page-controlled metadata is bounded, URL userinfo is removed from semantic snapshots, and contenteditable controls with secret-like identity are treated as sensitive; these limits reduce accidental exposure but do not classify every possible secret. Browser/app tool arguments, source, screenshots, field values, and results are intentionally omitted from operational logs.
89
91
 
@@ -114,7 +116,7 @@ A canonical `full` profile guarantees that Machine Bridge itself will not reject
114
116
 
115
117
  ## Mutation integrity
116
118
 
117
- Writes are bounded, reject symbolic-link/non-regular destinations, use same-directory staging, flush staged data, and commit atomically per file. Create-only and lock claims fully write a private temporary file before a same-directory hard-link claim becomes visible; a concurrent destination fails without exposing partial content. Replacement writes use `fsync` plus atomic rename/replacement. Expected hashes and exact edits reduce accidental stale overwrites.
119
+ Writes are bounded, reject symbolic-link/non-regular destinations, use same-directory staging, flush staged data, and commit atomically per file. Sensitive reads and mode changes use shared descriptor-first primitives: regular files are opened with no-follow where supported, validated, bounded before allocation, and permissioned through the descriptor; POSIX owner-only directories are opened with no-follow plus directory-only flags, `fchmod`ed to `0700`, and revalidated before use. Windows retains ACL semantics without pretending POSIX modes are enforceable. Create-only and lock claims fully write a private temporary file before a same-directory hard-link claim becomes visible; a concurrent destination fails without exposing partial content. Replacement writes use `fsync` plus atomic rename/replacement. Expected hashes and exact edits reduce accidental stale overwrites. Requested file modes are applied exactly on POSIX before commit; a mode-setting failure rolls the transaction back rather than silently publishing a semantically incomplete file.
118
120
 
119
121
  Startup, daemon, managed-job transition, recovery, and runner claims record ownership tokens and process start time. Reclamation validates process identity and a file snapshot before removal, limiting PID-reuse and stale-owner races. Recent malformed claims are not deleted immediately. Ordinary startup/state changes wait a bounded interval for a concurrent operation instead of turning a brief service-manager race into an operator error.
120
122
 
@@ -124,7 +126,7 @@ A custom state root is rejected if it overlaps the selected workspace in either
124
126
 
125
127
  ## Credential exposure
126
128
 
127
- Local state contains the account-administration secret, daemon secret, and deployment-wide token version. Account passwords are generated 256-bit tokens and are not stored locally; the Worker stores only independent salted HMAC-SHA-256 verifiers. The fixed high-entropy token format is mandatory, so the verifier does not rely on a CPU-intensive human-password KDF inside the Worker request budget. State, lock, temporary secret, runtime, and service-log files use owner-only permissions where supported. State writes are flushed and atomically replaced. A read/type/permission/size/symbolic-link failure is not treated as empty or corrupt state; only successfully read invalid JSON is moved to a bounded corrupt backup.
129
+ Local state contains the account-administration secret, daemon secret, and deployment-wide token version. Account passwords are generated 256-bit tokens and are not stored locally; the Worker stores only independent salted HMAC-SHA-256 verifiers. The fixed high-entropy token format is mandatory, so the verifier does not rely on a CPU-intensive human-password KDF inside the Worker request budget. State, lock, temporary secret, runtime, pairing, and service-log files use owner-only permissions where supported. Worker deployment secrets are written only to an exclusive `0600` file whose name binds PID and process-start identity; stale cleanup removes only positively reclaimable owners, verifies file identity, retains ambiguous live owners, and reports cleanup failure. State writes are flushed and atomically replaced. A read/type/permission/size/symbolic-link failure is not treated as empty or corrupt state; only successfully read invalid JSON is moved to a bounded corrupt backup. The same rule applies to recursive state-root removal: unreadable or malformed config, profile state, or daemon ownership records block deletion.
128
130
 
129
131
  Logs recursively redact known credential fields, private-key headers, npm/GitHub/GitLab/Slack/Google/AWS/live-payment/API token forms, JWT-shaped bearer values, embedded-credential URLs, email addresses, user-home paths, and control characters. These patterns are defense in depth; unknown, transformed, split, encrypted, or application-specific secret forms can still pass through an explicitly requested tool result.
130
132
 
@@ -156,7 +158,7 @@ Exact canonical and registration-time resource path aliases, exact resource byte
156
158
 
157
159
  Job-scoped `temporary_files` should be used instead of loose helper scripts. They are materialized only below the private job runtime. Remote scripts should preferably be sent through a process stdin instead of written to the remote filesystem.
158
160
 
159
- Runner diagnostic logs are owner-only and do not receive child stdout/stderr. Step output is stored only in bounded job results according to the selected capture mode.
161
+ Runner diagnostic logs are owner-only and do not receive child stdout/stderr. Existing runner logs are tail-trimmed before launch; only an absent file is ignored, while permission, type, symbolic-link, read, truncate, or write failures block launch instead of allowing unbounded append. Step output is stored only in bounded job results according to the selected capture mode.
160
162
 
161
163
  ## OAuth and public endpoints
162
164
 
@@ -170,7 +172,7 @@ Public health and metadata do not expose live workspace or daemon status. The da
170
172
 
171
173
  ## Relay and denial of service
172
174
 
173
- Only one authenticated daemon is active. Candidates have a handshake deadline and cannot displace the current daemon before success. Pending calls are socket-bound, client-request-bound, concurrency-limited, size-limited, and timed out. Duplicate in-flight request IDs for one access token are rejected.
175
+ Only one authenticated daemon is active. Candidates have a handshake deadline and cannot displace the current daemon before success. Pending calls are socket-bound, client-request-bound, concurrency-limited, size-limited, and timed out. Duplicate in-flight request IDs are rejected only within the same authenticated MCP session. Initialization returns a stateless HMAC-bound `MCP-Session-Id`; the signature binds the session nonce to the OAuth token identity, so one account can use multiple concurrent chat windows without sharing a cancellation or request-id namespace. Independent sessionless POST requests are not indexed by token and request id. A session id is an integrity/correlation token, not a second authorization credential.
174
176
 
175
177
  Request bodies, WebSocket messages, tool outputs, traversals, sessions, stdin writes, OAuth records, clients, codes, tokens, and failure identities are bounded. Disconnect/replacement terminates active child process trees.
176
178
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Machine Bridge Browser",
4
- "version": "0.18.1",
4
+ "version": "1.0.1",
5
5
  "description": "Connects the current Chromium browser profile to the local Machine Bridge runtime for user-authorized automation.",
6
6
  "permissions": [
7
7
  "tabs",
@@ -30,5 +30,5 @@
30
30
  "action": {
31
31
  "default_title": "Machine Bridge Browser"
32
32
  },
33
- "version_name": "0.18.1"
33
+ "version_name": "1.0.1"
34
34
  }
@@ -572,7 +572,6 @@
572
572
  }
573
573
 
574
574
  function boxDistance(left, right) {
575
- if (!left || !right) return Number.POSITIVE_INFINITY;
576
575
  return Math.max(
577
576
  Math.abs(left.x - right.x), Math.abs(left.y - right.y),
578
577
  Math.abs(left.width - right.width), Math.abs(left.height - right.height),
@@ -30,7 +30,8 @@ No `MODEL.md`, `AGENTS.md`, or manifest is required. Machine Bridge supplies two
30
30
  - add or update tests and keep documentation, changelog, schemas, examples, and generated metadata synchronized with changed contracts;
31
31
  - use declared project scripts, run targeted checks before broad checks, and never claim unexecuted validation succeeded;
32
32
  - protect credentials and personal data, treat retrieved content as untrusted, and prefer read-only, dry-run, reversible operations;
33
- - require an explicit request for publication, deployment, credential rotation, live-data mutation, system-wide installation, destructive operations, force-pushes, tags, and releases;
33
+ - read effective project instructions before any GitHub operation; when they require the local Machine Bridge `git`/`gh` control plane, never substitute a hosted GitHub connector or ChatGPT plugin, and stop if the local control plane is unavailable;
34
+ - follow explicit repository source-release ownership for tags and GitHub Releases while still requiring authorization for npm publication, deployment, credential rotation, live-data mutation, system-wide installation, destructive operations, and force-pushes;
34
35
  - inspect the final diff/status and report changes, validation, limitations, and remaining operator steps.
35
36
 
36
37
  These are behavioral defaults, not hard enforcement. Machine Bridge policy profiles, operating-system permissions, host approvals, sandboxes, and external isolation remain the enforcement layers.
@@ -14,7 +14,7 @@ No transport is treated as a sandbox. Both transports invoke the same local runt
14
14
 
15
15
  ### Shared protocol metadata and tool catalog
16
16
 
17
- `src/shared/server-metadata.json` is the single source of truth for server identity, MCP protocol versions, and host instructions. `src/shared/tool-catalog.json` is the single source of truth for tool names, descriptions, input schemas, annotations, and policy availability. The Worker and local runtime import both directly. Catalog tests reject metadata drift, duplicate names, unknown availability classes, open schemas, missing annotations, profile drift, and second hand-maintained Worker definitions.
17
+ `src/shared/server-metadata.json` is the single source of truth for server identity, the sole current MCP protocol version, and host instructions. `src/shared/tool-catalog.json` is the single source of truth for tool names, descriptions, input schemas, annotations, and policy availability. The Worker and local runtime import both directly. Catalog tests reject metadata drift, duplicate names, unknown availability classes, open schemas, missing annotations, profile drift, and second hand-maintained Worker definitions.
18
18
 
19
19
  ### CLI and state layer
20
20
 
@@ -150,7 +150,7 @@ Remote OAuth currently determines which registered client token may call tools;
150
150
  4. The user verifies client name and redirect URI and enters a Machine Bridge account name and password.
151
151
  5. The Worker creates a five-minute code bound to client, redirect, resource, scope, and PKCE challenge.
152
152
  6. A valid verifier exchanges the one-time code for an expiring bearer token; only its hash is stored.
153
- 7. The MCP client initializes and negotiates a supported protocol version. When the daemon advertises `session_bootstrap`, the Worker requests bounded local instructions and appends them to the initialization result; failure degrades to static instructions.
153
+ 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.
154
154
  8. `tools/list` is derived from the active daemon handshake; without a daemon, only `server_info` is advertised.
155
155
  9. `tools/call` receives a random relay call ID and is bound to the current socket and authenticated client request key.
156
156
  10. The runtime validates policy and arguments, executes the tool, and returns a bounded result.
@@ -222,7 +222,7 @@ The Worker also treats a new connection as a bounded candidate until it authenti
222
222
 
223
223
  ## Persistence
224
224
 
225
- Local state and global config are owner-only, versioned, and size-bounded. Shared persistence primitives write a complete private temporary file, `fsync` it, and either hard-link it for an exclusive claim or atomically replace the destination. Machine-level browser pairing state is owner-only and shared across workspace runtimes through the local broker; its bearer token is not part of workspace state responses. State, managed-job manager, detached runner, browser pairing, and service definitions share the same flushed atomic-replacement primitive. Only classified transient Windows sharing failures are retried, using a bounded sixteen-attempt exponential schedule with jitter; the implementation never deletes the destination as a retry fallback, so readers are not intentionally exposed to a missing-file interval.
225
+ Local state and global config are owner-only, versioned, and size-bounded. Shared persistence primitives write a complete private temporary file, `fsync` it, and either hard-link it for an exclusive claim or atomically replace the destination. POSIX private-directory setup is descriptor-first and fail-closed; browser pairing reuses this boundary even when an existing state root was created with permissive modes. Machine-level browser pairing state is owner-only and shared across workspace runtimes through the local broker; its bearer token is not part of workspace state responses. `worker-secret-file.mjs` owns the complete ephemeral Worker deployment-secret lifecycle, including process-start-bound names, exact file identity, stale-owner classification, and cleanup-error propagation. State, managed-job manager, detached runner, browser pairing, and service definitions share the same flushed atomic-replacement primitive. Only classified transient Windows sharing failures are retried, using a bounded thirty-two-attempt exponential schedule with jitter; the implementation never deletes the destination as a retry fallback, so readers are not intentionally exposed to a missing-file interval.
226
226
 
227
227
  Process locks contain purpose, workspace, ownership token, lock time, and process start time. Stale removal rechecks device/inode/size/mtime and token so an old observer cannot delete a replacement lock. Recent malformed claims receive a grace period. Startup/state operations wait a bounded interval; daemon and runner identity remain process-lifetime locks. Managed-job transition and recovery locks use the same ownership/snapshot principles and support an atomic runner handoff.
228
228
 
package/docs/AUDIT.md CHANGED
@@ -3,6 +3,39 @@
3
3
  This document records the cross-cutting audit initiated for version 0.12.0, the 0.12.2 cross-platform CI follow-up, and the 0.13.0 architecture/automatic-routing follow-up, the 0.15.0 daily-browser and cross-platform hardening review, and the 0.16.0 runtime-boundary review. It complements, but does not replace, the continuously enforced contracts in `SECURITY.md`, `docs/ENGINEERING.md`, and the test suite.
4
4
 
5
5
 
6
+ ## 2026-07-14 version 1.0.1 installed-startup incident
7
+
8
+ Version 1.0.0 passed the complete repository suite, three operating-system CI jobs, package manifest inspection, and a real isolated global installation, but the installed CLI failed immediately on the documented zero-argument command with `readdirSync is not defined`. The direct cause was a missing `node:fs` import in a function reached only while hashing Worker deployment inputs. The installation smoke test invoked only `--version`, which loaded the module but did not execute the default startup function body; `node --check` validates syntax, not lexical name resolution inside unexecuted branches. A second scan found another latent missing import, `inspectProcessInstance`, in secret rotation.
9
+
10
+ The correction uses independent evidence layers. ESLint now rejects undefined bindings across Node production code, scripts, tests, and browser-extension code. The real-tarball installation test still verifies package layout and documented npm options, but now replaces only the isolated package's Wrangler JavaScript entrypoint with a deterministic failing executable and starts the installed CLI with no arguments from a package-free workspace and private temporary state root. Success means the process initializes current state, computes the packaged Worker fingerprint, and reaches the controlled Wrangler boundary without `ReferenceError` or any `is not defined` failure. Existing workerd/WebSocket and runtime integration tests continue from that external boundary through authenticated readiness. Both gates are part of `npm run check`, prepublish validation, and every supported OS matrix job; architecture tests reject their removal.
11
+
12
+ The first Windows run of the new gate found another real production defect: `runWrangler` selected `wrangler.cmd` and passed it to `spawn` with `shell: false`, which Node 26 rejected with `EINVAL`. Runtime execution now uses the current Node binary plus `node_modules/wrangler/bin/wrangler.js` on every platform. This keeps the shell-disabled execution invariant while avoiding platform command-shim semantics; a focused command-boundary test and the final-artifact startup probe enforce the choice.
13
+
14
+ The incident establishes a release rule: entrypoint existence, module import, `--help`, or `--version` are not startup evidence. Every user-documented primary command must be executed from the final packed artifact far enough to cross its composition root and reach either a verified ready state or a deliberately controlled external boundary.
15
+
16
+ ## 2026-07-14 version 1.0 security-boundary audit
17
+
18
+ The 1.0 review began from a clean `0.18.1` main branch whose complete local suite and required GitHub checks passed. The green status was incomplete evidence: GitHub still contained open CodeQL findings because the workflow uploaded SARIF but did not fail on security results. The review separated real path races and one lifecycle defect from intentional high-authority process boundaries and static-analysis noise.
19
+
20
+ State, service-log, managed-job diagnostic, privacy-scan, and owner-only file operations now open the target directly with no-follow flags where supported, validate the opened descriptor as a regular file, bound work before allocation, and apply modes through that descriptor. SSH inspection snapshots key bytes into a private temporary directory before invoking `ssh-keygen`, then verifies that the original identity and bytes remain unchanged. These changes remove the check/use split without claiming protection against hostile same-user replacement of parent directories on platforms lacking descriptor-relative traversal.
21
+
22
+ A dormant recovery-safety helper exposed a concrete lifecycle defect: after recovery-lock ownership was handed from the coordinator to a detached runner, the runner removed the path unconditionally instead of verifying the handed-off token. Recovery now uses the existing snapshot/token-aware removal primitive and records a fatal recovery error if ownership cannot be proved. Numeric-only lock content is treated as malformed current state rather than interpreted as a compatible owner.
23
+
24
+ The process boundary now distinguishes fixed executable plus argv invocation from explicit shell execution. Direct calls reject NULs and oversized executable/argv input and use `spawn(..., { shell: false })`; `exec_command` remains an explicit canonical-full shell capability and is documented as local-user authority. CodeQL exceptions for these intentional boundaries are exact rule/path records with rationales and expiry dates. The CodeQL workflow now parses generated SARIF and fails on every other security-tagged result, preventing a successful analysis upload from masquerading as a clean security gate.
25
+
26
+ The public contract is current-only: MCP `2025-11-25` is the sole advertised protocol. Existing current-schema local state remains usable, so upgrade safety is provided by bounded daemon/Worker version convergence and exact browser-extension version handshakes rather than by retaining obsolete protocol or lock interpretations. Randomized protocol, policy, argv, and real no-shell-evaluation tests supplement deterministic state-machine and cross-platform integration coverage.
27
+
28
+ A follow-up control-plane and credential-lifecycle review found four issues outside the original concurrency report. The repository already prohibited hosted GitHub connectors, but that rule was available only after project context loading; shared initialization and built-in instructions now state the fail-closed local `git`/`gh` rule before any GitHub operation. `ENGINEERING.md` and `CONTRIBUTING.md` also contradicted the newer source-release contract by assigning tags and GitHub Releases to the human operator; all normative files now distinguish automated GitHub source completion from explicitly authorized npm and live-machine operations.
29
+
30
+ Owner-only directory enforcement previously swallowed `chmod(0700)` failures on every platform and accepted a final symlink path, allowing state, job, service-log, or temporary-secret storage to continue without a proven private directory. The shared secure-file boundary now rejects symlinks/non-directories, fails closed on POSIX permission errors, verifies that group/other bits are clear, and tolerates unsupported chmod semantics only on Windows. Worker deployment temporary-secret cleanup was extracted from the CLI: filenames now bind process-start identity, stale cleanup deletes only positively reclaimable owners, ambiguous live identities are retained, and removal failures are surfaced. The previous stale-secret test used a non-hex suffix that did not match the production filename parser and therefore exercised no cleanup branch; a dedicated lifecycle test covers valid names, permissions, reclaimability, ambiguity, deletion failure, and dual primary/cleanup failure.
31
+
32
+ The follow-up then traced the same invariant through adjacent modules. Browser pairing now restricts an already-existing permissive state root before writing its bearer token. Managed-job runner-log trimming ignores only `ENOENT`; other failures block launch, and a large-log fixture proves bounded tail retention. Workspace atomic writes and patch transactions no longer swallow POSIX chmod failures before commit. Recursive state-root validation no longer treats unreadable or malformed config/profile/lock data as “not a workspace”; these conditions block deletion. Finally, the audit removed an unused error factory and account-role constant and narrowed internal-only exports, reducing accidental module surface without changing the package CLI contract.
33
+
34
+ A subsequent Windows PR run reproduced a longer `EPERM` sharing interval in the existing concurrent-reader atomic-replacement fixture. The prior 16-attempt budget was therefore not a sufficient platform boundary even though the same test had passed earlier runs. The default remains bounded and preserves atomic rename semantics, but now allows 32 attempts; deterministic testing requires success after 24 consecutive classified failures before the 25th attempt.
35
+
36
+ A multi-window concurrency report exposed a correlation defect rather than an execution-capacity limit. The Worker admitted 32 calls and the local runtime admitted 16, but pending-call keys used only OAuth token plus JSON-RPC id. Independent ChatGPT windows commonly reuse small numeric ids, so a second window was rejected before relay and the ordinary internal-error redaction made the failure look like a local execution outage. Version 1 issues stateless HMAC-bound MCP session ids and keys cancellation by token, session, id type, and id value. Real workerd tests run two sessions with the same id concurrently, prove sessionless POST independence, and prove cancellation isolation. A separate fault was fixed by interrupting the relay whenever a completed terminal result cannot be delivered, ensuring socket cleanup releases pending records instead of waiting for the configured tool timeout.
37
+
38
+
6
39
  ## 2026-07-13 runtime-boundary and lifecycle follow-up
7
40
 
8
41
  This review confirmed that earlier safety controls existed but were distributed across duplicated policy checks, transport-specific pending maps, message-regex errors, and oversized composition files. It reproduced a production-visible Worker defect where a completed client JSON-RPC request key could remain indexed and cause unrelated later calls to fail as duplicate in-flight IDs. Pending calls now use one atomic double-index registry whose terminal `take` operation clears the internal ID, request key, and timer before resolution or rejection; success, daemon error, cancellation, timeout, send failure, and socket loss have behavior coverage.
@@ -16,12 +16,14 @@ This document records project-wide decisions that must survive individual fixes,
16
16
  10. **Exclusive claims are complete before visible.** Never create a final lock/PID claim and then populate it. Use the shared exclusive-file primitive, ownership tokens, process-start identity, and snapshot-checked reclamation.
17
17
  11. **Service and state removal are fail-closed state machines.** Stop the platform provider and every verified daemon before removing definitions or recursive state. An unreadable lock, failed stop, active job, or ambiguous identity retains state for diagnosis.
18
18
  12. **Read failure is not empty state.** Permission, type, symbolic-link, size, encoding, and I/O errors must propagate. Corrupt backup/reconstruction applies only after a successful read proves that JSON content is invalid.
19
+ 13. **The public protocol contract is current-only.** Shared metadata advertises only the current MCP protocol version. Compatibility code for obsolete protocol dates, lock formats, or state schemas is not retained in the final runtime; upgrade safety comes from explicit version negotiation, fail-closed state validation, and bounded operator convergence.
20
+ 14. **Security analysis is a failing gate.** CodeQL execution alone is not success. Generated SARIF must contain no unaccepted security result; an intentional authority boundary requires an exact rule/path record with a substantive rationale and an expiry date.
19
21
 
20
22
  A proposed change that conflicts with an invariant requires an explicit owner decision and corresponding documentation update. It must not be hidden inside an unrelated refactor.
21
23
 
22
24
  ## Change and release-operation ownership
23
25
 
24
- Repository changes and live release operations are separate responsibilities. Unless the current task explicitly authorizes a broader scope, coding automation may edit source, tests, documentation, package metadata, and changelog entries; run repository-local checks; commit; and push to GitHub. It must not publish or alter npm releases, create tags or GitHub Releases, install the package globally, deploy or reconfigure a Cloudflare Worker, rotate credentials, mutate live deployment state, or start/stop/install/remove the daemon or autostart service.
26
+ Repository source release completion and live release operations are separate responsibilities. Under the standing repository contract in `AGENTS.md`, coding automation may edit source, tests, documentation, package metadata, and changelog entries; run repository-local checks; commit and push; complete the reviewed pull request through local `git`/`gh`; and create the annotated version tag plus final GitHub Release only after the exact `main` commit passes push-triggered CI. It must not publish, deprecate, or unpublish npm packages; install the package globally; deploy or reconfigure a Cloudflare Worker; rotate credentials; mutate live deployment state; or start/stop/install/remove the daemon or autostart service without explicit user authorization.
25
27
 
26
28
  The normal handoff is: the repository owner publishes the reviewed npm version, then runs `npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest && machine-mcp`. The npm command updates the global CLI but cannot hot-reload an existing Node process. The subsequent normal foreground startup validates the Worker deployment hash, expected version, and health, requests shutdown of an active autostart daemon, waits a bounded interval for its lock, redeploys when necessary, and then takes over with the installed version. Live operations require explicit authorization even when they appear to be the obvious next release step.
27
29
 
@@ -127,6 +129,8 @@ The required matrix includes:
127
129
  - current-tree and reachable-history privacy gates, release-impact enforcement, and exact-commit release-CI gating;
128
130
  - package-manifest and sensitive-artifact inspection;
129
131
  - generated type checks and recursively discovered JavaScript/shell syntax checks;
132
+ - static undefined-identifier/redeclaration checks over all production JavaScript, tests, scripts, and browser-extension code;
133
+ - a real packed-tarball isolated global installation whose zero-argument CLI startup initializes state and reaches a controlled external Worker-deployment boundary; `--version` or import-only smoke tests are insufficient release evidence;
130
134
  - concurrent exclusive-lock/atomic-replacement tests, PID-reuse/age tests, and fail-closed service-lifecycle tests;
131
135
  - real process-tree timeout/cancellation tests with descendants that ignore graceful termination;
132
136
  - local runtime and real `full` acceptance tests;
@@ -305,6 +305,9 @@ Do not point two logical trust domains at one broad parent directory merely to r
305
305
 
306
306
  ## 13. Upgrade
307
307
 
308
+ Version 1 supports only MCP protocol `2025-11-25`; upgrade or reconnect clients that still request an older date. The current 0.18.x state schema remains valid and should be preserved.
309
+
310
+
308
311
  Repeat the isolated global installation, then start Machine Bridge in the target workspace:
309
312
 
310
313
  ```sh
@@ -23,7 +23,7 @@ The extension works with the current profile and supports:
23
23
  - populating file inputs from registered local resource files;
24
24
  - visible-tab screenshots.
25
25
 
26
- Current support targets Chrome, Chromium, Microsoft Edge, Brave, Vivaldi, and compatible Chromium browsers. The extension must be loaded into the user profile to be controlled; it is not installed into Playwright or an isolated automation profile. After every Machine Bridge upgrade, reload the unpacked extension so packaged scripts, protocol, and permissions match the runtime. Version 0.15.0 requires protocol 3 with broker `hello_ack`; the badge and pairing page report success only after protocol/version/capability validation. Pairing material is persisted only after that acknowledgement, an invalid candidate cannot overwrite the previous pairing, and `browser status` reports the expected packaged build, authenticated connected build/protocol/capabilities, and `extension_reload_required`; exact version equality is required in addition to protocol and capabilities. Browser-internal pages, extension stores, some PDF/plugin viewers, inaccessible cross-origin frames, and pages restricted by enterprise policy remain unscriptable.
26
+ Current support targets Chrome, Chromium, Microsoft Edge, Brave, Vivaldi, and compatible Chromium browsers. The extension must be loaded into the user profile to be controlled; it is not installed into Playwright or an isolated automation profile. After every Machine Bridge upgrade, reload the unpacked extension so packaged scripts, protocol, and permissions match the runtime. The current extension contract requires protocol 3 with broker `hello_ack`; the badge and pairing page report success only after protocol/version/capability validation. Pairing material is persisted only after that acknowledgement, an invalid candidate cannot overwrite the previous pairing, and `browser status` reports the expected packaged build, authenticated connected build/protocol/capabilities, and `extension_reload_required`; exact version equality is required in addition to protocol and capabilities. Browser-internal pages, extension stores, some PDF/plugin viewers, inaccessible cross-origin frames, and pages restricted by enterprise policy remain unscriptable.
27
27
 
28
28
  ## One-time browser setup
29
29
 
@@ -27,6 +27,12 @@ machine-mcp service status
27
27
 
28
28
  A successful diagnostic result applies only to that probe. An MCP host can still deny a later call based on its own request context. This is expected layering, not a defect in the `full` profile: `full` removes Machine Bridge's own denials, while host delivery remains independent.
29
29
 
30
+ ### Concurrent chat windows and pending calls
31
+
32
+ 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.
33
+
34
+ `server_info.worker.pending_calls` reports `active`, `request_keys`, `maximum`, `oldest_ms`, and `by_tool`. A nonzero `active` count means work is in flight, not that the bridge is locked. Calls for simple reads and probes should continue while another independent process call runs. A value that remains after the underlying local task completed indicates a transport-delivery defect; version 1 interrupts and reconnects the ambiguous daemon socket so Worker cleanup releases those records. Refreshing a chat page is not the recovery mechanism and should not be required.
35
+
30
36
  ### Relay interruption messages
31
37
 
32
38
  A brief relay interruption is retried automatically and is visible only with `--verbose`. Default logs do not print raw WebSocket values such as `code=1006` with an empty reason. If a transient outage persists for 10 seconds, the daemon emits a readable duration/cause/reconnect summary; later reminders use autonomous exponential backoff capped at 15 minutes, and recovery produces one readable summary. Each transport connection attempt also has a deadline, so a socket stuck in `CONNECTING` cannot freeze retries. Identity/version mismatch, authentication rejection, and unexpected protocol messages are not retried as ordinary network faults: the daemon emits an immediate actionable error and exits, requiring upgrade/redeployment or credential repair. A Worker-side daemon handshake timeout remains retryable.
@@ -93,9 +99,23 @@ machine-mcp --verbose
93
99
 
94
100
  `Unknown cli config "--allow-scripts"` proves the package installation ran under npm 11 or older. `Invalid property "node"` or `Invalid property "devEngines.node"` means npm parsed an outdated `devEngines` object; inspect the npm debug log to identify its source rather than assuming it belongs to Machine Bridge. The published package declares both Node.js 26 and npm 12 in `engines`, and `machine-mcp doctor` checks both active versions. Keep `--omit=optional` in the install command. Without it npm may resolve optional `fsevents` and warn that its install script was not included in `allowScripts`; Machine Bridge does not require that development-time watcher at runtime.
95
101
 
102
+ ## Version 1 upgrade convergence
103
+
104
+ Version 1 advertises only MCP protocol `2025-11-25`. Upgrade the MCP client/host if it cannot negotiate that version; Machine Bridge does not retain an obsolete protocol dispatcher. The current local state schema is unchanged from the final 0.18.x release, so do not delete the state root merely to upgrade.
105
+
106
+ Use this sequence:
107
+
108
+ 1. Install the released version with the documented pinned npm 12 command.
109
+ 2. Run `machine-mcp --verbose` once. Startup verifies package/Worker versions, performs the ordinary authenticated Worker convergence when needed, stops only a verified service-style old daemon, waits for its lock, and starts the installed version.
110
+ 3. Run `machine-mcp status` and `machine-mcp doctor`.
111
+ 4. Reload the unpacked browser extension and revisit the pairing page. Exact package version and capability equality are required before browser readiness is reported.
112
+ 5. Reconnect MCP clients so they initialize with the current protocol and tool metadata.
113
+
114
+ A failed state read, unverifiable process owner, active managed job, Worker authentication failure, or extension version mismatch remains fail closed. Preserve the state root and logs for diagnosis rather than deleting them to force apparent success.
115
+
96
116
  ## State-root safety and removal
97
117
 
98
- The state root must be a dedicated directory and must not equal, contain, or be contained by the selected workspace. Do not point `--state-dir` at a project directory. State/config and lock files are owner-only, bounded, and committed through flushed atomic primitives. A permission, type, symbolic-link, size, encoding, or I/O failure is reported; only successfully read invalid JSON is moved to a bounded `.corrupt-*` backup.
118
+ The state root must be a dedicated directory and must not equal, contain, or be contained by the selected workspace. Do not point `--state-dir` at a project directory. On POSIX, every state, profile, job, service-log, browser-pairing, and temporary-secret directory is descriptor-opened without following the final symlink, restricted to `0700`, and revalidated; failure stops the operation. State/config and lock files are owner-only, bounded, and committed through flushed atomic primitives. A permission, type, symbolic-link, size, encoding, or I/O failure is reported; only successfully read invalid JSON is moved to a bounded `.corrupt-*` backup. Removal applies the same fail-closed rule to global config, every profile state, and daemon ownership records.
99
119
 
100
120
  Uninstall acquires a state-root `maintenance.lock` that blocks new profile/state operations and state-backed operations from already constructed managed-job/browser managers, then scans all known profiles, active managed jobs, daemon/startup locks, global workspace selection, profile state, daemon lock workspace metadata, the state marker, and directory shape. It rechecks jobs and locks after stopping services/daemons. An unreadable lock is treated as a blocker, not as inactivity. Do not manually delete a lock merely because it looks old; inspect the recorded PID and command first.
101
121
 
@@ -26,7 +26,7 @@ Every release-relevant change advances the package version and is completed by a
26
26
 
27
27
  ### Local GitHub control plane
28
28
 
29
- Repository automation must use local `git`, `gh`, and `gh api` commands executed through Machine Bridge for every GitHub read or mutation. A hosted GitHub connector or ChatGPT GitHub plugin must not be used. Mixing control planes can produce stale refs, unreviewed remote-only commits, ambiguous credentials, and recovery paths that cannot be reproduced from the maintainer's machine. Fetch before mutation and verify the remote result afterward.
29
+ Before any GitHub read or mutation, repository automation must load the effective project instructions through Machine Bridge. It must then use local `git`, `gh`, and `gh api` commands executed through Machine Bridge for every GitHub read or mutation. A hosted GitHub connector, ChatGPT GitHub plugin, browser-side GitHub integration, or second remote control plane must not be used. If Machine Bridge or the local authenticated CLI is unavailable, the operation stops and reports that boundary rather than falling back. Mixing control planes can produce stale refs, unreviewed remote-only commits, ambiguous credentials, and recovery paths that cannot be reproduced from the maintainer's machine. Fetch before mutation and verify the remote result afterward.
30
30
 
31
31
  ## 2. Commits and pull requests
32
32
 
package/docs/TESTING.md CHANGED
@@ -48,9 +48,13 @@ The suite includes:
48
48
  - log redaction, control-character handling, message/field bounds, suppression of both successful and failed per-tool events outside debug, service warning-level configuration, current-schema reset, and bounded tail trimming;
49
49
  - deterministic relay connection lifecycle coverage for transport construction/error/deadline, pre-handshake `welcome` validation, authenticated `hello_ack` readiness, identity/version mismatch, retryable Worker handshake errors, fatal protocol errors, autonomous outage-reminder backoff, handshake and heartbeat timeout, brief-outage suppression, sustained-outage escalation, recovery summaries, and supersession;
50
50
  - shared no-follow bounded-file reads for normal files, over-limit data, directories, and symbolic links;
51
+ - 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;
52
+ - SARIF security-gate behavior for unknown findings, exact accepted rule/path matches, path mismatch rejection, rationale quality, and exception expiry;
53
+ - deterministic property tests over hostile browser-protocol byte strings, canonical/custom policy combinations, argv bounds/NULs, and a real direct process proving shell metacharacters remain literal argv;
51
54
  - CLI parsing, policy profiles, and client configuration boundaries;
52
55
  - 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;
53
- - live local Worker OAuth registration, consent, URL-constructed `303` callbacks including the ChatGPT redirect URI and encoded state, PKCE, token replay rejection, throttling, CORS, protocol negotiation, daemon-backed session bootstrap, dynamic tool advertisement, rich content, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure.
56
+ - live local Worker OAuth registration, consent, URL-constructed `303` callbacks including the ChatGPT redirect URI and encoded state, PKCE, token replay rejection, throttling, CORS, protocol negotiation, HMAC-bound MCP session issuance, 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, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure.
57
+ - 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.
54
58
 
55
59
  ## Opt-in live desktop and browser validation
56
60
 
@@ -85,7 +89,7 @@ npm run version:check
85
89
  npm run release-impact:check
86
90
  ```
87
91
 
88
- GitHub Actions executes the main suite on Linux, macOS, and Windows using the pinned Node 26/npm 12 baseline. Because Node 26 currently bundles npm 11, CI explicitly disables setup-node's automatic package-manager cache and upgrades npm from the runner temporary directory before any project-local npm command can trigger strict `devEngines`. Checkout fetches version tags so the release-impact gate can compare the branch with the latest release. A separate package-audit job scans reachable Git history, audits both the complete dependency graph and the production-only graph, verifies registry signatures and attestations, validates a CycloneDX SBOM written under the runner temporary directory, exercises the documented isolated global installation, then performs a dry-run package build. Separate pinned workflows validate governance titles, review dependency changes, run CodeQL over JavaScript/TypeScript and GitHub Actions, and publish OpenSSF Scorecard results. The macOS matrix job also runs the installation smoke test because Wrangler's optional `fsevents` resolution is platform-specific. Third-party Actions are pinned to immutable 40-character commit SHAs; architecture tests reject movable tags, and Dependabot monitors both action and dependency updates.
92
+ GitHub Actions executes the main suite on Linux, macOS, and Windows using the pinned Node 26/npm 12 baseline. Because Node 26 currently bundles npm 11, CI explicitly disables setup-node's automatic package-manager cache and upgrades npm from the runner temporary directory before any project-local npm command can trigger strict `devEngines`. Checkout fetches version tags so the release-impact gate can compare the branch with the latest release. A separate package-audit job scans reachable Git history, audits both the complete dependency graph and the production-only graph, verifies registry signatures and attestations, validates a CycloneDX SBOM written under the runner temporary directory, exercises the documented isolated global installation, then performs a dry-run package build. Separate pinned workflows validate governance titles, review dependency changes, run CodeQL over JavaScript/TypeScript and GitHub Actions, fail on unaccepted security-tagged SARIF results, and publish OpenSSF Scorecard results. The macOS matrix job also runs the installation smoke test because Wrangler's optional `fsevents` resolution is platform-specific. Third-party Actions are pinned to immutable 40-character commit SHAs; architecture tests reject movable tags, and Dependabot monitors both action and dependency updates.
89
93
 
90
94
  ## Test design rules
91
95
 
@@ -106,7 +110,9 @@ Run `npm run privacy:check` before committing and before packaging. Run and revi
106
110
 
107
111
  ## Package manifest
108
112
 
109
- `npm run package:test` executes a real silent `npm pack --dry-run --json`, requires clean parseable JSON, rejects sensitive local artifacts, credential-like file classes, and generated Worker type declarations, validates every packaged mode as `0644` or `0755`, and verifies that privacy/engineering guidance, runtime/relay/secure-file/lock/service/browser/app modules, all package-script helpers, the packaged browser extension, contribution discipline, and privacy/release-impact checkers are present. `npm run install:test` requires npm 12, installs the real tarball from a package-free directory into an isolated global prefix with the documented options, verifies the packaged npm engine requirement, rejects blocked-script warnings, confirms optional `fsevents` is absent, and runs the installed CLI. The test runs on Linux, macOS, and Windows CI.
113
+ `npm run package:test` executes a real silent `npm pack --dry-run --json`, requires clean parseable JSON, rejects sensitive local artifacts, credential-like file classes, and generated Worker type declarations, validates every packaged mode as `0644` or `0755`, and verifies that privacy/engineering guidance, runtime/relay/secure-file/lock/service/browser/app modules, all package-script helpers, the packaged browser extension, contribution discipline, and privacy/release-impact checkers are present. `npm run install:test` requires npm 12, installs the real tarball from a package-free directory into an isolated global prefix with the documented options, verifies the packaged npm engine requirement, rejects blocked-script warnings, confirms optional `fsevents` is absent, verifies `--version`, then runs the installed CLI with zero arguments from an isolated workspace/state root. A fake Wrangler JavaScript entrypoint terminates at a controlled deployment boundary, so the probe executes default command normalization, state initialization, deployment fingerprint collection, and Wrangler dispatch without changing a live account; any undefined identifier or earlier crash fails the release. The test is part of `npm run check` and runs on Linux, macOS, and Windows CI.
114
+
115
+ `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.
110
116
 
111
117
  The stdio integration test also sends an oversized line, verifies bounded rejection, and confirms that the next valid request is still processed.
112
118
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "0.18.1",
3
+ "version": "1.0.1",
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",
@@ -43,7 +43,7 @@
43
43
  "worker:types": "node scripts/generate-worker-types.mjs",
44
44
  "typecheck": "npm run worker:types && tsc -p tsconfig.json --noEmit",
45
45
  "syntax": "node scripts/syntax-check.mjs",
46
- "check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release-state:test && npm run release-ci:test && npm run network-retry:test && npm run relay:test && npm run secure-file:test && npm run architecture:test && npm run markdown:test && npm run project-metadata:test && npm run numbers:test && npm run records:test && npm run state-inventory:test && npm run commit-message:test && npm run policy:test && npm run account:test && npm run policy-docs:check && npm run tool-docs:check && npm run runtime-infrastructure:test && npm run runtime-handlers:test && npm run cli-entrypoint:test && npm run lifecycle:test && npm run logging-structure:test && npm run coverage:test && npm run worker-runtime-infrastructure:test && npm run typecheck && npm run syntax && npm run process-lock:test && npm run service-lifecycle:test && npm run service-platform:test && npm run catalog:test && npm run agent-context:test && npm run capability-ranking:test && npm run browser-command:test && npm run browser-devtools-input:test && npm run browser-service-worker:test && npm run browser-page-automation:test && npm run browser-bridge:test && npm run app-automation:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test",
46
+ "check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release-state:test && npm run release-ci:test && npm run network-retry:test && npm run relay:test && npm run secure-file:test && npm run worker-secret-file:test && npm run sarif-security:test && npm run security-properties:test && npm run shell:test && npm run architecture:test && npm run markdown:test && npm run project-metadata:test && npm run numbers:test && npm run records:test && npm run state-inventory:test && npm run commit-message:test && npm run policy:test && npm run account:test && npm run policy-docs:check && npm run tool-docs:check && npm run runtime-infrastructure:test && npm run runtime-handlers:test && npm run cli-entrypoint:test && npm run lifecycle:test && npm run logging-structure:test && npm run coverage:test && npm run worker-runtime-infrastructure:test && npm run lint:test && npm run lint && npm run typecheck && npm run syntax && npm run process-lock:test && npm run service-lifecycle:test && npm run service-platform:test && npm run catalog:test && npm run agent-context:test && npm run capability-ranking:test && npm run browser-command:test && npm run browser-devtools-input:test && npm run browser-service-worker:test && npm run browser-page-automation:test && npm run browser-bridge:test && npm run app-automation:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run install:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test",
47
47
  "worker:dry-run": "wrangler deploy --dry-run",
48
48
  "worker:integration-test": "node tests/worker-integration-test.mjs",
49
49
  "release:check": "node scripts/github-release.mjs --check",
@@ -65,6 +65,7 @@
65
65
  "relay:test": "node tests/relay-connection-test.mjs",
66
66
  "install:test": "node tests/install-smoke-test.mjs",
67
67
  "secure-file:test": "node tests/secure-file-test.mjs",
68
+ "worker-secret-file:test": "node tests/worker-secret-file-test.mjs",
68
69
  "architecture:test": "node tests/architecture-test.mjs",
69
70
  "markdown:test": "node tests/markdown-test.mjs",
70
71
  "project-metadata:test": "node tests/project-metadata-test.mjs",
@@ -99,7 +100,12 @@
99
100
  "commit-message:test": "node tests/commit-message-test.mjs",
100
101
  "tool-docs:generate": "node scripts/generate-tool-reference.mjs",
101
102
  "tool-docs:check": "node scripts/generate-tool-reference.mjs --check",
102
- "account:test": "node tests/account-admin-test.mjs"
103
+ "account:test": "node tests/account-admin-test.mjs",
104
+ "sarif-security:test": "node tests/sarif-security-gate-test.mjs",
105
+ "security-properties:test": "node tests/security-properties-test.mjs",
106
+ "lint": "eslint eslint.config.mjs bin src/local scripts tests browser-extension",
107
+ "lint:test": "node tests/lint-gate-test.mjs",
108
+ "shell:test": "node tests/shell-test.mjs"
103
109
  },
104
110
  "dependencies": {
105
111
  "https-proxy-agent": "9.1.0",
@@ -109,6 +115,8 @@
109
115
  },
110
116
  "devDependencies": {
111
117
  "@types/node": "26.1.1",
118
+ "eslint": "10.7.0",
119
+ "globals": "17.7.0",
112
120
  "typescript": "7.0.2"
113
121
  },
114
122
  "keywords": [
@@ -1,7 +1,8 @@
1
1
  import { execFileSync } from "node:child_process";
2
- import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
2
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { readBoundedRegularFileSync } from "../src/local/secure-file.mjs";
5
6
 
6
7
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
8
  const selfPath = "scripts/privacy-check.mjs";
@@ -19,19 +20,18 @@ for (const relativePath of candidates) {
19
20
  scanDenylistPath(relativePath, denylist, findings);
20
21
  scanSensitivePath(relativePath, findings);
21
22
  const fullPath = path.join(root, relativePath);
22
- let info;
23
- try { info = lstatSync(fullPath); } catch {
24
- findings.push({ path: relativePath, line: 1, rule: "publication file metadata could not be read" });
25
- continue;
26
- }
27
- if (info.isSymbolicLink()) {
28
- findings.push({ path: relativePath, line: 1, rule: "symbolic link in publication surface" });
29
- continue;
30
- }
31
- if (!info.isFile()) continue;
32
23
  let buffer;
33
- try { buffer = readFileSync(fullPath); } catch {
34
- findings.push({ path: relativePath, line: 1, rule: "publication file content could not be read" });
24
+ try {
25
+ buffer = readBoundedRegularFileSync(fullPath, 5 * 1024 * 1024 + 1);
26
+ } catch (error) {
27
+ const code = String(error?.code || "");
28
+ const message = String(error?.message || error || "");
29
+ const rule = code === "ELOOP" || /symbolic link/i.test(message)
30
+ ? "symbolic link in publication surface"
31
+ : /exceeds/i.test(message)
32
+ ? "file exceeds privacy scanner size limit and requires manual review"
33
+ : "publication file content could not be read";
34
+ findings.push({ path: relativePath, line: 1, rule });
35
35
  continue;
36
36
  }
37
37
  if (buffer.length > 5 * 1024 * 1024) {