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,54 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert filesystem identity fields without allowing IEEE-754 rounding to make distinct
|
|
5
|
+
* platform identifiers compare equal. BigInt observations are preferred; injected Number
|
|
6
|
+
* observations remain supported only when exactly representable. When the platform
|
|
7
|
+
* exposes a change-time generation, retain it so immediate inode reuse cannot create an
|
|
8
|
+
* ABA-equivalent identity during destructive snapshot checks.
|
|
9
|
+
* @param {unknown} info
|
|
10
|
+
* @param {string} [label]
|
|
11
|
+
*/
|
|
12
|
+
export function filesystemIdentity(info, label = "filesystem entry") {
|
|
13
|
+
if (!info || typeof info !== "object") throw new Error(`${label} identity is unavailable`);
|
|
14
|
+
const record = /** @type {Record<string, unknown>} */ (info);
|
|
15
|
+
const generation = filesystemGeneration(record, label);
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
dev: exactFilesystemInteger(record.dev, `${label} device`),
|
|
18
|
+
ino: exactFilesystemInteger(record.ino, `${label} inode`),
|
|
19
|
+
...generation,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** @param {{dev: bigint, ino: bigint, ctimeNs?: bigint, ctimeMs?: number} | null | undefined} left @param {{dev: bigint, ino: bigint, ctimeNs?: bigint, ctimeMs?: number} | null | undefined} right */
|
|
23
|
+
export function sameFilesystemIdentity(left, right) {
|
|
24
|
+
if (!left || !right || left.dev !== right.dev || left.ino !== right.ino) return false;
|
|
25
|
+
if (left.ctimeNs !== undefined || right.ctimeNs !== undefined) return left.ctimeNs !== undefined && left.ctimeNs === right.ctimeNs;
|
|
26
|
+
if (left.ctimeMs !== undefined || right.ctimeMs !== undefined) return left.ctimeMs !== undefined && left.ctimeMs === right.ctimeMs;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
/** @param {Record<string, unknown>} record @param {string} label */
|
|
30
|
+
function filesystemGeneration(record, label) {
|
|
31
|
+
if (record.ctimeNs !== undefined) return { ctimeNs: exactFilesystemInteger(record.ctimeNs, `${label} change time`) };
|
|
32
|
+
if (record.ctimeMs !== undefined) return { ctimeMs: filesystemTimeMs(record.ctimeMs, `${label} change time`) };
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @param {unknown} value @param {string} label */
|
|
37
|
+
export function exactFilesystemInteger(value, label) {
|
|
38
|
+
if (typeof value === "bigint") {
|
|
39
|
+
if (value < 0n) throw new Error(`${label} is invalid`);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return BigInt(value);
|
|
43
|
+
throw new Error(`${label} cannot be represented losslessly`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @param {unknown} value @param {string} label */
|
|
47
|
+
export function filesystemTimeMs(value, label) {
|
|
48
|
+
if (typeof value === "bigint") {
|
|
49
|
+
if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`${label} cannot be represented safely`);
|
|
50
|
+
return Number(value);
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
53
|
+
throw new Error(`${label} is invalid`);
|
|
54
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { BridgeError } from "./errors.mjs";
|
|
3
|
+
/** @type {Readonly<Record<string, string>>} */
|
|
4
|
+
export const STRUCTURED_GIT_FIXED_ENVIRONMENT = Object.freeze({
|
|
5
|
+
GIT_OPTIONAL_LOCKS: "0", GIT_CONFIG_NOSYSTEM: "1", GIT_ATTR_NOSYSTEM: "1", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0",
|
|
6
|
+
});
|
|
7
|
+
/** @param {unknown} value @returns {Readonly<Record<string, string>>} */
|
|
8
|
+
export function validateFixedProcessEnvironment(value) {
|
|
9
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
10
|
+
throw new BridgeError("invalid_request", "internal process environment must be an object");
|
|
11
|
+
}
|
|
12
|
+
/** @type {Record<string, string>} */
|
|
13
|
+
const normalized = Object.create(null);
|
|
14
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
15
|
+
if (!Object.hasOwn(STRUCTURED_GIT_FIXED_ENVIRONMENT, key) || raw !== STRUCTURED_GIT_FIXED_ENVIRONMENT[key]) {
|
|
16
|
+
throw new BridgeError("invalid_request", "internal process environment override is not approved");
|
|
17
|
+
}
|
|
18
|
+
normalized[key] = raw;
|
|
19
|
+
}
|
|
20
|
+
return Object.freeze(normalized);
|
|
21
|
+
}
|
|
@@ -4,17 +4,15 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
|
|
6
6
|
import { LocalRuntime } from "./runtime.mjs";
|
|
7
|
+
import { isTerminalManagedJobStatus } from "./managed-job-terminal.mjs";
|
|
7
8
|
import { generateSshKeyPair } from "./ssh-key.mjs";
|
|
8
9
|
import { runExecutable } from "./shell.mjs";
|
|
9
10
|
import { allToolNames, assertCanonicalFullPolicy, policyProfile } from "./tools.mjs";
|
|
11
|
+
const FULL_ACCESS_RESOURCE_WAIT_MS = 10_000;
|
|
12
|
+
const FULL_ACCESS_JOB_WAIT_MS = 5 * 60_000;
|
|
13
|
+
const FULL_ACCESS_JOB_CLEANUP_WAIT_MS = 30_000;
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
"succeeded", "failed", "cancelled", "runner_failed", "runner_launch_failed",
|
|
13
|
-
"recovery_failed", "recovery_exhausted", "succeeded_cleanup_failed",
|
|
14
|
-
"failed_cleanup_failed", "cancelled_cleanup_failed",
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
export async function runFullAccessTest({ workspace, policy = policyProfile("full", "explicit") } = {}) {
|
|
15
|
+
export async function runFullAccessTest({ workspace, policy = policyProfile("full", "explicit"), resourceCoordinatorOptions = null } = {}) {
|
|
18
16
|
const canonicalPolicy = assertCanonicalFullPolicy(policy);
|
|
19
17
|
const root = await mkdtemp(join(tmpdir(), "machine-mcp-full-test-"));
|
|
20
18
|
const jobRoot = join(root, "jobs");
|
|
@@ -28,6 +26,8 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
|
|
|
28
26
|
const previousSentinel = process.env[sentinelKey];
|
|
29
27
|
process.env[sentinelKey] = "visible";
|
|
30
28
|
let runtime;
|
|
29
|
+
let unsettledJobId = "";
|
|
30
|
+
let removeRoot = true;
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
33
|
runtime = new LocalRuntime({
|
|
@@ -37,6 +37,9 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
|
|
|
37
37
|
resources: {},
|
|
38
38
|
logger: silentLogger(),
|
|
39
39
|
recoverJobs: false,
|
|
40
|
+
resourceCoordinatorRoot: join(root, "resource-coordinator"),
|
|
41
|
+
resourceCoordinatorOptions,
|
|
42
|
+
processResourceWaitMs: FULL_ACCESS_RESOURCE_WAIT_MS,
|
|
40
43
|
});
|
|
41
44
|
|
|
42
45
|
const toolNames = runtime.tools();
|
|
@@ -68,7 +71,7 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
|
|
|
68
71
|
checks.push(check("full-parent-environment", inherited.code === 0 && inherited.stdout === "visible"));
|
|
69
72
|
|
|
70
73
|
const shellCommand = process.platform === "win32" ? "[Console]::Out.Write('full-shell')" : "printf full-shell";
|
|
71
|
-
const shell = await runtime.execCommand(shellCommand, 10);
|
|
74
|
+
const shell = await runtime.execCommand({ command: shellCommand, timeout_seconds: 10 });
|
|
72
75
|
checks.push(check("shell-execution", shell.code === 0 && shell.stdout.trim() === "full-shell"));
|
|
73
76
|
|
|
74
77
|
const key = await generateSshKeyPair({
|
|
@@ -141,7 +144,9 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
|
|
|
141
144
|
steps: [{ argv: [process.execPath, "{{temp:main.js}}", mainMarker], timeout_seconds: 10 }],
|
|
142
145
|
finally_steps: [{ argv: [process.execPath, "-e", "require('node:fs').writeFileSync(process.argv[1],'cleanup')", cleanupMarker], timeout_seconds: 10 }],
|
|
143
146
|
});
|
|
144
|
-
|
|
147
|
+
unsettledJobId = accepted.job_id;
|
|
148
|
+
const job = await waitForJob(runtime.managedJobManager, accepted.job_id, FULL_ACCESS_JOB_WAIT_MS);
|
|
149
|
+
unsettledJobId = "";
|
|
145
150
|
checks.push(check("detached-managed-job", job.status === "succeeded"
|
|
146
151
|
&& await fileEquals(mainMarker, "main")
|
|
147
152
|
&& await fileEquals(cleanupMarker, "cleanup"), {
|
|
@@ -171,21 +176,34 @@ export async function runFullAccessTest({ workspace, policy = policyProfile("ful
|
|
|
171
176
|
},
|
|
172
177
|
};
|
|
173
178
|
} finally {
|
|
174
|
-
runtime
|
|
179
|
+
if (runtime && unsettledJobId) {
|
|
180
|
+
try {
|
|
181
|
+
const status = runtime.managedJobManager.read({ job_id: unsettledJobId });
|
|
182
|
+
if (!isTerminalManagedJobStatus(status.status)) {
|
|
183
|
+
runtime.managedJobManager.cancel({ job_id: unsettledJobId });
|
|
184
|
+
await waitForJob(runtime.managedJobManager, unsettledJobId, FULL_ACCESS_JOB_CLEANUP_WAIT_MS);
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
removeRoot = false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
await runtime?.stop();
|
|
175
191
|
if (previousSentinel === undefined) delete process.env[sentinelKey];
|
|
176
192
|
else process.env[sentinelKey] = previousSentinel;
|
|
177
|
-
await rm(root, { recursive: true, force: true });
|
|
193
|
+
if (removeRoot) await rm(root, { recursive: true, force: true });
|
|
178
194
|
}
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
async function waitForJob(manager, jobId, timeoutMs) {
|
|
182
198
|
const deadline = createMonotonicDeadline(timeoutMs);
|
|
199
|
+
let lastStatus = "unknown";
|
|
183
200
|
while (!deadline.expired()) {
|
|
184
201
|
const value = manager.read({ job_id: jobId });
|
|
185
|
-
|
|
202
|
+
lastStatus = String(value.status || "unknown");
|
|
203
|
+
if (isTerminalManagedJobStatus(value.status)) return value;
|
|
186
204
|
await new Promise((resolvePromise) => { setTimeout(resolvePromise, 50); });
|
|
187
205
|
}
|
|
188
|
-
throw new Error(
|
|
206
|
+
throw new Error(`full access managed-job test timed out while status=${lastStatus}`);
|
|
189
207
|
}
|
|
190
208
|
|
|
191
209
|
async function equivalentPath(left, right) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { BridgeError } from "./errors.mjs";
|
|
2
|
+
import { assertStructuredGitCommitStateClear } from "./git-operation-state.mjs";
|
|
3
|
+
|
|
4
|
+
const GIT_HASH = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
5
|
+
|
|
6
|
+
export async function createStructuredGitCommit({ git, message, disabledHooksPath, runInternalProcess, gitExecutable, context = {}, environment = {} }) {
|
|
7
|
+
const base = [
|
|
8
|
+
"--no-pager",
|
|
9
|
+
`--git-dir=${git.gitDir}`,
|
|
10
|
+
`--work-tree=${git.root}`,
|
|
11
|
+
"-c", "core.fsmonitor=false",
|
|
12
|
+
"-c", "color.ui=false",
|
|
13
|
+
"-c", `core.hooksPath=${disabledHooksPath}`,
|
|
14
|
+
"-c", "commit.gpgSign=false",
|
|
15
|
+
];
|
|
16
|
+
const run = (args, stdin = null) => runInternalProcess(
|
|
17
|
+
gitExecutable(), [...base, ...args], 60_000, true, 64 * 1024, context, git.root, stdin, environment,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
await assertStructuredGitCommitStateClear(git.gitDir);
|
|
21
|
+
const treeResult = await run(["write-tree"]);
|
|
22
|
+
const tree = requireHash(treeResult, "Git staged index could not be written");
|
|
23
|
+
|
|
24
|
+
const headResult = await run(["rev-parse", "--verify", "HEAD^{commit}"]);
|
|
25
|
+
let oldHead = "";
|
|
26
|
+
if (headResult.code === 0) oldHead = requireHash(headResult, "Git HEAD is invalid");
|
|
27
|
+
|
|
28
|
+
const symbolic = await run(["symbolic-ref", "-q", "HEAD"]);
|
|
29
|
+
if (symbolic.code !== 0 && symbolic.code !== 1) throw commitFailure("Git HEAD could not be resolved", symbolic.code);
|
|
30
|
+
const symbolicRef = symbolic.code === 0 ? String(symbolic.stdout || "").trim() : "";
|
|
31
|
+
if (symbolicRef) {
|
|
32
|
+
const refValidation = await run(["check-ref-format", symbolicRef]);
|
|
33
|
+
if (refValidation.code !== 0) throw commitFailure("Git HEAD reference is invalid", refValidation.code);
|
|
34
|
+
}
|
|
35
|
+
if (!oldHead && !symbolicRef) throw commitFailure("Git repository has no commit parent or branch reference", headResult.code);
|
|
36
|
+
|
|
37
|
+
if (oldHead) {
|
|
38
|
+
const headTreeResult = await run(["rev-parse", "--verify", "HEAD^{tree}"]);
|
|
39
|
+
const headTree = requireHash(headTreeResult, "Git HEAD tree is invalid");
|
|
40
|
+
if (headTree === tree) throw commitFailure("Git staged index has no changes to commit", 1, "git_commit_empty");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const commitArgs = ["commit-tree", tree];
|
|
44
|
+
if (oldHead) commitArgs.push("-p", oldHead);
|
|
45
|
+
commitArgs.push("-F", "-");
|
|
46
|
+
const commitResult = await run(commitArgs, message);
|
|
47
|
+
const commit = requireHash(commitResult, "Git commit object could not be created");
|
|
48
|
+
|
|
49
|
+
const targetRef = symbolicRef || "HEAD";
|
|
50
|
+
const expected = oldHead || "0".repeat(tree.length);
|
|
51
|
+
const updateResult = await run(["update-ref", "-m", "machine-bridge-mcp structured commit", targetRef, commit, expected]);
|
|
52
|
+
if (updateResult.code !== 0) {
|
|
53
|
+
throw commitFailure("Git history changed concurrently or the reference update failed", updateResult.code, "git_commit_conflict");
|
|
54
|
+
}
|
|
55
|
+
return Object.freeze({ committed: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function requireHash(result, message) {
|
|
59
|
+
if (result.code !== 0) throw commitFailure(message, result.code);
|
|
60
|
+
const value = String(result.stdout || "").trim();
|
|
61
|
+
if (!GIT_HASH.test(value)) throw commitFailure(message, result.code);
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function commitFailure(message, code, reason = "git_commit_failed") {
|
|
66
|
+
return new BridgeError("execution_failed", message, {
|
|
67
|
+
details: { reason, code: Number.isInteger(code) ? code : 1 },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { BridgeError } from "./errors.mjs";
|
|
3
|
+
import { pathEntryIfExists } from "./path-inspection.mjs";
|
|
4
|
+
|
|
5
|
+
const MAX_GIT_CONFIG_BYTES = 256 * 1024;
|
|
6
|
+
|
|
7
|
+
export async function assertStructuredGitConfigSafe({
|
|
8
|
+
git,
|
|
9
|
+
inspectWorkingTree = false,
|
|
10
|
+
resolveExistingPath,
|
|
11
|
+
runInternalProcess,
|
|
12
|
+
gitExecutable,
|
|
13
|
+
context = {},
|
|
14
|
+
environment = {},
|
|
15
|
+
}) {
|
|
16
|
+
const files = new Set([join(git.commonDir, "config"), join(git.gitDir, "config.worktree")]);
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
const entry = await pathEntryIfExists(file);
|
|
19
|
+
if (!entry) continue;
|
|
20
|
+
if (entry.isSymbolicLink() || !entry.isFile() || Number(entry.nlink) !== 1 || Number(entry.size) > MAX_GIT_CONFIG_BYTES) {
|
|
21
|
+
throw unsafeConfigError();
|
|
22
|
+
}
|
|
23
|
+
const canonical = await resolveExistingPath(file, context);
|
|
24
|
+
const result = await runInternalProcess(
|
|
25
|
+
gitExecutable(),
|
|
26
|
+
["--no-pager", "config", "--file", canonical, "--no-includes", "--null", "--name-only", "--list"],
|
|
27
|
+
10_000,
|
|
28
|
+
true,
|
|
29
|
+
MAX_GIT_CONFIG_BYTES,
|
|
30
|
+
context,
|
|
31
|
+
git.root,
|
|
32
|
+
null,
|
|
33
|
+
environment,
|
|
34
|
+
);
|
|
35
|
+
if (result.code !== 0) throw unsafeConfigError();
|
|
36
|
+
const keys = String(result.stdout || "").split("\0").map((value) => value.trim().toLowerCase()).filter(Boolean);
|
|
37
|
+
if (keys.some((key) => key.startsWith("include.") || key.startsWith("includeif.") || key === "core.attributesfile" || key === "diff.orderfile"
|
|
38
|
+
|| key === "extensions.partialclone" || /^remote\..+\.(?:promisor|partialclonefilter)$/.test(key))) {
|
|
39
|
+
throw unsafeConfigError();
|
|
40
|
+
}
|
|
41
|
+
if (inspectWorkingTree && keys.some((key) => key === "core.excludesfile" || executableFilterKey(key))) {
|
|
42
|
+
throw unsafeConfigError();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function executableFilterKey(key) {
|
|
48
|
+
return /^filter\..+\.(?:clean|smudge|process)$/.test(key);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function unsafeConfigError() {
|
|
52
|
+
return new BridgeError("authorization_denied", "repository Git configuration is incompatible with the structured Git safety boundary", {
|
|
53
|
+
details: { reason: "unsafe_repository_git_config" },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { BridgeError } from "./errors.mjs";
|
|
4
|
+
|
|
5
|
+
const FULL_HASH = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
6
|
+
const SHORT_HASH = /^[0-9a-f]{4,64}$/;
|
|
7
|
+
|
|
8
|
+
/** @param {unknown} stdout @param {boolean} [includeAuthorEmail] */
|
|
9
|
+
export function parseStructuredGitLog(stdout, includeAuthorEmail = false) {
|
|
10
|
+
/** @type {Array<{hash: string, short: string, authored_at: string, author_name: string, subject: string, author_email?: string}>} */
|
|
11
|
+
const commits = [];
|
|
12
|
+
const records = String(stdout || "").split("\x1e");
|
|
13
|
+
for (let raw of records) {
|
|
14
|
+
if (!raw) continue;
|
|
15
|
+
if (raw === "\n" || raw === "\r\n") continue;
|
|
16
|
+
if (raw.startsWith("\r\n")) raw = raw.slice(2);
|
|
17
|
+
else if (raw.startsWith("\n")) raw = raw.slice(1);
|
|
18
|
+
if (!raw) continue;
|
|
19
|
+
const fields = raw.split("\x1f");
|
|
20
|
+
if (fields.length !== 6) throw parseError();
|
|
21
|
+
const [hash, short, authored_at, author_name, author_email, subject] = fields;
|
|
22
|
+
if (!FULL_HASH.test(hash) || !SHORT_HASH.test(short) || !validIsoDate(authored_at)) throw parseError();
|
|
23
|
+
/** @type {{hash: string, short: string, authored_at: string, author_name: string, subject: string, author_email?: string}} */
|
|
24
|
+
const commit = { hash, short, authored_at, author_name, subject };
|
|
25
|
+
if (includeAuthorEmail) commit.author_email = author_email;
|
|
26
|
+
commits.push(commit);
|
|
27
|
+
}
|
|
28
|
+
return commits;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @param {string} value */
|
|
32
|
+
function validIsoDate(value) {
|
|
33
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2}|Z)$/.test(value)
|
|
34
|
+
&& Number.isFinite(Date.parse(value));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseError() {
|
|
38
|
+
return new BridgeError("execution_failed", "Git log output failed structured framing validation", {
|
|
39
|
+
details: { reason: "git_log_parse_failed" },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { BridgeError } from "./errors.mjs";
|
|
4
|
+
import { pathEntryIfExists } from "./path-inspection.mjs";
|
|
5
|
+
import { readBoundedRegularFileSync } from "./secure-file.mjs";
|
|
6
|
+
|
|
7
|
+
const MAX_ALTERNATES_BYTES = 64 * 1024;
|
|
8
|
+
const MAX_ALTERNATE_STORES = 64;
|
|
9
|
+
|
|
10
|
+
export async function resolveGitMetadataBoundary({ gitDir, commonDir, resolveExistingPath, context = {} }) {
|
|
11
|
+
const objectDir = await requireDirectory(join(commonDir, "objects"), resolveExistingPath, context, "Git object directory");
|
|
12
|
+
const alternateObjectDirs = await resolveAlternateObjectStores(objectDir, resolveExistingPath, context);
|
|
13
|
+
const metadataPaths = [];
|
|
14
|
+
for (const candidate of [
|
|
15
|
+
join(gitDir, "HEAD"),
|
|
16
|
+
join(gitDir, "index"),
|
|
17
|
+
join(gitDir, "refs"),
|
|
18
|
+
join(gitDir, "logs"),
|
|
19
|
+
join(gitDir, "reftable"),
|
|
20
|
+
join(commonDir, "packed-refs"),
|
|
21
|
+
join(commonDir, "shallow"),
|
|
22
|
+
join(commonDir, "refs"),
|
|
23
|
+
join(commonDir, "logs"),
|
|
24
|
+
join(commonDir, "reftable"),
|
|
25
|
+
join(commonDir, "info"),
|
|
26
|
+
join(objectDir, "pack"),
|
|
27
|
+
join(objectDir, "info"),
|
|
28
|
+
]) {
|
|
29
|
+
const entry = await pathEntryIfExists(candidate);
|
|
30
|
+
if (!entry) continue;
|
|
31
|
+
if (entry.isSymbolicLink()) throw boundaryError("Git metadata path must not be a symbolic link");
|
|
32
|
+
metadataPaths.push(await resolveExistingPath(candidate, context));
|
|
33
|
+
}
|
|
34
|
+
return Object.freeze({ objectDir, alternateObjectDirs: Object.freeze(alternateObjectDirs), metadataPaths: Object.freeze(metadataPaths) });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function resolveAlternateObjectStores(primaryObjectDir, resolveExistingPath, context) {
|
|
38
|
+
const queue = [primaryObjectDir];
|
|
39
|
+
const seen = new Set([primaryObjectDir]);
|
|
40
|
+
const alternates = [];
|
|
41
|
+
while (queue.length) {
|
|
42
|
+
const objectDir = queue.shift();
|
|
43
|
+
const marker = join(objectDir, "info", "alternates");
|
|
44
|
+
const entry = await pathEntryIfExists(marker);
|
|
45
|
+
if (!entry) continue;
|
|
46
|
+
if (entry.isSymbolicLink() || !entry.isFile() || Number(entry.nlink) !== 1) throw boundaryError("Git alternates metadata must be a single-link regular file");
|
|
47
|
+
const bytes = readBoundedRegularFileSync(marker, MAX_ALTERNATES_BYTES, "Git alternates metadata", {
|
|
48
|
+
verifyPathIdentity: true,
|
|
49
|
+
rejectMultipleLinks: true,
|
|
50
|
+
});
|
|
51
|
+
const text = bytes.toString("utf8");
|
|
52
|
+
if (text.includes("\0") || text.includes("\r")) throw boundaryError("Git alternates metadata is invalid");
|
|
53
|
+
for (const raw of text.split("\n")) {
|
|
54
|
+
if (!raw) continue;
|
|
55
|
+
if (raw.length > 4096 || /[\u0000-\u001f\u007f]/.test(raw)) throw boundaryError("Git alternate object path is invalid");
|
|
56
|
+
const candidate = isAbsolute(raw) ? raw : resolve(objectDir, raw);
|
|
57
|
+
const canonical = await requireDirectory(candidate, resolveExistingPath, context, "Git alternate object directory");
|
|
58
|
+
if (seen.has(canonical)) continue;
|
|
59
|
+
if (seen.size >= MAX_ALTERNATE_STORES) throw new BridgeError("limit_exceeded", `Git alternate object store count exceeds ${MAX_ALTERNATE_STORES}`);
|
|
60
|
+
seen.add(canonical);
|
|
61
|
+
alternates.push(canonical);
|
|
62
|
+
queue.push(canonical);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return alternates;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function requireDirectory(path, resolveExistingPath, context, label) {
|
|
69
|
+
const canonical = await resolveExistingPath(path, context);
|
|
70
|
+
const info = await stat(canonical);
|
|
71
|
+
if (!info.isDirectory()) throw boundaryError(`${label} is unavailable`);
|
|
72
|
+
return canonical;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function boundaryError(message) {
|
|
76
|
+
return new BridgeError("path_boundary", message, { details: { reason: "git_metadata_boundary" } });
|
|
77
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { lstat, opendir } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { BridgeError } from "./errors.mjs";
|
|
5
|
+
export const MAX_GIT_METADATA_ENTRIES = 1_000_000;
|
|
6
|
+
export const MAX_GIT_METADATA_DEPTH = 64;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string[]} roots
|
|
10
|
+
* @param {{opendir?: typeof opendir, lstat?: typeof lstat, maximumEntries?: number, signal?: AbortSignal}} [options]
|
|
11
|
+
*/
|
|
12
|
+
export async function assertGitMetadataTreesSafe(roots, options = {}) {
|
|
13
|
+
const openDirectory = options.opendir || opendir;
|
|
14
|
+
const inspect = options.lstat || lstat;
|
|
15
|
+
const maximumEntries = Number(options.maximumEntries ?? MAX_GIT_METADATA_ENTRIES);
|
|
16
|
+
if (!Number.isSafeInteger(maximumEntries) || maximumEntries < 1) throw new TypeError("Git metadata entry limit must be a positive safe integer");
|
|
17
|
+
/** @type {Array<{path: string, depth: number}>} */
|
|
18
|
+
const queue = [];
|
|
19
|
+
for (const path of new Set(roots.map(String))) {
|
|
20
|
+
abortIfNeeded(options.signal);
|
|
21
|
+
const info = await inspect(path);
|
|
22
|
+
if (info.isSymbolicLink()) throw boundaryError();
|
|
23
|
+
if (info.isFile()) continue;
|
|
24
|
+
if (!info.isDirectory()) throw boundaryError();
|
|
25
|
+
queue.push({ path, depth: 0 });
|
|
26
|
+
}
|
|
27
|
+
let visited = 0;
|
|
28
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
29
|
+
abortIfNeeded(options.signal);
|
|
30
|
+
const current = queue[index];
|
|
31
|
+
const directory = await openDirectory(current.path);
|
|
32
|
+
try {
|
|
33
|
+
for await (const entry of directory) {
|
|
34
|
+
abortIfNeeded(options.signal);
|
|
35
|
+
visited += 1;
|
|
36
|
+
if (visited > maximumEntries) throw new BridgeError("limit_exceeded", `Git metadata entry count exceeds ${maximumEntries}`);
|
|
37
|
+
const target = join(current.path, entry.name);
|
|
38
|
+
if (entry.isSymbolicLink()) throw boundaryError();
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
if (current.depth >= MAX_GIT_METADATA_DEPTH) throw new BridgeError("limit_exceeded", `Git metadata depth exceeds ${MAX_GIT_METADATA_DEPTH}`);
|
|
41
|
+
queue.push({ path: target, depth: current.depth + 1 });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (entry.isFile()) continue;
|
|
45
|
+
const info = await inspect(target);
|
|
46
|
+
if (info.isSymbolicLink() || !info.isFile()) throw boundaryError();
|
|
47
|
+
}
|
|
48
|
+
} finally { await directory.close().catch(() => { /* Directory-handle close is best-effort after bounded traversal ends. */ }); }
|
|
49
|
+
}
|
|
50
|
+
return visited;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** @param {AbortSignal | undefined} signal */
|
|
54
|
+
function abortIfNeeded(signal) { if (signal?.aborted) throw signal.reason instanceof Error ? signal.reason : new BridgeError("cancelled", "Git metadata inspection cancelled"); }
|
|
55
|
+
function boundaryError() {
|
|
56
|
+
return new BridgeError("path_boundary", "Git metadata tree contains a symbolic link or special file", {
|
|
57
|
+
details: { reason: "git_metadata_boundary" },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { BridgeError } from "./errors.mjs";
|
|
5
|
+
import { pathEntryIfExists } from "./path-inspection.mjs";
|
|
6
|
+
|
|
7
|
+
const IN_PROGRESS_HISTORY_MARKERS = Object.freeze([
|
|
8
|
+
"MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "REBASE_HEAD", "BISECT_START", "rebase-apply", "rebase-merge", "sequencer",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
/** @param {string} gitDir */
|
|
12
|
+
export async function assertStructuredGitCommitStateClear(gitDir) {
|
|
13
|
+
for (const marker of IN_PROGRESS_HISTORY_MARKERS) {
|
|
14
|
+
if (!await pathEntryIfExists(join(gitDir, marker))) continue;
|
|
15
|
+
throw new BridgeError("execution_failed", "Git commit is unavailable while a merge, rebase, cherry-pick, revert, or bisect operation is in progress", {
|
|
16
|
+
details: { reason: "git_commit_repository_state", code: 1 },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|