machine-bridge-mcp 2.0.0 → 3.0.0-beta.102

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 (473) hide show
  1. package/CHANGELOG.md +1142 -0
  2. package/CONTRIBUTING.md +23 -25
  3. package/GOVERNANCE.md +2 -2
  4. package/README.md +58 -20
  5. package/SECURITY.md +161 -120
  6. package/SUPPORT.md +1 -1
  7. package/browser-extension/broker-auth.js +83 -0
  8. package/browser-extension/broker-liveness.js +35 -0
  9. package/browser-extension/browser-error-boundary.js +96 -0
  10. package/browser-extension/browser-operations.js +1456 -101
  11. package/browser-extension/devtools-input.js +195 -34
  12. package/browser-extension/devtools-observation.js +429 -0
  13. package/browser-extension/devtools-session.js +48 -0
  14. package/browser-extension/manifest.json +4 -3
  15. package/browser-extension/page-automation.js +578 -118
  16. package/browser-extension/pairing-bootstrap.js +42 -0
  17. package/browser-extension/pairing.js +28 -12
  18. package/browser-extension/service-worker.js +126 -106
  19. package/docs/AGENT_CONTEXT.md +13 -10
  20. package/docs/ARCHITECTURE.md +159 -67
  21. package/docs/AUDIT.md +1302 -4
  22. package/docs/CLIENTS.md +19 -6
  23. package/docs/COMPUTER_USE.md +297 -0
  24. package/docs/ENGINEERING.md +72 -13
  25. package/docs/GETTING_STARTED.md +18 -20
  26. package/docs/LOCAL_AUTHORIZATION.md +115 -72
  27. package/docs/LOCAL_AUTOMATION.md +27 -21
  28. package/docs/LOGGING.md +40 -19
  29. package/docs/MANAGED_JOBS.md +37 -34
  30. package/docs/MULTI_ACCOUNT.md +98 -37
  31. package/docs/OPERATIONS.md +145 -51
  32. package/docs/OVERVIEW.md +14 -11
  33. package/docs/POLICY_REFERENCE.md +71 -3
  34. package/docs/PRIVACY.md +41 -2
  35. package/docs/PROJECT_STANDARDS.md +29 -6
  36. package/docs/RELEASING.md +238 -64
  37. package/docs/TESTING.md +150 -39
  38. package/docs/THREAT_MODEL.md +164 -67
  39. package/docs/TOOL_REFERENCE.md +653 -60
  40. package/docs/UPGRADING.md +78 -18
  41. package/native/macos/MachineBridgeBackgroundInput.swift +1051 -0
  42. package/native/macos/MachineBridgeBackgroundInputSmokeFixture.swift +295 -0
  43. package/native/macos/MachineBridgeTrustBroker.swift +233 -0
  44. package/package.json +94 -16
  45. package/scripts/accepted-candidate-tarball.mjs +77 -0
  46. package/scripts/candidate-runtime-store.mjs +93 -0
  47. package/scripts/check-plan.mjs +73 -3
  48. package/scripts/check-runner.mjs +129 -17
  49. package/scripts/commit-message-check.mjs +5 -2
  50. package/scripts/consumer-package-security.mjs +293 -0
  51. package/scripts/coverage-check.mjs +251 -15
  52. package/scripts/coverage-generation.mjs +41 -0
  53. package/scripts/coverage-range-merge.mjs +41 -0
  54. package/scripts/foreground-daemon-recovery.mjs +88 -0
  55. package/scripts/generate-policy-reference.mjs +28 -5
  56. package/scripts/generate-tool-reference.mjs +1 -1
  57. package/scripts/generate-worker-types.mjs +21 -11
  58. package/scripts/github-backlog.mjs +11 -10
  59. package/scripts/github-push.mjs +62 -16
  60. package/scripts/github-release-asset.mjs +58 -0
  61. package/scripts/github-release.mjs +241 -132
  62. package/scripts/global-package-installation.mjs +68 -0
  63. package/scripts/hardened-npm-session.mjs +42 -0
  64. package/scripts/install-published-prerelease.mjs +198 -0
  65. package/scripts/local-release-acceptance.mjs +103 -45
  66. package/scripts/macos-background-input-smoke.mjs +515 -0
  67. package/scripts/network-retry.mjs +8 -1
  68. package/scripts/npm-global-prefix.mjs +27 -0
  69. package/scripts/npm-publication-policy.mjs +17 -0
  70. package/scripts/official-mcp-conformance.mjs +267 -0
  71. package/scripts/persistent-activation-process.mjs +101 -0
  72. package/scripts/prepare-pinned-npm.mjs +12 -69
  73. package/scripts/prerelease-activation.mjs +160 -0
  74. package/scripts/privacy-check.mjs +14 -6
  75. package/scripts/promotion-digest.mjs +128 -0
  76. package/scripts/publish-npm.mjs +260 -0
  77. package/scripts/published-release.mjs +132 -0
  78. package/scripts/release-acceptance.mjs +62 -54
  79. package/scripts/release-browser-extension-store.mjs +172 -0
  80. package/scripts/release-candidate-manifest.mjs +52 -0
  81. package/scripts/release-channel.mjs +106 -0
  82. package/scripts/release-diagnostic.mjs +41 -0
  83. package/scripts/release-impact-check.mjs +12 -22
  84. package/scripts/release-oauth-canary-core.mjs +325 -0
  85. package/scripts/release-oauth-canary-evidence.mjs +100 -0
  86. package/scripts/release-oauth-canary.mjs +127 -0
  87. package/scripts/release-publication-guard.mjs +67 -0
  88. package/scripts/release-soak.mjs +284 -0
  89. package/scripts/release-state.mjs +1 -1
  90. package/scripts/run-checks.mjs +44 -7
  91. package/scripts/run-worker-dry-run.mjs +21 -0
  92. package/scripts/sarif-security-gate.mjs +2 -1
  93. package/scripts/sbom-check.mjs +106 -0
  94. package/scripts/start-release-candidate.mjs +267 -44
  95. package/scripts/syntax-check.mjs +36 -14
  96. package/scripts/verification-generation-guard.mjs +15 -0
  97. package/scripts/verification-idle-sleep-guard.mjs +37 -0
  98. package/scripts/verification-state.mjs +108 -0
  99. package/scripts/wrangler-command-lifecycle.mjs +115 -0
  100. package/src/local/account-access.mjs +13 -1
  101. package/src/local/account-admin-response.mjs +92 -0
  102. package/src/local/account-admin.mjs +52 -19
  103. package/src/local/agent-context-projection.mjs +26 -7
  104. package/src/local/agent-context.mjs +88 -49
  105. package/src/local/agent-skill-discovery.mjs +16 -5
  106. package/src/local/app-automation-macos-jxa.mjs +350 -0
  107. package/src/local/app-automation.mjs +873 -163
  108. package/src/local/application-capability-projection.mjs +24 -0
  109. package/src/local/authority-context.mjs +106 -0
  110. package/src/local/autostart-log-maintenance.mjs +36 -0
  111. package/src/local/browser-bridge-http.mjs +5 -3
  112. package/src/local/browser-bridge.mjs +109 -31
  113. package/src/local/browser-broker-auth-http.mjs +67 -0
  114. package/src/local/browser-broker-auth.mjs +98 -0
  115. package/src/local/browser-broker-routes.mjs +63 -20
  116. package/src/local/browser-broker-server.mjs +17 -7
  117. package/src/local/browser-command.mjs +79 -30
  118. package/src/local/browser-computer-observation-service.mjs +266 -0
  119. package/src/local/browser-extension-identity.mjs +50 -0
  120. package/src/local/browser-extension-path.mjs +22 -0
  121. package/src/local/browser-extension-protocol.mjs +50 -11
  122. package/src/local/browser-operation-service.mjs +147 -117
  123. package/src/local/browser-pairing-grant.mjs +109 -0
  124. package/src/local/browser-pairing-http.mjs +36 -0
  125. package/src/local/browser-pairing-launch.mjs +58 -0
  126. package/src/local/browser-pairing-store.mjs +85 -48
  127. package/src/local/browser-request-registry.mjs +40 -14
  128. package/src/local/browser-request-settlement.mjs +78 -0
  129. package/src/local/browser-resource-input.mjs +74 -0
  130. package/src/local/browser-trusted-input-health.mjs +63 -0
  131. package/src/local/call-authority.mjs +21 -0
  132. package/src/local/call-capacity.mjs +45 -0
  133. package/src/local/call-registry-drain.mjs +54 -0
  134. package/src/local/call-registry.mjs +30 -15
  135. package/src/local/capability-observer.mjs +5 -0
  136. package/src/local/capability-ranking.mjs +3 -2
  137. package/src/local/child-process-settlement.mjs +132 -0
  138. package/src/local/cli-account-admin.mjs +40 -6
  139. package/src/local/cli-activate.mjs +162 -0
  140. package/src/local/cli-local-admin.mjs +20 -45
  141. package/src/local/cli-options.mjs +12 -13
  142. package/src/local/cli-ready-output.mjs +52 -0
  143. package/src/local/cli-service.mjs +91 -11
  144. package/src/local/cli.mjs +232 -150
  145. package/src/local/computer-use-application-observation.mjs +224 -0
  146. package/src/local/computer-use-arguments.mjs +270 -0
  147. package/src/local/computer-use-deadline.mjs +50 -0
  148. package/src/local/computer-use-dispatch-settlement.mjs +40 -0
  149. package/src/local/computer-use-expectation.mjs +171 -0
  150. package/src/local/computer-use-observation.mjs +769 -0
  151. package/src/local/computer-use-recovery.mjs +82 -0
  152. package/src/local/computer-use-result-budget.mjs +145 -0
  153. package/src/local/computer-use-snapshot-store.mjs +67 -0
  154. package/src/local/computer-use.mjs +2255 -0
  155. package/src/local/daemon-http-relay-auth.mjs +26 -0
  156. package/src/local/daemon-http-relay-connection.mjs +298 -0
  157. package/src/local/daemon-http-relay-request.mjs +64 -0
  158. package/src/local/daemon-http-relay-sequence.mjs +49 -0
  159. package/src/local/daemon-process.mjs +40 -7
  160. package/src/local/delegated-process-sandbox.mjs +148 -0
  161. package/src/local/device-identity.mjs +156 -49
  162. package/src/local/device-root-provider.mjs +81 -0
  163. package/src/local/directory-metadata.mjs +47 -0
  164. package/src/local/doctor-reporting.mjs +23 -0
  165. package/src/local/durable-process-spec.mjs +80 -0
  166. package/src/local/errors.mjs +25 -15
  167. package/src/local/exclusive-file.mjs +74 -26
  168. package/src/local/execution-limits.mjs +9 -0
  169. package/src/local/execution-routing.mjs +276 -0
  170. package/src/local/execution-surface.mjs +23 -0
  171. package/src/local/file-mutation-coordinator.mjs +47 -0
  172. package/src/local/file-snapshot-preservation.mjs +24 -0
  173. package/src/local/filesystem-identity.mjs +54 -0
  174. package/src/local/fixed-process-environment.mjs +21 -0
  175. package/src/local/full-access-test.mjs +31 -13
  176. package/src/local/git-commit.mjs +69 -0
  177. package/src/local/git-config-safety.mjs +55 -0
  178. package/src/local/git-log-parser.mjs +41 -0
  179. package/src/local/git-metadata-boundary.mjs +77 -0
  180. package/src/local/git-metadata-tree-safety.mjs +59 -0
  181. package/src/local/git-operation-state.mjs +19 -0
  182. package/src/local/git-service.mjs +165 -30
  183. package/src/local/hardened-npm-download-timeout.mjs +46 -0
  184. package/src/local/hardened-npm-download.mjs +109 -0
  185. package/src/local/hardened-npm-extract.mjs +20 -0
  186. package/src/local/hardened-npm-verification.mjs +104 -0
  187. package/src/local/hardened-npm.mjs +199 -0
  188. package/src/local/job-runner.mjs +244 -89
  189. package/src/local/lifecycle.mjs +6 -0
  190. package/src/local/log.mjs +19 -45
  191. package/src/local/macos-background-input.mjs +398 -0
  192. package/src/local/macos-idle-sleep-assertion.mjs +65 -0
  193. package/src/local/macos-trust-broker.mjs +404 -0
  194. package/src/local/managed-job-cancellation.mjs +42 -0
  195. package/src/local/managed-job-capacity.mjs +16 -0
  196. package/src/local/managed-job-directory-generation.mjs +60 -0
  197. package/src/local/managed-job-directory.mjs +75 -0
  198. package/src/local/managed-job-durable-process.mjs +49 -0
  199. package/src/local/managed-job-lock.mjs +46 -14
  200. package/src/local/managed-job-plan.mjs +38 -14
  201. package/src/local/managed-job-projection.mjs +23 -15
  202. package/src/local/managed-job-retention.mjs +128 -0
  203. package/src/local/managed-job-runner-claim.mjs +70 -0
  204. package/src/local/managed-job-runner.mjs +35 -10
  205. package/src/local/managed-job-storage.mjs +22 -6
  206. package/src/local/managed-job-terminal-maintenance.mjs +40 -0
  207. package/src/local/managed-job-terminal.mjs +150 -0
  208. package/src/local/managed-jobs.mjs +424 -287
  209. package/src/local/npm-cli.mjs +42 -0
  210. package/src/local/npm-environment.mjs +37 -0
  211. package/src/local/operation-authorization.mjs +68 -324
  212. package/src/local/operation-risk.mjs +51 -4
  213. package/src/local/owner-state-lock.mjs +118 -0
  214. package/src/local/package-identity.mjs +9 -0
  215. package/src/local/patch.mjs +20 -18
  216. package/src/local/path-inspection.mjs +23 -0
  217. package/src/local/policy.mjs +19 -0
  218. package/src/local/private-toolchain-integrity.mjs +54 -0
  219. package/src/local/process-execution.mjs +87 -75
  220. package/src/local/process-foreground-timeout.mjs +50 -0
  221. package/src/local/process-identity.mjs +41 -7
  222. package/src/local/process-nonreplayable-settlement.mjs +50 -0
  223. package/src/local/process-result-projection.mjs +22 -0
  224. package/src/local/process-session-events.mjs +47 -0
  225. package/src/local/process-session-remote-activity.mjs +10 -0
  226. package/src/local/process-session-termination.mjs +73 -0
  227. package/src/local/process-sessions.mjs +144 -112
  228. package/src/local/process-tracker.mjs +140 -13
  229. package/src/local/process-tree-ownership-types.d.ts +37 -0
  230. package/src/local/process-tree-ownership.mjs +57 -0
  231. package/src/local/process-tree-signal.mjs +34 -0
  232. package/src/local/process-tree-snapshot.mjs +77 -0
  233. package/src/local/process-tree-supervisor.mjs +69 -0
  234. package/src/local/process-tree.mjs +11 -53
  235. package/src/local/project-metadata.mjs +19 -6
  236. package/src/local/relay-call-recovery.mjs +58 -38
  237. package/src/local/relay-connection-classification.mjs +131 -0
  238. package/src/local/relay-connection-support.mjs +91 -0
  239. package/src/local/relay-connection.mjs +179 -272
  240. package/src/local/relay-diagnostics.mjs +91 -0
  241. package/src/local/relay-heartbeat.mjs +119 -0
  242. package/src/local/relay-liveness.mjs +109 -0
  243. package/src/local/relay-peer-diagnostics.mjs +29 -0
  244. package/src/local/release-runtime-lock.mjs +18 -0
  245. package/src/local/remote-activity-idle-sleep-guard.mjs +77 -0
  246. package/src/local/remote-configuration.mjs +48 -0
  247. package/src/local/resilient-relay-connection.mjs +178 -0
  248. package/src/local/resource-admission-diagnostics.mjs +52 -0
  249. package/src/local/resource-admission-policy.mjs +115 -0
  250. package/src/local/resource-admission.mjs +339 -0
  251. package/src/local/resource-build-root.mjs +83 -0
  252. package/src/local/resource-cargo-concurrency.mjs +38 -0
  253. package/src/local/resource-cmake-concurrency.mjs +42 -0
  254. package/src/local/resource-command-concurrency.mjs +37 -0
  255. package/src/local/resource-command-profile.mjs +176 -0
  256. package/src/local/resource-coordinator-accounting.mjs +21 -0
  257. package/src/local/resource-cpu-window.mjs +20 -0
  258. package/src/local/resource-disk-headroom.mjs +13 -0
  259. package/src/local/resource-elastic-memory.mjs +19 -0
  260. package/src/local/resource-elastic-request.mjs +36 -0
  261. package/src/local/resource-foreground-wait.mjs +29 -0
  262. package/src/local/resource-go-concurrency.mjs +43 -0
  263. package/src/local/resource-gradle-concurrency.mjs +57 -0
  264. package/src/local/resource-host-cache.mjs +39 -0
  265. package/src/local/resource-host-darwin.mjs +54 -0
  266. package/src/local/resource-host-linux.mjs +35 -0
  267. package/src/local/resource-host-sample-file.mjs +23 -0
  268. package/src/local/resource-host-snapshot.mjs +99 -0
  269. package/src/local/resource-lease-accounting.mjs +119 -0
  270. package/src/local/resource-light-command.mjs +6 -0
  271. package/src/local/resource-make-concurrency.mjs +42 -0
  272. package/src/local/resource-maven-concurrency.mjs +38 -0
  273. package/src/local/resource-ninja-command-concurrency.mjs +20 -0
  274. package/src/local/resource-ninja-concurrency.mjs +28 -0
  275. package/src/local/resource-operations.mjs +26 -10
  276. package/src/local/resource-pressure.mjs +67 -0
  277. package/src/local/resource-probe-command.mjs +35 -0
  278. package/src/local/resource-process-admission.mjs +59 -0
  279. package/src/local/resource-process-ancestry-cache.mjs +43 -0
  280. package/src/local/resource-process-ancestry.mjs +32 -0
  281. package/src/local/resource-process-priority.mjs +15 -0
  282. package/src/local/resource-project-key.mjs +53 -0
  283. package/src/local/resource-pytest-concurrency.mjs +33 -0
  284. package/src/local/resource-release-control-classification.mjs +14 -0
  285. package/src/local/resource-release-control-executable.mjs +70 -0
  286. package/src/local/resource-release-control-workspace.mjs +41 -0
  287. package/src/local/resource-request-contract.mjs +30 -0
  288. package/src/local/resource-script-classification.mjs +44 -0
  289. package/src/local/resource-shell-analysis.mjs +58 -0
  290. package/src/local/resource-staging-recovery.mjs +97 -0
  291. package/src/local/resource-swift-concurrency.mjs +31 -0
  292. package/src/local/resource-transaction-lock.mjs +189 -0
  293. package/src/local/resource-wait.mjs +48 -0
  294. package/src/local/resource-waiters.mjs +139 -0
  295. package/src/local/resource-xcode-command.mjs +7 -0
  296. package/src/local/resource-xcode-concurrency.mjs +34 -0
  297. package/src/local/resource-xcode-non-build.mjs +19 -0
  298. package/src/local/runtime-activation.mjs +700 -0
  299. package/src/local/runtime-activity-projection.mjs +48 -0
  300. package/src/local/runtime-capabilities.mjs +48 -15
  301. package/src/local/runtime-diagnostic-state.mjs +14 -0
  302. package/src/local/runtime-diagnostics.mjs +40 -13
  303. package/src/local/runtime-info-projection.mjs +60 -0
  304. package/src/local/runtime-info-relay-projection.mjs +21 -0
  305. package/src/local/runtime-paths.mjs +2 -1
  306. package/src/local/runtime-process-routing.mjs +33 -0
  307. package/src/local/runtime-relay-connection-options.mjs +44 -0
  308. package/src/local/runtime-relay-control.mjs +98 -0
  309. package/src/local/runtime-relay.mjs +26 -50
  310. package/src/local/runtime-reporting.mjs +37 -21
  311. package/src/local/runtime-resource-service.mjs +77 -0
  312. package/src/local/runtime-tool-handlers.mjs +13 -10
  313. package/src/local/runtime.mjs +244 -208
  314. package/src/local/secure-file.mjs +73 -20
  315. package/src/local/security-audit-dispatch.mjs +35 -0
  316. package/src/local/security-audit-log.mjs +215 -0
  317. package/src/local/security-audit-state.mjs +139 -0
  318. package/src/local/security-audit-storage.mjs +135 -0
  319. package/src/local/security-audit-warning.mjs +31 -0
  320. package/src/local/security-audit-worker.mjs +87 -0
  321. package/src/local/service-convergence.mjs +42 -1
  322. package/src/local/service-definition.mjs +24 -0
  323. package/src/local/service-environment.mjs +18 -10
  324. package/src/local/service-owner.mjs +146 -0
  325. package/src/local/service-ownership.mjs +17 -0
  326. package/src/local/service-restart-handoff.mjs +64 -0
  327. package/src/local/service-restart-scheduler.mjs +49 -0
  328. package/src/local/service-runtime-convergence.mjs +52 -0
  329. package/src/local/service-runtime.mjs +105 -0
  330. package/src/local/service-status.mjs +60 -0
  331. package/src/local/service.mjs +385 -92
  332. package/src/local/shell.mjs +39 -19
  333. package/src/local/ssh-key.mjs +158 -42
  334. package/src/local/state-inventory.mjs +50 -24
  335. package/src/local/state-owner-lock-inventory.mjs +23 -0
  336. package/src/local/state-root-owned-namespaces.mjs +83 -0
  337. package/src/local/state-root-retirement.mjs +89 -0
  338. package/src/local/state.mjs +386 -96
  339. package/src/local/stdio.mjs +96 -66
  340. package/src/local/support-state-projection.mjs +30 -0
  341. package/src/local/system-network-route.mjs +76 -0
  342. package/src/local/systemd-removal.mjs +20 -0
  343. package/src/local/tool-executor.mjs +85 -20
  344. package/src/local/tool-result-boundary.mjs +51 -0
  345. package/src/local/toolchain-operation-lock.mjs +41 -0
  346. package/src/local/tools.mjs +7 -5
  347. package/src/local/trusted-executable.mjs +53 -0
  348. package/src/local/trusted-git-executable.mjs +34 -0
  349. package/src/local/trusted-github-cli.mjs +24 -0
  350. package/src/local/windows-service-convergence.mjs +49 -0
  351. package/src/local/windows-service.mjs +52 -45
  352. package/src/local/worker-deployment-fingerprint.mjs +102 -0
  353. package/src/local/worker-deployment.mjs +48 -60
  354. package/src/local/worker-health.mjs +12 -4
  355. package/src/local/worker-secret-file.mjs +37 -17
  356. package/src/local/workspace-file-service.mjs +156 -239
  357. package/src/local/workspace-file-transaction.mjs +189 -0
  358. package/src/local/workspace-search.mjs +55 -0
  359. package/src/local/wrangler-toolchain/package-lock.json +1505 -0
  360. package/src/local/wrangler-toolchain/package.json +23 -0
  361. package/src/local/wrangler-toolchain-verification.mjs +135 -0
  362. package/src/local/wrangler-toolchain.mjs +188 -0
  363. package/src/shared/access-contract.json +7 -1
  364. package/src/shared/activation-recovery.mjs +49 -0
  365. package/src/shared/admin-auth.d.mts +2 -1
  366. package/src/shared/admin-auth.mjs +3 -3
  367. package/src/shared/authority-revocation.d.mts +9 -0
  368. package/src/shared/authority-revocation.mjs +28 -0
  369. package/src/shared/daemon-auth.d.mts +11 -0
  370. package/src/shared/daemon-auth.mjs +17 -0
  371. package/src/shared/device-session-auth.d.mts +20 -0
  372. package/src/shared/device-session-auth.mjs +51 -0
  373. package/src/shared/foreground-timeout.d.mts +15 -0
  374. package/src/shared/foreground-timeout.mjs +70 -0
  375. package/src/shared/log-redaction.d.mts +9 -0
  376. package/src/shared/log-redaction.mjs +51 -0
  377. package/src/shared/mcp-protocol.d.mts +27 -0
  378. package/src/shared/mcp-protocol.mjs +275 -0
  379. package/src/shared/project-overview-projection.d.mts +4 -0
  380. package/src/shared/project-overview-projection.mjs +96 -0
  381. package/src/shared/relay-contract.json +28 -0
  382. package/src/shared/result-projection.d.mts +2 -1
  383. package/src/shared/result-projection.mjs +13 -2
  384. package/src/shared/server-metadata.json +15 -9
  385. package/src/shared/tool-argument-validation.d.mts +17 -0
  386. package/src/shared/tool-argument-validation.mjs +325 -0
  387. package/src/shared/tool-call-capacity.d.mts +41 -0
  388. package/src/shared/tool-call-capacity.mjs +59 -0
  389. package/src/shared/tool-catalog.json +626 -57
  390. package/src/worker/access.ts +12 -0
  391. package/src/worker/account-admin.ts +55 -33
  392. package/src/worker/authority-revocations.ts +174 -0
  393. package/src/worker/authority.ts +52 -6
  394. package/src/worker/daemon-auth.ts +49 -68
  395. package/src/worker/daemon-channel.ts +36 -0
  396. package/src/worker/daemon-http-auth.ts +63 -0
  397. package/src/worker/daemon-http-channel.ts +120 -0
  398. package/src/worker/daemon-http-controller.ts +163 -0
  399. package/src/worker/daemon-http-protocol.ts +62 -0
  400. package/src/worker/daemon-http-registry.ts +82 -0
  401. package/src/worker/daemon-last-observation.ts +30 -0
  402. package/src/worker/daemon-liveness.ts +8 -6
  403. package/src/worker/daemon-ready-dispatch.ts +17 -0
  404. package/src/worker/daemon-ready-messages.ts +73 -0
  405. package/src/worker/daemon-ready-waiters.ts +79 -0
  406. package/src/worker/daemon-recovery-budget.ts +13 -0
  407. package/src/worker/daemon-registry.ts +58 -0
  408. package/src/worker/daemon-relay-diagnostics.ts +127 -0
  409. package/src/worker/daemon-socket-attachment.ts +55 -0
  410. package/src/worker/daemon-sockets.ts +72 -62
  411. package/src/worker/daemon-status.ts +38 -0
  412. package/src/worker/device-session-verifier.ts +129 -0
  413. package/src/worker/dpop.ts +157 -0
  414. package/src/worker/durable-process-timeout.ts +29 -0
  415. package/src/worker/errors.ts +57 -0
  416. package/src/worker/http.ts +112 -26
  417. package/src/worker/index.ts +446 -440
  418. package/src/worker/mcp-access.ts +57 -0
  419. package/src/worker/mcp-controller.ts +188 -0
  420. package/src/worker/mcp-http-accept.ts +11 -0
  421. package/src/worker/mcp-http-contract.ts +322 -0
  422. package/src/worker/mcp-initialization-compat.ts +141 -0
  423. package/src/worker/mcp-jsonrpc.ts +11 -29
  424. package/src/worker/mcp-removed-protocol.ts +17 -0
  425. package/src/worker/mcp-response-proxy.ts +135 -0
  426. package/src/worker/mcp-response-stream.ts +65 -0
  427. package/src/worker/mcp-stale-schema-compat.ts +57 -0
  428. package/src/worker/mcp-stream-proxy-contract.ts +48 -0
  429. package/src/worker/mcp-tool-call-input.ts +23 -0
  430. package/src/worker/nonce-store.ts +33 -35
  431. package/src/worker/oauth-authorization-page.ts +21 -1
  432. package/src/worker/oauth-client-admin.ts +52 -0
  433. package/src/worker/oauth-client-contract.ts +38 -0
  434. package/src/worker/oauth-controller.ts +107 -47
  435. package/src/worker/oauth-field-contract.ts +31 -0
  436. package/src/worker/oauth-record-contract.ts +7 -0
  437. package/src/worker/oauth-refresh-authority.ts +27 -0
  438. package/src/worker/oauth-refresh-exchange.ts +162 -0
  439. package/src/worker/oauth-refresh-families.ts +130 -27
  440. package/src/worker/oauth-refresh-persistence.ts +116 -0
  441. package/src/worker/oauth-state.ts +33 -18
  442. package/src/worker/oauth-store-validation.ts +131 -0
  443. package/src/worker/oauth-token-derivation.ts +33 -0
  444. package/src/worker/oauth-token-issuance.ts +119 -0
  445. package/src/worker/oauth-tokens.ts +56 -190
  446. package/src/worker/observability.ts +38 -5
  447. package/src/worker/pending-admission.ts +15 -0
  448. package/src/worker/pending-call-capacity.ts +108 -0
  449. package/src/worker/pending-call-contract.ts +42 -5
  450. package/src/worker/pending-call-deadlines.ts +75 -0
  451. package/src/worker/pending-calls.ts +131 -125
  452. package/src/worker/runtime-alarm-storage.ts +37 -0
  453. package/src/worker/runtime-alarm.ts +136 -0
  454. package/src/worker/server-info-activity.ts +36 -0
  455. package/src/worker/server-info-tool-delivery.ts +15 -0
  456. package/src/worker/server-info.ts +168 -0
  457. package/src/worker/tool-catalog.ts +47 -1
  458. package/src/worker/tool-timeout.ts +75 -14
  459. package/src/worker/websocket-protocol.ts +26 -2
  460. package/src/worker/worker-edge-guard.ts +90 -0
  461. package/src/worker/worker-edge-log.ts +65 -0
  462. package/src/worker/worker-entry.ts +52 -0
  463. package/src/worker/worker-mcp-config.ts +23 -0
  464. package/src/worker/worker-metadata.ts +48 -0
  465. package/src/worker/worker-rate-limit-key.ts +30 -0
  466. package/src/worker/worker-runtime-config.ts +20 -0
  467. package/src/worker/worker-static-routes.ts +54 -0
  468. package/src/worker/worker-task-lifetime.ts +18 -0
  469. package/tsconfig.local.json +17 -1
  470. package/wrangler.jsonc +13 -0
  471. package/src/local/cli-approval.mjs +0 -117
  472. package/src/local/operation-state-lock.mjs +0 -92
  473. package/src/worker/mcp-session.ts +0 -72
@@ -0,0 +1,54 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Convert filesystem identity fields without allowing IEEE-754 rounding to make distinct
5
+ * platform identifiers compare equal. BigInt observations are preferred; injected Number
6
+ * observations remain supported only when exactly representable. When the platform
7
+ * exposes a change-time generation, retain it so immediate inode reuse cannot create an
8
+ * ABA-equivalent identity during destructive snapshot checks.
9
+ * @param {unknown} info
10
+ * @param {string} [label]
11
+ */
12
+ export function filesystemIdentity(info, label = "filesystem entry") {
13
+ if (!info || typeof info !== "object") throw new Error(`${label} identity is unavailable`);
14
+ const record = /** @type {Record<string, unknown>} */ (info);
15
+ const generation = filesystemGeneration(record, label);
16
+ return Object.freeze({
17
+ dev: exactFilesystemInteger(record.dev, `${label} device`),
18
+ ino: exactFilesystemInteger(record.ino, `${label} inode`),
19
+ ...generation,
20
+ });
21
+ }
22
+ /** @param {{dev: bigint, ino: bigint, ctimeNs?: bigint, ctimeMs?: number} | null | undefined} left @param {{dev: bigint, ino: bigint, ctimeNs?: bigint, ctimeMs?: number} | null | undefined} right */
23
+ export function sameFilesystemIdentity(left, right) {
24
+ if (!left || !right || left.dev !== right.dev || left.ino !== right.ino) return false;
25
+ if (left.ctimeNs !== undefined || right.ctimeNs !== undefined) return left.ctimeNs !== undefined && left.ctimeNs === right.ctimeNs;
26
+ if (left.ctimeMs !== undefined || right.ctimeMs !== undefined) return left.ctimeMs !== undefined && left.ctimeMs === right.ctimeMs;
27
+ return true;
28
+ }
29
+ /** @param {Record<string, unknown>} record @param {string} label */
30
+ function filesystemGeneration(record, label) {
31
+ if (record.ctimeNs !== undefined) return { ctimeNs: exactFilesystemInteger(record.ctimeNs, `${label} change time`) };
32
+ if (record.ctimeMs !== undefined) return { ctimeMs: filesystemTimeMs(record.ctimeMs, `${label} change time`) };
33
+ return {};
34
+ }
35
+
36
+ /** @param {unknown} value @param {string} label */
37
+ export function exactFilesystemInteger(value, label) {
38
+ if (typeof value === "bigint") {
39
+ if (value < 0n) throw new Error(`${label} is invalid`);
40
+ return value;
41
+ }
42
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return BigInt(value);
43
+ throw new Error(`${label} cannot be represented losslessly`);
44
+ }
45
+
46
+ /** @param {unknown} value @param {string} label */
47
+ export function filesystemTimeMs(value, label) {
48
+ if (typeof value === "bigint") {
49
+ if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`${label} cannot be represented safely`);
50
+ return Number(value);
51
+ }
52
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
53
+ throw new Error(`${label} is invalid`);
54
+ }
@@ -0,0 +1,21 @@
1
+ // @ts-check
2
+ import { BridgeError } from "./errors.mjs";
3
+ /** @type {Readonly<Record<string, string>>} */
4
+ export const STRUCTURED_GIT_FIXED_ENVIRONMENT = Object.freeze({
5
+ GIT_OPTIONAL_LOCKS: "0", GIT_CONFIG_NOSYSTEM: "1", GIT_ATTR_NOSYSTEM: "1", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0",
6
+ });
7
+ /** @param {unknown} value @returns {Readonly<Record<string, string>>} */
8
+ export function validateFixedProcessEnvironment(value) {
9
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
10
+ throw new BridgeError("invalid_request", "internal process environment must be an object");
11
+ }
12
+ /** @type {Record<string, string>} */
13
+ const normalized = Object.create(null);
14
+ for (const [key, raw] of Object.entries(value)) {
15
+ if (!Object.hasOwn(STRUCTURED_GIT_FIXED_ENVIRONMENT, key) || raw !== STRUCTURED_GIT_FIXED_ENVIRONMENT[key]) {
16
+ throw new BridgeError("invalid_request", "internal process environment override is not approved");
17
+ }
18
+ normalized[key] = raw;
19
+ }
20
+ return Object.freeze(normalized);
21
+ }
@@ -4,17 +4,15 @@ import { join, resolve } from "node:path";
4
4
  import process from "node:process";
5
5
  import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
6
6
  import { LocalRuntime } from "./runtime.mjs";
7
+ import { isTerminalManagedJobStatus } from "./managed-job-terminal.mjs";
7
8
  import { generateSshKeyPair } from "./ssh-key.mjs";
8
9
  import { runExecutable } from "./shell.mjs";
9
10
  import { allToolNames, assertCanonicalFullPolicy, policyProfile } from "./tools.mjs";
11
+ const FULL_ACCESS_RESOURCE_WAIT_MS = 10_000;
12
+ const FULL_ACCESS_JOB_WAIT_MS = 5 * 60_000;
13
+ const FULL_ACCESS_JOB_CLEANUP_WAIT_MS = 30_000;
10
14
 
11
- const TERMINAL_JOB_STATES = new Set([
12
- "succeeded", "failed", "cancelled", "runner_failed", "runner_launch_failed",
13
- "recovery_failed", "recovery_exhausted", "succeeded_cleanup_failed",
14
- "failed_cleanup_failed", "cancelled_cleanup_failed",
15
- ]);
16
-
17
- export async function runFullAccessTest({ workspace, policy = policyProfile("full", "explicit") } = {}) {
15
+ export async function runFullAccessTest({ workspace, policy = policyProfile("full", "explicit"), resourceCoordinatorOptions = null } = {}) {
18
16
  const canonicalPolicy = assertCanonicalFullPolicy(policy);
19
17
  const root = await mkdtemp(join(tmpdir(), "machine-mcp-full-test-"));
20
18
  const jobRoot = join(root, "jobs");
@@ -28,6 +26,8 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
28
26
  const previousSentinel = process.env[sentinelKey];
29
27
  process.env[sentinelKey] = "visible";
30
28
  let runtime;
29
+ let unsettledJobId = "";
30
+ let removeRoot = true;
31
31
 
32
32
  try {
33
33
  runtime = new LocalRuntime({
@@ -37,6 +37,9 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
37
37
  resources: {},
38
38
  logger: silentLogger(),
39
39
  recoverJobs: false,
40
+ resourceCoordinatorRoot: join(root, "resource-coordinator"),
41
+ resourceCoordinatorOptions,
42
+ processResourceWaitMs: FULL_ACCESS_RESOURCE_WAIT_MS,
40
43
  });
41
44
 
42
45
  const toolNames = runtime.tools();
@@ -68,7 +71,7 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
68
71
  checks.push(check("full-parent-environment", inherited.code === 0 && inherited.stdout === "visible"));
69
72
 
70
73
  const shellCommand = process.platform === "win32" ? "[Console]::Out.Write('full-shell')" : "printf full-shell";
71
- const shell = await runtime.execCommand(shellCommand, 10);
74
+ const shell = await runtime.execCommand({ command: shellCommand, timeout_seconds: 10 });
72
75
  checks.push(check("shell-execution", shell.code === 0 && shell.stdout.trim() === "full-shell"));
73
76
 
74
77
  const key = await generateSshKeyPair({
@@ -141,7 +144,9 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
141
144
  steps: [{ argv: [process.execPath, "{{temp:main.js}}", mainMarker], timeout_seconds: 10 }],
142
145
  finally_steps: [{ argv: [process.execPath, "-e", "require('node:fs').writeFileSync(process.argv[1],'cleanup')", cleanupMarker], timeout_seconds: 10 }],
143
146
  });
144
- const job = await waitForJob(runtime.managedJobManager, accepted.job_id, 15_000);
147
+ unsettledJobId = accepted.job_id;
148
+ const job = await waitForJob(runtime.managedJobManager, accepted.job_id, FULL_ACCESS_JOB_WAIT_MS);
149
+ unsettledJobId = "";
145
150
  checks.push(check("detached-managed-job", job.status === "succeeded"
146
151
  && await fileEquals(mainMarker, "main")
147
152
  && await fileEquals(cleanupMarker, "cleanup"), {
@@ -171,21 +176,34 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
171
176
  },
172
177
  };
173
178
  } finally {
174
- runtime?.stop();
179
+ if (runtime && unsettledJobId) {
180
+ try {
181
+ const status = runtime.managedJobManager.read({ job_id: unsettledJobId });
182
+ if (!isTerminalManagedJobStatus(status.status)) {
183
+ runtime.managedJobManager.cancel({ job_id: unsettledJobId });
184
+ await waitForJob(runtime.managedJobManager, unsettledJobId, FULL_ACCESS_JOB_CLEANUP_WAIT_MS);
185
+ }
186
+ } catch {
187
+ removeRoot = false;
188
+ }
189
+ }
190
+ await runtime?.stop();
175
191
  if (previousSentinel === undefined) delete process.env[sentinelKey];
176
192
  else process.env[sentinelKey] = previousSentinel;
177
- await rm(root, { recursive: true, force: true });
193
+ if (removeRoot) await rm(root, { recursive: true, force: true });
178
194
  }
179
195
  }
180
196
 
181
197
  async function waitForJob(manager, jobId, timeoutMs) {
182
198
  const deadline = createMonotonicDeadline(timeoutMs);
199
+ let lastStatus = "unknown";
183
200
  while (!deadline.expired()) {
184
201
  const value = manager.read({ job_id: jobId });
185
- if (TERMINAL_JOB_STATES.has(value.status)) return value;
202
+ lastStatus = String(value.status || "unknown");
203
+ if (isTerminalManagedJobStatus(value.status)) return value;
186
204
  await new Promise((resolvePromise) => { setTimeout(resolvePromise, 50); });
187
205
  }
188
- throw new Error("full access managed-job test timed out");
206
+ throw new Error(`full access managed-job test timed out while status=${lastStatus}`);
189
207
  }
190
208
 
191
209
  async function equivalentPath(left, right) {
@@ -0,0 +1,69 @@
1
+ import { BridgeError } from "./errors.mjs";
2
+ import { assertStructuredGitCommitStateClear } from "./git-operation-state.mjs";
3
+
4
+ const GIT_HASH = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
5
+
6
+ export async function createStructuredGitCommit({ git, message, disabledHooksPath, runInternalProcess, gitExecutable, context = {}, environment = {} }) {
7
+ const base = [
8
+ "--no-pager",
9
+ `--git-dir=${git.gitDir}`,
10
+ `--work-tree=${git.root}`,
11
+ "-c", "core.fsmonitor=false",
12
+ "-c", "color.ui=false",
13
+ "-c", `core.hooksPath=${disabledHooksPath}`,
14
+ "-c", "commit.gpgSign=false",
15
+ ];
16
+ const run = (args, stdin = null) => runInternalProcess(
17
+ gitExecutable(), [...base, ...args], 60_000, true, 64 * 1024, context, git.root, stdin, environment,
18
+ );
19
+
20
+ await assertStructuredGitCommitStateClear(git.gitDir);
21
+ const treeResult = await run(["write-tree"]);
22
+ const tree = requireHash(treeResult, "Git staged index could not be written");
23
+
24
+ const headResult = await run(["rev-parse", "--verify", "HEAD^{commit}"]);
25
+ let oldHead = "";
26
+ if (headResult.code === 0) oldHead = requireHash(headResult, "Git HEAD is invalid");
27
+
28
+ const symbolic = await run(["symbolic-ref", "-q", "HEAD"]);
29
+ if (symbolic.code !== 0 && symbolic.code !== 1) throw commitFailure("Git HEAD could not be resolved", symbolic.code);
30
+ const symbolicRef = symbolic.code === 0 ? String(symbolic.stdout || "").trim() : "";
31
+ if (symbolicRef) {
32
+ const refValidation = await run(["check-ref-format", symbolicRef]);
33
+ if (refValidation.code !== 0) throw commitFailure("Git HEAD reference is invalid", refValidation.code);
34
+ }
35
+ if (!oldHead && !symbolicRef) throw commitFailure("Git repository has no commit parent or branch reference", headResult.code);
36
+
37
+ if (oldHead) {
38
+ const headTreeResult = await run(["rev-parse", "--verify", "HEAD^{tree}"]);
39
+ const headTree = requireHash(headTreeResult, "Git HEAD tree is invalid");
40
+ if (headTree === tree) throw commitFailure("Git staged index has no changes to commit", 1, "git_commit_empty");
41
+ }
42
+
43
+ const commitArgs = ["commit-tree", tree];
44
+ if (oldHead) commitArgs.push("-p", oldHead);
45
+ commitArgs.push("-F", "-");
46
+ const commitResult = await run(commitArgs, message);
47
+ const commit = requireHash(commitResult, "Git commit object could not be created");
48
+
49
+ const targetRef = symbolicRef || "HEAD";
50
+ const expected = oldHead || "0".repeat(tree.length);
51
+ const updateResult = await run(["update-ref", "-m", "machine-bridge-mcp structured commit", targetRef, commit, expected]);
52
+ if (updateResult.code !== 0) {
53
+ throw commitFailure("Git history changed concurrently or the reference update failed", updateResult.code, "git_commit_conflict");
54
+ }
55
+ return Object.freeze({ committed: true });
56
+ }
57
+
58
+ function requireHash(result, message) {
59
+ if (result.code !== 0) throw commitFailure(message, result.code);
60
+ const value = String(result.stdout || "").trim();
61
+ if (!GIT_HASH.test(value)) throw commitFailure(message, result.code);
62
+ return value;
63
+ }
64
+
65
+ function commitFailure(message, code, reason = "git_commit_failed") {
66
+ return new BridgeError("execution_failed", message, {
67
+ details: { reason, code: Number.isInteger(code) ? code : 1 },
68
+ });
69
+ }
@@ -0,0 +1,55 @@
1
+ import { join } from "node:path";
2
+ import { BridgeError } from "./errors.mjs";
3
+ import { pathEntryIfExists } from "./path-inspection.mjs";
4
+
5
+ const MAX_GIT_CONFIG_BYTES = 256 * 1024;
6
+
7
+ export async function assertStructuredGitConfigSafe({
8
+ git,
9
+ inspectWorkingTree = false,
10
+ resolveExistingPath,
11
+ runInternalProcess,
12
+ gitExecutable,
13
+ context = {},
14
+ environment = {},
15
+ }) {
16
+ const files = new Set([join(git.commonDir, "config"), join(git.gitDir, "config.worktree")]);
17
+ for (const file of files) {
18
+ const entry = await pathEntryIfExists(file);
19
+ if (!entry) continue;
20
+ if (entry.isSymbolicLink() || !entry.isFile() || Number(entry.nlink) !== 1 || Number(entry.size) > MAX_GIT_CONFIG_BYTES) {
21
+ throw unsafeConfigError();
22
+ }
23
+ const canonical = await resolveExistingPath(file, context);
24
+ const result = await runInternalProcess(
25
+ gitExecutable(),
26
+ ["--no-pager", "config", "--file", canonical, "--no-includes", "--null", "--name-only", "--list"],
27
+ 10_000,
28
+ true,
29
+ MAX_GIT_CONFIG_BYTES,
30
+ context,
31
+ git.root,
32
+ null,
33
+ environment,
34
+ );
35
+ if (result.code !== 0) throw unsafeConfigError();
36
+ const keys = String(result.stdout || "").split("\0").map((value) => value.trim().toLowerCase()).filter(Boolean);
37
+ if (keys.some((key) => key.startsWith("include.") || key.startsWith("includeif.") || key === "core.attributesfile" || key === "diff.orderfile"
38
+ || key === "extensions.partialclone" || /^remote\..+\.(?:promisor|partialclonefilter)$/.test(key))) {
39
+ throw unsafeConfigError();
40
+ }
41
+ if (inspectWorkingTree && keys.some((key) => key === "core.excludesfile" || executableFilterKey(key))) {
42
+ throw unsafeConfigError();
43
+ }
44
+ }
45
+ }
46
+
47
+ function executableFilterKey(key) {
48
+ return /^filter\..+\.(?:clean|smudge|process)$/.test(key);
49
+ }
50
+
51
+ function unsafeConfigError() {
52
+ return new BridgeError("authorization_denied", "repository Git configuration is incompatible with the structured Git safety boundary", {
53
+ details: { reason: "unsafe_repository_git_config" },
54
+ });
55
+ }
@@ -0,0 +1,41 @@
1
+ // @ts-check
2
+
3
+ import { BridgeError } from "./errors.mjs";
4
+
5
+ const FULL_HASH = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
6
+ const SHORT_HASH = /^[0-9a-f]{4,64}$/;
7
+
8
+ /** @param {unknown} stdout @param {boolean} [includeAuthorEmail] */
9
+ export function parseStructuredGitLog(stdout, includeAuthorEmail = false) {
10
+ /** @type {Array<{hash: string, short: string, authored_at: string, author_name: string, subject: string, author_email?: string}>} */
11
+ const commits = [];
12
+ const records = String(stdout || "").split("\x1e");
13
+ for (let raw of records) {
14
+ if (!raw) continue;
15
+ if (raw === "\n" || raw === "\r\n") continue;
16
+ if (raw.startsWith("\r\n")) raw = raw.slice(2);
17
+ else if (raw.startsWith("\n")) raw = raw.slice(1);
18
+ if (!raw) continue;
19
+ const fields = raw.split("\x1f");
20
+ if (fields.length !== 6) throw parseError();
21
+ const [hash, short, authored_at, author_name, author_email, subject] = fields;
22
+ if (!FULL_HASH.test(hash) || !SHORT_HASH.test(short) || !validIsoDate(authored_at)) throw parseError();
23
+ /** @type {{hash: string, short: string, authored_at: string, author_name: string, subject: string, author_email?: string}} */
24
+ const commit = { hash, short, authored_at, author_name, subject };
25
+ if (includeAuthorEmail) commit.author_email = author_email;
26
+ commits.push(commit);
27
+ }
28
+ return commits;
29
+ }
30
+
31
+ /** @param {string} value */
32
+ function validIsoDate(value) {
33
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2}|Z)$/.test(value)
34
+ && Number.isFinite(Date.parse(value));
35
+ }
36
+
37
+ function parseError() {
38
+ return new BridgeError("execution_failed", "Git log output failed structured framing validation", {
39
+ details: { reason: "git_log_parse_failed" },
40
+ });
41
+ }
@@ -0,0 +1,77 @@
1
+ import { stat } from "node:fs/promises";
2
+ import { isAbsolute, join, resolve } from "node:path";
3
+ import { BridgeError } from "./errors.mjs";
4
+ import { pathEntryIfExists } from "./path-inspection.mjs";
5
+ import { readBoundedRegularFileSync } from "./secure-file.mjs";
6
+
7
+ const MAX_ALTERNATES_BYTES = 64 * 1024;
8
+ const MAX_ALTERNATE_STORES = 64;
9
+
10
+ export async function resolveGitMetadataBoundary({ gitDir, commonDir, resolveExistingPath, context = {} }) {
11
+ const objectDir = await requireDirectory(join(commonDir, "objects"), resolveExistingPath, context, "Git object directory");
12
+ const alternateObjectDirs = await resolveAlternateObjectStores(objectDir, resolveExistingPath, context);
13
+ const metadataPaths = [];
14
+ for (const candidate of [
15
+ join(gitDir, "HEAD"),
16
+ join(gitDir, "index"),
17
+ join(gitDir, "refs"),
18
+ join(gitDir, "logs"),
19
+ join(gitDir, "reftable"),
20
+ join(commonDir, "packed-refs"),
21
+ join(commonDir, "shallow"),
22
+ join(commonDir, "refs"),
23
+ join(commonDir, "logs"),
24
+ join(commonDir, "reftable"),
25
+ join(commonDir, "info"),
26
+ join(objectDir, "pack"),
27
+ join(objectDir, "info"),
28
+ ]) {
29
+ const entry = await pathEntryIfExists(candidate);
30
+ if (!entry) continue;
31
+ if (entry.isSymbolicLink()) throw boundaryError("Git metadata path must not be a symbolic link");
32
+ metadataPaths.push(await resolveExistingPath(candidate, context));
33
+ }
34
+ return Object.freeze({ objectDir, alternateObjectDirs: Object.freeze(alternateObjectDirs), metadataPaths: Object.freeze(metadataPaths) });
35
+ }
36
+
37
+ async function resolveAlternateObjectStores(primaryObjectDir, resolveExistingPath, context) {
38
+ const queue = [primaryObjectDir];
39
+ const seen = new Set([primaryObjectDir]);
40
+ const alternates = [];
41
+ while (queue.length) {
42
+ const objectDir = queue.shift();
43
+ const marker = join(objectDir, "info", "alternates");
44
+ const entry = await pathEntryIfExists(marker);
45
+ if (!entry) continue;
46
+ if (entry.isSymbolicLink() || !entry.isFile() || Number(entry.nlink) !== 1) throw boundaryError("Git alternates metadata must be a single-link regular file");
47
+ const bytes = readBoundedRegularFileSync(marker, MAX_ALTERNATES_BYTES, "Git alternates metadata", {
48
+ verifyPathIdentity: true,
49
+ rejectMultipleLinks: true,
50
+ });
51
+ const text = bytes.toString("utf8");
52
+ if (text.includes("\0") || text.includes("\r")) throw boundaryError("Git alternates metadata is invalid");
53
+ for (const raw of text.split("\n")) {
54
+ if (!raw) continue;
55
+ if (raw.length > 4096 || /[\u0000-\u001f\u007f]/.test(raw)) throw boundaryError("Git alternate object path is invalid");
56
+ const candidate = isAbsolute(raw) ? raw : resolve(objectDir, raw);
57
+ const canonical = await requireDirectory(candidate, resolveExistingPath, context, "Git alternate object directory");
58
+ if (seen.has(canonical)) continue;
59
+ if (seen.size >= MAX_ALTERNATE_STORES) throw new BridgeError("limit_exceeded", `Git alternate object store count exceeds ${MAX_ALTERNATE_STORES}`);
60
+ seen.add(canonical);
61
+ alternates.push(canonical);
62
+ queue.push(canonical);
63
+ }
64
+ }
65
+ return alternates;
66
+ }
67
+
68
+ async function requireDirectory(path, resolveExistingPath, context, label) {
69
+ const canonical = await resolveExistingPath(path, context);
70
+ const info = await stat(canonical);
71
+ if (!info.isDirectory()) throw boundaryError(`${label} is unavailable`);
72
+ return canonical;
73
+ }
74
+
75
+ function boundaryError(message) {
76
+ return new BridgeError("path_boundary", message, { details: { reason: "git_metadata_boundary" } });
77
+ }
@@ -0,0 +1,59 @@
1
+ // @ts-check
2
+ import { lstat, opendir } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { BridgeError } from "./errors.mjs";
5
+ export const MAX_GIT_METADATA_ENTRIES = 1_000_000;
6
+ export const MAX_GIT_METADATA_DEPTH = 64;
7
+
8
+ /**
9
+ * @param {string[]} roots
10
+ * @param {{opendir?: typeof opendir, lstat?: typeof lstat, maximumEntries?: number, signal?: AbortSignal}} [options]
11
+ */
12
+ export async function assertGitMetadataTreesSafe(roots, options = {}) {
13
+ const openDirectory = options.opendir || opendir;
14
+ const inspect = options.lstat || lstat;
15
+ const maximumEntries = Number(options.maximumEntries ?? MAX_GIT_METADATA_ENTRIES);
16
+ if (!Number.isSafeInteger(maximumEntries) || maximumEntries < 1) throw new TypeError("Git metadata entry limit must be a positive safe integer");
17
+ /** @type {Array<{path: string, depth: number}>} */
18
+ const queue = [];
19
+ for (const path of new Set(roots.map(String))) {
20
+ abortIfNeeded(options.signal);
21
+ const info = await inspect(path);
22
+ if (info.isSymbolicLink()) throw boundaryError();
23
+ if (info.isFile()) continue;
24
+ if (!info.isDirectory()) throw boundaryError();
25
+ queue.push({ path, depth: 0 });
26
+ }
27
+ let visited = 0;
28
+ for (let index = 0; index < queue.length; index += 1) {
29
+ abortIfNeeded(options.signal);
30
+ const current = queue[index];
31
+ const directory = await openDirectory(current.path);
32
+ try {
33
+ for await (const entry of directory) {
34
+ abortIfNeeded(options.signal);
35
+ visited += 1;
36
+ if (visited > maximumEntries) throw new BridgeError("limit_exceeded", `Git metadata entry count exceeds ${maximumEntries}`);
37
+ const target = join(current.path, entry.name);
38
+ if (entry.isSymbolicLink()) throw boundaryError();
39
+ if (entry.isDirectory()) {
40
+ if (current.depth >= MAX_GIT_METADATA_DEPTH) throw new BridgeError("limit_exceeded", `Git metadata depth exceeds ${MAX_GIT_METADATA_DEPTH}`);
41
+ queue.push({ path: target, depth: current.depth + 1 });
42
+ continue;
43
+ }
44
+ if (entry.isFile()) continue;
45
+ const info = await inspect(target);
46
+ if (info.isSymbolicLink() || !info.isFile()) throw boundaryError();
47
+ }
48
+ } finally { await directory.close().catch(() => { /* Directory-handle close is best-effort after bounded traversal ends. */ }); }
49
+ }
50
+ return visited;
51
+ }
52
+
53
+ /** @param {AbortSignal | undefined} signal */
54
+ function abortIfNeeded(signal) { if (signal?.aborted) throw signal.reason instanceof Error ? signal.reason : new BridgeError("cancelled", "Git metadata inspection cancelled"); }
55
+ function boundaryError() {
56
+ return new BridgeError("path_boundary", "Git metadata tree contains a symbolic link or special file", {
57
+ details: { reason: "git_metadata_boundary" },
58
+ });
59
+ }
@@ -0,0 +1,19 @@
1
+ // @ts-check
2
+
3
+ import { join } from "node:path";
4
+ import { BridgeError } from "./errors.mjs";
5
+ import { pathEntryIfExists } from "./path-inspection.mjs";
6
+
7
+ const IN_PROGRESS_HISTORY_MARKERS = Object.freeze([
8
+ "MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "REBASE_HEAD", "BISECT_START", "rebase-apply", "rebase-merge", "sequencer",
9
+ ]);
10
+
11
+ /** @param {string} gitDir */
12
+ export async function assertStructuredGitCommitStateClear(gitDir) {
13
+ for (const marker of IN_PROGRESS_HISTORY_MARKERS) {
14
+ if (!await pathEntryIfExists(join(gitDir, marker))) continue;
15
+ throw new BridgeError("execution_failed", "Git commit is unavailable while a merge, rebase, cherry-pick, revert, or bisect operation is in progress", {
16
+ details: { reason: "git_commit_repository_state", code: 1 },
17
+ });
18
+ }
19
+ }