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.
- package/CHANGELOG.md +1142 -0
- package/CONTRIBUTING.md +23 -25
- package/GOVERNANCE.md +2 -2
- package/README.md +58 -20
- package/SECURITY.md +161 -120
- package/SUPPORT.md +1 -1
- package/browser-extension/broker-auth.js +83 -0
- package/browser-extension/broker-liveness.js +35 -0
- package/browser-extension/browser-error-boundary.js +96 -0
- package/browser-extension/browser-operations.js +1456 -101
- package/browser-extension/devtools-input.js +195 -34
- package/browser-extension/devtools-observation.js +429 -0
- package/browser-extension/devtools-session.js +48 -0
- package/browser-extension/manifest.json +4 -3
- package/browser-extension/page-automation.js +578 -118
- package/browser-extension/pairing-bootstrap.js +42 -0
- package/browser-extension/pairing.js +28 -12
- package/browser-extension/service-worker.js +126 -106
- package/docs/AGENT_CONTEXT.md +13 -10
- package/docs/ARCHITECTURE.md +159 -67
- package/docs/AUDIT.md +1302 -4
- package/docs/CLIENTS.md +19 -6
- package/docs/COMPUTER_USE.md +297 -0
- package/docs/ENGINEERING.md +72 -13
- package/docs/GETTING_STARTED.md +18 -20
- package/docs/LOCAL_AUTHORIZATION.md +115 -72
- package/docs/LOCAL_AUTOMATION.md +27 -21
- package/docs/LOGGING.md +40 -19
- package/docs/MANAGED_JOBS.md +37 -34
- package/docs/MULTI_ACCOUNT.md +98 -37
- package/docs/OPERATIONS.md +145 -51
- package/docs/OVERVIEW.md +14 -11
- package/docs/POLICY_REFERENCE.md +71 -3
- package/docs/PRIVACY.md +41 -2
- package/docs/PROJECT_STANDARDS.md +29 -6
- package/docs/RELEASING.md +238 -64
- package/docs/TESTING.md +150 -39
- package/docs/THREAT_MODEL.md +164 -67
- package/docs/TOOL_REFERENCE.md +653 -60
- package/docs/UPGRADING.md +78 -18
- package/native/macos/MachineBridgeBackgroundInput.swift +1051 -0
- package/native/macos/MachineBridgeBackgroundInputSmokeFixture.swift +295 -0
- package/native/macos/MachineBridgeTrustBroker.swift +233 -0
- package/package.json +94 -16
- package/scripts/accepted-candidate-tarball.mjs +77 -0
- package/scripts/candidate-runtime-store.mjs +93 -0
- package/scripts/check-plan.mjs +73 -3
- package/scripts/check-runner.mjs +129 -17
- package/scripts/commit-message-check.mjs +5 -2
- package/scripts/consumer-package-security.mjs +293 -0
- package/scripts/coverage-check.mjs +251 -15
- package/scripts/coverage-generation.mjs +41 -0
- package/scripts/coverage-range-merge.mjs +41 -0
- package/scripts/foreground-daemon-recovery.mjs +88 -0
- package/scripts/generate-policy-reference.mjs +28 -5
- package/scripts/generate-tool-reference.mjs +1 -1
- package/scripts/generate-worker-types.mjs +21 -11
- package/scripts/github-backlog.mjs +11 -10
- package/scripts/github-push.mjs +62 -16
- package/scripts/github-release-asset.mjs +58 -0
- package/scripts/github-release.mjs +241 -132
- package/scripts/global-package-installation.mjs +68 -0
- package/scripts/hardened-npm-session.mjs +42 -0
- package/scripts/install-published-prerelease.mjs +198 -0
- package/scripts/local-release-acceptance.mjs +103 -45
- package/scripts/macos-background-input-smoke.mjs +515 -0
- package/scripts/network-retry.mjs +8 -1
- package/scripts/npm-global-prefix.mjs +27 -0
- package/scripts/npm-publication-policy.mjs +17 -0
- package/scripts/official-mcp-conformance.mjs +267 -0
- package/scripts/persistent-activation-process.mjs +101 -0
- package/scripts/prepare-pinned-npm.mjs +12 -69
- package/scripts/prerelease-activation.mjs +160 -0
- package/scripts/privacy-check.mjs +14 -6
- package/scripts/promotion-digest.mjs +128 -0
- package/scripts/publish-npm.mjs +260 -0
- package/scripts/published-release.mjs +132 -0
- package/scripts/release-acceptance.mjs +62 -54
- package/scripts/release-browser-extension-store.mjs +172 -0
- package/scripts/release-candidate-manifest.mjs +52 -0
- package/scripts/release-channel.mjs +106 -0
- package/scripts/release-diagnostic.mjs +41 -0
- package/scripts/release-impact-check.mjs +12 -22
- package/scripts/release-oauth-canary-core.mjs +325 -0
- package/scripts/release-oauth-canary-evidence.mjs +100 -0
- package/scripts/release-oauth-canary.mjs +127 -0
- package/scripts/release-publication-guard.mjs +67 -0
- package/scripts/release-soak.mjs +284 -0
- package/scripts/release-state.mjs +1 -1
- package/scripts/run-checks.mjs +44 -7
- package/scripts/run-worker-dry-run.mjs +21 -0
- package/scripts/sarif-security-gate.mjs +2 -1
- package/scripts/sbom-check.mjs +106 -0
- package/scripts/start-release-candidate.mjs +267 -44
- package/scripts/syntax-check.mjs +36 -14
- package/scripts/verification-generation-guard.mjs +15 -0
- package/scripts/verification-idle-sleep-guard.mjs +37 -0
- package/scripts/verification-state.mjs +108 -0
- package/scripts/wrangler-command-lifecycle.mjs +115 -0
- package/src/local/account-access.mjs +13 -1
- package/src/local/account-admin-response.mjs +92 -0
- package/src/local/account-admin.mjs +52 -19
- package/src/local/agent-context-projection.mjs +26 -7
- package/src/local/agent-context.mjs +88 -49
- package/src/local/agent-skill-discovery.mjs +16 -5
- package/src/local/app-automation-macos-jxa.mjs +350 -0
- package/src/local/app-automation.mjs +873 -163
- package/src/local/application-capability-projection.mjs +24 -0
- package/src/local/authority-context.mjs +106 -0
- package/src/local/autostart-log-maintenance.mjs +36 -0
- package/src/local/browser-bridge-http.mjs +5 -3
- package/src/local/browser-bridge.mjs +109 -31
- package/src/local/browser-broker-auth-http.mjs +67 -0
- package/src/local/browser-broker-auth.mjs +98 -0
- package/src/local/browser-broker-routes.mjs +63 -20
- package/src/local/browser-broker-server.mjs +17 -7
- package/src/local/browser-command.mjs +79 -30
- package/src/local/browser-computer-observation-service.mjs +266 -0
- package/src/local/browser-extension-identity.mjs +50 -0
- package/src/local/browser-extension-path.mjs +22 -0
- package/src/local/browser-extension-protocol.mjs +50 -11
- package/src/local/browser-operation-service.mjs +147 -117
- package/src/local/browser-pairing-grant.mjs +109 -0
- package/src/local/browser-pairing-http.mjs +36 -0
- package/src/local/browser-pairing-launch.mjs +58 -0
- package/src/local/browser-pairing-store.mjs +85 -48
- package/src/local/browser-request-registry.mjs +40 -14
- package/src/local/browser-request-settlement.mjs +78 -0
- package/src/local/browser-resource-input.mjs +74 -0
- package/src/local/browser-trusted-input-health.mjs +63 -0
- package/src/local/call-authority.mjs +21 -0
- package/src/local/call-capacity.mjs +45 -0
- package/src/local/call-registry-drain.mjs +54 -0
- package/src/local/call-registry.mjs +30 -15
- package/src/local/capability-observer.mjs +5 -0
- package/src/local/capability-ranking.mjs +3 -2
- package/src/local/child-process-settlement.mjs +132 -0
- package/src/local/cli-account-admin.mjs +40 -6
- package/src/local/cli-activate.mjs +162 -0
- package/src/local/cli-local-admin.mjs +20 -45
- package/src/local/cli-options.mjs +12 -13
- package/src/local/cli-ready-output.mjs +52 -0
- package/src/local/cli-service.mjs +91 -11
- package/src/local/cli.mjs +232 -150
- package/src/local/computer-use-application-observation.mjs +224 -0
- package/src/local/computer-use-arguments.mjs +270 -0
- package/src/local/computer-use-deadline.mjs +50 -0
- package/src/local/computer-use-dispatch-settlement.mjs +40 -0
- package/src/local/computer-use-expectation.mjs +171 -0
- package/src/local/computer-use-observation.mjs +769 -0
- package/src/local/computer-use-recovery.mjs +82 -0
- package/src/local/computer-use-result-budget.mjs +145 -0
- package/src/local/computer-use-snapshot-store.mjs +67 -0
- package/src/local/computer-use.mjs +2255 -0
- package/src/local/daemon-http-relay-auth.mjs +26 -0
- package/src/local/daemon-http-relay-connection.mjs +298 -0
- package/src/local/daemon-http-relay-request.mjs +64 -0
- package/src/local/daemon-http-relay-sequence.mjs +49 -0
- package/src/local/daemon-process.mjs +40 -7
- package/src/local/delegated-process-sandbox.mjs +148 -0
- package/src/local/device-identity.mjs +156 -49
- package/src/local/device-root-provider.mjs +81 -0
- package/src/local/directory-metadata.mjs +47 -0
- package/src/local/doctor-reporting.mjs +23 -0
- package/src/local/durable-process-spec.mjs +80 -0
- package/src/local/errors.mjs +25 -15
- package/src/local/exclusive-file.mjs +74 -26
- package/src/local/execution-limits.mjs +9 -0
- package/src/local/execution-routing.mjs +276 -0
- package/src/local/execution-surface.mjs +23 -0
- package/src/local/file-mutation-coordinator.mjs +47 -0
- package/src/local/file-snapshot-preservation.mjs +24 -0
- package/src/local/filesystem-identity.mjs +54 -0
- package/src/local/fixed-process-environment.mjs +21 -0
- package/src/local/full-access-test.mjs +31 -13
- package/src/local/git-commit.mjs +69 -0
- package/src/local/git-config-safety.mjs +55 -0
- package/src/local/git-log-parser.mjs +41 -0
- package/src/local/git-metadata-boundary.mjs +77 -0
- package/src/local/git-metadata-tree-safety.mjs +59 -0
- package/src/local/git-operation-state.mjs +19 -0
- package/src/local/git-service.mjs +165 -30
- package/src/local/hardened-npm-download-timeout.mjs +46 -0
- package/src/local/hardened-npm-download.mjs +109 -0
- package/src/local/hardened-npm-extract.mjs +20 -0
- package/src/local/hardened-npm-verification.mjs +104 -0
- package/src/local/hardened-npm.mjs +199 -0
- package/src/local/job-runner.mjs +244 -89
- package/src/local/lifecycle.mjs +6 -0
- package/src/local/log.mjs +19 -45
- package/src/local/macos-background-input.mjs +398 -0
- package/src/local/macos-idle-sleep-assertion.mjs +65 -0
- package/src/local/macos-trust-broker.mjs +404 -0
- package/src/local/managed-job-cancellation.mjs +42 -0
- package/src/local/managed-job-capacity.mjs +16 -0
- package/src/local/managed-job-directory-generation.mjs +60 -0
- package/src/local/managed-job-directory.mjs +75 -0
- package/src/local/managed-job-durable-process.mjs +49 -0
- package/src/local/managed-job-lock.mjs +46 -14
- package/src/local/managed-job-plan.mjs +38 -14
- package/src/local/managed-job-projection.mjs +23 -15
- package/src/local/managed-job-retention.mjs +128 -0
- package/src/local/managed-job-runner-claim.mjs +70 -0
- package/src/local/managed-job-runner.mjs +35 -10
- package/src/local/managed-job-storage.mjs +22 -6
- package/src/local/managed-job-terminal-maintenance.mjs +40 -0
- package/src/local/managed-job-terminal.mjs +150 -0
- package/src/local/managed-jobs.mjs +424 -287
- package/src/local/npm-cli.mjs +42 -0
- package/src/local/npm-environment.mjs +37 -0
- package/src/local/operation-authorization.mjs +68 -324
- package/src/local/operation-risk.mjs +51 -4
- package/src/local/owner-state-lock.mjs +118 -0
- package/src/local/package-identity.mjs +9 -0
- package/src/local/patch.mjs +20 -18
- package/src/local/path-inspection.mjs +23 -0
- package/src/local/policy.mjs +19 -0
- package/src/local/private-toolchain-integrity.mjs +54 -0
- package/src/local/process-execution.mjs +87 -75
- package/src/local/process-foreground-timeout.mjs +50 -0
- package/src/local/process-identity.mjs +41 -7
- package/src/local/process-nonreplayable-settlement.mjs +50 -0
- package/src/local/process-result-projection.mjs +22 -0
- package/src/local/process-session-events.mjs +47 -0
- package/src/local/process-session-remote-activity.mjs +10 -0
- package/src/local/process-session-termination.mjs +73 -0
- package/src/local/process-sessions.mjs +144 -112
- package/src/local/process-tracker.mjs +140 -13
- package/src/local/process-tree-ownership-types.d.ts +37 -0
- package/src/local/process-tree-ownership.mjs +57 -0
- package/src/local/process-tree-signal.mjs +34 -0
- package/src/local/process-tree-snapshot.mjs +77 -0
- package/src/local/process-tree-supervisor.mjs +69 -0
- package/src/local/process-tree.mjs +11 -53
- package/src/local/project-metadata.mjs +19 -6
- package/src/local/relay-call-recovery.mjs +58 -38
- package/src/local/relay-connection-classification.mjs +131 -0
- package/src/local/relay-connection-support.mjs +91 -0
- package/src/local/relay-connection.mjs +179 -272
- package/src/local/relay-diagnostics.mjs +91 -0
- package/src/local/relay-heartbeat.mjs +119 -0
- package/src/local/relay-liveness.mjs +109 -0
- package/src/local/relay-peer-diagnostics.mjs +29 -0
- package/src/local/release-runtime-lock.mjs +18 -0
- package/src/local/remote-activity-idle-sleep-guard.mjs +77 -0
- package/src/local/remote-configuration.mjs +48 -0
- package/src/local/resilient-relay-connection.mjs +178 -0
- package/src/local/resource-admission-diagnostics.mjs +52 -0
- package/src/local/resource-admission-policy.mjs +115 -0
- package/src/local/resource-admission.mjs +339 -0
- package/src/local/resource-build-root.mjs +83 -0
- package/src/local/resource-cargo-concurrency.mjs +38 -0
- package/src/local/resource-cmake-concurrency.mjs +42 -0
- package/src/local/resource-command-concurrency.mjs +37 -0
- package/src/local/resource-command-profile.mjs +176 -0
- package/src/local/resource-coordinator-accounting.mjs +21 -0
- package/src/local/resource-cpu-window.mjs +20 -0
- package/src/local/resource-disk-headroom.mjs +13 -0
- package/src/local/resource-elastic-memory.mjs +19 -0
- package/src/local/resource-elastic-request.mjs +36 -0
- package/src/local/resource-foreground-wait.mjs +29 -0
- package/src/local/resource-go-concurrency.mjs +43 -0
- package/src/local/resource-gradle-concurrency.mjs +57 -0
- package/src/local/resource-host-cache.mjs +39 -0
- package/src/local/resource-host-darwin.mjs +54 -0
- package/src/local/resource-host-linux.mjs +35 -0
- package/src/local/resource-host-sample-file.mjs +23 -0
- package/src/local/resource-host-snapshot.mjs +99 -0
- package/src/local/resource-lease-accounting.mjs +119 -0
- package/src/local/resource-light-command.mjs +6 -0
- package/src/local/resource-make-concurrency.mjs +42 -0
- package/src/local/resource-maven-concurrency.mjs +38 -0
- package/src/local/resource-ninja-command-concurrency.mjs +20 -0
- package/src/local/resource-ninja-concurrency.mjs +28 -0
- package/src/local/resource-operations.mjs +26 -10
- package/src/local/resource-pressure.mjs +67 -0
- package/src/local/resource-probe-command.mjs +35 -0
- package/src/local/resource-process-admission.mjs +59 -0
- package/src/local/resource-process-ancestry-cache.mjs +43 -0
- package/src/local/resource-process-ancestry.mjs +32 -0
- package/src/local/resource-process-priority.mjs +15 -0
- package/src/local/resource-project-key.mjs +53 -0
- package/src/local/resource-pytest-concurrency.mjs +33 -0
- package/src/local/resource-release-control-classification.mjs +14 -0
- package/src/local/resource-release-control-executable.mjs +70 -0
- package/src/local/resource-release-control-workspace.mjs +41 -0
- package/src/local/resource-request-contract.mjs +30 -0
- package/src/local/resource-script-classification.mjs +44 -0
- package/src/local/resource-shell-analysis.mjs +58 -0
- package/src/local/resource-staging-recovery.mjs +97 -0
- package/src/local/resource-swift-concurrency.mjs +31 -0
- package/src/local/resource-transaction-lock.mjs +189 -0
- package/src/local/resource-wait.mjs +48 -0
- package/src/local/resource-waiters.mjs +139 -0
- package/src/local/resource-xcode-command.mjs +7 -0
- package/src/local/resource-xcode-concurrency.mjs +34 -0
- package/src/local/resource-xcode-non-build.mjs +19 -0
- package/src/local/runtime-activation.mjs +700 -0
- package/src/local/runtime-activity-projection.mjs +48 -0
- package/src/local/runtime-capabilities.mjs +48 -15
- package/src/local/runtime-diagnostic-state.mjs +14 -0
- package/src/local/runtime-diagnostics.mjs +40 -13
- package/src/local/runtime-info-projection.mjs +60 -0
- package/src/local/runtime-info-relay-projection.mjs +21 -0
- package/src/local/runtime-paths.mjs +2 -1
- package/src/local/runtime-process-routing.mjs +33 -0
- package/src/local/runtime-relay-connection-options.mjs +44 -0
- package/src/local/runtime-relay-control.mjs +98 -0
- package/src/local/runtime-relay.mjs +26 -50
- package/src/local/runtime-reporting.mjs +37 -21
- package/src/local/runtime-resource-service.mjs +77 -0
- package/src/local/runtime-tool-handlers.mjs +13 -10
- package/src/local/runtime.mjs +244 -208
- package/src/local/secure-file.mjs +73 -20
- package/src/local/security-audit-dispatch.mjs +35 -0
- package/src/local/security-audit-log.mjs +215 -0
- package/src/local/security-audit-state.mjs +139 -0
- package/src/local/security-audit-storage.mjs +135 -0
- package/src/local/security-audit-warning.mjs +31 -0
- package/src/local/security-audit-worker.mjs +87 -0
- package/src/local/service-convergence.mjs +42 -1
- package/src/local/service-definition.mjs +24 -0
- package/src/local/service-environment.mjs +18 -10
- package/src/local/service-owner.mjs +146 -0
- package/src/local/service-ownership.mjs +17 -0
- package/src/local/service-restart-handoff.mjs +64 -0
- package/src/local/service-restart-scheduler.mjs +49 -0
- package/src/local/service-runtime-convergence.mjs +52 -0
- package/src/local/service-runtime.mjs +105 -0
- package/src/local/service-status.mjs +60 -0
- package/src/local/service.mjs +385 -92
- package/src/local/shell.mjs +39 -19
- package/src/local/ssh-key.mjs +158 -42
- package/src/local/state-inventory.mjs +50 -24
- package/src/local/state-owner-lock-inventory.mjs +23 -0
- package/src/local/state-root-owned-namespaces.mjs +83 -0
- package/src/local/state-root-retirement.mjs +89 -0
- package/src/local/state.mjs +386 -96
- package/src/local/stdio.mjs +96 -66
- package/src/local/support-state-projection.mjs +30 -0
- package/src/local/system-network-route.mjs +76 -0
- package/src/local/systemd-removal.mjs +20 -0
- package/src/local/tool-executor.mjs +85 -20
- package/src/local/tool-result-boundary.mjs +51 -0
- package/src/local/toolchain-operation-lock.mjs +41 -0
- package/src/local/tools.mjs +7 -5
- package/src/local/trusted-executable.mjs +53 -0
- package/src/local/trusted-git-executable.mjs +34 -0
- package/src/local/trusted-github-cli.mjs +24 -0
- package/src/local/windows-service-convergence.mjs +49 -0
- package/src/local/windows-service.mjs +52 -45
- package/src/local/worker-deployment-fingerprint.mjs +102 -0
- package/src/local/worker-deployment.mjs +48 -60
- package/src/local/worker-health.mjs +12 -4
- package/src/local/worker-secret-file.mjs +37 -17
- package/src/local/workspace-file-service.mjs +156 -239
- package/src/local/workspace-file-transaction.mjs +189 -0
- package/src/local/workspace-search.mjs +55 -0
- package/src/local/wrangler-toolchain/package-lock.json +1505 -0
- package/src/local/wrangler-toolchain/package.json +23 -0
- package/src/local/wrangler-toolchain-verification.mjs +135 -0
- package/src/local/wrangler-toolchain.mjs +188 -0
- package/src/shared/access-contract.json +7 -1
- package/src/shared/activation-recovery.mjs +49 -0
- package/src/shared/admin-auth.d.mts +2 -1
- package/src/shared/admin-auth.mjs +3 -3
- package/src/shared/authority-revocation.d.mts +9 -0
- package/src/shared/authority-revocation.mjs +28 -0
- package/src/shared/daemon-auth.d.mts +11 -0
- package/src/shared/daemon-auth.mjs +17 -0
- package/src/shared/device-session-auth.d.mts +20 -0
- package/src/shared/device-session-auth.mjs +51 -0
- package/src/shared/foreground-timeout.d.mts +15 -0
- package/src/shared/foreground-timeout.mjs +70 -0
- package/src/shared/log-redaction.d.mts +9 -0
- package/src/shared/log-redaction.mjs +51 -0
- package/src/shared/mcp-protocol.d.mts +27 -0
- package/src/shared/mcp-protocol.mjs +275 -0
- package/src/shared/project-overview-projection.d.mts +4 -0
- package/src/shared/project-overview-projection.mjs +96 -0
- package/src/shared/relay-contract.json +28 -0
- package/src/shared/result-projection.d.mts +2 -1
- package/src/shared/result-projection.mjs +13 -2
- package/src/shared/server-metadata.json +15 -9
- package/src/shared/tool-argument-validation.d.mts +17 -0
- package/src/shared/tool-argument-validation.mjs +325 -0
- package/src/shared/tool-call-capacity.d.mts +41 -0
- package/src/shared/tool-call-capacity.mjs +59 -0
- package/src/shared/tool-catalog.json +626 -57
- package/src/worker/access.ts +12 -0
- package/src/worker/account-admin.ts +55 -33
- package/src/worker/authority-revocations.ts +174 -0
- package/src/worker/authority.ts +52 -6
- package/src/worker/daemon-auth.ts +49 -68
- package/src/worker/daemon-channel.ts +36 -0
- package/src/worker/daemon-http-auth.ts +63 -0
- package/src/worker/daemon-http-channel.ts +120 -0
- package/src/worker/daemon-http-controller.ts +163 -0
- package/src/worker/daemon-http-protocol.ts +62 -0
- package/src/worker/daemon-http-registry.ts +82 -0
- package/src/worker/daemon-last-observation.ts +30 -0
- package/src/worker/daemon-liveness.ts +8 -6
- package/src/worker/daemon-ready-dispatch.ts +17 -0
- package/src/worker/daemon-ready-messages.ts +73 -0
- package/src/worker/daemon-ready-waiters.ts +79 -0
- package/src/worker/daemon-recovery-budget.ts +13 -0
- package/src/worker/daemon-registry.ts +58 -0
- package/src/worker/daemon-relay-diagnostics.ts +127 -0
- package/src/worker/daemon-socket-attachment.ts +55 -0
- package/src/worker/daemon-sockets.ts +72 -62
- package/src/worker/daemon-status.ts +38 -0
- package/src/worker/device-session-verifier.ts +129 -0
- package/src/worker/dpop.ts +157 -0
- package/src/worker/durable-process-timeout.ts +29 -0
- package/src/worker/errors.ts +57 -0
- package/src/worker/http.ts +112 -26
- package/src/worker/index.ts +446 -440
- package/src/worker/mcp-access.ts +57 -0
- package/src/worker/mcp-controller.ts +188 -0
- package/src/worker/mcp-http-accept.ts +11 -0
- package/src/worker/mcp-http-contract.ts +322 -0
- package/src/worker/mcp-initialization-compat.ts +141 -0
- package/src/worker/mcp-jsonrpc.ts +11 -29
- package/src/worker/mcp-removed-protocol.ts +17 -0
- package/src/worker/mcp-response-proxy.ts +135 -0
- package/src/worker/mcp-response-stream.ts +65 -0
- package/src/worker/mcp-stale-schema-compat.ts +57 -0
- package/src/worker/mcp-stream-proxy-contract.ts +48 -0
- package/src/worker/mcp-tool-call-input.ts +23 -0
- package/src/worker/nonce-store.ts +33 -35
- package/src/worker/oauth-authorization-page.ts +21 -1
- package/src/worker/oauth-client-admin.ts +52 -0
- package/src/worker/oauth-client-contract.ts +38 -0
- package/src/worker/oauth-controller.ts +107 -47
- package/src/worker/oauth-field-contract.ts +31 -0
- package/src/worker/oauth-record-contract.ts +7 -0
- package/src/worker/oauth-refresh-authority.ts +27 -0
- package/src/worker/oauth-refresh-exchange.ts +162 -0
- package/src/worker/oauth-refresh-families.ts +130 -27
- package/src/worker/oauth-refresh-persistence.ts +116 -0
- package/src/worker/oauth-state.ts +33 -18
- package/src/worker/oauth-store-validation.ts +131 -0
- package/src/worker/oauth-token-derivation.ts +33 -0
- package/src/worker/oauth-token-issuance.ts +119 -0
- package/src/worker/oauth-tokens.ts +56 -190
- package/src/worker/observability.ts +38 -5
- package/src/worker/pending-admission.ts +15 -0
- package/src/worker/pending-call-capacity.ts +108 -0
- package/src/worker/pending-call-contract.ts +42 -5
- package/src/worker/pending-call-deadlines.ts +75 -0
- package/src/worker/pending-calls.ts +131 -125
- package/src/worker/runtime-alarm-storage.ts +37 -0
- package/src/worker/runtime-alarm.ts +136 -0
- package/src/worker/server-info-activity.ts +36 -0
- package/src/worker/server-info-tool-delivery.ts +15 -0
- package/src/worker/server-info.ts +168 -0
- package/src/worker/tool-catalog.ts +47 -1
- package/src/worker/tool-timeout.ts +75 -14
- package/src/worker/websocket-protocol.ts +26 -2
- package/src/worker/worker-edge-guard.ts +90 -0
- package/src/worker/worker-edge-log.ts +65 -0
- package/src/worker/worker-entry.ts +52 -0
- package/src/worker/worker-mcp-config.ts +23 -0
- package/src/worker/worker-metadata.ts +48 -0
- package/src/worker/worker-rate-limit-key.ts +30 -0
- package/src/worker/worker-runtime-config.ts +20 -0
- package/src/worker/worker-static-routes.ts +54 -0
- package/src/worker/worker-task-lifetime.ts +18 -0
- package/tsconfig.local.json +17 -1
- package/wrangler.jsonc +13 -0
- package/src/local/cli-approval.mjs +0 -117
- package/src/local/operation-state-lock.mjs +0 -92
- package/src/worker/mcp-session.ts +0 -72
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { BridgeError } from "./errors.mjs";
|
|
6
|
+
|
|
7
|
+
const MACOS_SANDBOX_EXEC = "/usr/bin/sandbox-exec";
|
|
8
|
+
let macosProbeResult;
|
|
9
|
+
|
|
10
|
+
export function delegatedProcessCommand({ command, args = [], workspace, runtimeDir, context = {}, platform = process.platform, forceDelegated = false } = {}) {
|
|
11
|
+
const principal = context?.authority?.principal;
|
|
12
|
+
if (!forceDelegated && (!principal || principal.kind !== "account" || principal.role === "owner")) {
|
|
13
|
+
return { command, args, isolation: "owner-or-local-user" };
|
|
14
|
+
}
|
|
15
|
+
if (platform === "darwin" && macosSandboxAvailable({ platform })) {
|
|
16
|
+
const profile = macosProfile({ workspace, runtimeDir });
|
|
17
|
+
return {
|
|
18
|
+
command: MACOS_SANDBOX_EXEC,
|
|
19
|
+
args: ["-p", profile, String(command), ...args.map(String)],
|
|
20
|
+
isolation: "macos-sandbox-exec-workspace",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
throw new BridgeError(
|
|
24
|
+
"policy_denied",
|
|
25
|
+
"delegated process execution requires a behavior-verified OS workspace sandbox; use the owner account or an isolated worker account/VM",
|
|
26
|
+
{ details: { reason: "delegated_process_isolation_unavailable", platform } },
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function delegatedProcessIsolationStatus(platform = process.platform) {
|
|
31
|
+
if (platform === "darwin" && macosSandboxAvailable({ platform })) {
|
|
32
|
+
return {
|
|
33
|
+
available: true,
|
|
34
|
+
provider: "macos-sandbox-exec-deny-default",
|
|
35
|
+
network: "allowed",
|
|
36
|
+
filesystem: "workspace-and-runtime-write; system-runtime-read",
|
|
37
|
+
keychain: "common Keychain CLI access denied by the behavior probe; not a complete same-user tenancy boundary",
|
|
38
|
+
apple_events: "not reachable through the deny-default profile",
|
|
39
|
+
residual: "sandbox-exec is a compatibility boundary, not separate OS-user tenancy; choose a narrow workspace",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
available: false,
|
|
44
|
+
provider: null,
|
|
45
|
+
network: "unavailable",
|
|
46
|
+
filesystem: "unavailable",
|
|
47
|
+
keychain: "unavailable",
|
|
48
|
+
apple_events: "unavailable",
|
|
49
|
+
residual: platform === "darwin"
|
|
50
|
+
? "sandbox-exec exists but the workspace read/write, outside-path denial, or Keychain-denial behavior probe failed; delegated execution fails closed"
|
|
51
|
+
: "no verified delegated process sandbox provider is installed; delegated execution fails closed",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function macosDelegatedSandboxProfile({ workspace, runtimeDir } = {}) {
|
|
56
|
+
return macosProfile({ workspace, runtimeDir });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function macosSandboxAvailable(options = {}) {
|
|
60
|
+
if (options.refresh === true) macosProbeResult = undefined;
|
|
61
|
+
if (macosProbeResult !== undefined) return macosProbeResult;
|
|
62
|
+
const platform = String(options.platform || process.platform);
|
|
63
|
+
const exists = typeof options.exists === "function" ? options.exists : existsSync;
|
|
64
|
+
if (platform !== "darwin" || !exists(MACOS_SANDBOX_EXEC)) {
|
|
65
|
+
macosProbeResult = false;
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const probe = typeof options.behaviorProbe === "function" ? options.behaviorProbe : probeMacosDelegatedSandbox;
|
|
69
|
+
macosProbeResult = probe({ spawnSyncProcess: options.spawnSync || spawnSync }) === true;
|
|
70
|
+
return macosProbeResult;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function probeMacosDelegatedSandbox(options = {}) {
|
|
74
|
+
const run = typeof options.spawnSyncProcess === "function" ? options.spawnSyncProcess : spawnSync;
|
|
75
|
+
const root = mkdtempSync(path.join(tmpdir(), "mbm-delegated-sandbox-probe-"));
|
|
76
|
+
const workspace = path.join(root, "workspace");
|
|
77
|
+
const runtimeDir = path.join(root, "runtime");
|
|
78
|
+
const outside = path.join(root, "outside.txt");
|
|
79
|
+
const outsideWrite = path.join(root, "outside-write.txt");
|
|
80
|
+
const workspaceWrite = path.join(workspace, "allowed-write.txt");
|
|
81
|
+
try {
|
|
82
|
+
mkdirSync(workspace, { mode: 0o700 });
|
|
83
|
+
mkdirSync(runtimeDir, { mode: 0o700 });
|
|
84
|
+
writeFileSync(path.join(workspace, "allowed.txt"), "allowed\n", { mode: 0o600 });
|
|
85
|
+
writeFileSync(outside, "blocked\n", { mode: 0o600 });
|
|
86
|
+
const profile = macosProfile({ workspace, runtimeDir });
|
|
87
|
+
const execute = (argv) => run(MACOS_SANDBOX_EXEC, ["-p", profile, ...argv], {
|
|
88
|
+
encoding: "utf8",
|
|
89
|
+
timeout: 5_000,
|
|
90
|
+
killSignal: "SIGKILL",
|
|
91
|
+
maxBuffer: 64 * 1024,
|
|
92
|
+
windowsHide: true,
|
|
93
|
+
env: { PATH: "/usr/bin:/bin:/usr/sbin:/sbin", HOME: workspace, TMPDIR: runtimeDir, LANG: "C", LC_ALL: "C" },
|
|
94
|
+
});
|
|
95
|
+
const allowedRead = execute(["/bin/cat", path.join(workspace, "allowed.txt")]);
|
|
96
|
+
const allowedWrite = execute(["/bin/sh", "-c", `printf allowed > ${shellQuote(workspaceWrite)}`]);
|
|
97
|
+
const blockedRead = execute(["/bin/cat", outside]);
|
|
98
|
+
const blockedWrite = execute(["/bin/sh", "-c", `printf blocked > ${shellQuote(outsideWrite)}`]);
|
|
99
|
+
const keychain = execute(["/usr/bin/security", "list-keychains"]);
|
|
100
|
+
return allowedRead.status === 0 && allowedWrite.status === 0 && existsSync(workspaceWrite)
|
|
101
|
+
&& blockedRead.status !== 0 && blockedWrite.status !== 0 && !existsSync(outsideWrite)
|
|
102
|
+
&& keychain.status !== 0;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
} finally {
|
|
106
|
+
rmSync(root, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function macosProfile({ workspace, runtimeDir }) {
|
|
111
|
+
const readable = [
|
|
112
|
+
"/System",
|
|
113
|
+
"/usr",
|
|
114
|
+
"/bin",
|
|
115
|
+
"/sbin",
|
|
116
|
+
"/Library",
|
|
117
|
+
"/Applications",
|
|
118
|
+
"/opt/homebrew",
|
|
119
|
+
"/nix/store",
|
|
120
|
+
"/private/etc",
|
|
121
|
+
"/private/var/db",
|
|
122
|
+
"/dev",
|
|
123
|
+
workspace,
|
|
124
|
+
runtimeDir,
|
|
125
|
+
].filter(Boolean);
|
|
126
|
+
const writable = [workspace, runtimeDir, "/private/tmp"].filter(Boolean);
|
|
127
|
+
return [
|
|
128
|
+
"(version 1)",
|
|
129
|
+
"(deny default)",
|
|
130
|
+
"(allow process*)",
|
|
131
|
+
"(allow signal (target self))",
|
|
132
|
+
"(allow sysctl-read)",
|
|
133
|
+
"(allow mach-lookup)",
|
|
134
|
+
"(allow ipc-posix-shm)",
|
|
135
|
+
"(allow network*)",
|
|
136
|
+
"(allow file-read-metadata)",
|
|
137
|
+
...readable.map((value) => `(allow file-read* (subpath ${sandboxString(path.resolve(value))}))`),
|
|
138
|
+
...writable.map((value) => `(allow file-write* (subpath ${sandboxString(path.resolve(value))}))`),
|
|
139
|
+
].join(" ");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function sandboxString(value) {
|
|
143
|
+
return `"${String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function shellQuote(value) {
|
|
147
|
+
return `'${String(value).replaceAll("'", `'\\''`)}'`;
|
|
148
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { createHash, createPrivateKey, generateKeyPairSync, randomBytes, sign
|
|
1
|
+
import { createHash, createPrivateKey, generateKeyPairSync, randomBytes, sign } from "node:crypto";
|
|
2
2
|
import { DAEMON_AUTH_SCHEME, DAEMON_PREFLIGHT_SCHEME, daemonAuthTranscript, daemonPreflightTranscript } from "../shared/daemon-auth.mjs";
|
|
3
|
+
import {
|
|
4
|
+
DEVICE_SESSION_CERTIFICATE_SCHEME,
|
|
5
|
+
DEVICE_SESSION_MAX_LIFETIME_SECONDS,
|
|
6
|
+
canonicalPublicJwk,
|
|
7
|
+
deviceSessionCertificateTranscript,
|
|
8
|
+
} from "../shared/device-session-auth.mjs";
|
|
3
9
|
|
|
4
10
|
const DEVICE_KEY_TYPE = "EC";
|
|
5
11
|
const DEVICE_CURVE = "P-256";
|
|
12
|
+
const SESSION_CERTIFICATE_HEADER = "X-Bridge-Device-Certificate";
|
|
6
13
|
|
|
7
14
|
export function createDeviceIdentity() {
|
|
8
15
|
const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
@@ -19,90 +26,190 @@ export function createDeviceIdentity() {
|
|
|
19
26
|
};
|
|
20
27
|
}
|
|
21
28
|
|
|
29
|
+
export function createDeviceSessionIdentity(rootIdentity, workerOrigin, server, version, now = Date.now()) {
|
|
30
|
+
validateDeviceIdentity(rootIdentity);
|
|
31
|
+
const draft = createDeviceSessionDraft(rootIdentity, workerOrigin, server, version, now);
|
|
32
|
+
return finalizeDeviceSessionIdentity(draft, signDeviceTranscript(rootIdentity, draft.transcript));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createDeviceSessionDraft(rootIdentity, workerOrigin, server, version, now = Date.now()) {
|
|
36
|
+
validatePublicDeviceRoot(rootIdentity);
|
|
37
|
+
const issuedAt = Math.floor(Number(now) / 1000);
|
|
38
|
+
if (!Number.isSafeInteger(issuedAt) || issuedAt <= 0) throw new Error("device session timestamp is invalid");
|
|
39
|
+
const expiresAt = issuedAt + DEVICE_SESSION_MAX_LIFETIME_SECONDS;
|
|
40
|
+
const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
41
|
+
const privateJwk = privateKey.export({ format: "jwk" });
|
|
42
|
+
const publicJwk = publicKey.export({ format: "jwk" });
|
|
43
|
+
validatePrivateDeviceJwk(privateJwk);
|
|
44
|
+
validatePublicDeviceJwk(publicJwk);
|
|
45
|
+
const nonce = randomBytes(24).toString("base64url");
|
|
46
|
+
const certificateBody = {
|
|
47
|
+
scheme: DEVICE_SESSION_CERTIFICATE_SCHEME,
|
|
48
|
+
root_key_id: rootIdentity.keyId,
|
|
49
|
+
public_jwk: canonicalPublicJwk(publicJwk),
|
|
50
|
+
issued_at: issuedAt,
|
|
51
|
+
expires_at: expiresAt,
|
|
52
|
+
nonce,
|
|
53
|
+
};
|
|
54
|
+
const transcript = deviceSessionCertificateTranscript({
|
|
55
|
+
workerOrigin,
|
|
56
|
+
server,
|
|
57
|
+
version,
|
|
58
|
+
rootKeyId: rootIdentity.keyId,
|
|
59
|
+
publicJwk,
|
|
60
|
+
issuedAt,
|
|
61
|
+
expiresAt,
|
|
62
|
+
nonce,
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
transcript,
|
|
66
|
+
certificateBody,
|
|
67
|
+
session: {
|
|
68
|
+
scheme: DAEMON_AUTH_SCHEME,
|
|
69
|
+
privateJwk,
|
|
70
|
+
publicJwk,
|
|
71
|
+
keyId: deviceKeyId(publicJwk),
|
|
72
|
+
createdAt: new Date(Number(now)).toISOString(),
|
|
73
|
+
expiresAt: new Date(expiresAt * 1000).toISOString(),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function finalizeDeviceSessionIdentity(draft, signature) {
|
|
79
|
+
if (!draft || typeof draft !== "object" || !draft.session || !draft.certificateBody) throw new Error("device session draft is invalid");
|
|
80
|
+
if (!/^[A-Za-z0-9_-]{86}$/.test(String(signature || ""))) throw new Error("device session root signature is invalid");
|
|
81
|
+
const identity = {
|
|
82
|
+
...draft.session,
|
|
83
|
+
certificate: { ...draft.certificateBody, signature },
|
|
84
|
+
};
|
|
85
|
+
return validateDeviceSessionIdentity(identity);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function validatePublicDeviceRoot(identity) {
|
|
89
|
+
if (!identity || typeof identity !== "object" || Array.isArray(identity)) throw new Error("device root identity is missing");
|
|
90
|
+
if (identity.scheme !== DAEMON_AUTH_SCHEME) throw new Error("device root identity scheme is invalid");
|
|
91
|
+
validatePublicDeviceJwk(identity.publicJwk);
|
|
92
|
+
if (identity.keyId !== deviceKeyId(identity.publicJwk)) throw new Error("device root identity key id is invalid");
|
|
93
|
+
if (!Number.isFinite(Date.parse(String(identity.createdAt || "")))) throw new Error("device root identity creation time is invalid");
|
|
94
|
+
return identity;
|
|
95
|
+
}
|
|
96
|
+
|
|
22
97
|
export function publicDeviceJwkJson(identity) {
|
|
23
98
|
const publicJwk = identity?.publicJwk;
|
|
24
99
|
validatePublicDeviceJwk(publicJwk);
|
|
25
100
|
return JSON.stringify(publicJwk);
|
|
26
101
|
}
|
|
27
102
|
|
|
28
|
-
|
|
29
103
|
export function createDaemonPreflightHeaders(identity, workerOrigin, server, version, now = Date.now()) {
|
|
30
|
-
|
|
104
|
+
validateDeviceSessionIdentity(identity, now);
|
|
31
105
|
const issuedAt = Math.floor(Number(now) / 1000);
|
|
32
106
|
if (!Number.isSafeInteger(issuedAt) || issuedAt <= 0) throw new Error("device preflight timestamp is invalid");
|
|
33
107
|
const nonce = randomBytes(24).toString("base64url");
|
|
34
108
|
const transcript = daemonPreflightTranscript({ workerOrigin, server, version, nonce, issuedAt });
|
|
35
|
-
const
|
|
36
|
-
const signature = sign("sha256", Buffer.from(transcript, "utf8"), { key, dsaEncoding: "ieee-p1363" }).toString("base64url");
|
|
109
|
+
const signature = signDeviceTranscript(identity, transcript);
|
|
37
110
|
return {
|
|
38
111
|
"X-Bridge-Device-Scheme": DAEMON_PREFLIGHT_SCHEME,
|
|
39
|
-
"X-Bridge-Device-Key":
|
|
112
|
+
"X-Bridge-Device-Key": identity.keyId,
|
|
40
113
|
"X-Bridge-Device-Nonce": nonce,
|
|
41
114
|
"X-Bridge-Device-Time": String(issuedAt),
|
|
42
115
|
"X-Bridge-Device-Signature": signature,
|
|
116
|
+
[SESSION_CERTIFICATE_HEADER]: encodeDeviceSessionCertificate(identity),
|
|
43
117
|
};
|
|
44
118
|
}
|
|
45
119
|
|
|
120
|
+
export function encodeDeviceSessionCertificate(identity) {
|
|
121
|
+
validateDeviceSessionIdentity(identity);
|
|
122
|
+
return Buffer.from(JSON.stringify(identity.certificate), "utf8").toString("base64url");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function signWithDeviceSessionIdentity(identity, transcript) {
|
|
126
|
+
validateDeviceSessionIdentity(identity);
|
|
127
|
+
const text = String(transcript || "");
|
|
128
|
+
if (!text || Buffer.byteLength(text) > 64 * 1024) throw new Error("device session signing transcript is empty or too large");
|
|
129
|
+
return signDeviceTranscript(identity, text);
|
|
130
|
+
}
|
|
131
|
+
|
|
46
132
|
export async function createDaemonAuthentication(identity, welcome, instanceId) {
|
|
47
|
-
|
|
133
|
+
validateDeviceSessionIdentity(identity);
|
|
48
134
|
const auth = welcome?.authentication;
|
|
49
|
-
if (!auth || auth.scheme !== DAEMON_AUTH_SCHEME) throw new Error("Worker did not
|
|
50
|
-
const
|
|
135
|
+
if (!auth || auth.scheme !== DAEMON_AUTH_SCHEME) throw new Error("Worker did not provide a supported device challenge");
|
|
136
|
+
const challenge = String(auth.challenge || "");
|
|
137
|
+
const issuedAt = Number(auth.issued_at);
|
|
138
|
+
const expiresAt = Number(auth.expires_at);
|
|
139
|
+
if (!/^daemon_challenge_[A-Za-z0-9_-]{40,96}$/.test(challenge)) throw new Error("Worker device challenge is invalid");
|
|
140
|
+
if (!Number.isSafeInteger(issuedAt) || !Number.isSafeInteger(expiresAt) || expiresAt <= issuedAt) throw new Error("Worker device challenge lifetime is invalid");
|
|
141
|
+
if (Math.floor(Date.now() / 1000) > expiresAt) throw new Error("Worker device challenge expired");
|
|
51
142
|
const transcript = daemonAuthTranscript({
|
|
52
|
-
challenge
|
|
53
|
-
workerOrigin,
|
|
54
|
-
server: welcome.server,
|
|
55
|
-
version: welcome.version,
|
|
143
|
+
challenge,
|
|
144
|
+
workerOrigin: String(welcome.worker_origin || ""),
|
|
145
|
+
server: String(welcome.server || ""),
|
|
146
|
+
version: String(welcome.version || ""),
|
|
56
147
|
instanceId,
|
|
57
|
-
issuedAt
|
|
148
|
+
issuedAt,
|
|
58
149
|
});
|
|
59
|
-
const privateKey = await webcrypto.subtle.importKey(
|
|
60
|
-
"jwk",
|
|
61
|
-
identity.privateJwk,
|
|
62
|
-
{ name: "ECDSA", namedCurve: DEVICE_CURVE },
|
|
63
|
-
false,
|
|
64
|
-
["sign"],
|
|
65
|
-
);
|
|
66
|
-
const signature = await webcrypto.subtle.sign(
|
|
67
|
-
{ name: "ECDSA", hash: "SHA-256" },
|
|
68
|
-
privateKey,
|
|
69
|
-
new TextEncoder().encode(transcript),
|
|
70
|
-
);
|
|
71
150
|
return {
|
|
72
151
|
scheme: DAEMON_AUTH_SCHEME,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
signature:
|
|
152
|
+
key_id: identity.keyId,
|
|
153
|
+
challenge,
|
|
154
|
+
issued_at: issuedAt,
|
|
155
|
+
signature: signDeviceTranscript(identity, transcript),
|
|
77
156
|
};
|
|
78
157
|
}
|
|
79
158
|
|
|
80
|
-
export function deviceKeyId(publicJwk) {
|
|
81
|
-
validatePublicDeviceJwk(publicJwk);
|
|
82
|
-
const canonical = JSON.stringify({ crv: publicJwk.crv, kty: publicJwk.kty, x: publicJwk.x, y: publicJwk.y });
|
|
83
|
-
return `device_${createHash("sha256").update(canonical).digest("base64url").slice(0, 32)}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
159
|
export function validateDeviceIdentity(identity) {
|
|
87
160
|
if (!identity || typeof identity !== "object" || Array.isArray(identity)) throw new Error("device identity is missing");
|
|
88
|
-
if (identity.scheme !== DAEMON_AUTH_SCHEME) throw new Error("device identity scheme is
|
|
161
|
+
if (identity.scheme !== DAEMON_AUTH_SCHEME) throw new Error("device identity scheme is invalid");
|
|
89
162
|
validatePrivateDeviceJwk(identity.privateJwk);
|
|
90
163
|
validatePublicDeviceJwk(identity.publicJwk);
|
|
91
|
-
const
|
|
92
|
-
if (identity.
|
|
164
|
+
const expectedPublic = publicFromPrivate(identity.privateJwk);
|
|
165
|
+
if (!samePublicJwk(expectedPublic, identity.publicJwk)) throw new Error("device identity public and private keys do not match");
|
|
166
|
+
if (identity.keyId !== deviceKeyId(identity.publicJwk)) throw new Error("device identity key id is invalid");
|
|
167
|
+
if (!Number.isFinite(Date.parse(String(identity.createdAt || "")))) throw new Error("device identity creation time is invalid");
|
|
93
168
|
return identity;
|
|
94
169
|
}
|
|
95
170
|
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
171
|
+
export function validateDeviceSessionIdentity(identity, now = Date.now()) {
|
|
172
|
+
validateDeviceIdentity(identity);
|
|
173
|
+
const certificate = identity.certificate;
|
|
174
|
+
if (!certificate || typeof certificate !== "object" || Array.isArray(certificate)) throw new Error("device session certificate is missing");
|
|
175
|
+
if (certificate.scheme !== DEVICE_SESSION_CERTIFICATE_SCHEME) throw new Error("device session certificate scheme is invalid");
|
|
176
|
+
if (identity.keyId !== deviceKeyId(certificate.public_jwk) || !samePublicJwk(identity.publicJwk, certificate.public_jwk)) {
|
|
177
|
+
throw new Error("device session certificate key mismatch");
|
|
178
|
+
}
|
|
179
|
+
const expiresAt = Number(certificate.expires_at);
|
|
180
|
+
if (!Number.isSafeInteger(expiresAt) || Math.floor(Number(now) / 1000) > expiresAt) throw new Error("device session certificate expired");
|
|
181
|
+
if (!/^[A-Za-z0-9_-]{86}$/.test(String(certificate.signature || ""))) throw new Error("device session certificate signature is invalid");
|
|
182
|
+
return identity;
|
|
99
183
|
}
|
|
100
184
|
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
185
|
+
export function deviceKeyId(publicJwk) {
|
|
186
|
+
validatePublicDeviceJwk(publicJwk);
|
|
187
|
+
const canonical = JSON.stringify(canonicalPublicJwk(publicJwk));
|
|
188
|
+
return `device_${createHash("sha256").update(canonical).digest("base64url").slice(0, 32)}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function signDeviceTranscript(identity, transcript) {
|
|
192
|
+
validatePrivateDeviceJwk(identity?.privateJwk);
|
|
193
|
+
const key = createPrivateKey({ key: identity.privateJwk, format: "jwk" });
|
|
194
|
+
return sign("sha256", Buffer.from(transcript, "utf8"), { key, dsaEncoding: "ieee-p1363" }).toString("base64url");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function publicFromPrivate(privateJwk) {
|
|
198
|
+
const privateKey = createPrivateKey({ key: privateJwk, format: "jwk" });
|
|
199
|
+
return privateKey.export({ format: "jwk" });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function validatePrivateDeviceJwk(value) {
|
|
203
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("device private key is invalid");
|
|
204
|
+
if (value.kty !== DEVICE_KEY_TYPE || value.crv !== DEVICE_CURVE || typeof value.d !== "string") throw new Error("device private key is invalid");
|
|
205
|
+
validatePublicDeviceJwk(value);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function validatePublicDeviceJwk(value) {
|
|
209
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("device public key is invalid");
|
|
210
|
+
if (value.kty !== DEVICE_KEY_TYPE || value.crv !== DEVICE_CURVE || typeof value.x !== "string" || typeof value.y !== "string") throw new Error("device public key is invalid");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function samePublicJwk(left, right) {
|
|
214
|
+
return left?.kty === right?.kty && left?.crv === right?.crv && left?.x === right?.x && left?.y === right?.y;
|
|
108
215
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDeviceIdentity,
|
|
3
|
+
createDeviceSessionDraft,
|
|
4
|
+
createDeviceSessionIdentity,
|
|
5
|
+
finalizeDeviceSessionIdentity,
|
|
6
|
+
validateDeviceIdentity,
|
|
7
|
+
validatePublicDeviceRoot,
|
|
8
|
+
} from "./device-identity.mjs";
|
|
9
|
+
import {
|
|
10
|
+
configuredMacosTrustBrokerPath,
|
|
11
|
+
ensureMacosSecureDeviceRoot,
|
|
12
|
+
isMacosSecureDeviceRoot,
|
|
13
|
+
signWithMacosSecureDeviceRoot,
|
|
14
|
+
} from "./macos-trust-broker.mjs";
|
|
15
|
+
|
|
16
|
+
export function validateDeviceRootIdentity(identity) {
|
|
17
|
+
if (isMacosSecureDeviceRoot(identity)) return identity;
|
|
18
|
+
return validateDeviceIdentity(identity);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function ensurePreferredDeviceRoot({
|
|
22
|
+
profileDir,
|
|
23
|
+
workspaceHash,
|
|
24
|
+
existing = null,
|
|
25
|
+
rotate = false,
|
|
26
|
+
platform = process.platform,
|
|
27
|
+
brokerPath = undefined,
|
|
28
|
+
env = process.env,
|
|
29
|
+
secureRootFactory = ensureMacosSecureDeviceRoot,
|
|
30
|
+
} = {}) {
|
|
31
|
+
const configuredBroker = brokerPath === undefined ? configuredMacosTrustBrokerPath(env) : brokerPath;
|
|
32
|
+
const selectedBroker = configuredBroker || (isMacosSecureDeviceRoot(existing) ? existing.brokerPath : null);
|
|
33
|
+
if (platform === "darwin" && selectedBroker) {
|
|
34
|
+
return secureRootFactory({
|
|
35
|
+
profileDir,
|
|
36
|
+
workspaceHash,
|
|
37
|
+
existing,
|
|
38
|
+
rotate,
|
|
39
|
+
brokerPath: selectedBroker,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (!rotate && existing) return validateDeviceRootIdentity(existing);
|
|
43
|
+
return createDeviceIdentity();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function createDeviceSessionForRoot(identity, workerOrigin, server, version, {
|
|
47
|
+
profileDir,
|
|
48
|
+
now = Date.now(),
|
|
49
|
+
reason = "Authorize Machine Bridge startup",
|
|
50
|
+
} = {}) {
|
|
51
|
+
if (!isMacosSecureDeviceRoot(identity)) return createDeviceSessionIdentity(validateDeviceIdentity(identity), workerOrigin, server, version, now);
|
|
52
|
+
validatePublicDeviceRoot(identity);
|
|
53
|
+
const draft = createDeviceSessionDraft(identity, workerOrigin, server, version, now);
|
|
54
|
+
const signature = signWithMacosSecureDeviceRoot(identity, draft.transcript, { profileDir, reason });
|
|
55
|
+
return finalizeDeviceSessionIdentity(draft, signature);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function deviceRootProviderStatus(identity, { env = process.env } = {}) {
|
|
59
|
+
if (isMacosSecureDeviceRoot(identity)) {
|
|
60
|
+
return {
|
|
61
|
+
provider: identity.provider,
|
|
62
|
+
private_key_exportable: false,
|
|
63
|
+
root_storage: "Secure Enclave",
|
|
64
|
+
broker_provisioned: true,
|
|
65
|
+
session_signing: "one user-presence operation per daemon start",
|
|
66
|
+
reconnect_signing: "ephemeral in-memory key",
|
|
67
|
+
key_id: identity.keyId,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
validateDeviceIdentity(identity);
|
|
71
|
+
return {
|
|
72
|
+
provider: "portable-jwk-v1",
|
|
73
|
+
private_key_exportable: true,
|
|
74
|
+
root_storage: "owner-only local state",
|
|
75
|
+
broker_provisioned: Boolean(String(env?.MBM_MACOS_TRUST_BROKER || "").trim()),
|
|
76
|
+
secure_enclave_enrollment: "requires a provisioning-profile-validated app-like broker configured through MBM_MACOS_TRUST_BROKER",
|
|
77
|
+
session_signing: "local file-backed root",
|
|
78
|
+
reconnect_signing: "ephemeral in-memory key",
|
|
79
|
+
key_id: identity.keyId,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { opendir } from "node:fs/promises";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { pathEntryIfExists } from "./path-inspection.mjs";
|
|
6
|
+
|
|
7
|
+
export const DIRECTORY_METADATA_BATCH_SIZE = 16;
|
|
8
|
+
|
|
9
|
+
export async function* directoryEntriesWithMetadata(directory, options = {}) {
|
|
10
|
+
const openDirectory = options.openDirectory || opendir;
|
|
11
|
+
const inspect = options.inspect || pathEntryIfExists;
|
|
12
|
+
const throwIfCancelled = typeof options.throwIfCancelled === "function" ? options.throwIfCancelled : () => {};
|
|
13
|
+
const context = options.context || {};
|
|
14
|
+
const batchSize = positiveBatchSize(options.batchSize);
|
|
15
|
+
const handle = await openDirectory(directory);
|
|
16
|
+
let batch = [];
|
|
17
|
+
for await (const entry of handle) {
|
|
18
|
+
throwIfCancelled(context);
|
|
19
|
+
batch.push({ entry, path: resolve(directory, entry.name) });
|
|
20
|
+
if (batch.length < batchSize) continue;
|
|
21
|
+
const settled = await inspectBatch(batch, inspect, throwIfCancelled, context);
|
|
22
|
+
batch = [];
|
|
23
|
+
for (const result of settled) {
|
|
24
|
+
throwIfCancelled(context);
|
|
25
|
+
if (result.status === "rejected") throw result.reason;
|
|
26
|
+
yield result.value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (!batch.length) return;
|
|
30
|
+
const settled = await inspectBatch(batch, inspect, throwIfCancelled, context);
|
|
31
|
+
for (const result of settled) {
|
|
32
|
+
throwIfCancelled(context);
|
|
33
|
+
if (result.status === "rejected") throw result.reason;
|
|
34
|
+
yield result.value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function inspectBatch(batch, inspect, throwIfCancelled, context) {
|
|
39
|
+
return Promise.allSettled(batch.map(async (item) => {
|
|
40
|
+
throwIfCancelled(context);
|
|
41
|
+
return { ...item, info: await inspect(item.path) };
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function positiveBatchSize(value) {
|
|
46
|
+
return Number.isInteger(value) && value > 0 && value <= 64 ? value : DIRECTORY_METADATA_BATCH_SIZE;
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const DOCTOR_RUNTIME_SCOPE = Object.freeze({
|
|
2
|
+
running_service_process_inspected: false,
|
|
3
|
+
remote_relay_inspected: false,
|
|
4
|
+
reason: "doctor uses an isolated local runtime; inspect authenticated server_info.daemon.relay_transport for the running service relay",
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export function doctorRuntimeCheckProjection(check = {}) {
|
|
8
|
+
const layer = String(check.layer || "unknown");
|
|
9
|
+
const skipped = check.skipped === true;
|
|
10
|
+
const detail = skipped && layer === "remote-relay"
|
|
11
|
+
? "not inspected (doctor uses an isolated local runtime; inspect authenticated server_info.daemon.relay_transport)"
|
|
12
|
+
: skipped
|
|
13
|
+
? `skipped (${check.error_class || "not applicable"})`
|
|
14
|
+
: check.ok === true
|
|
15
|
+
? "ok"
|
|
16
|
+
: check.error_class || "failed";
|
|
17
|
+
return {
|
|
18
|
+
name: `runtime:${layer}`,
|
|
19
|
+
ok: skipped || check.ok === true,
|
|
20
|
+
applicable: !skipped,
|
|
21
|
+
detail,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { BridgeError } from "./errors.mjs";
|
|
4
|
+
import { delegatedProcessCommand } from "./delegated-process-sandbox.mjs";
|
|
5
|
+
import { MAX_COMMAND_BYTES, validateArgv } from "./process-contract.mjs";
|
|
6
|
+
import { durableProcessExecutionTimeoutSeconds, durableRegisteredCommandTimeoutSeconds } from "./process-foreground-timeout.mjs";
|
|
7
|
+
import { workspaceShellCommand } from "./shell.mjs";
|
|
8
|
+
|
|
9
|
+
export async function prepareDurableDirectProcess(service, args, context = {}) {
|
|
10
|
+
service.policyGate.assert("run_process");
|
|
11
|
+
const idempotencyKey = durableProcessIdempotencyKey(args, context);
|
|
12
|
+
const argv = validateArgv(args.argv);
|
|
13
|
+
const cwd = await service.resolveExistingPath(args.cwd || ".", context);
|
|
14
|
+
if (!(await stat(cwd)).isDirectory()) throw new BridgeError("invalid_request", "cwd is not a directory");
|
|
15
|
+
return durableSpec(service, {
|
|
16
|
+
sourceTool: "run_process",
|
|
17
|
+
name: `durable run_process: ${basename(argv[0])}`,
|
|
18
|
+
argv,
|
|
19
|
+
cwd,
|
|
20
|
+
timeoutSeconds: durableProcessExecutionTimeoutSeconds(args.timeout_seconds),
|
|
21
|
+
idempotencyKey,
|
|
22
|
+
}, context);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function prepareDurableRegisteredProcess(service, args, context = {}) {
|
|
26
|
+
service.policyGate.assert("run_local_command");
|
|
27
|
+
const idempotencyKey = durableProcessIdempotencyKey(args, context);
|
|
28
|
+
const command = await service.resolveLocalCommand(args, context);
|
|
29
|
+
const argv = validateArgv(command.argv);
|
|
30
|
+
const cwd = await service.resolveExistingPath(command.cwd, context);
|
|
31
|
+
if (!(await stat(cwd)).isDirectory()) throw new BridgeError("invalid_request", "registered command cwd is not a directory");
|
|
32
|
+
return durableSpec(service, {
|
|
33
|
+
sourceTool: "run_local_command",
|
|
34
|
+
name: `durable command: ${String(command.name || basename(argv[0])).slice(0, 96)}`,
|
|
35
|
+
argv,
|
|
36
|
+
cwd,
|
|
37
|
+
timeoutSeconds: durableRegisteredCommandTimeoutSeconds(args, command),
|
|
38
|
+
idempotencyKey,
|
|
39
|
+
}, context);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function prepareDurableShellProcess(service, args, context = {}) {
|
|
43
|
+
service.policyGate.assert("exec_command");
|
|
44
|
+
const idempotencyKey = durableProcessIdempotencyKey(args, context);
|
|
45
|
+
const command = args?.command;
|
|
46
|
+
if (!command || typeof command !== "string") throw new BridgeError("invalid_request", "command is required");
|
|
47
|
+
if (command.includes("\0")) throw new BridgeError("invalid_request", "command contains a NUL byte");
|
|
48
|
+
if (Buffer.byteLength(command) > MAX_COMMAND_BYTES) throw new BridgeError("limit_exceeded", `command exceeds maximum size (${MAX_COMMAND_BYTES} bytes)`);
|
|
49
|
+
const shell = workspaceShellCommand(command);
|
|
50
|
+
return durableSpec(service, {
|
|
51
|
+
sourceTool: "exec_command",
|
|
52
|
+
name: "durable shell command",
|
|
53
|
+
argv: [shell.cmd, ...shell.args],
|
|
54
|
+
cwd: service.workspace,
|
|
55
|
+
timeoutSeconds: durableProcessExecutionTimeoutSeconds(args.timeout_seconds),
|
|
56
|
+
idempotencyKey,
|
|
57
|
+
}, context);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function durableSpec(service, spec, context) {
|
|
61
|
+
const launch = delegatedProcessCommand({
|
|
62
|
+
command: process.execPath,
|
|
63
|
+
args: [],
|
|
64
|
+
workspace: service.workspace,
|
|
65
|
+
runtimeDir: service.runtimeDir,
|
|
66
|
+
context,
|
|
67
|
+
});
|
|
68
|
+
return { ...spec, delegatedProcess: launch.isolation !== "owner-or-local-user" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function durableProcessIdempotencyKey(args, context) {
|
|
72
|
+
const key = args?.idempotency_key;
|
|
73
|
+
if (context?.origin === "relay" && key === undefined) {
|
|
74
|
+
throw new BridgeError("invalid_request", "remote durable process execution requires idempotency_key before dispatch so an ambiguous acceptance response can be recovered without duplicate execution", {
|
|
75
|
+
retryable: false,
|
|
76
|
+
details: { side_effects_started: false, recovery_credential_required: "idempotency_key" },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return key;
|
|
80
|
+
}
|