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
|
@@ -1,77 +1,203 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
1
2
|
import { stat } from "node:fs/promises";
|
|
2
|
-
import {
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
5
|
import { BridgeError } from "./errors.mjs";
|
|
6
|
+
import { STRUCTURED_GIT_FIXED_ENVIRONMENT } from "./fixed-process-environment.mjs";
|
|
7
|
+
import { createStructuredGitCommit } from "./git-commit.mjs";
|
|
8
|
+
import { assertStructuredGitConfigSafe } from "./git-config-safety.mjs";
|
|
9
|
+
import { resolveGitMetadataBoundary } from "./git-metadata-boundary.mjs";
|
|
10
|
+
import { assertGitMetadataTreesSafe } from "./git-metadata-tree-safety.mjs";
|
|
11
|
+
import { parseStructuredGitLog } from "./git-log-parser.mjs";
|
|
4
12
|
import { clampInteger } from "./numbers.mjs";
|
|
13
|
+
import { pathEntryIfExists } from "./path-inspection.mjs";
|
|
14
|
+
import { readBoundedRegularFileSync } from "./secure-file.mjs";
|
|
15
|
+
export const MAX_GIT_COMMIT_MESSAGE_BYTES = 64 * 1024;
|
|
16
|
+
const STRUCTURED_GIT_ENVIRONMENT = STRUCTURED_GIT_FIXED_ENVIRONMENT;
|
|
5
17
|
|
|
6
18
|
export class GitService {
|
|
7
|
-
constructor({ resolveExistingPath, displayPath,
|
|
19
|
+
constructor({ resolveExistingPath, resolveWritePath, displayPath, runInternalProcess, gitExecutable, maximumBytes, disabledHooksPath = "" }) {
|
|
8
20
|
this.resolveExistingPath = resolveExistingPath;
|
|
21
|
+
this.resolveWritePath = typeof resolveWritePath === "function" ? resolveWritePath : resolveExistingPath;
|
|
9
22
|
this.displayPath = displayPath;
|
|
10
|
-
this.
|
|
23
|
+
this.runInternalProcess = runInternalProcess;
|
|
24
|
+
this.gitExecutable = typeof gitExecutable === "function" ? gitExecutable : () => String(gitExecutable || "");
|
|
11
25
|
this.maximumBytes = maximumBytes;
|
|
26
|
+
this.disabledHooksPath = String(disabledHooksPath || "");
|
|
12
27
|
}
|
|
13
28
|
|
|
14
29
|
async status(args = {}, context = {}) {
|
|
15
30
|
const git = await this.context(args.path || ".", context);
|
|
16
31
|
if (!git.ok) return git.result;
|
|
17
|
-
|
|
32
|
+
await this.assertSafeConfig(git, context, true);
|
|
33
|
+
const commandArgs = [...gitCommandPrefix(git), "-c", "status.submoduleSummary=false", "status", "--short", "--branch", "--ignore-submodules=all"];
|
|
18
34
|
if (git.pathspec) commandArgs.push("--", git.pathspec);
|
|
19
|
-
const result = await this.
|
|
20
|
-
|
|
35
|
+
const result = await this.runInternalProcess(
|
|
36
|
+
this.gitExecutable(), commandArgs, 30_000, true, 512 * 1024, context, git.root, null, STRUCTURED_GIT_ENVIRONMENT,
|
|
37
|
+
);
|
|
38
|
+
return { ...result, path: this.displayPath(git.target, context), gitRoot: this.displayPath(git.root, context) };
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
async diff(args = {}, context = {}) {
|
|
24
42
|
const maxBytes = clampInteger(args.max_bytes, 1024 * 1024, 1, this.maximumBytes);
|
|
25
43
|
const git = await this.context(args.path || ".", context);
|
|
26
|
-
if (!git.ok) return { ...git.result, path: this.displayPath(git.target) };
|
|
27
|
-
|
|
44
|
+
if (!git.ok) return { ...git.result, path: this.displayPath(git.target, context) };
|
|
45
|
+
await this.assertSafeConfig(git, context, args.staged !== true);
|
|
46
|
+
const commandArgs = [...gitCommandPrefix(git), "-c", "diff.external=", "diff", "--no-ext-diff", "--no-textconv", "--ignore-submodules=all"];
|
|
28
47
|
if (args.staged) commandArgs.push("--cached");
|
|
29
48
|
if (git.pathspec) commandArgs.push("--", git.pathspec);
|
|
30
|
-
const result = await this.
|
|
31
|
-
|
|
49
|
+
const result = await this.runInternalProcess(
|
|
50
|
+
this.gitExecutable(), commandArgs, 60_000, true, maxBytes, context, git.root, null, STRUCTURED_GIT_ENVIRONMENT,
|
|
51
|
+
);
|
|
52
|
+
return { ...result, path: this.displayPath(git.target, context), gitRoot: this.displayPath(git.root, context), staged: args.staged === true };
|
|
32
53
|
}
|
|
33
54
|
|
|
34
55
|
async log(args = {}, context = {}) {
|
|
35
56
|
const git = await this.context(args.path || ".", context);
|
|
36
|
-
if (!git.ok) return { ...git.result, path: this.displayPath(git.target) };
|
|
57
|
+
if (!git.ok) return { ...git.result, path: this.displayPath(git.target, context) };
|
|
58
|
+
await this.assertSafeConfig(git, context, false);
|
|
37
59
|
const maxCount = clampInteger(args.max_count, 20, 1, 100);
|
|
38
60
|
const format = "%H%x1f%h%x1f%aI%x1f%an%x1f%ae%x1f%s%x1e";
|
|
39
|
-
const commandArgs = ["-c", "
|
|
61
|
+
const commandArgs = [...gitCommandPrefix(git), "-c", "log.showSignature=false", "log", "--no-show-signature", `--max-count=${maxCount}`, `--format=${format}`];
|
|
40
62
|
if (git.pathspec) commandArgs.push("--", git.pathspec);
|
|
41
|
-
const result = await this.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return commit;
|
|
47
|
-
});
|
|
48
|
-
return { code: result.code, stderr: result.stderr, commits, path: this.displayPath(git.target), gitRoot: this.displayPath(git.root) };
|
|
63
|
+
const result = await this.runInternalProcess(
|
|
64
|
+
this.gitExecutable(), commandArgs, 30_000, true, 1024 * 1024, context, git.root, null, STRUCTURED_GIT_ENVIRONMENT,
|
|
65
|
+
);
|
|
66
|
+
const commits = parseStructuredGitLog(result.stdout, args.include_author_email === true);
|
|
67
|
+
return { code: result.code, stderr: result.stderr, commits, path: this.displayPath(git.target, context), gitRoot: this.displayPath(git.root, context) };
|
|
49
68
|
}
|
|
50
69
|
|
|
51
70
|
async show(args = {}, context = {}) {
|
|
52
71
|
const git = await this.context(args.path || ".", context);
|
|
53
|
-
if (!git.ok) return { ...git.result, path: this.displayPath(git.target) };
|
|
72
|
+
if (!git.ok) return { ...git.result, path: this.displayPath(git.target, context) };
|
|
73
|
+
await this.assertSafeConfig(git, context, false);
|
|
54
74
|
const revision = validateRevision(args.revision || "HEAD");
|
|
55
75
|
const maxBytes = clampInteger(args.max_bytes, 1024 * 1024, 1, this.maximumBytes);
|
|
56
|
-
const commandArgs = ["-c", "
|
|
76
|
+
const commandArgs = [...gitCommandPrefix(git), "-c", "diff.external=", "-c", "log.showSignature=false", "show", "--no-ext-diff", "--no-textconv", "--no-show-signature", "--ignore-submodules=all", "--decorate=no", revision];
|
|
57
77
|
if (git.pathspec) commandArgs.push("--", git.pathspec);
|
|
58
|
-
const result = await this.
|
|
59
|
-
|
|
78
|
+
const result = await this.runInternalProcess(
|
|
79
|
+
this.gitExecutable(), commandArgs, 60_000, true, maxBytes, context, git.root, null, STRUCTURED_GIT_ENVIRONMENT,
|
|
80
|
+
);
|
|
81
|
+
return { ...result, revision, path: this.displayPath(git.target, context), gitRoot: this.displayPath(git.root, context) };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async commit(args = {}, context = {}) {
|
|
85
|
+
const message = validateCommitMessage(args.message);
|
|
86
|
+
const git = await this.context(args.path || ".", context);
|
|
87
|
+
if (!git.ok) throw new BridgeError("execution_failed", "Git commit requires a repository path");
|
|
88
|
+
await this.assertSafeConfig(git, context, true);
|
|
89
|
+
for (const target of new Set([git.root, git.gitDir, git.commonDir, git.objectDir, ...git.metadataPaths])) {
|
|
90
|
+
await this.resolveWritePath(target, context);
|
|
91
|
+
}
|
|
92
|
+
const disabledHooksPath = this.disabledHooksPath || join(tmpdir(), `machine-bridge-mcp-disabled-hooks-${randomBytes(16).toString("hex")}`);
|
|
93
|
+
if (!isAbsolute(disabledHooksPath)) throw new BridgeError("integrity_error", "Git commit hook isolation path is unavailable");
|
|
94
|
+
await createStructuredGitCommit({
|
|
95
|
+
git,
|
|
96
|
+
message,
|
|
97
|
+
disabledHooksPath,
|
|
98
|
+
runInternalProcess: this.runInternalProcess,
|
|
99
|
+
gitExecutable: this.gitExecutable,
|
|
100
|
+
context,
|
|
101
|
+
environment: STRUCTURED_GIT_ENVIRONMENT,
|
|
102
|
+
});
|
|
103
|
+
return { code: 0, committed: true, path: this.displayPath(git.target, context), gitRoot: this.displayPath(git.root, context) };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
assertSafeConfig(git, context, inspectWorkingTree) {
|
|
107
|
+
return assertStructuredGitConfigSafe({
|
|
108
|
+
git,
|
|
109
|
+
inspectWorkingTree,
|
|
110
|
+
resolveExistingPath: this.resolveExistingPath,
|
|
111
|
+
runInternalProcess: this.runInternalProcess,
|
|
112
|
+
gitExecutable: this.gitExecutable,
|
|
113
|
+
context,
|
|
114
|
+
environment: STRUCTURED_GIT_ENVIRONMENT,
|
|
115
|
+
});
|
|
60
116
|
}
|
|
61
117
|
|
|
62
118
|
async context(inputPath, context = {}) {
|
|
63
|
-
const target = await this.resolveExistingPath(inputPath);
|
|
119
|
+
const target = await this.resolveExistingPath(inputPath, context);
|
|
64
120
|
const info = await stat(target);
|
|
65
121
|
const cwd = info.isDirectory() ? target : dirname(target);
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
const
|
|
69
|
-
const repoRelative = relative(root, target);
|
|
122
|
+
const repository = await this.discoverRepository(cwd, context);
|
|
123
|
+
if (!repository) return { ok: false, target, result: { code: 128, stdout: "", stderr: "not a Git repository within the permitted path boundary" } };
|
|
124
|
+
const repoRelative = relative(repository.root, target);
|
|
70
125
|
if (repoRelative.startsWith(`..${sep}`) || repoRelative === ".." || isAbsolute(repoRelative)) {
|
|
71
126
|
return { ok: false, target, result: { code: 128, stdout: "", stderr: "target is outside the detected git repository" } };
|
|
72
127
|
}
|
|
73
|
-
return { ok: true, target,
|
|
128
|
+
return { ok: true, target, ...repository, pathspec: repoRelative || "" };
|
|
74
129
|
}
|
|
130
|
+
|
|
131
|
+
async discoverRepository(start, context = {}) {
|
|
132
|
+
let root = await this.resolveExistingPath(start, context);
|
|
133
|
+
while (true) {
|
|
134
|
+
const marker = join(root, ".git");
|
|
135
|
+
const entry = await pathEntryIfExists(marker);
|
|
136
|
+
if (entry) {
|
|
137
|
+
if (entry.isSymbolicLink()) throw new BridgeError("path_boundary", "Git metadata marker must not be a symbolic link");
|
|
138
|
+
let gitDir;
|
|
139
|
+
if (entry.isDirectory()) gitDir = await this.resolveExistingPath(marker, context);
|
|
140
|
+
else if (entry.isFile()) {
|
|
141
|
+
const value = readGitPathFile(marker, "Git metadata pointer", true);
|
|
142
|
+
gitDir = await this.resolveExistingPath(isAbsolute(value) ? value : resolve(root, value), context);
|
|
143
|
+
} else throw new BridgeError("invalid_request", "Git metadata marker must be a directory or regular pointer file");
|
|
144
|
+
const gitInfo = await stat(gitDir);
|
|
145
|
+
if (!gitInfo.isDirectory()) throw new BridgeError("invalid_request", "Git metadata directory is unavailable");
|
|
146
|
+
const commonDir = await this.resolveCommonDirectory(gitDir, context);
|
|
147
|
+
const metadata = await resolveGitMetadataBoundary({
|
|
148
|
+
gitDir,
|
|
149
|
+
commonDir,
|
|
150
|
+
resolveExistingPath: this.resolveExistingPath,
|
|
151
|
+
context,
|
|
152
|
+
});
|
|
153
|
+
if (requiresMetadataTreeScan(context)) {
|
|
154
|
+
await assertGitMetadataTreesSafe([metadata.objectDir, ...metadata.alternateObjectDirs, ...metadata.metadataPaths], { signal: context.signal });
|
|
155
|
+
}
|
|
156
|
+
return { root, gitDir, commonDir, ...metadata };
|
|
157
|
+
}
|
|
158
|
+
const parent = dirname(root);
|
|
159
|
+
if (parent === root) return null;
|
|
160
|
+
try { root = await this.resolveExistingPath(parent, context); }
|
|
161
|
+
catch (error) { if (error?.code === "path_boundary") return null; throw error; }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async resolveCommonDirectory(gitDir, context = {}) {
|
|
166
|
+
const marker = join(gitDir, "commondir");
|
|
167
|
+
const entry = await pathEntryIfExists(marker);
|
|
168
|
+
if (!entry) return gitDir;
|
|
169
|
+
if (entry.isSymbolicLink() || !entry.isFile()) throw new BridgeError("path_boundary", "Git common-directory pointer must be a regular file");
|
|
170
|
+
const value = readGitPathFile(marker, "Git common-directory pointer", false);
|
|
171
|
+
const commonDir = await this.resolveExistingPath(isAbsolute(value) ? value : resolve(gitDir, value), context);
|
|
172
|
+
const info = await stat(commonDir);
|
|
173
|
+
if (!info.isDirectory()) throw new BridgeError("invalid_request", "Git common directory is unavailable");
|
|
174
|
+
return commonDir;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function gitCommandPrefix(git) {
|
|
179
|
+
return [
|
|
180
|
+
"--no-pager",
|
|
181
|
+
"--literal-pathspecs",
|
|
182
|
+
`--git-dir=${git.gitDir}`,
|
|
183
|
+
`--work-tree=${git.root}`,
|
|
184
|
+
"-c", "core.fsmonitor=false",
|
|
185
|
+
"-c", "color.ui=false",
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
function requiresMetadataTreeScan(context) {
|
|
189
|
+
return Boolean(context?.authority) && context.authority.effectivePolicy?.unrestrictedPaths !== true;
|
|
190
|
+
}
|
|
191
|
+
function readGitPathFile(file, label, gitdirPrefix) {
|
|
192
|
+
const buffer = readBoundedRegularFileSync(file, 4096, label, {
|
|
193
|
+
verifyPathIdentity: true,
|
|
194
|
+
rejectMultipleLinks: true,
|
|
195
|
+
});
|
|
196
|
+
const text = buffer.toString("utf8");
|
|
197
|
+
const pattern = gitdirPrefix ? /^gitdir: ([^\r\n\0]+)\r?\n?$/ : /^([^\r\n\0]+)\r?\n?$/;
|
|
198
|
+
const match = pattern.exec(text);
|
|
199
|
+
if (!match || !match[1]) throw new BridgeError("invalid_request", `${label} is invalid`);
|
|
200
|
+
return match[1];
|
|
75
201
|
}
|
|
76
202
|
|
|
77
203
|
function validateRevision(value) {
|
|
@@ -81,3 +207,12 @@ function validateRevision(value) {
|
|
|
81
207
|
}
|
|
82
208
|
return revision;
|
|
83
209
|
}
|
|
210
|
+
|
|
211
|
+
function validateCommitMessage(value) {
|
|
212
|
+
if (typeof value !== "string" || !value.trim()) throw new BridgeError("invalid_request", "Git commit message is required");
|
|
213
|
+
if (value.includes("\0")) throw new BridgeError("invalid_request", "Git commit message contains a NUL byte");
|
|
214
|
+
if (Buffer.byteLength(value, "utf8") > MAX_GIT_COMMIT_MESSAGE_BYTES) {
|
|
215
|
+
throw new BridgeError("limit_exceeded", `Git commit message exceeds ${MAX_GIT_COMMIT_MESSAGE_BYTES} bytes`);
|
|
216
|
+
}
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 60_000;
|
|
2
|
+
const DEFAULT_MAXIMUM_DURATION_MS = 300_000;
|
|
3
|
+
|
|
4
|
+
export function createHardenedDownloadTimeout(artifactName, request, options = {}) {
|
|
5
|
+
const schedule = typeof options.schedule === "function" ? options.schedule : setTimeout;
|
|
6
|
+
const cancel = typeof options.cancel === "function" ? options.cancel : clearTimeout;
|
|
7
|
+
const idleMs = boundedDuration(options.idleMs, DEFAULT_IDLE_TIMEOUT_MS, 1, DEFAULT_IDLE_TIMEOUT_MS);
|
|
8
|
+
const maximumMs = boundedDuration(options.maximumMs, DEFAULT_MAXIMUM_DURATION_MS, idleMs, DEFAULT_MAXIMUM_DURATION_MS);
|
|
9
|
+
let idleTimer = null;
|
|
10
|
+
let maximumTimer = null;
|
|
11
|
+
let finished = false;
|
|
12
|
+
|
|
13
|
+
const cancelTimers = () => {
|
|
14
|
+
if (idleTimer !== null) cancel(idleTimer);
|
|
15
|
+
if (maximumTimer !== null) cancel(maximumTimer);
|
|
16
|
+
idleTimer = null;
|
|
17
|
+
maximumTimer = null;
|
|
18
|
+
};
|
|
19
|
+
const fail = (message) => {
|
|
20
|
+
if (finished) return;
|
|
21
|
+
finished = true;
|
|
22
|
+
cancelTimers();
|
|
23
|
+
request.destroy(Object.assign(new Error(`${artifactName} tarball ${message}`), { code: "ETIMEDOUT" }));
|
|
24
|
+
};
|
|
25
|
+
const armIdle = () => {
|
|
26
|
+
if (finished) return;
|
|
27
|
+
if (idleTimer !== null) cancel(idleTimer);
|
|
28
|
+
idleTimer = schedule(() => fail("download stalled"), idleMs);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
maximumTimer = schedule(() => fail("download exceeded its maximum duration"), maximumMs);
|
|
32
|
+
armIdle();
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
progress: armIdle,
|
|
35
|
+
clear() {
|
|
36
|
+
finished = true;
|
|
37
|
+
cancelTimers();
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function boundedDuration(value, fallback, minimum, maximum) {
|
|
43
|
+
const number = Number(value);
|
|
44
|
+
if (!Number.isFinite(number)) return fallback;
|
|
45
|
+
return Math.min(maximum, Math.max(minimum, Math.floor(number)));
|
|
46
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import https from "node:https";
|
|
2
|
+
import { createHardenedDownloadTimeout } from "./hardened-npm-download-timeout.mjs";
|
|
3
|
+
const MAX_DOWNLOAD_BYTES = 24 * 1024 * 1024;
|
|
4
|
+
const DOWNLOAD_ATTEMPTS = 3; const RETRY_BASE_MS = 750;
|
|
5
|
+
const HTTP_PROTOCOLS = new Set(["http:", "https:"]); const CONTROL_CHARACTERS = /[\0\r\n]/;
|
|
6
|
+
const PROXY_URL_KEYS = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"]; const NO_PROXY_KEYS = ["NO_PROXY", "no_proxy"];
|
|
7
|
+
const TRANSIENT_NETWORK_CODES = new Set(["ECONNRESET", "ETIMEDOUT", "EAI_AGAIN", "ENETUNREACH", "ECONNREFUSED", "ECONNABORTED", "EPIPE"]);
|
|
8
|
+
export async function downloadHardenedNpmArtifact(artifact, options = {}) {
|
|
9
|
+
for (let attempt = 1; attempt <= DOWNLOAD_ATTEMPTS; attempt += 1) {
|
|
10
|
+
try { return await downloadAttempt(artifact, options); }
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (attempt === DOWNLOAD_ATTEMPTS || !retryableDownloadError(error)) throw error;
|
|
13
|
+
await retryDelay(attempt, options);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`${artifact.name} tarball download exhausted retries`);
|
|
17
|
+
}
|
|
18
|
+
function downloadAttempt(artifact, options) {
|
|
19
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
20
|
+
const target = new URL(artifact.url);
|
|
21
|
+
if (target.protocol !== "https:" || target.username || target.password || target.port || target.search || target.hash) {
|
|
22
|
+
rejectPromise(new Error(`${artifact.name} tarball URL is not an exact HTTPS registry URL`)); return;
|
|
23
|
+
}
|
|
24
|
+
let context = null;
|
|
25
|
+
let request;
|
|
26
|
+
try {
|
|
27
|
+
context = prepareRequestAgent(target, options);
|
|
28
|
+
const requestFn = typeof options.request === "function" ? options.request : https.request;
|
|
29
|
+
request = requestFn({
|
|
30
|
+
protocol: target.protocol, hostname: target.hostname, method: "GET", path: target.pathname,
|
|
31
|
+
headers: { Accept: "application/octet-stream", "User-Agent": "machine-bridge-mcp-hardened-npm" },
|
|
32
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
33
|
+
});
|
|
34
|
+
} catch (error) { rejectPromise(cleanupResult(context, error, `${artifact.name} tarball download setup failed`)); return; }
|
|
35
|
+
let settled = false;
|
|
36
|
+
const timeout = createHardenedDownloadTimeout(artifact.name, request, options.downloadTimeout);
|
|
37
|
+
const finish = (error, value) => {
|
|
38
|
+
if (settled) return;
|
|
39
|
+
settled = true; timeout.clear();
|
|
40
|
+
const outcome = cleanupResult(context, error, `${artifact.name} tarball download failed`);
|
|
41
|
+
if (outcome) rejectPromise(outcome); else resolvePromise(value);
|
|
42
|
+
};
|
|
43
|
+
request.once("error", (error) => finish(error));
|
|
44
|
+
request.once("response", (response) => {
|
|
45
|
+
timeout.progress(); const status = Number(response.statusCode || 0);
|
|
46
|
+
if (status !== 200) {
|
|
47
|
+
response.resume();
|
|
48
|
+
finish(Object.assign(new Error(`failed to download ${artifact.name} ${artifact.version}: HTTP ${status || "unknown"}`), { statusCode: status })); return;
|
|
49
|
+
}
|
|
50
|
+
const declaredBytes = Number(response.headers["content-length"]);
|
|
51
|
+
if (Number.isFinite(declaredBytes) && declaredBytes > artifact.maximumBytes) {
|
|
52
|
+
response.destroy(); finish(new Error(`${artifact.name} tarball exceeds its byte limit`)); return;
|
|
53
|
+
}
|
|
54
|
+
const chunks = []; let total = 0;
|
|
55
|
+
response.on("data", (chunk) => {
|
|
56
|
+
timeout.progress(); const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); total += buffer.length;
|
|
57
|
+
if (total > artifact.maximumBytes || total > MAX_DOWNLOAD_BYTES) {
|
|
58
|
+
response.destroy(Object.assign(new Error(`${artifact.name} tarball exceeds its byte limit`), { code: "ERR_RESPONSE_TOO_LARGE" })); return;
|
|
59
|
+
}
|
|
60
|
+
chunks.push(buffer);
|
|
61
|
+
});
|
|
62
|
+
response.once("error", (error) => finish(error)); response.once("aborted", () => finish(Object.assign(new Error(`${artifact.name} tarball response aborted`), { code: "ECONNRESET" })));
|
|
63
|
+
response.once("end", () => finish(null, Buffer.concat(chunks, total)));
|
|
64
|
+
});
|
|
65
|
+
try { request.end(); } catch (error) { finish(error); }
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function prepareRequestAgent(target, options) {
|
|
69
|
+
if (typeof options.proxyAgentForUrl === "function") return { agent: options.proxyAgentForUrl(target.href, options.proxyResolver)?.agent || null };
|
|
70
|
+
if (Object.hasOwn(options, "agent")) return { agent: options.agent || null };
|
|
71
|
+
const proxyEnv = normalizeProxyEnvironment(options.proxyEnv ?? process.env);
|
|
72
|
+
const agent = (typeof options.createAgent === "function" ? options.createAgent : proxyAgentForHttp)(proxyEnv);
|
|
73
|
+
if (!agent || typeof agent !== "object") throw new Error("hardened npm proxy agent could not be initialized");
|
|
74
|
+
return { agent, dispose: typeof agent.destroy === "function" ? () => agent.destroy() : null };
|
|
75
|
+
}
|
|
76
|
+
function normalizeProxyEnvironment(source) {
|
|
77
|
+
if (!source || typeof source !== "object") throw proxyConfigurationError("HTTP proxy environment must be an object");
|
|
78
|
+
const normalized = {};
|
|
79
|
+
for (const key of PROXY_URL_KEYS) {
|
|
80
|
+
if (source[key] === undefined || source[key] === null) continue;
|
|
81
|
+
const value = String(source[key]).trim();
|
|
82
|
+
if (!value) { normalized[key] = ""; continue; }
|
|
83
|
+
if (CONTROL_CHARACTERS.test(value)) throw proxyConfigurationError("HTTP proxy configuration contains a prohibited control character");
|
|
84
|
+
let proxyUrl; try { proxyUrl = new URL(value); } catch { throw proxyConfigurationError("HTTP proxy configuration is not a valid URL"); }
|
|
85
|
+
if (!HTTP_PROTOCOLS.has(proxyUrl.protocol)) throw proxyConfigurationError("HTTP proxy configuration must use HTTP or HTTPS");
|
|
86
|
+
normalized[key] = proxyUrl.href;
|
|
87
|
+
}
|
|
88
|
+
for (const key of NO_PROXY_KEYS) {
|
|
89
|
+
if (source[key] === undefined || source[key] === null) continue;
|
|
90
|
+
const value = String(source[key]);
|
|
91
|
+
if (CONTROL_CHARACTERS.test(value)) throw proxyConfigurationError("NO_PROXY configuration contains a prohibited control character");
|
|
92
|
+
normalized[key] = value;
|
|
93
|
+
}
|
|
94
|
+
return normalized;
|
|
95
|
+
}
|
|
96
|
+
function retryableDownloadError(error) {
|
|
97
|
+
const status = Number(error?.statusCode || 0);
|
|
98
|
+
return TRANSIENT_NETWORK_CODES.has(String(error?.code || "")) || String(error?.message || "").trim().toLowerCase() === "aborted" || status === 429 || (status >= 500 && status <= 599);
|
|
99
|
+
}
|
|
100
|
+
function retryDelay(attempt, options) {
|
|
101
|
+
const milliseconds = Math.min(RETRY_BASE_MS * attempt, 3_000);
|
|
102
|
+
return typeof options.sleep === "function" ? Promise.resolve(options.sleep(milliseconds)) : new Promise((resolvePromise) => { setTimeout(resolvePromise, milliseconds); });
|
|
103
|
+
}
|
|
104
|
+
function proxyAgentForHttp(proxyEnv) { return new https.Agent({ proxyEnv }); }
|
|
105
|
+
function proxyConfigurationError(message) { return Object.assign(new Error(message), { code: "http_proxy_configuration" }); }
|
|
106
|
+
function cleanupResult(context, primary, message) {
|
|
107
|
+
try { context?.dispose?.(); return primary; }
|
|
108
|
+
catch (cleanup) { return primary ? new AggregateError([primary, cleanup], `${message} and proxy-agent cleanup was incomplete`) : cleanup; }
|
|
109
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { resolveTrustedExecutable } from "./trusted-executable.mjs";
|
|
3
|
+
|
|
4
|
+
export function resolveHardenedNpmTarExecutable(stateRoot, options = {}) {
|
|
5
|
+
const windowsRoot = String(options.windowsRoot || process.env.SystemRoot || process.env.WINDIR || "C:\\Windows");
|
|
6
|
+
return resolveTrustedExecutable({
|
|
7
|
+
platform: options.platform,
|
|
8
|
+
candidates: [
|
|
9
|
+
options.tarExecutable,
|
|
10
|
+
path.join(windowsRoot, "System32", "tar.exe"),
|
|
11
|
+
"/usr/bin/tar",
|
|
12
|
+
"/bin/tar",
|
|
13
|
+
"/usr/local/bin/tar",
|
|
14
|
+
].filter(Boolean),
|
|
15
|
+
stateRoot,
|
|
16
|
+
home: options.home,
|
|
17
|
+
label: "tar extractor",
|
|
18
|
+
reason: "trusted_tar_unavailable",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { lstatSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { replaceFileAtomicallySync } from "./exclusive-file.mjs";
|
|
4
|
+
import {
|
|
5
|
+
privateToolchainIntegrityError,
|
|
6
|
+
throwOperationalOrIntegrity,
|
|
7
|
+
} from "./private-toolchain-integrity.mjs";
|
|
8
|
+
import { ensureOwnerOnlyDirectorySync, readBoundedRegularFileSync } from "./secure-file.mjs";
|
|
9
|
+
|
|
10
|
+
const MAX_MARKER_BYTES = 16 * 1024;
|
|
11
|
+
const HARDENED_NPM_MARKER = ".machine-bridge-mcp-hardened-npm.json";
|
|
12
|
+
|
|
13
|
+
export function verifyHardenedNpmTree(root, artifacts, execute) {
|
|
14
|
+
const target = path.resolve(root);
|
|
15
|
+
ensureOwnerOnlyDirectorySync(target);
|
|
16
|
+
const npmRoot = path.join(target, "package");
|
|
17
|
+
const nodeModules = path.join(npmRoot, "node_modules");
|
|
18
|
+
requireRealDirectory(npmRoot, "hardened npm package root");
|
|
19
|
+
requireRealDirectory(nodeModules, "hardened npm node_modules");
|
|
20
|
+
verifyHardenedPackageIdentity(npmRoot, "npm", artifacts[0].version);
|
|
21
|
+
verifyHardenedPackageIdentity(path.join(nodeModules, "undici"), "undici", artifacts[1].version);
|
|
22
|
+
verifyHardenedPackageIdentity(path.join(nodeModules, "brace-expansion"), "brace-expansion", artifacts[2].version);
|
|
23
|
+
const binRoot = path.join(npmRoot, "bin");
|
|
24
|
+
requireRealDirectory(binRoot, "hardened npm bin directory");
|
|
25
|
+
const cli = path.join(binRoot, "npm-cli.js");
|
|
26
|
+
let info;
|
|
27
|
+
try { info = lstatSync(cli); }
|
|
28
|
+
catch (error) { throwOperationalOrIntegrity(error, "hardened npm CLI is missing or unreadable"); }
|
|
29
|
+
if (info.isSymbolicLink() || !info.isFile() || Number(info.nlink) !== 1 || (process.platform !== "win32" && (Number(info.mode) & 0o022) !== 0)) {
|
|
30
|
+
throw privateToolchainIntegrityError("hardened npm CLI is not a private real regular file");
|
|
31
|
+
}
|
|
32
|
+
const reported = String(execute(process.execPath, [cli, "--version"], { capture: true })).trim();
|
|
33
|
+
if (reported !== artifacts[0].version) {
|
|
34
|
+
throw privateToolchainIntegrityError(`hardened npm CLI reported ${reported || "no version"}`);
|
|
35
|
+
}
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
cli,
|
|
38
|
+
version: artifacts[0].version,
|
|
39
|
+
undiciVersion: artifacts[1].version,
|
|
40
|
+
braceExpansionVersion: artifacts[2].version,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function requireRealDirectory(directory, label) {
|
|
45
|
+
let info;
|
|
46
|
+
try { info = lstatSync(directory); }
|
|
47
|
+
catch (error) { throwOperationalOrIntegrity(error, `${label} is missing or unreadable`); }
|
|
48
|
+
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
49
|
+
throw privateToolchainIntegrityError(`${label} is not a real directory`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function readHardenedNpmMarker(root) {
|
|
54
|
+
let bytes;
|
|
55
|
+
try {
|
|
56
|
+
bytes = readBoundedRegularFileSync(path.join(root, HARDENED_NPM_MARKER), MAX_MARKER_BYTES, "hardened npm marker", {
|
|
57
|
+
verifyPathIdentity: true,
|
|
58
|
+
rejectMultipleLinks: true,
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error?.code === "ENOENT") return null;
|
|
62
|
+
throwOperationalOrIntegrity(error, "hardened npm marker is structurally invalid");
|
|
63
|
+
}
|
|
64
|
+
try { return JSON.parse(bytes.toString("utf8")); }
|
|
65
|
+
catch (error) { throw privateToolchainIntegrityError("hardened npm marker is not valid JSON", error); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function hardenedNpmMarkerMatches(marker, identity) {
|
|
69
|
+
return marker?.schema_version === 1
|
|
70
|
+
&& marker.digest === identity.digest
|
|
71
|
+
&& marker.npm === identity.npmVersion
|
|
72
|
+
&& marker.undici === identity.undiciVersion
|
|
73
|
+
&& marker.brace_expansion === identity.braceExpansionVersion;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function writeHardenedNpmMarker(root, identity) {
|
|
77
|
+
replaceFileAtomicallySync(path.join(root, HARDENED_NPM_MARKER), `${JSON.stringify({
|
|
78
|
+
schema_version: 1,
|
|
79
|
+
digest: identity.digest,
|
|
80
|
+
npm: identity.npmVersion,
|
|
81
|
+
undici: identity.undiciVersion,
|
|
82
|
+
brace_expansion: identity.braceExpansionVersion,
|
|
83
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function verifyHardenedPackageIdentity(directory, name, version) {
|
|
87
|
+
requireRealDirectory(directory, `${name} package directory`);
|
|
88
|
+
const file = path.join(directory, "package.json");
|
|
89
|
+
let bytes;
|
|
90
|
+
try {
|
|
91
|
+
bytes = readBoundedRegularFileSync(file, 2 * 1024 * 1024, `${name} package manifest`, {
|
|
92
|
+
verifyPathIdentity: true,
|
|
93
|
+
rejectMultipleLinks: true,
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throwOperationalOrIntegrity(error, `${name} package manifest is missing or structurally invalid`);
|
|
97
|
+
}
|
|
98
|
+
let manifest;
|
|
99
|
+
try { manifest = JSON.parse(bytes.toString("utf8")); }
|
|
100
|
+
catch (error) { throw privateToolchainIntegrityError(`${name} package manifest is not valid JSON`, error); }
|
|
101
|
+
if (manifest?.name !== name || manifest?.version !== version) {
|
|
102
|
+
throw privateToolchainIntegrityError(`${name} package identity does not match ${version}`);
|
|
103
|
+
}
|
|
104
|
+
}
|