machine-bridge-mcp 3.0.0-beta.197 → 3.0.0-beta.199

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 (220) hide show
  1. package/CHANGELOG.md +112 -10
  2. package/README.md +20 -5
  3. package/SECURITY.md +3 -3
  4. package/SUPPORT.md +2 -0
  5. package/bin/machine-mcp.mjs +2 -2
  6. package/browser-extension/broker-auth.js +5 -3
  7. package/browser-extension/browser-operation-contract.js +92 -0
  8. package/browser-extension/browser-operations.js +64 -139
  9. package/browser-extension/manifest.json +1 -2
  10. package/browser-extension/page-automation.js +50 -28
  11. package/browser-extension/pairing-bootstrap.js +6 -7
  12. package/browser-extension/pairing.js +14 -4
  13. package/browser-extension/service-worker.js +9 -13
  14. package/docs/AGENT_CONTEXT.md +3 -16
  15. package/docs/AUDIT.md +11 -6
  16. package/docs/CLIENTS.md +2 -11
  17. package/docs/COMPUTER_USE.md +9 -9
  18. package/docs/GETTING_STARTED.md +1 -10
  19. package/docs/LOCAL_AUTHORIZATION.md +3 -3
  20. package/docs/LOCAL_AUTOMATION.md +6 -2
  21. package/docs/LOGGING.md +16 -9
  22. package/docs/MANAGED_JOBS.md +8 -8
  23. package/docs/MULTI_ACCOUNT.md +2 -2
  24. package/docs/OPERATIONS.md +42 -24
  25. package/docs/OVERVIEW.md +1 -1
  26. package/docs/PRIVACY.md +5 -1
  27. package/docs/THREAT_MODEL.md +5 -3
  28. package/docs/TOOL_REFERENCE.md +4 -8
  29. package/docs/UPGRADING.md +20 -4
  30. package/mbm.cmd +3 -3
  31. package/native/macos/MachineBridgeTrustBroker.swift +52 -8
  32. package/package.json +7 -6
  33. package/scripts/consumer-package-security.mjs +80 -51
  34. package/scripts/github-release.mjs +4 -4
  35. package/scripts/hardened-npm-session.mjs +2 -0
  36. package/scripts/install-published-prerelease.mjs +1 -1
  37. package/scripts/prerelease-activation.mjs +22 -9
  38. package/scripts/privacy-check.mjs +99 -8
  39. package/scripts/promotion-digest.mjs +11 -7
  40. package/scripts/publish-npm.mjs +14 -16
  41. package/scripts/release-acceptance.mjs +12 -8
  42. package/scripts/release-candidate-manifest.mjs +12 -9
  43. package/scripts/release-channel.mjs +1 -1
  44. package/scripts/release-impact-check.mjs +2 -1
  45. package/scripts/release-oauth-canary-core.mjs +16 -9
  46. package/scripts/release-oauth-canary-evidence.mjs +10 -8
  47. package/scripts/release-soak.mjs +14 -11
  48. package/scripts/start-release-candidate.mjs +1 -1
  49. package/src/local/account-access.mjs +2 -1
  50. package/src/local/account-admin.mjs +22 -12
  51. package/src/local/agent-context-projection.mjs +4 -1
  52. package/src/local/agent-context.mjs +11 -10
  53. package/src/local/agent-skill-discovery.mjs +49 -14
  54. package/src/local/agent-text-file.mjs +38 -13
  55. package/src/local/app-automation.mjs +71 -120
  56. package/src/local/authority-context.mjs +10 -6
  57. package/src/local/browser-bridge.mjs +2 -9
  58. package/src/local/browser-broker-auth.mjs +9 -8
  59. package/src/local/browser-broker-server.mjs +8 -2
  60. package/src/local/browser-command.mjs +3 -1
  61. package/src/local/browser-computer-observation-service.mjs +10 -17
  62. package/src/local/browser-extension-identity.mjs +4 -2
  63. package/src/local/browser-operation-service.mjs +9 -14
  64. package/src/local/browser-pairing-grant.mjs +35 -35
  65. package/src/local/browser-pairing-http.mjs +2 -2
  66. package/src/local/browser-pairing-launch.mjs +4 -4
  67. package/src/local/browser-pairing-store.mjs +64 -66
  68. package/src/local/call-authority.mjs +14 -5
  69. package/src/local/cf-authentication.mjs +8 -7
  70. package/src/local/cli-account-admin.mjs +1 -2
  71. package/src/local/cli-local-admin.mjs +38 -12
  72. package/src/local/cli-policy.mjs +1 -1
  73. package/src/local/cli.mjs +18 -23
  74. package/src/local/computer-use-arguments.mjs +2 -6
  75. package/src/local/computer-use-observation-contract.mjs +53 -13
  76. package/src/local/computer-use-observation.mjs +19 -9
  77. package/src/local/computer-use.mjs +7 -4
  78. package/src/local/daemon-http-relay-connection.mjs +9 -6
  79. package/src/local/daemon-http-relay-request.mjs +8 -1
  80. package/src/local/daemon-http-relay-sequence.mjs +6 -4
  81. package/src/local/delegated-process-sandbox.mjs +28 -6
  82. package/src/local/device-identity.mjs +35 -22
  83. package/src/local/durable-process-spec.mjs +2 -6
  84. package/src/local/exclusive-publication-recovery.mjs +44 -1
  85. package/src/local/full-access-test.mjs +11 -5
  86. package/src/local/git-metadata-tree-safety.mjs +20 -20
  87. package/src/local/git-version.mjs +11 -0
  88. package/src/local/hardened-npm-download.mjs +2 -2
  89. package/src/local/hardened-npm-launcher.mjs +48 -0
  90. package/src/local/hardened-npm.mjs +9 -7
  91. package/src/local/job-runner.mjs +61 -47
  92. package/src/local/loopback-health.mjs +5 -0
  93. package/src/local/macos-trust-broker.mjs +30 -25
  94. package/src/local/managed-job-active-child.mjs +10 -8
  95. package/src/local/managed-job-dependencies.mjs +4 -4
  96. package/src/local/managed-job-listing.mjs +2 -2
  97. package/src/local/managed-job-lock.mjs +20 -17
  98. package/src/local/managed-job-output-redaction.mjs +21 -10
  99. package/src/local/managed-job-plan.mjs +4 -2
  100. package/src/local/managed-job-projection.mjs +8 -2
  101. package/src/local/managed-job-recovery-history.mjs +49 -0
  102. package/src/local/managed-job-relaunch.mjs +8 -7
  103. package/src/local/managed-job-retention-policy.mjs +5 -5
  104. package/src/local/managed-job-runner-claim.mjs +17 -12
  105. package/src/local/managed-job-runner-liveness.mjs +14 -5
  106. package/src/local/managed-job-runner.mjs +11 -11
  107. package/src/local/managed-job-state-validation.mjs +2 -0
  108. package/src/local/managed-job-terminal-maintenance.mjs +3 -2
  109. package/src/local/managed-job-terminal.mjs +4 -3
  110. package/src/local/managed-job-transient-recovery-capacity.mjs +7 -7
  111. package/src/local/managed-jobs.mjs +33 -22
  112. package/src/local/npm-audit-report.mjs +21 -0
  113. package/src/local/npm-environment.mjs +19 -25
  114. package/src/local/observability.mjs +4 -7
  115. package/src/local/operation-risk.mjs +12 -0
  116. package/src/local/owner-state-lock.mjs +4 -4
  117. package/src/local/process-contract.mjs +9 -0
  118. package/src/local/process-execution.mjs +14 -12
  119. package/src/local/process-identity.mjs +25 -8
  120. package/src/local/process-sessions.mjs +7 -4
  121. package/src/local/process-tracker.mjs +20 -8
  122. package/src/local/process-tree-ownership.mjs +1 -1
  123. package/src/local/relay-connection-support.mjs +2 -1
  124. package/src/local/relay-connection.mjs +1 -2
  125. package/src/local/remote-configuration.mjs +4 -2
  126. package/src/local/resource-admission.mjs +13 -6
  127. package/src/local/resource-lease-accounting.mjs +1 -1
  128. package/src/local/resource-operations.mjs +2 -2
  129. package/src/local/resource-process-ancestry.mjs +1 -1
  130. package/src/local/resource-request-contract.mjs +3 -2
  131. package/src/local/resource-staging-recovery.mjs +11 -8
  132. package/src/local/resource-transaction-lock.mjs +4 -4
  133. package/src/local/resource-waiters.mjs +8 -9
  134. package/src/local/runtime-activation.mjs +13 -8
  135. package/src/local/runtime-diagnostic-projection.mjs +1 -1
  136. package/src/local/runtime-package-identity.mjs +9 -8
  137. package/src/local/runtime-relay-connection-options.mjs +13 -6
  138. package/src/local/runtime.mjs +9 -4
  139. package/src/local/secure-file.mjs +4 -4
  140. package/src/local/security-audit-activity.mjs +22 -8
  141. package/src/local/security-audit-beta198-migration.mjs +50 -0
  142. package/src/local/security-audit-dispatch.mjs +23 -11
  143. package/src/local/security-audit-gate.mjs +38 -0
  144. package/src/local/security-audit-log.mjs +20 -16
  145. package/src/local/security-audit-state.mjs +57 -20
  146. package/src/local/security-audit-storage.mjs +21 -12
  147. package/src/local/service-convergence.mjs +1 -1
  148. package/src/local/service-environment.mjs +4 -4
  149. package/src/local/service-ownership.mjs +1 -1
  150. package/src/local/shell.mjs +2 -1
  151. package/src/local/ssh-key.mjs +15 -19
  152. package/src/local/state-inventory.mjs +4 -4
  153. package/src/local/state-root-owned-namespaces.mjs +0 -11
  154. package/src/local/state.mjs +38 -47
  155. package/src/local/stdio.mjs +2 -2
  156. package/src/local/tool-executor.mjs +23 -6
  157. package/src/local/windows-service.mjs +13 -7
  158. package/src/local/worker-deployment-fingerprint.mjs +45 -14
  159. package/src/local/worker-deployment.mjs +40 -18
  160. package/src/local/worker-health.mjs +7 -4
  161. package/src/local/worker-identity-contract.mjs +14 -0
  162. package/src/local/worker-secret-file.mjs +18 -6
  163. package/src/local/workspace-file-service.mjs +4 -4
  164. package/src/local/workspace-file-transaction.mjs +5 -5
  165. package/src/local/workspace-profile-migration.mjs +5 -4
  166. package/src/local/wrangler-toolchain/package-lock.json +118 -118
  167. package/src/local/wrangler-toolchain/package.json +3 -1
  168. package/src/local/wrangler-toolchain-installed-tree.mjs +42 -0
  169. package/src/local/wrangler-toolchain-verification.mjs +19 -33
  170. package/src/local/wrangler-toolchain.mjs +52 -45
  171. package/src/shared/activation-recovery.mjs +2 -2
  172. package/src/shared/admin-auth.mjs +6 -7
  173. package/src/shared/authority-revocation.mjs +8 -7
  174. package/src/shared/daemon-auth.mjs +6 -7
  175. package/src/shared/device-session-auth.mjs +8 -7
  176. package/src/shared/log-redaction.mjs +15 -2
  177. package/src/shared/sensitive-value-patterns.mjs +2 -2
  178. package/src/shared/server-metadata.json +3 -3
  179. package/src/shared/tool-argument-validation.d.mts +1 -1
  180. package/src/shared/tool-catalog.json +4 -8
  181. package/src/worker/access.ts +2 -1
  182. package/src/worker/account-admin.ts +7 -3
  183. package/src/worker/authority-revocations.ts +50 -16
  184. package/src/worker/daemon-auth.ts +8 -8
  185. package/src/worker/daemon-http-channel.ts +18 -9
  186. package/src/worker/daemon-http-controller.ts +24 -7
  187. package/src/worker/daemon-http-protocol.ts +1 -1
  188. package/src/worker/daemon-liveness.ts +9 -3
  189. package/src/worker/daemon-ready-dispatch.ts +2 -0
  190. package/src/worker/daemon-ready-waiters.ts +1 -1
  191. package/src/worker/daemon-registry.ts +1 -1
  192. package/src/worker/daemon-relay-diagnostics.ts +1 -0
  193. package/src/worker/daemon-socket-attachment.ts +1 -1
  194. package/src/worker/device-session-verifier.ts +11 -7
  195. package/src/worker/dpop.ts +11 -6
  196. package/src/worker/http.ts +5 -1
  197. package/src/worker/index.ts +42 -41
  198. package/src/worker/managed-job-hosted-authority.ts +3 -3
  199. package/src/worker/mcp-controller.ts +3 -3
  200. package/src/worker/mcp-job-monitor-store.ts +2 -2
  201. package/src/worker/mcp-job-monitor-ui.ts +5 -5
  202. package/src/worker/mcp-response-stream.ts +13 -14
  203. package/src/worker/{mcp-stale-schema-compat.ts → mcp-stale-schema-guidance.ts} +1 -1
  204. package/src/worker/mcp-subscription-capacity.ts +3 -3
  205. package/src/worker/oauth-client-admin.ts +2 -4
  206. package/src/worker/oauth-client-contract.ts +3 -4
  207. package/src/worker/oauth-controller.ts +33 -54
  208. package/src/worker/oauth-refresh-exchange.ts +13 -6
  209. package/src/worker/oauth-refresh-families.ts +26 -15
  210. package/src/worker/oauth-refresh-persistence.ts +6 -17
  211. package/src/worker/oauth-state.ts +18 -42
  212. package/src/worker/oauth-store-validation.ts +14 -10
  213. package/src/worker/oauth-token-issuance.ts +6 -9
  214. package/src/worker/oauth-tokens.ts +46 -7
  215. package/src/worker/observability.ts +5 -10
  216. package/src/worker/pending-call-capacity.ts +1 -1
  217. package/src/worker/worker-continuity-evidence.ts +5 -6
  218. package/src/worker/worker-entry.ts +1 -1
  219. package/src/worker/worker-rate-limit-key.ts +1 -1
  220. package/src/local/resource-legacy-staging-recovery.mjs +0 -71
package/CHANGELOG.md CHANGED
@@ -1,16 +1,118 @@
1
1
  # Changelog
2
2
 
3
- ## 3.0.0-beta.197 - 2026-09-30
3
+ ## 3.0.0-beta.199 - 2026-10-08
4
4
 
5
- - Pin source tooling and the integrity-verified hardened npm bootstrap to brace-expansion 5.0.12, fixing the newly disclosed parser denial-of-service advisories. Preserve clean global consumer installs without postinstall hooks; declare the audited private Wrangler build provider and supply its executable runner before native cf builds.
6
-
7
- - Migrate Worker builds and deployments to native cf configuration and prebuilt artifacts; preserve the Worker name, SQLite Durable Object export, rate-limit namespaces, existing variables, and secrets.
8
- - Require structured cf authentication before unattended activation and bind deployment fingerprints to the exact private source snapshot and pinned toolchain.
9
-
10
- - Add an isolated, exact cf deployment toolchain with a compatible Wrangler builder and patched Undici.
11
- - Verify the deterministic compatibility artifact for cf beta.5 before its network factory runs; reject unknown or tampered bundle bytes.
12
- - Synchronize the package, Worker, and browser extension prerelease version declarations.
5
+ - Resolve GHSA-wq5f-xc86-pv6w (CVE-2026-96889) by pinning Sharp 0.35.5 in both the source checkout and the isolated Cloudflare deployment toolchain, including their independent dependency locks and lifecycle-script allowlists.
6
+ - Require the installed private toolchain dependency tree and its integrity marker to match the patched Sharp version; reject vulnerable installed versions before deployment tooling is used.
7
+ - Raise the consumer-package Sharp minimum to the first patched version and add vulnerable-version and patched-version regression cases.
8
+ - Fail closed on malformed private toolchain npm versions, dependency-tree metadata and coerced package versions; avoid reflecting raw private npm diagnostics in errors.
9
+ - Preserve fail-closed npm audit, supply-chain verification and OpenSSF Scorecard review gates without adding a vulnerability exception.
10
+ - Remove the obsolete boolean `computer_act.include_post_screenshot` compatibility option; require `post_screenshot=auto|always|never`, reject obsolete inputs, and advance the tool schema generation.
11
+ - Replace destructive state-root removal advice on obsolete persisted schemas with preservation and supported upgrade/backup recovery guidance.
12
+ - Keep managed-job fatal operational diagnostics to a fixed error classification, avoiding arbitrary exception-message disclosure to logs.
13
+ - Extend that fixed-diagnostic boundary through runner startup before the job claim is confirmed, so missing or unreadable initial state cannot expose private paths, raw filesystem errors or exception stacks.
14
+ - Require exact JSON types for persisted process ownership, runner claims, active-child claims, resource transaction locks, leases and waiters; reject string/array coercion before liveness, recovery, cleanup, termination or critical-section decisions.
15
+ - Require exact string credentials, integer ports and string broker endpoints for current browser-pairing state; arrays and numeric-string ports no longer acquire pairing or authentication authority through JavaScript conversion.
16
+ - Remove the retired browser-pairing migration marker from the current persisted model and advance the pairing schema to version 3, while preserving one exact one-way beta.198 stable-state upgrade that retains both credentials and the selected port; pending beta.198 migrations and older, malformed, coercible or extra-field envelopes remain fail closed.
17
+ - Remove the redundant managed-job result `recovered` compatibility boolean; recovery outcome is represented only by the terminal status enum, and interrupted recovery never replays main steps.
18
+ - Require Git 2.50 or later for reachable-history privacy scanning instead of carrying an older `rev-list` output parser; fail before scanning when the trusted Git version is too old or malformed.
19
+ - Reject non-string OAuth grant types, authorization codes, PKCE verifiers, client identifiers, redirect URIs, resources, scopes and refresh tokens before credential lookup or consumption.
20
+ - Apply the Sharp 0.35.5 minimum independently to consumer CycloneDX validation as well as the installed dependency tree, including malformed and prerelease version rejection.
21
+ - Reject coercible legacy resource-staging publisher PID evidence instead of accepting strings, arrays or booleans as process identities; retain the staging files on malformed ownership.
22
+ - Refuse staging reclamation when the publisher PID encoded in a current-format filename exceeds safe integer bounds, rather than treating invalid process identity as permission to delete.
23
+ - Remove the retired version-2 capability-lease/release-task cleanup compatibility surface from current documentation and state-root removal; unsupported older state is preserved and rejected instead of interpreted by the current runtime.
24
+ - Reject non-string browser extension IDs, origins, and manifest public keys instead of allowing arrays or other values to acquire extension identity through JavaScript string coercion.
25
+ - Treat synchronous security-audit worker transport failures as terminal worker failures, settle all pending audit promises, mark health unavailable, and account for records that could not be persisted instead of leaving queued callers hanging.
26
+ - Stop interpreting retired schema-v1 Worker continuity evidence; unsupported persisted continuity schemas now fail closed to empty current-schema diagnostics instead of partially migrating historical counters.
27
+ - Reject non-string persisted daemon socket roles instead of allowing arrays or other values to pass role validation through JavaScript string coercion.
28
+ - Add a random per-daemon pseudonym correlation epoch to new security-audit events so incident review can identify runtime-key rotation boundaries without creating stable cross-restart principal or target identifiers.
29
+ - Remove the redundant browser-extension `tabs` permission; existing `<all_urls>` host authority already supplies tab metadata for scriptable pages, while restricted browser-internal pages now expose less metadata to the extension.
30
+ - Route npm publication cleanup warnings through the shared release diagnostic sanitizer so a successful publication cannot leak local home paths or credential-shaped values through best-effort cleanup messages.
31
+ - Mark browser-derived page text, accessibility labels, DOM attributes, and screenshots as untrusted data in remote MCP server instructions so webpage prompt injection is not presented as authorization or higher-precedence guidance.
32
+ - Repair the privacy regression fixture so Git-version boundary assertions use Node's strict assertion API instead of the test file's boolean-only helper, restoring coverage-gate execution of the privacy suite.
33
+ - Remove schema-v1 migration rationale from the always-injected MCP server instructions now that the runtime is current-schema-only; historical rationale remains in release history instead of consuming every client context.
34
+ - Reject Git version components outside JavaScript safe-integer bounds so malformed trusted-Git version output cannot satisfy the privacy-history minimum through numeric infinity coercion.
35
+ - Keep promotion-digest npm pack metadata type-exact by rejecting non-string paths and non-integer file modes instead of normalizing coercible values into release evidence.
36
+ - Replace stale fixed beta.1 commands in the release guide with an explicit next-version variable and a soak-confirmation command derived from the current package version, preventing copy/paste of an obsolete prerelease identifier.
37
+ - Reject non-string local account-admin lookup targets instead of allowing arrays or other values to select a privileged account through string coercion.
38
+ - Require persisted resource lease, waiter, transaction-lock, and newly bound child PIDs to be positive safe integers so precision-losing process identities fail as integrity errors instead of being silently reclaimed or bound.
39
+ - Apply the same safe-integer PID boundary to the shared owner-state lock primitive so malformed toolchain and maintenance ownership evidence cannot be reclaimed after precision loss.
40
+ - Preserve an in-flight browser pairing exchange after invalid final challenge, nonce, or proof attempts; the one-time grant is now consumed only after the final HMAC proof succeeds, preventing unauthenticated failures from deleting a legitimate pending exchange.
41
+ - Require service network/TLS environment values to remain native strings on capture and reload so malformed persisted arrays or numbers cannot be coerced into active proxy or certificate configuration.
42
+ - Make shared device-session and daemon-authentication transcript builders type-exact so arrays, numeric strings, or other coercible values cannot acquire signed protocol semantics if a future caller omits its own pre-validation.
43
+ - Keep process-tree ownership snapshots type-exact by accepting only positive safe-integer child PIDs, matching persisted active-child and lock ownership boundaries.
44
+ - Advance security-audit state to schema 3 while retaining one exact, one-way beta.198 schema-1 data migration: prior event objects and hashes remain unchanged under a schema-3 envelope with an explicit legacy-prefix count, while all newly appended events require current pseudonym, operation-correlation, and settlement fields.
45
+ - Split browser operation scalar/response-contract validation into a fixed preloaded extension module and lower the browser-operations responsibility budget, removing its near-limit 1900-line concentration without changing dispatch semantics.
46
+ - Stop reclaiming malformed daemon/startup/maintenance and managed-job lock files by age alone. Current lock publication is complete-before-visible, so malformed owner metadata is now retained as an integrity blocker; only structurally valid owners with provably stale process identity are automatically reclaimed.
47
+ - Keep browser pairing types exact after the URL parsing boundary: the service worker and HMAC bootstrap now require native integer ports, string grants, and numeric clocks instead of accepting coercible arrays or numeric strings.
48
+ - Gate remote non-read-only dispatch on durable security-audit health and a persisted `dispatch_intent`. Intent and terminal records share a runtime-private operation reference; the intent deliberately proves only that the authorized pre-dispatch boundary was reached, so a timeout cannot be misread as proof that the handler started.
49
+ - Make the audit pre-dispatch gate consume an explicit local side-effect classification rather than MCP annotation metadata, and regression-check public `readOnlyHint` values against that security classification so catalog drift cannot silently weaken the gate.
50
+ - Expose whether managed-job recovery attribution is complete without inventing beta.198 history: beta.199 recovery entries must form a contiguous suffix ending at the current attempt count, while prior-version attempt counts with no persisted reason/time remain explicitly `recovery_history_complete=false`.
51
+ - Keep daemon relay targets type-exact from persisted/runtime configuration through WebSocket and HTTPS fallback: non-string Worker URLs are rejected rather than being coerced into authenticated network origins, and malformed HTTP relay URLs use one fixed error classification.
52
+ - Apply the same exact-string Worker origin/name boundary to deployment health verification so malformed persisted arrays cannot be normalized into healthy Worker identity evidence.
53
+ - Keep current security-audit hashes, pseudonym epochs, timestamps, and numeric counters type-exact; arrays and numeric strings no longer become valid-looking audit evidence through regular-expression or numeric coercion.
54
+ - Tighten both current and beta.198 audit readers to the actual writer image: canonical ISO timestamps, bounded token/text forms, and non-negative safe-integer duration/byte counters. Hash-consistent values that no supported writer could emit are rejected as integrity failures.
55
+ - Reject precision-losing process IDs in resource ancestry sampling and lease accounting so malformed `ps`/PowerShell evidence cannot collapse distinct PIDs into one JavaScript number.
56
+ - Retire pre-beta.198 temporary Worker-secret filename compatibility. Cleanup now recognizes only the current PID + creation-time + process-start-time + 12-hex nonce shape; obsolete lookalike secret files are retained as explicit integrity blockers instead of being weakly attributed or silently ignored.
57
+ - Keep local device-root/session authentication primitives type-exact: public roots now pass shared canonical P-256 validation, authentication clocks require native finite numbers, and session-signing transcripts require native strings rather than coercible values.
58
+ - Preserve security-audit evidence types end to end: the daemon no longer coerces injected audit clocks before persistence, and recent-activity projection no longer normalizes string/array timestamps or byte counters into valid-looking evidence.
59
+ - Keep managed-job retention timestamps type-exact before destructive maintenance: malformed staged `created_at` values no longer gain expiry authority through string coercion, and terminal retention ordering only parses native timestamp strings.
60
+ - Make account-administration authentication transcripts and local request construction type-exact: origin/method/path/hash/key/nonce fields no longer accept coercible values, signed body bytes must already be a string, and display names are rejected rather than stringified.
61
+ - Require daemon candidate/readiness/liveness timestamps to be native canonical ISO strings before they can establish freshness or alarm deadlines; JavaScript-coercible arrays and alternate timestamp spellings no longer become liveness evidence.
62
+ - Document the existing `file:` browser-automation privacy boundary explicitly: when Chromium file-URL access is enabled, local file-backed tabs are data-bearing automation surfaces rather than ordinary harmless tab metadata.
63
+ - Add an explicit offline `machine-mcp browser reset` revocation path. It serializes through state maintenance, refuses active runtime/job ownership, rechecks that the loopback broker is stopped, and atomically rotates both broker HMAC credentials without exposing their replacements.
64
+ - Correct the browser-pairing storage model in the threat/automation docs: authoritative broker credentials remain in owner-only Machine Bridge state, while the extension stores its extension-token copy in Chromium local extension storage for restart persistence and may retain an unusable stale copy after local state reset.
65
+ - Keep the macOS Secure Enclave broker boundary type-exact: configured/provisioned broker paths and signing transcripts now require native strings, preventing coercible arrays or objects from acquiring trust-broker path or signature semantics.
66
+ - Redact browser pairing grants and managed-job recovery/control credentials by URL parameter name at the final logging boundary, including extension/runtime token parameters, while leaving harmless names such as `grant_type` intact.
67
+ - Keep browser pairing grant and launch clocks/ports type-exact and internally consistent: native safe integers are required, one wall-clock sample governs each bootstrap transition, and grant creation rejects timestamps that cannot produce the protocol's canonical 13-digit expiry.
68
+ - Tighten process-identity evidence to the current writer/probe image: snapshot PIDs must be safe integers, snapshot start times must already be numeric, and persisted owner timestamps must be canonical ISO strings rather than merely parseable dates.
69
+ - When browser history-state cleanup fails on the ephemeral pairing page, replace the current location with the token-free `/pair` URL before stopping; pairing still fails closed, but the short-lived grant no longer remains indefinitely in the visible fragment/history entry.
70
+ - Clear pairing bootstrap material from the content-script closure immediately after successful or ordinary failed pairing; retain it only for the explicit short-lived manual-repair branch that still needs the same grant.
71
+ - On POSIX, synchronize the security-audit parent directory after atomic state replacement before acknowledging a batch, so a pre-dispatch intent is not reported persisted solely because the temporary file contents reached storage before the rename metadata.
72
+ - Require the signed DPoP `htm` claim to match the normalized expected HTTP method exactly instead of uppercasing the proof claim, aligning the verifier with RFC 9449-style exact method binding and mature OAuth implementations.
73
+ - Clarify incident diagnostics so recent audit `pending/unknown` effect-settlement counts are not mistaken for a live termination queue; current process termination remains a separate runtime snapshot.
74
+ - Restore the local CLI browser-pairing launcher helper after compatibility-copy cleanup removed its imports while leaving the default dependency reference in place; CLI startup and `--version` no longer fail before argument dispatch.
75
+ - Update the page-automation VM regression harness for browser-global Web Crypto and automation API version 6, keeping the production document epoch on `crypto.randomUUID()` instead of weakening it for Node test contexts.
76
+ - Remove stale version-2 lease and schema-1 migration rationale from current architecture, operations, and release guidance now that those compatibility readers and surfaces are no longer supported.
77
+ - Expand the privacy publication scan beyond Git-visible files to every literal `package.json#files` tree, closing a gap where Git-ignored files such as `src/local/*.local` were still included by `npm pack` but escaped the privacy gate.
78
+ - Require canonical current account roles in every persisted OAuth authority record and remove the online bad-role repair branch; malformed or non-canonical role state now fails the current-schema boundary instead of being rewritten at runtime.
79
+ - Remove the unreachable local-admin external-launch helper and its process-spawn/browser-launch dependencies, leaving the current pairing launch path owned only by the browser pairing service.
80
+ - Reject prototype-shaped or otherwise malformed account IDs before indexing the persisted account table, and require runtime tool-handler dispatch to use own properties only.
81
+ - Extend the publication privacy gate across npm's implicit README/LICENSE/package/main/bin/man surfaces, closing a second gap where a Git-ignored root README could still be auto-included by `npm pack`.
82
+ - Document the residual same-user ancestor-directory race for ordinary workspace file mutations instead of overstating path canonicalization as an atomic cross-platform directory-handle boundary.
83
+ - Document that the owner-only autostart network snapshot can persist embedded proxy authentication and private certificate paths even though those values are excluded from status output and logs.
84
+ - Reject malformed hosted managed-job dependency IDs before capability verification instead of string-coercing array or other non-string values and deferring rejection to the daemon plan parser.
85
+ - Keep OAuth, admin, and daemon edge throttling bound to the caller network identity even when an attacker adds arbitrary Authorization headers; credential-scoped rate-limit buckets are now reserved for the MCP endpoint that actually authenticates with those credentials.
86
+ - Clarify the local-resource confidentiality boundary: browser/application mutation results do not echo injected resource content, while the destination receives it and later explicit value/source/screenshot observations may expose content the destination now holds.
87
+ - Keep security-audit evidence type-exact: malformed non-string target hashes, principal identifiers, roles, tools, outcomes, and risk categories now fall back to null/unknown evidence instead of being string-coerced into valid-looking audit records.
88
+ - Enforce exact string types for browser pairing grants, challenges, proofs, and credentials, and require an integer pairing port instead of accepting equivalent values through JavaScript coercion.
89
+ - Enforce exact string types for resource-request family and contention identities so malformed persisted/request metadata cannot cross the coordinator contract through JavaScript coercion.
90
+ - Require exact string resource names and resource-file paths in managed-job planning, keeping resource aliases and persisted plan evidence type-exact with the rest of the job schema.
91
+ - Keep MCP subscription and pending-read per-account capacity keys type-exact instead of normalizing non-string runtime values into account identities.
92
+ - Correct multi-account protocol documentation so removed initialization-era requests are described as rejected, not as a compatibility path.
93
+ - Validate relay account/client/family identity again at the in-flight call attribution boundary instead of coercing malformed principals into revocation metadata.
94
+ - Reject unknown in-flight principal kinds instead of misattributing them as local calls, preserving fail-closed revocation and interruption attribution.
95
+ - Persist at most three privacy-bounded managed-job recovery attribution records with fixed interruption reasons and exact timestamps, expose them with the strict recovery-attempt count, and reject coercible or malformed recovery metadata instead of using it to drive automatic relaunch.
96
+ - Require exact current Worker deployment identity types and formats for Worker names, OAuth token-version secrets, historical Worker names, and deployment fingerprints; arrays and other coercible values no longer become valid deployment identity through string conversion.
97
+ - Require runtime package entry, expected package identity, platform selector, and packaged manifest version to have their exact string types before service activation provenance is accepted; malformed manifest arrays can no longer stringify into a valid release version.
98
+ - Rename the fail-closed cached-schema helper from “compatibility” to “guidance” to make explicit that stale host schemas receive refresh instructions but never regain retired execution semantics.
99
+ - Remove the unused asynchronous `auditWarning` request-context mutation; audit persistence failures remain reported through the bounded security-audit failure reporter instead of writing dead state after a response may already have settled.
100
+ - Remove documentation and result-shape accommodation for older browser extensions: the current handshake already requires the exact packaged extension and `computer_observation_v1`, and missing tab-metadata verification evidence now projects as unverified rather than a legacy tri-state.
101
+ - Remove the unwritten legacy `operation-authorization.lock` scan/migration path and parse the current `security-audit.lock` with its actual owner-state contract, restoring live PID attribution in state inventory.
102
+ - Make the current OAuth store contract genuinely current-only: reject one-character legacy account names and clients missing the current DCR registration identity/revision instead of keeping them authorization-readable, and remove stale-registration status from the admin projection.
103
+ - Fail a managed-job step immediately when a still-live spawned child has no verifiable process-start identity; the runner terminates that child instead of allowing an active claim that crash recovery could only classify as ambiguous.
104
+ - Extend private Wrangler toolchain reuse verification to the executable install-script dependencies `esbuild` and `workerd`, and bump the audit marker schema so existing cached installs are revalidated under the stronger contract.
105
+ - Remove the obsolete browser setup note for migrating older local-candidate extension paths; current setup documents only the stable unpacked-extension path.
106
+ - Split managed-job recovery history accounting out of general state validation so recovery attribution has a dedicated bounded module instead of expanding the core status validator beyond its responsibility boundary.
107
+ - Reject multiply linked package files while computing the promotion digest so an alternate hard-link path cannot mutate candidate content behind the verified package path.
108
+ - Centralize Worker-name and OAuth token-version validation in one current identity contract instead of duplicating security-sensitive regular expressions across state, deployment fingerprinting, health checks, and state inventory.
109
+ - Retain temporary Worker-secret files when filename ownership PID/timestamp evidence exceeds safe-integer bounds instead of treating malformed process identity as reclaimable stale state; legacy secret cleanup remains only to remove credential residue safely.
110
+ - Validate tool arguments after tool/role authority gating but before relay operation risk/path authorization, so malformed remote inputs cannot trigger local path classification or other operation-level inspection before schema rejection.
111
+ - Require hardened-npm artifact descriptors and explicit proxy/NO_PROXY overrides to use native string/integer types, preventing coercible arrays or numeric strings from becoming supply-chain identity or network-routing configuration.
112
+ - Extend managed-job resource-output defense-in-depth redaction to exact Base64URL encodings, while documenting that arbitrary transformed or application-specific representations still require `capture_output: "discard"` when credentials may be echoed.
113
+ - Record a privacy-safe error class when a successfully delivered transient-process result cannot clear its stronger pending-recovery marker, preserving the user result while making temporary quota/retention over-protection diagnosable.
114
+ - Keep runtime shutdown on the ownership-checked process-tree supervisor: forced drain now uses zero-additional-grace supervised escalation, preserves already-pending ownership checks, and waits for retained descendant-escalation settlement instead of replacing it with an unverified direct group `SIGKILL`.
13
115
 
14
116
  ## Historical releases
15
117
 
16
- Historical prerelease entries remain available from Git tags and repository release records. From a source checkout, inspect the changelog shipped by a prior release with `git show v<version>:CHANGELOG.md`. This current file intentionally describes only the active candidate and the history location.
118
+ Historical prerelease entries remain available from Git tags and repository history. From a source checkout, inspect the changelog of a prior revision with `git show <revision>:CHANGELOG.md`. This current file intentionally describes only the active candidate and the history location.
package/README.md CHANGED
@@ -25,7 +25,7 @@ Support boundaries are defined in [SUPPORT.md](SUPPORT.md). Contribution, conduc
25
25
  - bounded file, patch, Git, process, diagnostic, application, browser, and managed-job tools;
26
26
  - account roles whose authority is intersected with the connected daemon policy;
27
27
  - root-certified ephemeral daemon sessions, trusted OAuth client binding, refresh-family ownership, and non-escalatable account roles;
28
- - structured, privacy-conscious lifecycle events, a worker-thread-isolated tamper-evident audit chain, and stable error codes;
28
+ - structured, privacy-conscious lifecycle events, a worker-thread-isolated self-verifying audit hash chain with documented same-user rewrite limits, and stable error codes;
29
29
  - control-plane resilience through end-to-end reserved diagnostic capacity, event-loop-aware relay liveness, and explicit draining-process accounting;
30
30
  - fail-closed state, lock, release, package, and supply-chain checks.
31
31
 
@@ -176,7 +176,13 @@ The shared source of truth is `src/shared/policy-contract.json`. The generated m
176
176
 
177
177
  For routine remote health checks, prefer `server_info` with `detail: "summary"`; the empty/default call remains full diagnostics and, for owner/full callers, includes a privacy-bounded durable continuity summary that survives Worker isolate replacement without retaining identities, call IDs, tool arguments, results, endpoints, or close reasons. For routine workspace inventory, `project_overview` also accepts `detail: "summary"`; it preserves policy/tool counts and top-level names/types without repeating exact tool arrays, account identity, routing fingerprints, or per-entry paths/sizes. Its empty/default call likewise remains full for compatibility. For remote calls, `server_info.authorization.effective_policy` and, when exact membership is needed, the full projection's `effective_tools` are authoritative. Daemon policy and tools describe only the local capability ceiling before account-role and host-side filtering.
178
178
 
179
- `tools/list` is the authenticated account's current discovery catalog. Discovery instructions and tool descriptions carry execution/orchestration semantics, so both `server/discover` and `tools/list` advertise `ttlMs=0`. Current MCP 2026-07-28 remote discovery also advertises `tools.listChanged=true`: a client that opts into `toolsListChanged` through `subscriptions/listen` receives a correlated acknowledgement and level-trigger `notifications/tools/list_changed` event, then re-fetches `tools/list`. The request-scoped subscription remains open until explicit cancellation or the advertised bounded server lease expires; the lease is a fail-safe for HTTP disconnects that the Worker runtime cannot reliably observe and does not replace the initial level-trigger/refetch contract. Every host-visible tool description carries `Tool schema generation N`; `server_info.tool_delivery` exposes the current `tool_schema_generation`, `tool_schema_server_version`, and `tool_list_ttl_ms`, while explicitly reporting that Machine Bridge cannot observe which schema generation an external host has actually cached. A generation change therefore requires the subscription/refetch path or another host-side schema refresh plus post-activation verification. Discovery is not authority: every `tools/call` is still intersected with the current end-to-end-ready daemon policy and tool ceiling, and fails retryably with `unavailable` when no daemon is ready. `server_info.tool_delivery` also distinguishes the advertised catalog from the currently effective daemon/account intersection. WebSocket is the preferred daemon transport: verified ready traffic resumes immediately, but reconnect attempt history resets only after five seconds of generation-stable ready uptime so a shorter ready/close flap keeps its prior exponential backoff position. It requests a protocol-level Ping after five seconds and gives an actually dispatched Ping its full ten-second Pong deadline, then uses one independent fifteen-second application-confirmation window before a ready WSS may be terminated as a transport black hole. A protocol Pong or explicit application `pong` during that second stage preserves WSS; ordinary tool/control inbound remains receive-side evidence and cannot clear transport suspicion, while local event-loop stalls cancel remote suspicion and use the separate recovery-grace path. The periodic application heartbeat remains twenty-five/seventy-five seconds after end-to-end readiness, and the Worker keeps a wider ninety-second fallback. WSS connect attempts have a thirty-second outer budget. Signed HTTPS is independent of that budget: a verified healthy WSS keeps HTTPS fallback stopped; on first-stage WSS suspicion the same root-certified ephemeral device identity may prewarm HTTPS in standby, verified WSS recovery stops that prewarm, and a real WSS loss promotes the path to exact-generation takeover while aborting any obsolete standby request. Fallback requests bind the fixed route/origin/server/version, a short-lived nonce, timestamp, and exact body hash; they use a seven-second ordinary request deadline, a twelve-second liveness window, a five-second verified-ready idle poll cadence, a thirty-second standby retry cadence, and a 750 ms minimum request-start interval for handshake or queued outbound work. The first authenticated exchange enters probing immediately, so verified readiness requires at most two bounded exchanges rather than a separate challenge round trip. Candidate → probing → verified-ready handover prevents the Worker from dispatching until the daemon has processed `ready_ack` and returned sequenced `https_ready`; a same-instance takeover may retire a Worker-side zombie WSS only after the signed candidate preconditions pass. Both directions use bounded contiguous transport sequences, so a lost HTTP response retransmits the same transport envelope and duplicates are discarded before business handling; this does not restore MCP sessions, recovery GET, `Last-Event-ID`, or public result persistence. Same-instance `resume_calls` / `resume_calls_ack` remains authoritative for in-flight ownership. A planned daemon shutdown is a different boundary: before a ready runtime closes its relay it sends `daemon_draining`; the Worker settles calls still owned by that daemon with structured `daemon_planned_drain` recovery and acknowledges the drain. In-flight `read_job` is explicitly read-only recoverable with `recovery.mode=read_same_job` plus the original `job_id`; resume by reading that same job after reconnect. This does not transfer an executing call to a new daemon process, whose random instance identity remains intentionally distinct, and it cannot revive an external assistant turn that has already ended. The daemon sends `resume_calls_ack.missing_ids` only after replacement readiness, only for IDs absent from both its active-call set and unacknowledged-result ledger, and only while it still has fail-closed proof that missing ownership means the call did not execute locally. If a completed-but-unacknowledged result expires, `diagnose_runtime.runtime.relay_result_recovery.automatic_redelivery_safe` becomes false and missing-ID automatic redelivery is disabled rather than risking duplicate side effects. A safe proven-undelivered call may be retransmitted with the same call ID, arguments, authority, and a reduced timeout inside the original execution deadline; a call that may have executed is never automatically replayed. Separately, if that same daemon already executed a call and its terminal result is delayed by the transport break, the Worker may retain the original in-memory settlement owner for one fixed fifteen-second window beyond its original settlement deadline, including when that original settlement deadline was already at the ordinary per-tool ceiling. That delivery-only extension cannot authorize redelivery or new execution, cannot accumulate across repeated handovers, and does not create client-visible replay or durable result state. A new call may wait up to fifteen seconds for a verified daemon channel, but measured recovery time is deducted from that call's original execution budget instead of extending the hosted foreground envelope. Hosted synchronous calls otherwise retain their ordinary 20-second execution plus separate five-second Worker settlement margin; configurable browser/application tools retain 20-second ordinary defaults, compound `computer_observe` / `computer_act` retain 30-second defaults, and the explicit remote maximum remains 45 seconds. Remote `exec_command`, `run_process`, and `run_local_command` require a caller-held `idempotency_key`, commit a principal-bound one-step managed job, and remain recoverable through bounded same-response `read_job` follow-up when the current task needs terminal state. Hosted active `read_job` uses a server-side 40-second long-poll by default and returns earlier on meaningful job progress or terminal state; `wait_ms=0` requests an immediate checkpoint, while every public hosted call is capped at 60 seconds. The default remains 40 seconds: live hosted evidence carried both the default and an explicit 60-second read, while beta.151 later reproduced `mcp_network_error` on a second explicit 180-second read even though the Worker-to-daemon WebSocket remained continuously ready. Longer tasks therefore keep the same `job_id` and use another server-paced read rather than one overlong host request. This keeps long-task waiting inside Machine Bridge within the demonstrated per-call host lifetime; the 40-second interval also bounds interaction density to at most 150 reads for a synthetic unchanged 100-minute job, but that arithmetic does not prove that one assistant response can survive the aggregate duration or call count. If a real host/tool boundary ends a response, preserve the durable identifier and resume the same operation later rather than resubmitting its side effect. `start_process` remains daemon-lifetime interactive state; hosted `read_process` permits paced same-response follow-up, defaults an omitted relay `wait_ms` to the one-second blocking cap, and paces another would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary. Active job/process reads do not force a user-turn handoff. Callers must not infer or preempt a host/tool deadline from elapsed wall-clock time: while calls continue to be accepted and the task still needs the result, bounded same-response follow-up may continue. Handoff is reserved for an actual observed host/tool boundary, required external input or authorization, or an explicit user checkpoint, while busy loops and status-surface substitution remain prohibited. The durable process façade preserves account/tool authority and delegated workspace sandbox rather than expanding privileges.
179
+ `tools/list` is the authenticated account's discovery catalog. Both `server/discover` and `tools/list` advertise `ttlMs=0`, and every host-visible tool description carries `Tool schema generation N`. Remote discovery supports `tools.listChanged=true`: `subscriptions/listen` acknowledges `toolsListChanged`, emits a level-trigger `notifications/tools/list_changed`, and stays open until cancellation or its bounded lease expires. Clients re-fetch the catalog; the server cannot prove which generation a host cached. Discovery does not grant authority: calls still require the current account/ready-daemon intersection.
180
+
181
+ WebSocket is preferred. An actually dispatched Ping has a ten-second Pong deadline, followed by one independent fifteen-second application-confirmation window. A protocol Pong or explicit application `pong` during that second stage preserves WSS. Local event-loop stalls use a separate recovery path. WSS connect attempts have a thirty-second outer budget. Healthy WSS keeps signed HTTPS fallback stopped; suspicion permits standby prewarming, and actual loss permits takeover of that exact generation. The daemon sends `resume_calls_ack.missing_ids` only after replacement readiness and only with fail-closed proof that absent ownership means non-execution. A possibly executed call is never automatically replayed. See [architecture](https://github.com/YuLeiFuYun/machine-bridge-mcp/blob/main/docs/ARCHITECTURE.md#daemon-reconnect-and-replacement) for transport sequencing, deadlines, and diagnostics.
182
+
183
+ Planned shutdown sends `daemon_draining` before relay close. An interrupted `read_job` returns `recovery.mode=read_same_job`; preserve its `job_id` and `recovery_key` and read that same job after reconnect. Replacement does not transfer ordinary executing calls to a new daemon.
184
+
185
+ Hosted synchronous calls otherwise retain their ordinary 20-second execution plus separate five-second Worker settlement margin. Configurable browser/application tools use 20-second defaults; compound `computer_observe` / `computer_act` retain 30-second defaults, with a 45-second explicit maximum. Durable process acceptance and continuation have separate budgets below. These limits do not establish external host receipt or a whole-assistant-turn deadline.
180
186
 
181
187
  `full` is the daemon capability ceiling. An authenticated owner may exercise it without per-operation approval IDs. Delegated reviewer, editor, and operator accounts remain inside immutable role ceilings; out-of-role operations are denied rather than converted into a temporary elevation workflow. Process sessions, retained output, and managed jobs are additionally bound to account, client, and refresh-token family. See [local authorization](docs/LOCAL_AUTHORIZATION.md).
182
188
 
@@ -197,7 +203,15 @@ For stateful GUI trajectories, owner/full callers can use the higher-level `comp
197
203
 
198
204
  ## Durable work and local resources
199
205
 
200
- Remote request-owned foreground work uses the hosted reply-safe budgets described above; configurable browser/application calls may explicitly request at most 45 seconds, while remote `exec_command`, `run_process`, and `run_local_command` are durable one-step jobs with a 10-second acceptance envelope and an independent 1–600-second child execution budget after admission. A continuous process that legitimately needs more than 600 seconds must use `start_job`: managed-job main/finally steps default to 600 seconds and may explicitly request up to 21,600 seconds (six hours), with resource admission occurring before that execution timer begins. The Worker retains separate settlement ownership for five additional seconds, but neither that margin nor its internal stream metrics prove that an external MCP host consumed the terminal frame. Keep unrelated mutations and validation independently terminal, but batch one coherent non-interactive command sequence into a repository umbrella command or multi-step `start_job` instead of creating one host-visible one-step job per tiny probe. A timeout is a protocol result, not proof that descendant cleanup has already completed; a remote owner can inspect `diagnose_runtime.runtime.processes`, while local stdio exposes `server_info.runtime.processes`. Non-owner accounts receive authority-scoped readiness rather than machine-wide process activity. Remote process sessions are for interactive stdin or incremental output, not a substitute for ordinary durable work: hosted `read_process` reports `status_polling_mode=paced_followup`, caps the actual output/exit blocking wait at one second, and paces a repeated would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary instead of returning a rapid running checkpoint. When the current task needs more output or terminal state, the same session may be read again in the same assistant response without busy-looping. Non-interactive work should use durable `run_process`/`read_job`; multi-step, cleanup-sensitive, or daemon-restart-surviving workflows should use managed jobs, which persist ordered argv steps and `finally_steps` under owner-only local state and continue across an MCP disconnect. Hosted durable acceptance returns `job_id`, `recovery_key`, and `control_key`: preserve them together, use the read capability for `read_job` and `depends_on`, and use the control capability for `cancel_job`; a bare job ID is not remote recovery authority. On an MCP Apps-capable host, an active `start_job` may report `ui_monitor_candidate=true`, `ui_monitor_render_tool=render_job_monitor`, and a fresh random `ui_monitor_id`, but it remains a data/execution tool and carries no UI template. **Job polling ownership is not task ownership:** if remaining same-response work needs that job's terminal result, do not render the monitor merely to shorten the response; preserve `job_id` + `recovery_key` and continue bounded server-paced `read_job` calls. Render the monitor only when the current task can deliberately transfer that job's status polling without needing its terminal result for remaining same-response work. When intentionally rendering, preserve the start-result ID, call `render_job_monitor` once with the exact accepted `job_id` + `recovery_key` + `ui_monitor_id`, and do not depend on the render result being planner-visible. Then issue one model-visible `read_job` with the same three values. A matching claimed View on an ordinary job reports `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, `host_turn_handoff_recommended=false`, and `follow_up_read_required=false`; those fields suppress duplicate model polling for that job only and do not require ending unrelated or still-actionable assistant work. The sole host-turn exception is an explicitly persisted `continuation_mode=task_supervisor`: only when that durable job's own reviewed plan already encapsulates all remaining noninteractive task work may a later active claimed read report `host_turn_handoff_recommended=true`. Machine Bridge can validate the persisted continuation declaration, active job state, and current monitor claim, but it cannot infer conversation-level task completeness; that completeness remains an owner/caller semantic assertion. That handoff is owned by the durable supervisor, not by the Job Monitor, and grants the View no execution authority. The monitor then uses app-only `read_job_monitor` for fixed 40-second status waits instead of emitting background model-visible `read_job` events. Each app-origin status call has a 60-second response watchdog; missing/transport-failed and explicitly retryable status responses receive at most six automatic retries with 1/2/4/8/15/30-second backoff. Exhausted or non-retryable failures pause for inspection, and no retry resubmits the durable job. An older View claim cannot transfer a newer turn because its monitor ID differs. Unsupported hosts, or hosts that can render MCP Apps but cannot proxy app-origin server tools, retain the existing contract: active relay-origin `read_job` reports `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. Its hosted default is a 40-second server-side long-poll, so an unchanged long job occupies one bounded live MCP response rather than forcing rapid host-side checkpoints. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is available only when an immediate checkpoint is actually wanted, and public hosted `wait_ms` is capped at 60 seconds; longer work continues through another paced read of the same `job_id` plus its preserved `recovery_key`. At the default, a synthetic 100-minute unchanged job has an anti-amplification ceiling of 150 status reads, while continuously changing nonterminal progress is separately bounded by the 30-second coalescing floor; those are density estimates rather than proof of aggregate same-response host lifetime. A known job may be followed through paced same-response `read_job` calls while those calls continue to be accepted and the task still needs the result; after an actual host/tool boundary, later recovery must continue from the same `job_id`. Completed one-step process carriers are lower-priority terminal retention than explicit managed jobs, so removable helper history is reclaimed first under the 512-state durable cap. A hosted helper whose current response still requires `read_job` continuation is non-evictable during the fixed 24-hour undelivered-result grace because Machine Bridge explicitly told the caller to follow that durable handle. If the 512-state store is full and every retained slot is non-evictable, the existing typed retryable `limit_exceeded` backpressure rejects the incoming job instead of deleting promised recovery evidence. Once Machine Bridge produces a terminal hosted response for the helper—during initial settlement or a later `read_job`—the private marker drops to the shorter thirty-minute bounded newest-16 transient delivery reserve because only outer response-delivery uncertainty remains; an undelivered marker older than 24 hours likewise stops hard protection and becomes ordinary eviction material. For relay non-owner one-step process carriers, acceptance additionally caps unresolved pending recovery at 16 records per account ID plus account version across all OAuth clients and refresh families; changing client/family does not reset that fairness bound. The check is serialized by the same root capacity transaction before new status publication or process launch and uses retained job state rather than a separate counter. A securely valid persisted same-key job can still be recovered at quota because it adds no reservation, but an empty/statusless deterministic directory is only an incomplete publication and must pass quota again. Hosted terminal delivery or the existing 24-hour terminal expiry releases the account slot. Owner/local work is not subject to this account quota, and the hosted inventory does not expose raw account-quota counts or principal identifiers. The daemon/local `list_jobs.jobs` inventory keeps a 50-record durable-first primary window and `recent_process_recovery` remains capped at 16 additional authority-visible handles for local administration. The hosted Worker projection deliberately removes those job IDs/names/handles and exposes aggregate retained/capacity/activity state only, so one hosted conversation cannot discover another workflow's durable handle through shared owner inventory. Hosted recovery therefore depends on the capability returned by the original acceptance; legacy or lost-capability jobs require local CLI/stdio administration. Owner/local `capacity` diagnostics expose only coarse `durable_terminal` and `transient_terminal` counts; this improves recovery visibility without pretending that Worker acknowledgement proves an external host rendered the final assistant message. Long cross-job workflows can declare `depends_on`: the dependent job remains pre-execution `queued/dependency_wait` without spawning its main child until all upstream jobs succeed, and an upstream failure settles `dependency_failed` instead of leaving a file-poll loop waiting for an artifact that can never appear. Active/staged dependency plans pin referenced retained results until the dependency-bearing plan is terminal. A valid `job_id` that is no longer retained returns typed `not_found`; that absence is not proof that its underlying side effect never executed. `list_jobs` remains inventory rather than a substitute polling loop, and `server_info`/`diagnose_runtime` remain diagnostic surfaces rather than alternate wait channels. Elapsed minutes are not evidence that an external host deadline is near; return the durable recovery identifier for a later turn only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint.
206
+ Remote `exec_command`, `run_process`, and `run_local_command` require a caller-held `idempotency_key` and become principal-bound one-step jobs before execution. Their acceptance envelope is ten seconds; child execution is independently bounded to 1–600 seconds after resource admission. Use `start_job` for a continuous command needing more time: main/finally steps default to 600 seconds and may request up to 21,600 seconds (six hours). Batch coherent non-interactive work into one plan or repository umbrella command. Process sessions serve interactive stdin/incremental output and retain daemon-lifetime ownership; hosted `read_process` reports `status_polling_mode=paced_followup`, with a one-second actual blocking cap and a fifteen-second would-block cooldown.
207
+
208
+ Preserve `job_id`, `recovery_key`, and `control_key` from acceptance. Reads/dependencies require the recovery capability; cancellation requires the control capability. A bare ID is not hosted authority. When terminal state is needed, continue bounded same-response `read_job` follow-up: active reads report `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. The default long-poll is 40 seconds, terminal checks are bounded to five-second intervals, and nonterminal progress is coalesced for at least 30 seconds; step-name changes alone do not wake a hosted read. Explicit `wait_ms=0` requests a checkpoint and the public maximum is 60 seconds. Do not busy-loop or substitute diagnostic calls for known-job reads. Elapsed minutes do not justify handoff; after an actual host/tool boundary, recover the same job instead of repeating its side effect.
209
+
210
+ An active `start_job` may offer a Job Monitor on an MCP Apps-capable host. Polling ownership is separate from task ownership: if remaining work needs the result, keep following `read_job`. Otherwise render once with the accepted `job_id`, `recovery_key`, and `ui_monitor_id`, then read with those same values to verify the View claim. A claimed ordinary job reports `status_polling_mode=ui_monitor`, `follow_up_read_required=false`, and `host_turn_handoff_recommended=false`. Only an explicit `continuation_mode=task_supervisor` plan that already owns all remaining noninteractive work may recommend handoff; completeness remains an owner/caller semantic assertion. Monitor failures never resubmit execution.
211
+
212
+ The retained-state cap is 512. A hosted helper awaiting its promised terminal response is non-evictable during the fixed 24-hour undelivered-result grace; a store with no removable slot rejects new work with retryable `limit_exceeded`. After terminal delivery, helpers have a bounded newest-16/thirty-minute reserve and lower eviction priority than explicit durable history. Delegated one-step pending recovery is capped at 16 records per account ID plus account version across all OAuth clients and refresh families, checked before new state or process launch; a valid same-key replay adds no slot, while a statusless directory must pass admission again. Local CLI/stdio retains detailed inventory; hosted `list_jobs` exposes aggregates, so lost capabilities require local administration. A retained-record `not_found` does not prove non-execution.
213
+
214
+ Jobs can declare `depends_on`: the dependent remains `queued/dependency_wait` before main execution, upstream failure yields `dependency_failed`, and active/staged dependencies protect referenced results. Register secrets as local resource aliases and put idempotent cleanup in `finally_steps`. Timeout or cancellation alone does not prove process-tree settlement. See [managed jobs](docs/MANAGED_JOBS.md) for examples, retention, recovery, and privacy boundaries.
201
215
 
202
216
  The repository-tracked `workflow-bundle.json` is the project-native lifecycle authority for local readiness. Its `project-native:npm:check:fast` and `project-native:npm:check:full` stages must remain `sandbox: required`; a machine-local or untracked lifecycle file must not silently lower high-risk containment. Release architecture checks enforce both Git tracking and those required Seatbelt modes, so the job-local continuation contract above is validated under the same repository-owned verification authority used for the candidate.
203
217
 
@@ -207,7 +221,7 @@ On macOS, authorized remote activity uses a bounded idle-sleep assertion so ordi
207
221
 
208
222
  When `run_process` or `exec_command` returns a child exit code and bounded stdout/stderr, the local process did run. For nested tools such as `ssh`, a remote forced-command usage message or command allowlist is therefore evidence from the target-side authorization layer, not evidence that Machine Bridge blocked process execution. Diagnose and change the narrowest failing layer instead of widening the `full` profile, which already removes Machine Bridge's own shell and path restrictions.
209
223
 
210
- Credentials and files can be registered by alias without returning their contents through MCP:
224
+ Credentials and files can be registered by alias so the injection operation does not place their contents in MCP arguments or echo them in its result:
211
225
 
212
226
  ```sh
213
227
  machine-mcp resource add maintenance-key ~/.ssh/example_maintenance_ed25519
@@ -216,6 +230,7 @@ machine-mcp job submit plan.json
216
230
  ```
217
231
 
218
232
  See [docs/MANAGED_JOBS.md](docs/MANAGED_JOBS.md) for integrity checks, recovery, redaction, cleanup, and residual risks.
233
+ For browser or application injection, the destination receives the content by design and can copy, render, or transmit it; later observations of that destination can therefore expose content it now holds.
219
234
 
220
235
  ## Operations
221
236
 
@@ -229,7 +244,7 @@ machine-mcp workspace show|set|reset
229
244
  machine-mcp idle-sleep show|set MODE
230
245
  machine-mcp service status|install|start|stop|uninstall
231
246
  machine-mcp account list|clients|revoke-client|add|role|enable|disable|rotate-password|remove
232
- machine-mcp browser status|setup|pair|path
247
+ machine-mcp browser status|setup|pair|reset|path
233
248
  machine-mcp resource add|list|check|remove
234
249
  machine-mcp job submit|inspect|list|read|cancel
235
250
  machine-mcp rotate-secrets
package/SECURITY.md CHANGED
@@ -71,7 +71,7 @@ The roles are:
71
71
  - `operator`: workspace-confined mutation and direct process execution;
72
72
  - `owner`: complete bridge authority within the daemon policy ceiling.
73
73
 
74
- No approval ID, refresh token, reconnect, client registration, or legacy lease can expand a role. Out-of-role operations fail with `authorization_denied`.
74
+ No approval ID, refresh token, reconnect, or client registration can expand a role. Out-of-role operations fail with `authorization_denied`.
75
75
 
76
76
  The Worker filters the stable discovery catalog by account role. Discovery is not authorization: each call is separately intersected with the current end-to-end-ready daemon capability ceiling, and the local runtime independently recomputes the role and policy boundary before dispatch. Account disablement, role change, password rotation, account removal, client revocation, token-version rotation, and refresh-family replay invalidate the appropriate credentials.
77
77
 
@@ -113,7 +113,7 @@ Remote daemon authentication uses a long-term device root and a root-certified e
113
113
 
114
114
  The default provider on every platform, including macOS, is an owner-only portable P-256 root. Its private JWK is exportable by the daemon OS user and is reported as such in `server_info`. The root signs a 24-hour ephemeral session certificate at daemon startup; the ephemeral private key remains in memory and signs WebSocket preflight, Worker challenge, reconnect, and account-administration requests. Portable startup does not access Keychain and does not request user presence.
115
115
 
116
- macOS can opt into a non-exportable Secure Enclave root only through `MBM_MACOS_TRUST_BROKER`. The configured executable must be an app-like broker signed by an Apple development or distribution identity with provisioning-profile-validated data-protection Keychain entitlements. Machine Bridge validates its canonical path, file mutability, strict code signature, signing identifier, Team ID, protocol responses, and a real create/delete Secure Enclave key probe before enrollment. The broker identity remains bound to the root and is revalidated before later use.
116
+ macOS can opt into a non-exportable Secure Enclave root only through `MBM_MACOS_TRUST_BROKER`. The configured executable must be an app-like broker signed by an Apple development or distribution identity with provisioning-profile-validated data-protection Keychain entitlements. Machine Bridge validates its canonical path, file mutability, strict code signature, signing identifier, Team ID, protocol responses, and a real create/delete Secure Enclave key probe before enrollment. The broker identity remains bound to the root and is revalidated before later use. The native broker verifies the returned key's Secure Enclave token, private-key class, EC type, and 256-bit size before use or export. Only an explicit key-not-found result permits creation; other lookup failures preserve existing state. Deletion binds the validated key reference, and signing input is bounded before Keychain access.
117
117
 
118
118
  The prompt frequency is therefore:
119
119
 
@@ -189,7 +189,7 @@ After trusted input dispatch begins, an ambiguous failure is reported as unknown
189
189
 
190
190
  Computer Use screenshots share the ordinary MCP result-size boundary. When an image would make an observation result too large, the image is omitted before the snapshot ID is published and pixel-action authority is disabled. When a post-action image would overflow the same boundary, it is removed from the returned/stored post snapshot while the already-established mutation settlement and bounded continuation handle are preserved; the result never turns that dispatched action into a generic retryable size failure. Semantic state may still be returned. The experimental macOS background visual backend is disabled unless explicitly configured and successfully probed; Accessibility, Automation, Screen Recording, and the operating system remain independent enforcement boundaries.
191
191
 
192
- 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. In particular, raw serialized HTML may contain hidden bootstrap/session/account/authentication values that are not visible in the rendered page; `browser_get_source` therefore should be used only when raw markup is required, with semantic inspection preferred for routine browser work.
192
+ Local resources may be injected without placing their bytes in the mutating MCP arguments or echoing them in that mutation result, but the destination page or application still receives them. Once delivered, the destination can copy, render, or transmit the value; a later explicit value inspection, screenshot, page source read, or other observation may therefore expose content the destination now holds. In particular, raw serialized HTML may contain hidden bootstrap/session/account/authentication values that are not visible in the rendered page; `browser_get_source` therefore should be used only when raw markup is required, and `browser_inspect_page` should normally keep `include_values=false`.
193
193
 
194
194
  ## Network egress boundaries
195
195
 
package/SUPPORT.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  The supported runtime is the pinned Node.js 26 and npm 12 baseline on current GitHub-hosted Linux, macOS, and Windows environments. Older Node/npm releases and obsolete MCP protocol versions are not compatibility targets.
6
6
 
7
+ Source-release privacy tooling requires Git 2.50 or later. The reachable-history scanner intentionally relies on the machine-parsable NUL-delimited `git rev-list` object format introduced in Git 2.50 and does not carry an older-output compatibility parser.
8
+
7
9
  The project supports the current package version and a direct upgrade from the immediately preceding published version when both use the current local state and policy schemas. See [Upgrading](docs/UPGRADING.md).
8
10
 
9
11
  ## Where to ask
@@ -4,8 +4,8 @@ import { sanitizeLogText } from "../src/local/log.mjs";
4
4
 
5
5
  main().catch(error => {
6
6
  const message = process.env.MBM_DEBUG === "1"
7
- ? sanitizeLogText(error?.stack || error?.message || String(error))
7
+ ? (error?.stack || error?.message || String(error))
8
8
  : (error?.message || String(error));
9
- console.error(`Error: ${message}`);
9
+ console.error(`Error: ${sanitizeLogText(message)}`);
10
10
  process.exitCode = 1;
11
11
  });
@@ -6,7 +6,7 @@
6
6
  const BROKER_AUTH_REQUEST_VALUE = "machine-bridge-browser-v2";
7
7
 
8
8
  async function extensionProtocol(endpoint, token) {
9
- if (!TOKEN.test(String(token || ""))) throw new Error("browser pairing token is invalid");
9
+ if (typeof token !== "string" || !TOKEN.test(token)) throw new Error("browser pairing token is invalid");
10
10
  const parsed = parseEndpoint(endpoint);
11
11
  if (!parsed) throw new Error("browser broker endpoint is invalid");
12
12
  const clientChallenge = randomBase64Url(24);
@@ -27,8 +27,9 @@
27
27
  }
28
28
 
29
29
  function parseEndpoint(value) {
30
+ if (typeof value !== "string") return null;
30
31
  let parsed;
31
- try { parsed = new URL(String(value || "")); } catch { return null; }
32
+ try { parsed = new URL(value); } catch { return null; }
32
33
  const port = Number(parsed.port);
33
34
  if (parsed.protocol !== "ws:" || parsed.hostname !== "127.0.0.1" || parsed.pathname !== "/extension"
34
35
  || parsed.username || parsed.password || parsed.search || parsed.hash
@@ -40,8 +41,9 @@
40
41
  return parsed ? `http://127.0.0.1:${parsed.port}/pair` : "";
41
42
  }
42
43
  function parsePairingPage(value) {
44
+ if (typeof value !== "string") return null;
43
45
  let parsed;
44
- try { parsed = new URL(String(value || "")); } catch { return null; }
46
+ try { parsed = new URL(value); } catch { return null; }
45
47
  const port = Number(parsed.port);
46
48
  if (parsed.protocol !== "http:" || parsed.hostname !== "127.0.0.1" || parsed.pathname !== "/pair"
47
49
  || parsed.username || parsed.password || parsed.search || parsed.hash
@@ -0,0 +1,92 @@
1
+ (() => {
2
+ if (globalThis.__machineBridgeBrowserOperationContract) return;
3
+ const MUTATING_METHODS = new Set([
4
+ "manage_tabs", "point_action", "backend_node_action", "action", "fill_form", "upload_files", "screenshot",
5
+ ]);
6
+ const MUTATION_RESULT_SETTLEMENT_UNKNOWN = "browser mutation may have completed; the action outcome is unknown because its result could not be delivered. Inspect browser state before retrying.";
7
+ function exactFiniteNumber(value) {
8
+ return typeof value === "number" && Number.isFinite(value) ? (Object.is(value, -0) ? 0 : value) : null;
9
+ }
10
+ function exactPositiveFiniteNumber(value) {
11
+ const number = exactFiniteNumber(value);
12
+ return number !== null && number > 0 ? number : null;
13
+ }
14
+ function exactNormalizedCoordinate(value) {
15
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value < 1
16
+ ? (Object.is(value, -0) ? 0 : value) : null;
17
+ }
18
+ function exactPositiveInteger(value) { return Number.isSafeInteger(value) && value > 0 ? value : null; }
19
+ function exactNonNegativeInteger(value) { return Number.isSafeInteger(value) && value >= 0 ? value : null; }
20
+ function exactBoolean(value, label, fallback) {
21
+ if (value === undefined) return fallback;
22
+ if (typeof value !== "boolean") throw new Error(`${label} must be boolean before dispatch`);
23
+ return value;
24
+ }
25
+ function exactBoundedInteger(value, label, fallback, min, max) {
26
+ if (value === undefined) return fallback;
27
+ if (!Number.isSafeInteger(value) || value < min || value > max) throw new Error(`${label} is invalid before dispatch`);
28
+ return value;
29
+ }
30
+ function exactImageFormat(value, label, fallback = "png") {
31
+ if (value === undefined) return fallback;
32
+ if (typeof value !== "string" || !["png", "jpeg"].includes(value)) throw new Error(`${label} is invalid before dispatch`);
33
+ return value;
34
+ }
35
+ function exactOptionalSha256(value, label) {
36
+ if (value === undefined || value === "") return "";
37
+ if (typeof value !== "string" || !/^[A-Fa-f0-9]{64}$/.test(value)) throw new Error(`${label} is invalid before dispatch`);
38
+ return value.toLowerCase();
39
+ }
40
+ function exactOptionalText(value, label, maxLength) {
41
+ if (value === undefined || value === "") return "";
42
+ if (typeof value !== "string" || value.includes("\0") || value.length > maxLength) throw new Error(`${label} is invalid before dispatch`);
43
+ return value;
44
+ }
45
+ function exactOptionalAuthorityString(value, maxLength) {
46
+ if (value === undefined || value === "") return "";
47
+ if (typeof value !== "string" || value.length > maxLength || value.includes("\0")) {
48
+ throw new Error("snapshot authority string is invalid before dispatch");
49
+ }
50
+ return value;
51
+ }
52
+ function requiredSnapshotAuthorityString(value, label, maxLength) {
53
+ const text = exactOptionalAuthorityString(value, maxLength);
54
+ if (!text) throw new Error(`${label} is required before dispatch`);
55
+ return text;
56
+ }
57
+ function requiredSnapshotViewport(value) {
58
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("visual snapshot viewport is required before dispatch");
59
+ const output = {};
60
+ for (const key of ["width", "height", "scale"]) {
61
+ const number = exactPositiveFiniteNumber(value[key]);
62
+ if (number === null) throw new Error(`visual snapshot viewport ${key} is invalid before dispatch`);
63
+ output[key] = number;
64
+ }
65
+ return output;
66
+ }
67
+ function methodMayMutate(method) { return typeof method === "string" && MUTATING_METHODS.has(method); }
68
+ function responsePayload({ id, ok, result, error = "", method = "", maxBytes }) {
69
+ const mutatingSuccess = ok === true && methodMayMutate(method);
70
+ let payload;
71
+ try { payload = JSON.stringify({ type: "response", id, ok, ...(ok ? { result } : { error }) }); }
72
+ catch {
73
+ payload = JSON.stringify({ type: "response", id, ok: false,
74
+ error: mutatingSuccess ? MUTATION_RESULT_SETTLEMENT_UNKNOWN : "browser result could not be serialized" });
75
+ }
76
+ const resultLimit = Number.isSafeInteger(maxBytes) && maxBytes > 0 ? maxBytes : 0;
77
+ if (resultLimit === 0 || new TextEncoder().encode(payload).byteLength > resultLimit) {
78
+ payload = JSON.stringify({ type: "response", id, ok: false,
79
+ error: mutatingSuccess ? MUTATION_RESULT_SETTLEMENT_UNKNOWN : "browser result exceeds maximum size" });
80
+ }
81
+ return payload;
82
+ }
83
+ Object.defineProperty(globalThis, "__machineBridgeBrowserOperationContract", {
84
+ value: Object.freeze({
85
+ exactFiniteNumber, exactPositiveFiniteNumber, exactNormalizedCoordinate, exactPositiveInteger,
86
+ exactNonNegativeInteger, exactBoolean, exactBoundedInteger, exactImageFormat, exactOptionalSha256,
87
+ exactOptionalText, requiredSnapshotAuthorityString, requiredSnapshotViewport,
88
+ exactOptionalAuthorityString, methodMayMutate, responsePayload,
89
+ }),
90
+ configurable: false,
91
+ });
92
+ })();