apex-code 0.0.1-alpha.8 → 0.0.4
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 +231 -0
- package/README.md +9 -8
- package/dist/cli/args.d.ts +26 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +72 -10
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +2 -1
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/mcp-command.d.ts +9 -0
- package/dist/cli/mcp-command.d.ts.map +1 -0
- package/dist/cli/mcp-command.js +53 -0
- package/dist/cli/mcp-command.js.map +1 -0
- package/dist/cli/project-trust.d.ts.map +1 -1
- package/dist/cli/project-trust.js +3 -1
- package/dist/cli/project-trust.js.map +1 -1
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +2 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +94 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +45 -18
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +3 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +183 -15
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +728 -117
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +6 -7
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +7 -0
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/checkpoints/git-checkpoints.d.ts +39 -0
- package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
- package/dist/core/checkpoints/git-checkpoints.js +251 -0
- package/dist/core/checkpoints/git-checkpoints.js.map +1 -0
- package/dist/core/checkpoints/session-checkpoints.d.ts +23 -0
- package/dist/core/checkpoints/session-checkpoints.d.ts.map +1 -0
- package/dist/core/checkpoints/session-checkpoints.js +36 -0
- package/dist/core/checkpoints/session-checkpoints.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +7 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +9 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +59 -36
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/defaults.d.ts +1 -0
- package/dist/core/defaults.d.ts.map +1 -1
- package/dist/core/defaults.js +9 -0
- package/dist/core/defaults.js.map +1 -1
- package/dist/core/delegation/runtime.d.ts +6 -5
- package/dist/core/delegation/runtime.d.ts.map +1 -1
- package/dist/core/delegation/runtime.js +4 -3
- package/dist/core/delegation/runtime.js.map +1 -1
- package/dist/core/durable-state/sqlite.d.ts.map +1 -1
- package/dist/core/durable-state/sqlite.js +26 -2
- package/dist/core/durable-state/sqlite.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +5 -2
- package/dist/core/exec.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +123 -50
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +45 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/source-runtime.d.ts +14 -0
- package/dist/core/extensions/source-runtime.d.ts.map +1 -0
- package/dist/core/extensions/source-runtime.js +56 -0
- package/dist/core/extensions/source-runtime.js.map +1 -0
- package/dist/core/extensions/types.d.ts +60 -11
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +3 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/formatter-lifecycle.d.ts +69 -0
- package/dist/core/formatter-lifecycle.d.ts.map +1 -0
- package/dist/core/formatter-lifecycle.js +360 -0
- package/dist/core/formatter-lifecycle.js.map +1 -0
- package/dist/core/hooks/command-handler.d.ts +19 -0
- package/dist/core/hooks/command-handler.d.ts.map +1 -0
- package/dist/core/hooks/command-handler.js +112 -0
- package/dist/core/hooks/command-handler.js.map +1 -0
- package/dist/core/hooks/http-handler.d.ts +10 -0
- package/dist/core/hooks/http-handler.d.ts.map +1 -0
- package/dist/core/hooks/http-handler.js +36 -0
- package/dist/core/hooks/http-handler.js.map +1 -0
- package/dist/core/hooks/loader.d.ts +29 -0
- package/dist/core/hooks/loader.d.ts.map +1 -0
- package/dist/core/hooks/loader.js +139 -0
- package/dist/core/hooks/loader.js.map +1 -0
- package/dist/core/hooks/runtime.d.ts +13 -0
- package/dist/core/hooks/runtime.d.ts.map +1 -0
- package/dist/core/hooks/runtime.js +63 -0
- package/dist/core/hooks/runtime.js.map +1 -0
- package/dist/core/hooks/types.d.ts +83 -0
- package/dist/core/hooks/types.d.ts.map +1 -0
- package/dist/core/hooks/types.js +24 -0
- package/dist/core/hooks/types.js.map +1 -0
- package/dist/core/keybindings.d.ts +20 -19
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +26 -5
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/mcp/config.d.ts +16 -0
- package/dist/core/mcp/config.d.ts.map +1 -0
- package/dist/core/mcp/config.js +166 -0
- package/dist/core/mcp/config.js.map +1 -0
- package/dist/core/mcp/connector.d.ts +35 -0
- package/dist/core/mcp/connector.d.ts.map +1 -0
- package/dist/core/mcp/connector.js +105 -0
- package/dist/core/mcp/connector.js.map +1 -0
- package/dist/core/mcp/contract.d.ts +19 -0
- package/dist/core/mcp/contract.d.ts.map +1 -0
- package/dist/core/mcp/contract.js +89 -0
- package/dist/core/mcp/contract.js.map +1 -0
- package/dist/core/mcp/mcp-tool.d.ts +32 -0
- package/dist/core/mcp/mcp-tool.d.ts.map +1 -0
- package/dist/core/mcp/mcp-tool.js +171 -0
- package/dist/core/mcp/mcp-tool.js.map +1 -0
- package/dist/core/mcp/metadata-cache.d.ts +29 -0
- package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
- package/dist/core/mcp/metadata-cache.js +99 -0
- package/dist/core/mcp/metadata-cache.js.map +1 -0
- package/dist/core/mcp/oauth/authorize.d.ts +21 -0
- package/dist/core/mcp/oauth/authorize.d.ts.map +1 -0
- package/dist/core/mcp/oauth/authorize.js +32 -0
- package/dist/core/mcp/oauth/authorize.js.map +1 -0
- package/dist/core/mcp/oauth/discover.d.ts +27 -0
- package/dist/core/mcp/oauth/discover.d.ts.map +1 -0
- package/dist/core/mcp/oauth/discover.js +116 -0
- package/dist/core/mcp/oauth/discover.js.map +1 -0
- package/dist/core/mcp/oauth/flow.d.ts +31 -0
- package/dist/core/mcp/oauth/flow.d.ts.map +1 -0
- package/dist/core/mcp/oauth/flow.js +175 -0
- package/dist/core/mcp/oauth/flow.js.map +1 -0
- package/dist/core/mcp/oauth/mcp-token.d.ts +51 -0
- package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -0
- package/dist/core/mcp/oauth/mcp-token.js +129 -0
- package/dist/core/mcp/oauth/mcp-token.js.map +1 -0
- package/dist/core/mcp/oauth/pkce.d.ts +12 -0
- package/dist/core/mcp/oauth/pkce.d.ts.map +1 -0
- package/dist/core/mcp/oauth/pkce.js +11 -0
- package/dist/core/mcp/oauth/pkce.js.map +1 -0
- package/dist/core/mcp/oauth/register.d.ts +16 -0
- package/dist/core/mcp/oauth/register.d.ts.map +1 -0
- package/dist/core/mcp/oauth/register.js +42 -0
- package/dist/core/mcp/oauth/register.js.map +1 -0
- package/dist/core/mcp/oauth/token-client.d.ts +33 -0
- package/dist/core/mcp/oauth/token-client.d.ts.map +1 -0
- package/dist/core/mcp/oauth/token-client.js +65 -0
- package/dist/core/mcp/oauth/token-client.js.map +1 -0
- package/dist/core/mcp/runtime.d.ts +20 -0
- package/dist/core/mcp/runtime.d.ts.map +1 -0
- package/dist/core/mcp/runtime.js +37 -0
- package/dist/core/mcp/runtime.js.map +1 -0
- package/dist/core/mcp/schema.d.ts +22 -0
- package/dist/core/mcp/schema.d.ts.map +1 -0
- package/dist/core/mcp/schema.js +20 -0
- package/dist/core/mcp/schema.js.map +1 -0
- package/dist/core/mcp/server-manager.d.ts +43 -0
- package/dist/core/mcp/server-manager.d.ts.map +1 -0
- package/dist/core/mcp/server-manager.js +142 -0
- package/dist/core/mcp/server-manager.js.map +1 -0
- package/dist/core/mcp/types.d.ts +100 -0
- package/dist/core/mcp/types.d.ts.map +1 -0
- package/dist/core/mcp/types.js +8 -0
- package/dist/core/mcp/types.js.map +1 -0
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +3 -1
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +14 -8
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/models-store.d.ts.map +1 -1
- package/dist/core/models-store.js +2 -1
- package/dist/core/models-store.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +22 -14
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/permissions/gate.d.ts.map +1 -1
- package/dist/core/permissions/gate.js +45 -4
- package/dist/core/permissions/gate.js.map +1 -1
- package/dist/core/permissions/operations.d.ts +20 -0
- package/dist/core/permissions/operations.d.ts.map +1 -0
- package/dist/core/permissions/operations.js +9 -0
- package/dist/core/permissions/operations.js.map +1 -0
- package/dist/core/permissions/policy-command.d.ts +42 -0
- package/dist/core/permissions/policy-command.d.ts.map +1 -0
- package/dist/core/permissions/policy-command.js +44 -0
- package/dist/core/permissions/policy-command.js.map +1 -0
- package/dist/core/permissions/responder.d.ts +49 -2
- package/dist/core/permissions/responder.d.ts.map +1 -1
- package/dist/core/permissions/responder.js +20 -8
- package/dist/core/permissions/responder.js.map +1 -1
- package/dist/core/permissions/rules.d.ts.map +1 -1
- package/dist/core/permissions/rules.js +11 -1
- package/dist/core/permissions/rules.js.map +1 -1
- package/dist/core/permissions/store.d.ts +18 -0
- package/dist/core/permissions/store.d.ts.map +1 -1
- package/dist/core/permissions/store.js +89 -8
- package/dist/core/permissions/store.js.map +1 -1
- package/dist/core/pi-manifest.d.ts.map +1 -1
- package/dist/core/pi-manifest.js +2 -1
- package/dist/core/pi-manifest.js.map +1 -1
- package/dist/core/policy-executor.d.ts +47 -0
- package/dist/core/policy-executor.d.ts.map +1 -0
- package/dist/core/policy-executor.js +228 -0
- package/dist/core/policy-executor.js.map +1 -0
- package/dist/core/policy-loader.d.ts +72 -0
- package/dist/core/policy-loader.d.ts.map +1 -0
- package/dist/core/policy-loader.js +195 -0
- package/dist/core/policy-loader.js.map +1 -0
- package/dist/core/project-trust.d.ts +1 -1
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +2 -1
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +3 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sandbox/bwrap-arguments.d.ts +41 -0
- package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
- package/dist/core/sandbox/bwrap-arguments.js +136 -0
- package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
- package/dist/core/sandbox/child-entry.d.ts.map +1 -1
- package/dist/core/sandbox/child-entry.js +3 -1
- package/dist/core/sandbox/child-entry.js.map +1 -1
- package/dist/core/sandbox/cli-launch.d.ts +84 -4
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
- package/dist/core/sandbox/cli-launch.js +115 -16
- package/dist/core/sandbox/cli-launch.js.map +1 -1
- package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/cli-supervisor.js +50 -2
- package/dist/core/sandbox/cli-supervisor.js.map +1 -1
- package/dist/core/sandbox/full-access.d.ts +28 -0
- package/dist/core/sandbox/full-access.d.ts.map +1 -0
- package/dist/core/sandbox/full-access.js +63 -0
- package/dist/core/sandbox/full-access.js.map +1 -0
- package/dist/core/sandbox/git-identity.d.ts +57 -0
- package/dist/core/sandbox/git-identity.d.ts.map +1 -0
- package/dist/core/sandbox/git-identity.js +78 -0
- package/dist/core/sandbox/git-identity.js.map +1 -0
- package/dist/core/sandbox/host-approval.d.ts +42 -0
- package/dist/core/sandbox/host-approval.d.ts.map +1 -0
- package/dist/core/sandbox/host-approval.js +99 -0
- package/dist/core/sandbox/host-approval.js.map +1 -0
- package/dist/core/sandbox/linux-backend.d.ts +14 -0
- package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
- package/dist/core/sandbox/linux-backend.js +158 -80
- package/dist/core/sandbox/linux-backend.js.map +1 -1
- package/dist/core/sandbox/macos-backend.d.ts +5 -0
- package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
- package/dist/core/sandbox/macos-backend.js +164 -5
- package/dist/core/sandbox/macos-backend.js.map +1 -1
- package/dist/core/sandbox/network-proxy.d.ts +19 -0
- package/dist/core/sandbox/network-proxy.d.ts.map +1 -1
- package/dist/core/sandbox/network-proxy.js +93 -19
- package/dist/core/sandbox/network-proxy.js.map +1 -1
- package/dist/core/sandbox/policy.d.ts +8 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -1
- package/dist/core/sandbox/policy.js +29 -12
- package/dist/core/sandbox/policy.js.map +1 -1
- package/dist/core/sandbox/profiles.d.ts +25 -0
- package/dist/core/sandbox/profiles.d.ts.map +1 -0
- package/dist/core/sandbox/profiles.js +23 -0
- package/dist/core/sandbox/profiles.js.map +1 -0
- package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
- package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/command-client.js +86 -0
- package/dist/core/sandbox/rpc/command-client.js.map +1 -0
- package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
- package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/command-proxy.js +120 -0
- package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -1
- package/dist/core/sandbox/rpc/credential-proxy.js +2 -96
- package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -1
- package/dist/core/sandbox/rpc/framing.d.ts +35 -0
- package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/framing.js +114 -0
- package/dist/core/sandbox/rpc/framing.js.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts +47 -0
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js +222 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +66 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.js +209 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
- package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
- package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
- package/dist/core/sandbox/supervisor-temp.js +21 -0
- package/dist/core/sandbox/supervisor-temp.js.map +1 -0
- package/dist/core/sandbox/supervisor.d.ts +12 -1
- package/dist/core/sandbox/supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/supervisor.js +15 -1
- package/dist/core/sandbox/supervisor.js.map +1 -1
- package/dist/core/sandbox/terminal-handoff.d.ts +68 -0
- package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-handoff.js +221 -0
- package/dist/core/sandbox/terminal-handoff.js.map +1 -0
- package/dist/core/sandbox/terminal-size.d.ts +13 -0
- package/dist/core/sandbox/terminal-size.d.ts.map +1 -1
- package/dist/core/sandbox/terminal-size.js +23 -2
- package/dist/core/sandbox/terminal-size.js.map +1 -1
- package/dist/core/sdk.d.ts +24 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +63 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-export.d.ts +4 -0
- package/dist/core/session-export.d.ts.map +1 -0
- package/dist/core/session-export.js +32 -0
- package/dist/core/session-export.js.map +1 -0
- package/dist/core/session-manager.d.ts +13 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +34 -6
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-diagnostics.d.ts +9 -0
- package/dist/core/settings-diagnostics.d.ts.map +1 -0
- package/dist/core/settings-diagnostics.js +21 -0
- package/dist/core/settings-diagnostics.js.map +1 -0
- package/dist/core/settings-manager.d.ts +172 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +160 -11
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +51 -33
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +8 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/background-shell.d.ts +39 -0
- package/dist/core/tools/background-shell.d.ts.map +1 -0
- package/dist/core/tools/background-shell.js +81 -0
- package/dist/core/tools/background-shell.js.map +1 -0
- package/dist/core/tools/bash-command-segments.d.ts.map +1 -1
- package/dist/core/tools/bash-command-segments.js +2 -0
- package/dist/core/tools/bash-command-segments.js.map +1 -1
- package/dist/core/tools/bash.d.ts +44 -16
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +264 -32
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/contract-snapshot.d.ts +58 -0
- package/dist/core/tools/contract-snapshot.d.ts.map +1 -0
- package/dist/core/tools/contract-snapshot.js +51 -0
- package/dist/core/tools/contract-snapshot.js.map +1 -0
- package/dist/core/tools/contract.d.ts +53 -5
- package/dist/core/tools/contract.d.ts.map +1 -1
- package/dist/core/tools/contract.js +22 -2
- package/dist/core/tools/contract.js.map +1 -1
- package/dist/core/tools/delegate.d.ts +6 -4
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +21 -11
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +48 -5
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +217 -8
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +8 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +72 -10
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +5 -2
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +5 -2
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +14 -2
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +12 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -2
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/lsp.d.ts +19 -1
- package/dist/core/tools/lsp.d.ts.map +1 -1
- package/dist/core/tools/lsp.js +163 -10
- package/dist/core/tools/lsp.js.map +1 -1
- package/dist/core/tools/path-permission.d.ts +3 -0
- package/dist/core/tools/path-permission.d.ts.map +1 -1
- package/dist/core/tools/path-permission.js +16 -4
- package/dist/core/tools/path-permission.js.map +1 -1
- package/dist/core/tools/path-utils.d.ts +27 -2
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +221 -4
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/powershell.d.ts +16 -0
- package/dist/core/tools/powershell.d.ts.map +1 -0
- package/dist/core/tools/powershell.js +39 -0
- package/dist/core/tools/powershell.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +20 -11
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/test.d.ts +54 -9
- package/dist/core/tools/test.d.ts.map +1 -1
- package/dist/core/tools/test.js +239 -19
- package/dist/core/tools/test.js.map +1 -1
- package/dist/core/tools/write.d.ts +7 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +64 -9
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +2 -1
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/core/verification-lifecycle.d.ts +77 -0
- package/dist/core/verification-lifecycle.d.ts.map +1 -0
- package/dist/core/verification-lifecycle.js +146 -0
- package/dist/core/verification-lifecycle.js.map +1 -0
- package/dist/core/workspace/artifacts.d.ts +45 -0
- package/dist/core/workspace/artifacts.d.ts.map +1 -0
- package/dist/core/workspace/artifacts.js +165 -0
- package/dist/core/workspace/artifacts.js.map +1 -0
- package/dist/core/workspace/comparison.d.ts +27 -0
- package/dist/core/workspace/comparison.d.ts.map +1 -0
- package/dist/core/workspace/comparison.js +94 -0
- package/dist/core/workspace/comparison.js.map +1 -0
- package/dist/core/workspace/git-observer.d.ts +48 -0
- package/dist/core/workspace/git-observer.d.ts.map +1 -0
- package/dist/core/workspace/git-observer.js +346 -0
- package/dist/core/workspace/git-observer.js.map +1 -0
- package/dist/core/workspace/projection.d.ts +3 -0
- package/dist/core/workspace/projection.d.ts.map +1 -0
- package/dist/core/workspace/projection.js +56 -0
- package/dist/core/workspace/projection.js.map +1 -0
- package/dist/core/workspace/state.d.ts +133 -0
- package/dist/core/workspace/state.d.ts.map +1 -0
- package/dist/core/workspace/state.js +96 -0
- package/dist/core/workspace/state.js.map +1 -0
- package/dist/extensions/llama/client.d.ts +6 -0
- package/dist/extensions/llama/client.d.ts.map +1 -1
- package/dist/extensions/llama/client.js +7 -0
- package/dist/extensions/llama/client.js.map +1 -1
- package/dist/extensions/llama/index.d.ts.map +1 -1
- package/dist/extensions/llama/index.js +2 -0
- package/dist/extensions/llama/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts +3 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +29 -4
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +6 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +58 -23
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +4 -3
- package/dist/migrations.js.map +1 -1
- package/dist/modes/acp/server.d.ts +55 -0
- package/dist/modes/acp/server.d.ts.map +1 -0
- package/dist/modes/acp/server.js +244 -0
- package/dist/modes/acp/server.js.map +1 -0
- package/dist/modes/acp/translate.d.ts +37 -0
- package/dist/modes/acp/translate.d.ts.map +1 -0
- package/dist/modes/acp/translate.js +50 -0
- package/dist/modes/acp/translate.js.map +1 -0
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +29 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +182 -23
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +7 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +20 -0
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +9 -4
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +20 -9
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +7 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +7 -3
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +19 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +70 -10
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +10 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +56 -14
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/permission-preview.d.ts +15 -0
- package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -0
- package/dist/modes/interactive/components/permission-preview.js +33 -0
- package/dist/modes/interactive/components/permission-preview.js.map +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +2 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +9 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +128 -56
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
- package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
- package/dist/modes/interactive/components/settings-submenu.js +165 -0
- package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +74 -17
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +23 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +64 -10
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +2 -1
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +45 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +395 -43
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +12 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +20 -9
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +16 -13
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +20 -6
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +27 -12
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +7 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +3 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +12 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts +1 -0
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +196 -10
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +2 -1
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/management-http.d.ts +5 -3
- package/dist/utils/management-http.d.ts.map +1 -1
- package/dist/utils/management-http.js +14 -10
- package/dist/utils/management-http.js.map +1 -1
- package/dist/utils/shell.d.ts +3 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +23 -9
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts +1 -0
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +60 -1
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/text.d.ts +8 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +9 -0
- package/dist/utils/text.js.map +1 -0
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +6 -4
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +19 -0
- package/docs/custom-provider.md +12 -9
- package/docs/environment-variables.md +1 -1
- package/docs/extensions.md +35 -7
- package/docs/json.md +7 -2
- package/docs/keybindings.md +9 -9
- package/docs/llama-cpp.md +2 -0
- package/docs/models.md +8 -2
- package/docs/packages.md +2 -2
- package/docs/quickstart.md +1 -1
- package/docs/rpc.md +32 -3
- package/docs/sdk.md +32 -2
- package/docs/settings.md +23 -7
- package/docs/skills.md +4 -3
- package/docs/terminal-setup.md +23 -7
- package/docs/usage.md +10 -5
- package/docs/windows.md +24 -2
- package/examples/extensions/border-status-editor.ts +1 -1
- package/examples/extensions/built-in-tool-renderer.ts +8 -4
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/git-checkpoint.ts +24 -36
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +2 -2
- package/examples/extensions/notify.ts +3 -1
- package/examples/extensions/permission-gate.ts +1 -1
- package/examples/extensions/plan-mode/index.ts +1 -1
- package/examples/extensions/rpc-demo.ts +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +1 -1
- package/examples/extensions/subagent/index.ts +6 -1
- package/examples/extensions/titlebar-spinner.ts +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/rpc-extension-ui.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +439 -405
- package/package.json +17 -17
- package/dist/server/create-harness.d.ts +0 -26
- package/dist/server/create-harness.d.ts.map +0 -1
- package/dist/server/create-harness.js +0 -107
- package/dist/server/create-harness.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"macos-backend.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/macos-backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,oBAAoB,CAAC;AAKtD,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,iBAAiB,CAAC;AACrE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,0BAA0B;IAC1C,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AA6ED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,cAAc,CAoJ9F","sourcesContent":["import { spawn, spawnSync } from \"node:child_process\";\nimport { mkdirSync, realpathSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { createSandboxNetworkProxy, type SandboxNetworkProxy } from \"./network-proxy.ts\";\nimport type { SandboxBackend, SandboxLaunch } from \"./supervisor.ts\";\nimport type { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface MacosSandboxBackendOptions {\n\t/** Injectable only for preflight tests; production uses the running platform. */\n\tplatform?: NodeJS.Platform;\n\tcommandExists?: (command: string) => boolean;\n\tviolationStore?: SandboxViolationStore;\n\t/**\n\t * Injectable only so violation attribution can be tested off macOS; production\n\t * spawns `sandbox-exec` directly. Without this the decision about what counts as a\n\t * violation would be verifiable only on a macOS host.\n\t */\n\tspawnChild?: typeof spawn;\n}\n\nfunction sandboxExecExists(command: string): boolean {\n\t// sandbox-exec has no --version flag; a real, minimal profile run is the\n\t// standard existence-and-basic-functionality probe (see the 2b.5 prototype's\n\t// own positive control).\n\tconst result = spawnSync(command, [\"-p\", \"(version 1)(allow default)\", \"/usr/bin/true\"], {\n\t\tstdio: \"ignore\",\n\t\ttimeout: 2_000,\n\t});\n\treturn result.status === 0;\n}\n\nfunction waitForExit(child: ReturnType<typeof spawn>): Promise<number> {\n\treturn new Promise((resolve, reject) => {\n\t\tchild.once(\"error\", reject);\n\t\tchild.once(\"exit\", (code) => resolve(code ?? 1));\n\t});\n}\n\n/**\n * Unlike Linux's bwrap (which needs each mount ancestor declared for its bind-mount\n * tree), Seatbelt's `subpath` is recursive from a single root, so only the directory\n * containing each path is needed -- no ancestor walk. Each directory is resolved to\n * its real (symlink-free) path: Seatbelt canonicalizes the path it actually checks\n * against a `subpath` rule (confirmed empirically -- a rule built from an\n * unresolved symlinked path, e.g. Homebrew's own layout, silently never matches).\n */\nfunction readOnlyDirectories(paths: readonly string[]): string[] {\n\tconst directories = paths.map((path) => dirname(resolve(path)));\n\tconst canonical = directories.map((directory) => {\n\t\ttry {\n\t\t\treturn realpathSync(directory);\n\t\t} catch {\n\t\t\treturn directory;\n\t\t}\n\t});\n\treturn [...new Set(canonical)];\n}\n\n/**\n * Canonicalize a channel socket path for a Seatbelt literal. Only the directory can be\n * resolved -- the socket file need not exist yet when the profile is written -- and the\n * same symlink rule as `readOnlyDirectories` applies: Seatbelt matches the canonical\n * path, so an unresolved one silently never matches.\n */\nfunction canonicalSocketPath(path: string): string {\n\tconst directory = dirname(resolve(path));\n\ttry {\n\t\treturn join(realpathSync(directory), basename(path));\n\t} catch {\n\t\treturn join(directory, basename(path));\n\t}\n}\n\n/** Seatbelt refuses through the blocked call's own errno text, not a wrapper message. */\nconst DENIAL_EVIDENCE = /Operation not permitted|Permission denied|EPERM|EACCES/i;\n\nfunction classifySandboxFailure(stderr: string): \"filesystem\" | \"network\" | \"unknown\" | undefined {\n\t// Confirmed empirically (2b.5 prototype): unlike Linux's bwrap, macOS Seatbelt\n\t// denials surface through the launched command's own generic \"Operation not\n\t// permitted\"/EPERM text for filesystem AND network refusals alike -- there is no\n\t// macOS-specific phrase equivalent to Linux's distinct \"Network is unreachable\".\n\t// Only sandbox-exec's own wrapper-level exec refusal (a sign our own profile\n\t// forgot to allow a binary, not a policy decision) is unambiguous from stderr\n\t// text. Anything else that still shows a refusal stays \"unknown\";\n\t// network-proxy.ts's own precise per-request recording is the reliable \"network\"\n\t// signal on this platform.\n\tif (/execvp\\(\\) of .* failed: Operation not permitted/i.test(stderr)) return \"filesystem\";\n\tif (DENIAL_EVIDENCE.test(stderr)) return \"unknown\";\n\t// No refusal in evidence. The coarseness above is about telling filesystem from\n\t// network, not about telling a denial from an ordinary failure -- a non-zero exit\n\t// on its own says nothing about the boundary, and reporting it as a violation\n\t// sends the reader looking for a refusal that never happened.\n\treturn undefined;\n}\n\n/**\n * macOS's platform adapter. Seatbelt (`sandbox-exec`) allows reads broadly except\n * the invoking account's home directory, denies writes outside the workspace, and\n * denies network outside the sandbox proxy's exact loopback port -- narrower than\n * the wildcard `localhost:*` pattern some other sandboxes use. This mirrors Linux's\n * actual posture (`--ro-bind / /` plus a hidden `/home`) rather than the narrower\n * read-allowlist this design started with, which broke ordinary process startup.\n */\nexport function createMacosSandboxBackend(options?: MacosSandboxBackendOptions): SandboxBackend {\n\tconst platform = options?.platform ?? process.platform;\n\tif (platform !== \"darwin\") {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"OS sandbox is supported on macOS only.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"macOS sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\tconst hasCommand = options?.commandExists ?? sandboxExecExists;\n\tconst violationStore = options?.violationStore;\n\tlet proxy: SandboxNetworkProxy | undefined;\n\n\tif (!hasCommand(\"sandbox-exec\")) {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"sandbox-exec (Seatbelt) is required for OS sandboxing.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"macOS sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\treturn {\n\t\tstatus: { kind: \"enforced\" },\n\t\tasync launch(launch: SandboxLaunch): Promise<number> {\n\t\t\t// Same reasoning as readOnlyDirectories: on macOS the workspace itself can\n\t\t\t// be reached through a symlink (e.g. os.tmpdir()'s /var -> /private/var),\n\t\t\t// and Seatbelt's subpath match is against the canonical path, not the one\n\t\t\t// the caller happened to spell. Resolve once, use everywhere below.\n\t\t\tconst workspace = realpathSync(launch.policy.workspace);\n\t\t\tconst stateDirectory = join(workspace, \".apex-code\", \"sandbox-state\");\n\t\t\tmkdirSync(stateDirectory, { recursive: true });\n\n\t\t\tconst violationCountBeforeLaunch = violationStore?.totalCount ?? 0;\n\n\t\t\tproxy = await createSandboxNetworkProxy({\n\t\t\t\ttcpHost: \"127.0.0.1\",\n\t\t\t\tallowedHosts: launch.policy.allowedHosts,\n\t\t\t\tviolationStore,\n\t\t\t});\n\t\t\tconst proxyPort = proxy.port as number;\n\n\t\t\t// Seatbelt cannot remap a path, so the child connects to the channel socket at\n\t\t\t// its host path, and the profile allows outbound to exactly that one socket.\n\t\t\tconst credentialChannelSocket = launch.credentialChannel\n\t\t\t\t? canonicalSocketPath(launch.credentialChannel.hostSocketPath)\n\t\t\t\t: undefined;\n\n\t\t\tconst readOnlyDirs = readOnlyDirectories([process.execPath, launch.command, ...(launch.readOnlyPaths ?? [])]);\n\t\t\tconst readOnlyFiles = (launch.readOnlyFiles ?? []).map((path) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn realpathSync(path);\n\t\t\t\t} catch {\n\t\t\t\t\treturn resolve(path);\n\t\t\t\t}\n\t\t\t});\n\t\t\tlet userHome: string | undefined;\n\t\t\ttry {\n\t\t\t\tuserHome = realpathSync(homedir());\n\t\t\t} catch {\n\t\t\t\tuserHome = homedir();\n\t\t\t}\n\n\t\t\tconst profileLines = [\n\t\t\t\t\"(version 1)\",\n\t\t\t\t// bsd.sb's baseline is broader than Linux's opt-in bind-mount model --\n\t\t\t\t// a real gap this design accepts, documented in the spec's fourth\n\t\t\t\t// amendment -- but it is the only combination the 2b.5 prototype found\n\t\t\t\t// that lets a normal Unix child process (dyld, /bin/sh) actually start.\n\t\t\t\t'(import \"bsd.sb\")',\n\t\t\t\t\"(allow process-exec*)\",\n\t\t\t\t// Separate from process-exec* -- confirmed empirically: a shell that\n\t\t\t\t// forks a child shell (e.g. `sh -c 'a' && sh -c 'b'`) failed with\n\t\t\t\t// \"fork: Operation not permitted\" without this, even with exec allowed.\n\t\t\t\t\"(allow process-fork)\",\n\t\t\t\t// Matches Linux's actual posture (bwrap's `--ro-bind / /`): broad read,\n\t\t\t\t// narrow write. A read-only allowlist alone (this design's first cut)\n\t\t\t\t// breaks ordinary process startup -- confirmed empirically, getcwd()\n\t\t\t\t// itself needs to traverse the workspace's ancestor directories, which\n\t\t\t\t// no read rule scoped only to the workspace or runtime paths covers.\n\t\t\t\t\"(allow file-read*)\",\n\t\t\t\t'(deny file-read* (subpath (param \"USER_HOME\")))',\n\t\t\t\t...readOnlyDirs.map((_, index) => `(allow file-read* (subpath (param \"RO_${index}\")))`),\n\t\t\t\t...readOnlyFiles.map((_, index) => `(allow file-read* (subpath (param \"RO_FILE_${index}\")))`),\n\t\t\t\t'(allow file-read* (subpath (param \"WORKSPACE\")))',\n\t\t\t\t\"(deny file-write*)\",\n\t\t\t\t'(allow file-write* (subpath (param \"WORKSPACE\")))',\n\t\t\t\t\"(deny network*)\",\n\t\t\t\t'(allow network-outbound (remote ip (param \"PROXY_ADDR\")))',\n\t\t\t\t...(credentialChannelSocket\n\t\t\t\t\t? [`(allow network-outbound (remote unix-socket (literal \"${credentialChannelSocket}\")))`]\n\t\t\t\t\t: []),\n\t\t\t];\n\t\t\tconst profilePath = join(stateDirectory, \"profile.sb\");\n\t\t\twriteFileSync(profilePath, profileLines.join(\"\\n\"));\n\n\t\t\tconst params: string[] = [\"-D\", `USER_HOME=${userHome}`];\n\t\t\treadOnlyDirs.forEach((dir, index) => {\n\t\t\t\tparams.push(\"-D\", `RO_${index}=${dir}`);\n\t\t\t});\n\t\t\treadOnlyFiles.forEach((file, index) => {\n\t\t\t\tparams.push(\"-D\", `RO_FILE_${index}=${file}`);\n\t\t\t});\n\t\t\tparams.push(\"-D\", `WORKSPACE=${workspace}`);\n\t\t\tparams.push(\"-D\", `PROXY_ADDR=localhost:${proxyPort}`);\n\n\t\t\tconst spawnSandboxExec = options?.spawnChild ?? spawn;\n\t\t\tconst child = spawnSandboxExec(\n\t\t\t\t\"sandbox-exec\",\n\t\t\t\t[\"-f\", profilePath, ...params, \"--\", launch.command, ...launch.args],\n\t\t\t\t{\n\t\t\t\t\tcwd: workspace,\n\t\t\t\t\tenv: {\n\t\t\t\t\t\t...launch.environment,\n\t\t\t\t\t\tHOME: launch.environment?.HOME ?? stateDirectory,\n\t\t\t\t\t\tTMPDIR: launch.environment?.TMPDIR ?? stateDirectory,\n\t\t\t\t\t\tHTTP_PROXY: `http://127.0.0.1:${proxyPort}`,\n\t\t\t\t\t\tHTTPS_PROXY: `http://127.0.0.1:${proxyPort}`,\n\t\t\t\t\t},\n\t\t\t\t\tstdio: [\"inherit\", \"inherit\", \"pipe\"],\n\t\t\t\t},\n\t\t\t);\n\t\t\tlet stderr = \"\";\n\t\t\tchild.stderr?.setEncoding(\"utf8\");\n\t\t\tchild.stderr?.on(\"data\", (chunk: string) => {\n\t\t\t\tstderr += chunk;\n\t\t\t\tprocess.stderr.write(chunk);\n\t\t\t});\n\t\t\tconst exitCode = await waitForExit(child);\n\t\t\tawait proxy?.close();\n\t\t\tconst proxyAlreadyRecordedAViolation = (violationStore?.totalCount ?? 0) > violationCountBeforeLaunch;\n\t\t\tconst kind = exitCode !== 0 && !proxyAlreadyRecordedAViolation ? classifySandboxFailure(stderr) : undefined;\n\t\t\tif (kind) {\n\t\t\t\tviolationStore?.add({\n\t\t\t\t\tkind,\n\t\t\t\t\tcommand: [launch.command, ...launch.args].join(\" \"),\n\t\t\t\t\tdetail: stderr.trim(),\n\t\t\t\t\ttimestamp: new Date(),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn exitCode;\n\t\t},\n\t\tasync close() {\n\t\t\tawait proxy?.close();\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"macos-backend.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/macos-backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,oBAAoB,CAAC;AAOtD,OAAO,EAEN,KAAK,wBAAwB,EAI7B,MAAM,wBAAwB,CAAC;AAQhC,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,iBAAiB,CAAC;AAQrE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,0BAA0B;IAC1C,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,KAAK,CAAC;IAC1B,qFAAqF;IACrF,wBAAwB,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnF,sFAAsF;IACtF,wBAAwB,CAAC,EAAE,MAAM,CAAC;CAClC;AA+FD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,cAAc,CA8O9F","sourcesContent":["import { spawn, spawnSync } from \"node:child_process\";\nimport { mkdirSync, realpathSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { typeScriptSourceConfigFiles } from \"../extensions/source-runtime.ts\";\nimport { createCommandEscalationApprover, createCredentialReleaser, createHostApprover } from \"./host-approval.ts\";\nimport { createSandboxNetworkProxy, type SandboxNetworkProxy } from \"./network-proxy.ts\";\nimport {\n\tCOMMAND_ESCALATION_SOCKET_VARIABLE,\n\ttype CommandEscalationRequest,\n\ttype CommandEscalationResult,\n\tcreateCommandEscalationProxy,\n\tresolveCommandEscalationChannelPaths,\n} from \"./rpc/command-proxy.ts\";\nimport {\n\tfillHostGitCredential,\n\tGIT_CREDENTIAL_SOCKET_VARIABLE,\n\tresolveGitCredentialChannelPaths,\n\twriteGitCredentialHelper,\n} from \"./rpc/git-credential-helper.ts\";\nimport { createGitCredentialProxy, type GitCredentialProxy } from \"./rpc/git-credential-proxy.ts\";\nimport type { SandboxBackend, SandboxLaunch } from \"./supervisor.ts\";\nimport {\n\tcreateTerminalHandoff,\n\tTERMINAL_HANDOFF_ACK_PATH_VARIABLE,\n\tTERMINAL_HANDOFF_PATH_VARIABLE,\n\ttype TerminalHandoff,\n} from \"./terminal-handoff.ts\";\nimport { publishTerminalSize, TERMINAL_SIZE_PATH_VARIABLE } from \"./terminal-size.ts\";\nimport type { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface MacosSandboxBackendOptions {\n\t/** Injectable only for preflight tests; production uses the running platform. */\n\tplatform?: NodeJS.Platform;\n\tcommandExists?: (command: string) => boolean;\n\tviolationStore?: SandboxViolationStore;\n\t/**\n\t * Injectable only so violation attribution can be tested off macOS; production\n\t * spawns `sandbox-exec` directly. Without this the decision about what counts as a\n\t * violation would be verifiable only on a macOS host.\n\t */\n\tspawnChild?: typeof spawn;\n\t/** Injectable only for tests, which have no terminal to approve an escalation at. */\n\trequestCommandEscalation?: (request: CommandEscalationRequest) => Promise<boolean>;\n\t/** Retained for source compatibility; launch state is now required on each launch. */\n\tsupervisorStateDirectory?: string;\n}\n\nconst ESCALATION_OUTPUT_LIMIT = 64 * 1024;\n\nfunction seatbeltParameter(value: string): string {\n\treturn value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"\\n\", \"\\\\n\").replaceAll('\"', '\\\\\"');\n}\n\nfunction appendBounded(current: string, chunk: string, limit = ESCALATION_OUTPUT_LIMIT): string {\n\tif (current.length >= limit) return current;\n\treturn current + chunk.slice(0, limit - current.length);\n}\n\nfunction sandboxExecExists(command: string): boolean {\n\t// sandbox-exec has no --version flag; a real, minimal profile run is the\n\t// standard existence-and-basic-functionality probe (see the 2b.5 prototype's\n\t// own positive control).\n\tconst result = spawnSync(command, [\"-p\", \"(version 1)(allow default)\", \"/usr/bin/true\"], {\n\t\tstdio: \"ignore\",\n\t\ttimeout: 2_000,\n\t});\n\treturn result.status === 0;\n}\n\nfunction waitForExit(child: ReturnType<typeof spawn>): Promise<number> {\n\treturn new Promise((resolve, reject) => {\n\t\tchild.once(\"error\", reject);\n\t\tchild.once(\"exit\", (code) => resolve(code ?? 1));\n\t});\n}\n\n/**\n * Unlike Linux's bwrap (which needs each mount ancestor declared for its bind-mount\n * tree), Seatbelt's `subpath` is recursive from a single root, so only the directory\n * containing each path is needed -- no ancestor walk. Each directory is resolved to\n * its real (symlink-free) path: Seatbelt canonicalizes the path it actually checks\n * against a `subpath` rule (confirmed empirically -- a rule built from an\n * unresolved symlinked path, e.g. Homebrew's own layout, silently never matches).\n */\nfunction readOnlyDirectories(paths: readonly string[]): string[] {\n\tconst directories = paths.map((path) => {\n\t\tconst resolved = resolve(path);\n\t\ttry {\n\t\t\treturn statSync(resolved).isDirectory() ? resolved : dirname(resolved);\n\t\t} catch {\n\t\t\treturn dirname(resolved);\n\t\t}\n\t});\n\tconst canonical = directories.map((directory) => {\n\t\ttry {\n\t\t\treturn realpathSync(directory);\n\t\t} catch {\n\t\t\treturn directory;\n\t\t}\n\t});\n\treturn [...new Set(canonical)];\n}\n\n/**\n * Canonicalize a channel socket path for a Seatbelt literal. Only the directory can be\n * resolved -- the socket file need not exist yet when the profile is written -- and the\n * same symlink rule as `readOnlyDirectories` applies: Seatbelt matches the canonical\n * path, so an unresolved one silently never matches.\n */\nfunction canonicalSocketPath(path: string): string {\n\tconst directory = dirname(resolve(path));\n\ttry {\n\t\treturn join(realpathSync(directory), basename(path));\n\t} catch {\n\t\treturn join(directory, basename(path));\n\t}\n}\n\n/** Seatbelt refuses through the blocked call's own errno text, not a wrapper message. */\nconst DENIAL_EVIDENCE = /Operation not permitted|Permission denied|EPERM|EACCES/i;\n\nfunction classifySandboxFailure(stderr: string): \"filesystem\" | \"network\" | \"unknown\" | undefined {\n\t// Confirmed empirically (2b.5 prototype): unlike Linux's bwrap, macOS Seatbelt\n\t// denials surface through the launched command's own generic \"Operation not\n\t// permitted\"/EPERM text for filesystem AND network refusals alike -- there is no\n\t// macOS-specific phrase equivalent to Linux's distinct \"Network is unreachable\".\n\t// Only sandbox-exec's own wrapper-level exec refusal (a sign our own profile\n\t// forgot to allow a binary, not a policy decision) is unambiguous from stderr\n\t// text. Anything else that still shows a refusal stays \"unknown\";\n\t// network-proxy.ts's own precise per-request recording is the reliable \"network\"\n\t// signal on this platform.\n\tif (/execvp\\(\\) of .* failed: Operation not permitted/i.test(stderr)) return \"filesystem\";\n\tif (DENIAL_EVIDENCE.test(stderr)) return \"unknown\";\n\t// No refusal in evidence. The coarseness above is about telling filesystem from\n\t// network, not about telling a denial from an ordinary failure -- a non-zero exit\n\t// on its own says nothing about the boundary, and reporting it as a violation\n\t// sends the reader looking for a refusal that never happened.\n\treturn undefined;\n}\n\n/**\n * macOS's platform adapter. Seatbelt (`sandbox-exec`) allows reads broadly except\n * the invoking account's home directory, denies writes outside the workspace, and\n * denies network outside the sandbox proxy's exact loopback port -- narrower than\n * the wildcard `localhost:*` pattern some other sandboxes use. This mirrors Linux's\n * actual posture (`--ro-bind / /` plus a hidden `/home`) rather than the narrower\n * read-allowlist this design started with, which broke ordinary process startup.\n */\nexport function createMacosSandboxBackend(options?: MacosSandboxBackendOptions): SandboxBackend {\n\tconst platform = options?.platform ?? process.platform;\n\tif (platform !== \"darwin\") {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"OS sandbox is supported on macOS only.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"macOS sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\tconst hasCommand = options?.commandExists ?? sandboxExecExists;\n\tconst violationStore = options?.violationStore;\n\tlet proxy: SandboxNetworkProxy | undefined;\n\tlet handoff: TerminalHandoff | undefined;\n\tlet gitCredentialProxy: GitCredentialProxy | undefined;\n\tlet gitCredentialDirectory: string | undefined;\n\tlet escalationProxy: Awaited<ReturnType<typeof createCommandEscalationProxy>> | undefined;\n\tlet escalationDirectory: string | undefined;\n\tif (!hasCommand(\"sandbox-exec\")) {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"sandbox-exec (Seatbelt) is required for OS sandboxing.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"macOS sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\treturn {\n\t\tstatus: { kind: \"enforced\" },\n\t\tasync launch(launch: SandboxLaunch): Promise<number> {\n\t\t\t// Same reasoning as readOnlyDirectories: on macOS the workspace itself can\n\t\t\t// be reached through a symlink (e.g. os.tmpdir()'s /var -> /private/var),\n\t\t\t// and Seatbelt's subpath match is against the canonical path, not the one\n\t\t\t// the caller happened to spell. Resolve once, use everywhere below.\n\t\t\tconst workspace = realpathSync(launch.policy.workspace);\n\t\t\tconst writableRoots = launch.policy.additionalWritableRoots;\n\t\t\tconst stateDirectory = join(workspace, \".apex-code\", \"sandbox-state\");\n\t\t\tmkdirSync(stateDirectory, { recursive: true });\n\t\t\tconst supervisorStateDirectory = launch.supervisorStateDirectory;\n\t\t\tmkdirSync(supervisorStateDirectory, { recursive: true, mode: 0o700 });\n\t\t\tconst acknowledgementPath = join(stateDirectory, \"terminal-handoff-ack\");\n\n\t\t\tconst violationCountBeforeLaunch = violationStore?.totalCount ?? 0;\n\n\t\t\thandoff = createTerminalHandoff(supervisorStateDirectory, { acknowledgementPath });\n\t\t\t// Both channels are AF_UNIX servers, which Windows has not got. Nothing is lost\n\t\t\t// by skipping them there: ADR 0005 leaves Windows unsupported and this backend\n\t\t\t// reports `unavailable` on any real Windows host. They are reachable here only\n\t\t\t// because the violation-attribution tests run this body off-macOS on purpose,\n\t\t\t// with `spawnChild` injected, and those must keep working on every runner.\n\t\t\tconst unixSocketsAvailable = process.platform !== \"win32\";\n\t\t\tconst gitCredentialPaths = unixSocketsAvailable ? resolveGitCredentialChannelPaths() : undefined;\n\t\t\tgitCredentialDirectory = gitCredentialPaths?.hostSocketDirectory;\n\t\t\tproxy = await createSandboxNetworkProxy({\n\t\t\t\ttcpHost: \"127.0.0.1\",\n\t\t\t\tallowedHosts: launch.policy.allowedHosts,\n\t\t\t\tviolationStore,\n\t\t\t\t// Undefined without a terminal, which leaves the proxy's own\n\t\t\t\t// deny-without-asking path in place for headless, print, JSON, and RPC.\n\t\t\t\trequestApproval: createHostApprover({ handoff }),\n\t\t\t});\n\t\t\t// After the network proxy, because reachability is its answer to give.\n\t\t\tgitCredentialProxy =\n\t\t\t\tgitCredentialPaths &&\n\t\t\t\t(await createGitCredentialProxy({\n\t\t\t\t\tsocketPath: gitCredentialPaths.hostSocketPath,\n\t\t\t\t\tisHostAllowed: (host) => proxy?.isHostReachable(host) ?? false,\n\t\t\t\t\trequestRelease: createCredentialReleaser({ handoff }),\n\t\t\t\t\t// The supervisor's own environment: this runs outside the boundary and must\n\t\t\t\t\t// resolve against the real host home the child cannot see.\n\t\t\t\t\tfillCredential: (request) => fillHostGitCredential(request, { environment: process.env }),\n\t\t\t\t\tviolationStore,\n\t\t\t\t}));\n\t\t\twriteGitCredentialHelper(supervisorStateDirectory);\n\n\t\t\tconst escalationPaths = unixSocketsAvailable ? resolveCommandEscalationChannelPaths() : undefined;\n\t\t\tescalationDirectory = escalationPaths?.hostSocketDirectory;\n\t\t\t// Seatbelt cannot remap a path, so the child reaches this socket at its host\n\t\t\t// path, exactly as it reaches the credential channel.\n\t\t\tescalationProxy =\n\t\t\t\tescalationPaths &&\n\t\t\t\t(await createCommandEscalationProxy({\n\t\t\t\t\tsocketPath: escalationPaths.hostSocketPath,\n\t\t\t\t\trequestApproval: options?.requestCommandEscalation ?? createCommandEscalationApprover({ handoff }),\n\t\t\t\t\tviolationStore,\n\t\t\t\t\trunEscalated: (request) =>\n\t\t\t\t\t\trunEscalatedCommand({\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\tlaunch,\n\t\t\t\t\t\t\tworkspace,\n\t\t\t\t\t\t\tsupervisorStateDirectory,\n\t\t\t\t\t\t\tspawnSandboxExec: options?.spawnChild ?? spawn,\n\t\t\t\t\t\t}),\n\t\t\t\t}));\n\t\t\tconst proxyPort = proxy.port as number;\n\n\t\t\t// Seatbelt cannot remap a path, so the child connects to the channel socket at\n\t\t\t// its host path, and the profile allows outbound to exactly that one socket.\n\t\t\tconst credentialChannelSocket = launch.credentialChannel\n\t\t\t\t? canonicalSocketPath(launch.credentialChannel.hostSocketPath)\n\t\t\t\t: undefined;\n\n\t\t\tconst readOnlyDirs = readOnlyDirectories([\n\t\t\t\tprocess.execPath,\n\t\t\t\tlaunch.command,\n\t\t\t\t...(launch.readOnlyPaths ?? []),\n\t\t\t\tsupervisorStateDirectory,\n\t\t\t]);\n\t\t\t// Seatbelt denies the whole home directory before re-allowing named paths,\n\t\t\t// so a `.ts` extension loaded inside the sandbox fails on its tsconfig\n\t\t\t// rather than on anything the extension does. Empty off a source runtime.\n\t\t\tconst readOnlyFiles = [...(launch.readOnlyFiles ?? []), ...typeScriptSourceConfigFiles()].map((path) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn realpathSync(path);\n\t\t\t\t} catch {\n\t\t\t\t\treturn resolve(path);\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst terminalSizePath = join(supervisorStateDirectory, \"terminal-size\");\n\t\t\tconst stopPublishingTerminalSize = publishTerminalSize(terminalSizePath);\n\t\t\tlet userHome: string | undefined;\n\t\t\ttry {\n\t\t\t\tuserHome = realpathSync(homedir());\n\t\t\t} catch {\n\t\t\t\tuserHome = homedir();\n\t\t\t}\n\n\t\t\tconst profileLines = [\n\t\t\t\t\"(version 1)\",\n\t\t\t\t// bsd.sb's baseline is broader than Linux's opt-in bind-mount model --\n\t\t\t\t// a real gap this design accepts, documented in the spec's fourth\n\t\t\t\t// amendment -- but it is the only combination the 2b.5 prototype found\n\t\t\t\t// that lets a normal Unix child process (dyld, /bin/sh) actually start.\n\t\t\t\t'(import \"bsd.sb\")',\n\t\t\t\t\"(allow process-exec*)\",\n\t\t\t\t// Separate from process-exec* -- confirmed empirically: a shell that\n\t\t\t\t// forks a child shell (e.g. `sh -c 'a' && sh -c 'b'`) failed with\n\t\t\t\t// \"fork: Operation not permitted\" without this, even with exec allowed.\n\t\t\t\t\"(allow process-fork)\",\n\t\t\t\t// Matches Linux's actual posture (bwrap's `--ro-bind / /`): broad read,\n\t\t\t\t// narrow write. A read-only allowlist alone (this design's first cut)\n\t\t\t\t// breaks ordinary process startup -- confirmed empirically, getcwd()\n\t\t\t\t// itself needs to traverse the workspace's ancestor directories, which\n\t\t\t\t// no read rule scoped only to the workspace or runtime paths covers.\n\t\t\t\t\"(allow file-read*)\",\n\t\t\t\t'(deny file-read* (subpath (param \"USER_HOME\")))',\n\t\t\t\t...readOnlyDirs.map((_, index) => `(allow file-read* (subpath (param \"RO_${index}\")))`),\n\t\t\t\t...readOnlyFiles.map((_, index) => `(allow file-read* (subpath (param \"RO_FILE_${index}\")))`),\n\t\t\t\t'(allow file-read* (subpath (param \"WORKSPACE\")))',\n\t\t\t\t\"(deny file-write*)\",\n\t\t\t\t'(allow file-write* (subpath (param \"WORKSPACE\")))',\n\t\t\t\t// Each extra root is allowed exactly as the workspace is, and only ever from\n\t\t\t\t// an argv-parsed flag: a repository that could name its own writable root\n\t\t\t\t// would be granting itself authority (ADR 0016).\n\t\t\t\t...writableRoots.map((_, index) => `(allow file-read* (subpath (param \"RW_${index}\")))`),\n\t\t\t\t...writableRoots.map((_, index) => `(allow file-write* (subpath (param \"RW_${index}\")))`),\n\t\t\t\t\"(deny network*)\",\n\t\t\t\t'(allow network-outbound (remote ip (param \"PROXY_ADDR\")))',\n\t\t\t\t...(credentialChannelSocket\n\t\t\t\t\t? [`(allow network-outbound (remote unix-socket (literal \"${credentialChannelSocket}\")))`]\n\t\t\t\t\t: []),\n\t\t\t\t...(gitCredentialPaths\n\t\t\t\t\t? [`(allow network-outbound (remote unix-socket (literal \"${gitCredentialPaths.hostSocketPath}\")))`]\n\t\t\t\t\t: []),\n\t\t\t\t...(escalationPaths\n\t\t\t\t\t? [`(allow network-outbound (remote unix-socket (literal \"${escalationPaths.hostSocketPath}\")))`]\n\t\t\t\t\t: []),\n\t\t\t];\n\t\t\tconst profilePath = join(supervisorStateDirectory, \"profile.sb\");\n\t\t\twriteFileSync(profilePath, profileLines.join(\"\\n\"));\n\n\t\t\tconst params: string[] = [\"-D\", `USER_HOME=${userHome}`];\n\t\t\treadOnlyDirs.forEach((dir, index) => {\n\t\t\t\tparams.push(\"-D\", `RO_${index}=${dir}`);\n\t\t\t});\n\t\t\treadOnlyFiles.forEach((file, index) => {\n\t\t\t\tparams.push(\"-D\", `RO_FILE_${index}=${file}`);\n\t\t\t});\n\t\t\tparams.push(\"-D\", `WORKSPACE=${workspace}`);\n\t\t\twritableRoots.forEach((root, index) => {\n\t\t\t\tparams.push(\"-D\", `RW_${index}=${root}`);\n\t\t\t});\n\t\t\tparams.push(\"-D\", `PROXY_ADDR=localhost:${proxyPort}`);\n\n\t\t\tconst spawnSandboxExec = options?.spawnChild ?? spawn;\n\t\t\tconst child = spawnSandboxExec(\n\t\t\t\t\"sandbox-exec\",\n\t\t\t\t[\"-f\", profilePath, ...params, \"--\", launch.command, ...launch.args],\n\t\t\t\t{\n\t\t\t\t\tcwd: workspace,\n\t\t\t\t\tenv: {\n\t\t\t\t\t\t...launch.environment,\n\t\t\t\t\t\tHOME: launch.environment?.HOME ?? stateDirectory,\n\t\t\t\t\t\tTMPDIR: launch.environment?.TMPDIR ?? stateDirectory,\n\t\t\t\t\t\tHTTP_PROXY: `http://127.0.0.1:${proxyPort}`,\n\t\t\t\t\t\tHTTPS_PROXY: `http://127.0.0.1:${proxyPort}`,\n\t\t\t\t\t\t[TERMINAL_HANDOFF_PATH_VARIABLE]: supervisorStateDirectory,\n\t\t\t\t\t\t[TERMINAL_HANDOFF_ACK_PATH_VARIABLE]: acknowledgementPath,\n\t\t\t\t\t\t[TERMINAL_SIZE_PATH_VARIABLE]: terminalSizePath,\n\t\t\t\t\t\t...(gitCredentialPaths\n\t\t\t\t\t\t\t? { [GIT_CREDENTIAL_SOCKET_VARIABLE]: gitCredentialPaths.childSocketPath }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t...(escalationPaths ? { [COMMAND_ESCALATION_SOCKET_VARIABLE]: escalationPaths.childSocketPath } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tstdio: [\"inherit\", \"inherit\", \"pipe\"],\n\t\t\t\t},\n\t\t\t);\n\t\t\tlet stderr = \"\";\n\t\t\tchild.stderr?.setEncoding(\"utf8\");\n\t\t\tchild.stderr?.on(\"data\", (chunk: string) => {\n\t\t\t\tstderr += chunk;\n\t\t\t\tprocess.stderr.write(chunk);\n\t\t\t});\n\t\t\tconst exitCode = await waitForExit(child);\n\t\t\tstopPublishingTerminalSize();\n\t\t\tawait proxy?.close();\n\t\t\tconst proxyAlreadyRecordedAViolation = (violationStore?.totalCount ?? 0) > violationCountBeforeLaunch;\n\t\t\tconst kind = exitCode !== 0 && !proxyAlreadyRecordedAViolation ? classifySandboxFailure(stderr) : undefined;\n\t\t\tif (kind) {\n\t\t\t\tviolationStore?.add({\n\t\t\t\t\tkind,\n\t\t\t\t\tcommand: [launch.command, ...launch.args].join(\" \"),\n\t\t\t\t\tdetail: stderr.trim(),\n\t\t\t\t\ttimestamp: new Date(),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn exitCode;\n\t\t},\n\t\tasync close() {\n\t\t\thandoff?.stop();\n\t\t\tawait escalationProxy?.close();\n\t\t\tif (escalationDirectory) rmSync(escalationDirectory, { force: true, recursive: true });\n\t\t\tawait gitCredentialProxy?.close();\n\t\t\tif (gitCredentialDirectory) rmSync(gitCredentialDirectory, { force: true, recursive: true });\n\t\t\tawait proxy?.close();\n\t\t},\n\t};\n}\n\nasync function runEscalatedCommand(options: {\n\trequest: CommandEscalationRequest;\n\tlaunch: SandboxLaunch;\n\tworkspace: string;\n\tsupervisorStateDirectory: string;\n\tspawnSandboxExec: typeof spawn;\n}): Promise<CommandEscalationResult> {\n\tconst profilePath = join(options.supervisorStateDirectory, `escalation-${process.pid}-${Date.now()}.sb`);\n\tconst writableRoots = [...options.launch.policy.additionalWritableRoots, options.request.writableRoot].map((root) =>\n\t\tresolve(root),\n\t);\n\twriteFileSync(\n\t\tprofilePath,\n\t\t[\n\t\t\t\"(version 1)\",\n\t\t\t'(import \"bsd.sb\")',\n\t\t\t\"(allow process-exec*)\",\n\t\t\t\"(allow process-fork)\",\n\t\t\t\"(allow file-read*)\",\n\t\t\t\"(deny file-write*)\",\n\t\t\t`(allow file-write* (subpath \"${seatbeltParameter(options.workspace)}\"))`,\n\t\t\t...writableRoots.map((root) => `(allow file-write* (subpath \"${seatbeltParameter(root)}\"))`),\n\t\t\t\"(deny network*)\",\n\t\t].join(\"\\n\"),\n\t\t{ mode: 0o600 },\n\t);\n\ttry {\n\t\tconst child = options.spawnSandboxExec(\n\t\t\t\"sandbox-exec\",\n\t\t\t[\"-f\", profilePath, \"--\", \"/bin/sh\", \"-c\", options.request.command],\n\t\t\t{\n\t\t\t\tcwd: options.workspace,\n\t\t\t\tenv: {\n\t\t\t\t\tPATH: options.launch.environment?.PATH,\n\t\t\t\t\tHOME: options.launch.environment?.HOME,\n\t\t\t\t\tTMPDIR: options.launch.environment?.TMPDIR,\n\t\t\t\t},\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t},\n\t\t);\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout?.setEncoding(\"utf8\");\n\t\tchild.stdout?.on(\"data\", (chunk: string) => {\n\t\t\tstdout = appendBounded(stdout, chunk);\n\t\t});\n\t\tchild.stderr?.setEncoding(\"utf8\");\n\t\tchild.stderr?.on(\"data\", (chunk: string) => {\n\t\t\tstderr = appendBounded(stderr, chunk);\n\t\t});\n\t\tconst code = await new Promise<number>((resolveCode) => {\n\t\t\tchild.once(\"error\", () => resolveCode(1));\n\t\t\tchild.once(\"exit\", (exitCode) => resolveCode(exitCode ?? 1));\n\t\t});\n\t\treturn { code, stdout, stderr };\n\t} finally {\n\t\trmSync(profilePath, { force: true });\n\t}\n}\n"]}
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { mkdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { typeScriptSourceConfigFiles } from "../extensions/source-runtime.js";
|
|
6
|
+
import { createCommandEscalationApprover, createCredentialReleaser, createHostApprover } from "./host-approval.js";
|
|
5
7
|
import { createSandboxNetworkProxy } from "./network-proxy.js";
|
|
8
|
+
import { COMMAND_ESCALATION_SOCKET_VARIABLE, createCommandEscalationProxy, resolveCommandEscalationChannelPaths, } from "./rpc/command-proxy.js";
|
|
9
|
+
import { fillHostGitCredential, GIT_CREDENTIAL_SOCKET_VARIABLE, resolveGitCredentialChannelPaths, writeGitCredentialHelper, } from "./rpc/git-credential-helper.js";
|
|
10
|
+
import { createGitCredentialProxy } from "./rpc/git-credential-proxy.js";
|
|
11
|
+
import { createTerminalHandoff, TERMINAL_HANDOFF_ACK_PATH_VARIABLE, TERMINAL_HANDOFF_PATH_VARIABLE, } from "./terminal-handoff.js";
|
|
12
|
+
import { publishTerminalSize, TERMINAL_SIZE_PATH_VARIABLE } from "./terminal-size.js";
|
|
13
|
+
const ESCALATION_OUTPUT_LIMIT = 64 * 1024;
|
|
14
|
+
function seatbeltParameter(value) {
|
|
15
|
+
return value.replaceAll("\\", "\\\\").replaceAll("\n", "\\n").replaceAll('"', '\\"');
|
|
16
|
+
}
|
|
17
|
+
function appendBounded(current, chunk, limit = ESCALATION_OUTPUT_LIMIT) {
|
|
18
|
+
if (current.length >= limit)
|
|
19
|
+
return current;
|
|
20
|
+
return current + chunk.slice(0, limit - current.length);
|
|
21
|
+
}
|
|
6
22
|
function sandboxExecExists(command) {
|
|
7
23
|
// sandbox-exec has no --version flag; a real, minimal profile run is the
|
|
8
24
|
// standard existence-and-basic-functionality probe (see the 2b.5 prototype's
|
|
@@ -28,7 +44,15 @@ function waitForExit(child) {
|
|
|
28
44
|
* unresolved symlinked path, e.g. Homebrew's own layout, silently never matches).
|
|
29
45
|
*/
|
|
30
46
|
function readOnlyDirectories(paths) {
|
|
31
|
-
const directories = paths.map((path) =>
|
|
47
|
+
const directories = paths.map((path) => {
|
|
48
|
+
const resolved = resolve(path);
|
|
49
|
+
try {
|
|
50
|
+
return statSync(resolved).isDirectory() ? resolved : dirname(resolved);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return dirname(resolved);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
32
56
|
const canonical = directories.map((directory) => {
|
|
33
57
|
try {
|
|
34
58
|
return realpathSync(directory);
|
|
@@ -98,6 +122,11 @@ export function createMacosSandboxBackend(options) {
|
|
|
98
122
|
const hasCommand = options?.commandExists ?? sandboxExecExists;
|
|
99
123
|
const violationStore = options?.violationStore;
|
|
100
124
|
let proxy;
|
|
125
|
+
let handoff;
|
|
126
|
+
let gitCredentialProxy;
|
|
127
|
+
let gitCredentialDirectory;
|
|
128
|
+
let escalationProxy;
|
|
129
|
+
let escalationDirectory;
|
|
101
130
|
if (!hasCommand("sandbox-exec")) {
|
|
102
131
|
return {
|
|
103
132
|
status: { kind: "unavailable", reason: "sandbox-exec (Seatbelt) is required for OS sandboxing." },
|
|
@@ -115,22 +144,77 @@ export function createMacosSandboxBackend(options) {
|
|
|
115
144
|
// and Seatbelt's subpath match is against the canonical path, not the one
|
|
116
145
|
// the caller happened to spell. Resolve once, use everywhere below.
|
|
117
146
|
const workspace = realpathSync(launch.policy.workspace);
|
|
147
|
+
const writableRoots = launch.policy.additionalWritableRoots;
|
|
118
148
|
const stateDirectory = join(workspace, ".apex-code", "sandbox-state");
|
|
119
149
|
mkdirSync(stateDirectory, { recursive: true });
|
|
150
|
+
const supervisorStateDirectory = launch.supervisorStateDirectory;
|
|
151
|
+
mkdirSync(supervisorStateDirectory, { recursive: true, mode: 0o700 });
|
|
152
|
+
const acknowledgementPath = join(stateDirectory, "terminal-handoff-ack");
|
|
120
153
|
const violationCountBeforeLaunch = violationStore?.totalCount ?? 0;
|
|
154
|
+
handoff = createTerminalHandoff(supervisorStateDirectory, { acknowledgementPath });
|
|
155
|
+
// Both channels are AF_UNIX servers, which Windows has not got. Nothing is lost
|
|
156
|
+
// by skipping them there: ADR 0005 leaves Windows unsupported and this backend
|
|
157
|
+
// reports `unavailable` on any real Windows host. They are reachable here only
|
|
158
|
+
// because the violation-attribution tests run this body off-macOS on purpose,
|
|
159
|
+
// with `spawnChild` injected, and those must keep working on every runner.
|
|
160
|
+
const unixSocketsAvailable = process.platform !== "win32";
|
|
161
|
+
const gitCredentialPaths = unixSocketsAvailable ? resolveGitCredentialChannelPaths() : undefined;
|
|
162
|
+
gitCredentialDirectory = gitCredentialPaths?.hostSocketDirectory;
|
|
121
163
|
proxy = await createSandboxNetworkProxy({
|
|
122
164
|
tcpHost: "127.0.0.1",
|
|
123
165
|
allowedHosts: launch.policy.allowedHosts,
|
|
124
166
|
violationStore,
|
|
167
|
+
// Undefined without a terminal, which leaves the proxy's own
|
|
168
|
+
// deny-without-asking path in place for headless, print, JSON, and RPC.
|
|
169
|
+
requestApproval: createHostApprover({ handoff }),
|
|
125
170
|
});
|
|
171
|
+
// After the network proxy, because reachability is its answer to give.
|
|
172
|
+
gitCredentialProxy =
|
|
173
|
+
gitCredentialPaths &&
|
|
174
|
+
(await createGitCredentialProxy({
|
|
175
|
+
socketPath: gitCredentialPaths.hostSocketPath,
|
|
176
|
+
isHostAllowed: (host) => proxy?.isHostReachable(host) ?? false,
|
|
177
|
+
requestRelease: createCredentialReleaser({ handoff }),
|
|
178
|
+
// The supervisor's own environment: this runs outside the boundary and must
|
|
179
|
+
// resolve against the real host home the child cannot see.
|
|
180
|
+
fillCredential: (request) => fillHostGitCredential(request, { environment: process.env }),
|
|
181
|
+
violationStore,
|
|
182
|
+
}));
|
|
183
|
+
writeGitCredentialHelper(supervisorStateDirectory);
|
|
184
|
+
const escalationPaths = unixSocketsAvailable ? resolveCommandEscalationChannelPaths() : undefined;
|
|
185
|
+
escalationDirectory = escalationPaths?.hostSocketDirectory;
|
|
186
|
+
// Seatbelt cannot remap a path, so the child reaches this socket at its host
|
|
187
|
+
// path, exactly as it reaches the credential channel.
|
|
188
|
+
escalationProxy =
|
|
189
|
+
escalationPaths &&
|
|
190
|
+
(await createCommandEscalationProxy({
|
|
191
|
+
socketPath: escalationPaths.hostSocketPath,
|
|
192
|
+
requestApproval: options?.requestCommandEscalation ?? createCommandEscalationApprover({ handoff }),
|
|
193
|
+
violationStore,
|
|
194
|
+
runEscalated: (request) => runEscalatedCommand({
|
|
195
|
+
request,
|
|
196
|
+
launch,
|
|
197
|
+
workspace,
|
|
198
|
+
supervisorStateDirectory,
|
|
199
|
+
spawnSandboxExec: options?.spawnChild ?? spawn,
|
|
200
|
+
}),
|
|
201
|
+
}));
|
|
126
202
|
const proxyPort = proxy.port;
|
|
127
203
|
// Seatbelt cannot remap a path, so the child connects to the channel socket at
|
|
128
204
|
// its host path, and the profile allows outbound to exactly that one socket.
|
|
129
205
|
const credentialChannelSocket = launch.credentialChannel
|
|
130
206
|
? canonicalSocketPath(launch.credentialChannel.hostSocketPath)
|
|
131
207
|
: undefined;
|
|
132
|
-
const readOnlyDirs = readOnlyDirectories([
|
|
133
|
-
|
|
208
|
+
const readOnlyDirs = readOnlyDirectories([
|
|
209
|
+
process.execPath,
|
|
210
|
+
launch.command,
|
|
211
|
+
...(launch.readOnlyPaths ?? []),
|
|
212
|
+
supervisorStateDirectory,
|
|
213
|
+
]);
|
|
214
|
+
// Seatbelt denies the whole home directory before re-allowing named paths,
|
|
215
|
+
// so a `.ts` extension loaded inside the sandbox fails on its tsconfig
|
|
216
|
+
// rather than on anything the extension does. Empty off a source runtime.
|
|
217
|
+
const readOnlyFiles = [...(launch.readOnlyFiles ?? []), ...typeScriptSourceConfigFiles()].map((path) => {
|
|
134
218
|
try {
|
|
135
219
|
return realpathSync(path);
|
|
136
220
|
}
|
|
@@ -138,6 +222,8 @@ export function createMacosSandboxBackend(options) {
|
|
|
138
222
|
return resolve(path);
|
|
139
223
|
}
|
|
140
224
|
});
|
|
225
|
+
const terminalSizePath = join(supervisorStateDirectory, "terminal-size");
|
|
226
|
+
const stopPublishingTerminalSize = publishTerminalSize(terminalSizePath);
|
|
141
227
|
let userHome;
|
|
142
228
|
try {
|
|
143
229
|
userHome = realpathSync(homedir());
|
|
@@ -169,13 +255,24 @@ export function createMacosSandboxBackend(options) {
|
|
|
169
255
|
'(allow file-read* (subpath (param "WORKSPACE")))',
|
|
170
256
|
"(deny file-write*)",
|
|
171
257
|
'(allow file-write* (subpath (param "WORKSPACE")))',
|
|
258
|
+
// Each extra root is allowed exactly as the workspace is, and only ever from
|
|
259
|
+
// an argv-parsed flag: a repository that could name its own writable root
|
|
260
|
+
// would be granting itself authority (ADR 0016).
|
|
261
|
+
...writableRoots.map((_, index) => `(allow file-read* (subpath (param "RW_${index}")))`),
|
|
262
|
+
...writableRoots.map((_, index) => `(allow file-write* (subpath (param "RW_${index}")))`),
|
|
172
263
|
"(deny network*)",
|
|
173
264
|
'(allow network-outbound (remote ip (param "PROXY_ADDR")))',
|
|
174
265
|
...(credentialChannelSocket
|
|
175
266
|
? [`(allow network-outbound (remote unix-socket (literal "${credentialChannelSocket}")))`]
|
|
176
267
|
: []),
|
|
268
|
+
...(gitCredentialPaths
|
|
269
|
+
? [`(allow network-outbound (remote unix-socket (literal "${gitCredentialPaths.hostSocketPath}")))`]
|
|
270
|
+
: []),
|
|
271
|
+
...(escalationPaths
|
|
272
|
+
? [`(allow network-outbound (remote unix-socket (literal "${escalationPaths.hostSocketPath}")))`]
|
|
273
|
+
: []),
|
|
177
274
|
];
|
|
178
|
-
const profilePath = join(
|
|
275
|
+
const profilePath = join(supervisorStateDirectory, "profile.sb");
|
|
179
276
|
writeFileSync(profilePath, profileLines.join("\n"));
|
|
180
277
|
const params = ["-D", `USER_HOME=${userHome}`];
|
|
181
278
|
readOnlyDirs.forEach((dir, index) => {
|
|
@@ -185,6 +282,9 @@ export function createMacosSandboxBackend(options) {
|
|
|
185
282
|
params.push("-D", `RO_FILE_${index}=${file}`);
|
|
186
283
|
});
|
|
187
284
|
params.push("-D", `WORKSPACE=${workspace}`);
|
|
285
|
+
writableRoots.forEach((root, index) => {
|
|
286
|
+
params.push("-D", `RW_${index}=${root}`);
|
|
287
|
+
});
|
|
188
288
|
params.push("-D", `PROXY_ADDR=localhost:${proxyPort}`);
|
|
189
289
|
const spawnSandboxExec = options?.spawnChild ?? spawn;
|
|
190
290
|
const child = spawnSandboxExec("sandbox-exec", ["-f", profilePath, ...params, "--", launch.command, ...launch.args], {
|
|
@@ -195,6 +295,13 @@ export function createMacosSandboxBackend(options) {
|
|
|
195
295
|
TMPDIR: launch.environment?.TMPDIR ?? stateDirectory,
|
|
196
296
|
HTTP_PROXY: `http://127.0.0.1:${proxyPort}`,
|
|
197
297
|
HTTPS_PROXY: `http://127.0.0.1:${proxyPort}`,
|
|
298
|
+
[TERMINAL_HANDOFF_PATH_VARIABLE]: supervisorStateDirectory,
|
|
299
|
+
[TERMINAL_HANDOFF_ACK_PATH_VARIABLE]: acknowledgementPath,
|
|
300
|
+
[TERMINAL_SIZE_PATH_VARIABLE]: terminalSizePath,
|
|
301
|
+
...(gitCredentialPaths
|
|
302
|
+
? { [GIT_CREDENTIAL_SOCKET_VARIABLE]: gitCredentialPaths.childSocketPath }
|
|
303
|
+
: {}),
|
|
304
|
+
...(escalationPaths ? { [COMMAND_ESCALATION_SOCKET_VARIABLE]: escalationPaths.childSocketPath } : {}),
|
|
198
305
|
},
|
|
199
306
|
stdio: ["inherit", "inherit", "pipe"],
|
|
200
307
|
});
|
|
@@ -205,6 +312,7 @@ export function createMacosSandboxBackend(options) {
|
|
|
205
312
|
process.stderr.write(chunk);
|
|
206
313
|
});
|
|
207
314
|
const exitCode = await waitForExit(child);
|
|
315
|
+
stopPublishingTerminalSize();
|
|
208
316
|
await proxy?.close();
|
|
209
317
|
const proxyAlreadyRecordedAViolation = (violationStore?.totalCount ?? 0) > violationCountBeforeLaunch;
|
|
210
318
|
const kind = exitCode !== 0 && !proxyAlreadyRecordedAViolation ? classifySandboxFailure(stderr) : undefined;
|
|
@@ -219,8 +327,59 @@ export function createMacosSandboxBackend(options) {
|
|
|
219
327
|
return exitCode;
|
|
220
328
|
},
|
|
221
329
|
async close() {
|
|
330
|
+
handoff?.stop();
|
|
331
|
+
await escalationProxy?.close();
|
|
332
|
+
if (escalationDirectory)
|
|
333
|
+
rmSync(escalationDirectory, { force: true, recursive: true });
|
|
334
|
+
await gitCredentialProxy?.close();
|
|
335
|
+
if (gitCredentialDirectory)
|
|
336
|
+
rmSync(gitCredentialDirectory, { force: true, recursive: true });
|
|
222
337
|
await proxy?.close();
|
|
223
338
|
},
|
|
224
339
|
};
|
|
225
340
|
}
|
|
341
|
+
async function runEscalatedCommand(options) {
|
|
342
|
+
const profilePath = join(options.supervisorStateDirectory, `escalation-${process.pid}-${Date.now()}.sb`);
|
|
343
|
+
const writableRoots = [...options.launch.policy.additionalWritableRoots, options.request.writableRoot].map((root) => resolve(root));
|
|
344
|
+
writeFileSync(profilePath, [
|
|
345
|
+
"(version 1)",
|
|
346
|
+
'(import "bsd.sb")',
|
|
347
|
+
"(allow process-exec*)",
|
|
348
|
+
"(allow process-fork)",
|
|
349
|
+
"(allow file-read*)",
|
|
350
|
+
"(deny file-write*)",
|
|
351
|
+
`(allow file-write* (subpath "${seatbeltParameter(options.workspace)}"))`,
|
|
352
|
+
...writableRoots.map((root) => `(allow file-write* (subpath "${seatbeltParameter(root)}"))`),
|
|
353
|
+
"(deny network*)",
|
|
354
|
+
].join("\n"), { mode: 0o600 });
|
|
355
|
+
try {
|
|
356
|
+
const child = options.spawnSandboxExec("sandbox-exec", ["-f", profilePath, "--", "/bin/sh", "-c", options.request.command], {
|
|
357
|
+
cwd: options.workspace,
|
|
358
|
+
env: {
|
|
359
|
+
PATH: options.launch.environment?.PATH,
|
|
360
|
+
HOME: options.launch.environment?.HOME,
|
|
361
|
+
TMPDIR: options.launch.environment?.TMPDIR,
|
|
362
|
+
},
|
|
363
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
364
|
+
});
|
|
365
|
+
let stdout = "";
|
|
366
|
+
let stderr = "";
|
|
367
|
+
child.stdout?.setEncoding("utf8");
|
|
368
|
+
child.stdout?.on("data", (chunk) => {
|
|
369
|
+
stdout = appendBounded(stdout, chunk);
|
|
370
|
+
});
|
|
371
|
+
child.stderr?.setEncoding("utf8");
|
|
372
|
+
child.stderr?.on("data", (chunk) => {
|
|
373
|
+
stderr = appendBounded(stderr, chunk);
|
|
374
|
+
});
|
|
375
|
+
const code = await new Promise((resolveCode) => {
|
|
376
|
+
child.once("error", () => resolveCode(1));
|
|
377
|
+
child.once("exit", (exitCode) => resolveCode(exitCode ?? 1));
|
|
378
|
+
});
|
|
379
|
+
return { code, stdout, stderr };
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
rmSync(profilePath, { force: true });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
226
385
|
//# sourceMappingURL=macos-backend.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"macos-backend.js","sourceRoot":"","sources":["../../../src/core/sandbox/macos-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, spawnSync } from \"node:child_process\";\nimport { mkdirSync, realpathSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { createSandboxNetworkProxy, type SandboxNetworkProxy } from \"./network-proxy.ts\";\nimport type { SandboxBackend, SandboxLaunch } from \"./supervisor.ts\";\nimport type { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface MacosSandboxBackendOptions {\n\t/** Injectable only for preflight tests; production uses the running platform. */\n\tplatform?: NodeJS.Platform;\n\tcommandExists?: (command: string) => boolean;\n\tviolationStore?: SandboxViolationStore;\n\t/**\n\t * Injectable only so violation attribution can be tested off macOS; production\n\t * spawns `sandbox-exec` directly. Without this the decision about what counts as a\n\t * violation would be verifiable only on a macOS host.\n\t */\n\tspawnChild?: typeof spawn;\n}\n\nfunction sandboxExecExists(command: string): boolean {\n\t// sandbox-exec has no --version flag; a real, minimal profile run is the\n\t// standard existence-and-basic-functionality probe (see the 2b.5 prototype's\n\t// own positive control).\n\tconst result = spawnSync(command, [\"-p\", \"(version 1)(allow default)\", \"/usr/bin/true\"], {\n\t\tstdio: \"ignore\",\n\t\ttimeout: 2_000,\n\t});\n\treturn result.status === 0;\n}\n\nfunction waitForExit(child: ReturnType<typeof spawn>): Promise<number> {\n\treturn new Promise((resolve, reject) => {\n\t\tchild.once(\"error\", reject);\n\t\tchild.once(\"exit\", (code) => resolve(code ?? 1));\n\t});\n}\n\n/**\n * Unlike Linux's bwrap (which needs each mount ancestor declared for its bind-mount\n * tree), Seatbelt's `subpath` is recursive from a single root, so only the directory\n * containing each path is needed -- no ancestor walk. Each directory is resolved to\n * its real (symlink-free) path: Seatbelt canonicalizes the path it actually checks\n * against a `subpath` rule (confirmed empirically -- a rule built from an\n * unresolved symlinked path, e.g. Homebrew's own layout, silently never matches).\n */\nfunction readOnlyDirectories(paths: readonly string[]): string[] {\n\tconst directories = paths.map((path) => dirname(resolve(path)));\n\tconst canonical = directories.map((directory) => {\n\t\ttry {\n\t\t\treturn realpathSync(directory);\n\t\t} catch {\n\t\t\treturn directory;\n\t\t}\n\t});\n\treturn [...new Set(canonical)];\n}\n\n/**\n * Canonicalize a channel socket path for a Seatbelt literal. Only the directory can be\n * resolved -- the socket file need not exist yet when the profile is written -- and the\n * same symlink rule as `readOnlyDirectories` applies: Seatbelt matches the canonical\n * path, so an unresolved one silently never matches.\n */\nfunction canonicalSocketPath(path: string): string {\n\tconst directory = dirname(resolve(path));\n\ttry {\n\t\treturn join(realpathSync(directory), basename(path));\n\t} catch {\n\t\treturn join(directory, basename(path));\n\t}\n}\n\n/** Seatbelt refuses through the blocked call's own errno text, not a wrapper message. */\nconst DENIAL_EVIDENCE = /Operation not permitted|Permission denied|EPERM|EACCES/i;\n\nfunction classifySandboxFailure(stderr: string): \"filesystem\" | \"network\" | \"unknown\" | undefined {\n\t// Confirmed empirically (2b.5 prototype): unlike Linux's bwrap, macOS Seatbelt\n\t// denials surface through the launched command's own generic \"Operation not\n\t// permitted\"/EPERM text for filesystem AND network refusals alike -- there is no\n\t// macOS-specific phrase equivalent to Linux's distinct \"Network is unreachable\".\n\t// Only sandbox-exec's own wrapper-level exec refusal (a sign our own profile\n\t// forgot to allow a binary, not a policy decision) is unambiguous from stderr\n\t// text. Anything else that still shows a refusal stays \"unknown\";\n\t// network-proxy.ts's own precise per-request recording is the reliable \"network\"\n\t// signal on this platform.\n\tif (/execvp\\(\\) of .* failed: Operation not permitted/i.test(stderr)) return \"filesystem\";\n\tif (DENIAL_EVIDENCE.test(stderr)) return \"unknown\";\n\t// No refusal in evidence. The coarseness above is about telling filesystem from\n\t// network, not about telling a denial from an ordinary failure -- a non-zero exit\n\t// on its own says nothing about the boundary, and reporting it as a violation\n\t// sends the reader looking for a refusal that never happened.\n\treturn undefined;\n}\n\n/**\n * macOS's platform adapter. Seatbelt (`sandbox-exec`) allows reads broadly except\n * the invoking account's home directory, denies writes outside the workspace, and\n * denies network outside the sandbox proxy's exact loopback port -- narrower than\n * the wildcard `localhost:*` pattern some other sandboxes use. This mirrors Linux's\n * actual posture (`--ro-bind / /` plus a hidden `/home`) rather than the narrower\n * read-allowlist this design started with, which broke ordinary process startup.\n */\nexport function createMacosSandboxBackend(options?: MacosSandboxBackendOptions): SandboxBackend {\n\tconst platform = options?.platform ?? process.platform;\n\tif (platform !== \"darwin\") {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"OS sandbox is supported on macOS only.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"macOS sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\tconst hasCommand = options?.commandExists ?? sandboxExecExists;\n\tconst violationStore = options?.violationStore;\n\tlet proxy: SandboxNetworkProxy | undefined;\n\n\tif (!hasCommand(\"sandbox-exec\")) {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"sandbox-exec (Seatbelt) is required for OS sandboxing.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"macOS sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\treturn {\n\t\tstatus: { kind: \"enforced\" },\n\t\tasync launch(launch: SandboxLaunch): Promise<number> {\n\t\t\t// Same reasoning as readOnlyDirectories: on macOS the workspace itself can\n\t\t\t// be reached through a symlink (e.g. os.tmpdir()'s /var -> /private/var),\n\t\t\t// and Seatbelt's subpath match is against the canonical path, not the one\n\t\t\t// the caller happened to spell. Resolve once, use everywhere below.\n\t\t\tconst workspace = realpathSync(launch.policy.workspace);\n\t\t\tconst stateDirectory = join(workspace, \".apex-code\", \"sandbox-state\");\n\t\t\tmkdirSync(stateDirectory, { recursive: true });\n\n\t\t\tconst violationCountBeforeLaunch = violationStore?.totalCount ?? 0;\n\n\t\t\tproxy = await createSandboxNetworkProxy({\n\t\t\t\ttcpHost: \"127.0.0.1\",\n\t\t\t\tallowedHosts: launch.policy.allowedHosts,\n\t\t\t\tviolationStore,\n\t\t\t});\n\t\t\tconst proxyPort = proxy.port as number;\n\n\t\t\t// Seatbelt cannot remap a path, so the child connects to the channel socket at\n\t\t\t// its host path, and the profile allows outbound to exactly that one socket.\n\t\t\tconst credentialChannelSocket = launch.credentialChannel\n\t\t\t\t? canonicalSocketPath(launch.credentialChannel.hostSocketPath)\n\t\t\t\t: undefined;\n\n\t\t\tconst readOnlyDirs = readOnlyDirectories([process.execPath, launch.command, ...(launch.readOnlyPaths ?? [])]);\n\t\t\tconst readOnlyFiles = (launch.readOnlyFiles ?? []).map((path) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn realpathSync(path);\n\t\t\t\t} catch {\n\t\t\t\t\treturn resolve(path);\n\t\t\t\t}\n\t\t\t});\n\t\t\tlet userHome: string | undefined;\n\t\t\ttry {\n\t\t\t\tuserHome = realpathSync(homedir());\n\t\t\t} catch {\n\t\t\t\tuserHome = homedir();\n\t\t\t}\n\n\t\t\tconst profileLines = [\n\t\t\t\t\"(version 1)\",\n\t\t\t\t// bsd.sb's baseline is broader than Linux's opt-in bind-mount model --\n\t\t\t\t// a real gap this design accepts, documented in the spec's fourth\n\t\t\t\t// amendment -- but it is the only combination the 2b.5 prototype found\n\t\t\t\t// that lets a normal Unix child process (dyld, /bin/sh) actually start.\n\t\t\t\t'(import \"bsd.sb\")',\n\t\t\t\t\"(allow process-exec*)\",\n\t\t\t\t// Separate from process-exec* -- confirmed empirically: a shell that\n\t\t\t\t// forks a child shell (e.g. `sh -c 'a' && sh -c 'b'`) failed with\n\t\t\t\t// \"fork: Operation not permitted\" without this, even with exec allowed.\n\t\t\t\t\"(allow process-fork)\",\n\t\t\t\t// Matches Linux's actual posture (bwrap's `--ro-bind / /`): broad read,\n\t\t\t\t// narrow write. A read-only allowlist alone (this design's first cut)\n\t\t\t\t// breaks ordinary process startup -- confirmed empirically, getcwd()\n\t\t\t\t// itself needs to traverse the workspace's ancestor directories, which\n\t\t\t\t// no read rule scoped only to the workspace or runtime paths covers.\n\t\t\t\t\"(allow file-read*)\",\n\t\t\t\t'(deny file-read* (subpath (param \"USER_HOME\")))',\n\t\t\t\t...readOnlyDirs.map((_, index) => `(allow file-read* (subpath (param \"RO_${index}\")))`),\n\t\t\t\t...readOnlyFiles.map((_, index) => `(allow file-read* (subpath (param \"RO_FILE_${index}\")))`),\n\t\t\t\t'(allow file-read* (subpath (param \"WORKSPACE\")))',\n\t\t\t\t\"(deny file-write*)\",\n\t\t\t\t'(allow file-write* (subpath (param \"WORKSPACE\")))',\n\t\t\t\t\"(deny network*)\",\n\t\t\t\t'(allow network-outbound (remote ip (param \"PROXY_ADDR\")))',\n\t\t\t\t...(credentialChannelSocket\n\t\t\t\t\t? [`(allow network-outbound (remote unix-socket (literal \"${credentialChannelSocket}\")))`]\n\t\t\t\t\t: []),\n\t\t\t];\n\t\t\tconst profilePath = join(stateDirectory, \"profile.sb\");\n\t\t\twriteFileSync(profilePath, profileLines.join(\"\\n\"));\n\n\t\t\tconst params: string[] = [\"-D\", `USER_HOME=${userHome}`];\n\t\t\treadOnlyDirs.forEach((dir, index) => {\n\t\t\t\tparams.push(\"-D\", `RO_${index}=${dir}`);\n\t\t\t});\n\t\t\treadOnlyFiles.forEach((file, index) => {\n\t\t\t\tparams.push(\"-D\", `RO_FILE_${index}=${file}`);\n\t\t\t});\n\t\t\tparams.push(\"-D\", `WORKSPACE=${workspace}`);\n\t\t\tparams.push(\"-D\", `PROXY_ADDR=localhost:${proxyPort}`);\n\n\t\t\tconst spawnSandboxExec = options?.spawnChild ?? spawn;\n\t\t\tconst child = spawnSandboxExec(\n\t\t\t\t\"sandbox-exec\",\n\t\t\t\t[\"-f\", profilePath, ...params, \"--\", launch.command, ...launch.args],\n\t\t\t\t{\n\t\t\t\t\tcwd: workspace,\n\t\t\t\t\tenv: {\n\t\t\t\t\t\t...launch.environment,\n\t\t\t\t\t\tHOME: launch.environment?.HOME ?? stateDirectory,\n\t\t\t\t\t\tTMPDIR: launch.environment?.TMPDIR ?? stateDirectory,\n\t\t\t\t\t\tHTTP_PROXY: `http://127.0.0.1:${proxyPort}`,\n\t\t\t\t\t\tHTTPS_PROXY: `http://127.0.0.1:${proxyPort}`,\n\t\t\t\t\t},\n\t\t\t\t\tstdio: [\"inherit\", \"inherit\", \"pipe\"],\n\t\t\t\t},\n\t\t\t);\n\t\t\tlet stderr = \"\";\n\t\t\tchild.stderr?.setEncoding(\"utf8\");\n\t\t\tchild.stderr?.on(\"data\", (chunk: string) => {\n\t\t\t\tstderr += chunk;\n\t\t\t\tprocess.stderr.write(chunk);\n\t\t\t});\n\t\t\tconst exitCode = await waitForExit(child);\n\t\t\tawait proxy?.close();\n\t\t\tconst proxyAlreadyRecordedAViolation = (violationStore?.totalCount ?? 0) > violationCountBeforeLaunch;\n\t\t\tconst kind = exitCode !== 0 && !proxyAlreadyRecordedAViolation ? classifySandboxFailure(stderr) : undefined;\n\t\t\tif (kind) {\n\t\t\t\tviolationStore?.add({\n\t\t\t\t\tkind,\n\t\t\t\t\tcommand: [launch.command, ...launch.args].join(\" \"),\n\t\t\t\t\tdetail: stderr.trim(),\n\t\t\t\t\ttimestamp: new Date(),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn exitCode;\n\t\t},\n\t\tasync close() {\n\t\t\tawait proxy?.close();\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"macos-backend.js","sourceRoot":"","sources":["../../../src/core/sandbox/macos-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, spawnSync } from \"node:child_process\";\nimport { mkdirSync, realpathSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { typeScriptSourceConfigFiles } from \"../extensions/source-runtime.ts\";\nimport { createCommandEscalationApprover, createCredentialReleaser, createHostApprover } from \"./host-approval.ts\";\nimport { createSandboxNetworkProxy, type SandboxNetworkProxy } from \"./network-proxy.ts\";\nimport {\n\tCOMMAND_ESCALATION_SOCKET_VARIABLE,\n\ttype CommandEscalationRequest,\n\ttype CommandEscalationResult,\n\tcreateCommandEscalationProxy,\n\tresolveCommandEscalationChannelPaths,\n} from \"./rpc/command-proxy.ts\";\nimport {\n\tfillHostGitCredential,\n\tGIT_CREDENTIAL_SOCKET_VARIABLE,\n\tresolveGitCredentialChannelPaths,\n\twriteGitCredentialHelper,\n} from \"./rpc/git-credential-helper.ts\";\nimport { createGitCredentialProxy, type GitCredentialProxy } from \"./rpc/git-credential-proxy.ts\";\nimport type { SandboxBackend, SandboxLaunch } from \"./supervisor.ts\";\nimport {\n\tcreateTerminalHandoff,\n\tTERMINAL_HANDOFF_ACK_PATH_VARIABLE,\n\tTERMINAL_HANDOFF_PATH_VARIABLE,\n\ttype TerminalHandoff,\n} from \"./terminal-handoff.ts\";\nimport { publishTerminalSize, TERMINAL_SIZE_PATH_VARIABLE } from \"./terminal-size.ts\";\nimport type { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface MacosSandboxBackendOptions {\n\t/** Injectable only for preflight tests; production uses the running platform. */\n\tplatform?: NodeJS.Platform;\n\tcommandExists?: (command: string) => boolean;\n\tviolationStore?: SandboxViolationStore;\n\t/**\n\t * Injectable only so violation attribution can be tested off macOS; production\n\t * spawns `sandbox-exec` directly. Without this the decision about what counts as a\n\t * violation would be verifiable only on a macOS host.\n\t */\n\tspawnChild?: typeof spawn;\n\t/** Injectable only for tests, which have no terminal to approve an escalation at. */\n\trequestCommandEscalation?: (request: CommandEscalationRequest) => Promise<boolean>;\n\t/** Retained for source compatibility; launch state is now required on each launch. */\n\tsupervisorStateDirectory?: string;\n}\n\nconst ESCALATION_OUTPUT_LIMIT = 64 * 1024;\n\nfunction seatbeltParameter(value: string): string {\n\treturn value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"\\n\", \"\\\\n\").replaceAll('\"', '\\\\\"');\n}\n\nfunction appendBounded(current: string, chunk: string, limit = ESCALATION_OUTPUT_LIMIT): string {\n\tif (current.length >= limit) return current;\n\treturn current + chunk.slice(0, limit - current.length);\n}\n\nfunction sandboxExecExists(command: string): boolean {\n\t// sandbox-exec has no --version flag; a real, minimal profile run is the\n\t// standard existence-and-basic-functionality probe (see the 2b.5 prototype's\n\t// own positive control).\n\tconst result = spawnSync(command, [\"-p\", \"(version 1)(allow default)\", \"/usr/bin/true\"], {\n\t\tstdio: \"ignore\",\n\t\ttimeout: 2_000,\n\t});\n\treturn result.status === 0;\n}\n\nfunction waitForExit(child: ReturnType<typeof spawn>): Promise<number> {\n\treturn new Promise((resolve, reject) => {\n\t\tchild.once(\"error\", reject);\n\t\tchild.once(\"exit\", (code) => resolve(code ?? 1));\n\t});\n}\n\n/**\n * Unlike Linux's bwrap (which needs each mount ancestor declared for its bind-mount\n * tree), Seatbelt's `subpath` is recursive from a single root, so only the directory\n * containing each path is needed -- no ancestor walk. Each directory is resolved to\n * its real (symlink-free) path: Seatbelt canonicalizes the path it actually checks\n * against a `subpath` rule (confirmed empirically -- a rule built from an\n * unresolved symlinked path, e.g. Homebrew's own layout, silently never matches).\n */\nfunction readOnlyDirectories(paths: readonly string[]): string[] {\n\tconst directories = paths.map((path) => {\n\t\tconst resolved = resolve(path);\n\t\ttry {\n\t\t\treturn statSync(resolved).isDirectory() ? resolved : dirname(resolved);\n\t\t} catch {\n\t\t\treturn dirname(resolved);\n\t\t}\n\t});\n\tconst canonical = directories.map((directory) => {\n\t\ttry {\n\t\t\treturn realpathSync(directory);\n\t\t} catch {\n\t\t\treturn directory;\n\t\t}\n\t});\n\treturn [...new Set(canonical)];\n}\n\n/**\n * Canonicalize a channel socket path for a Seatbelt literal. Only the directory can be\n * resolved -- the socket file need not exist yet when the profile is written -- and the\n * same symlink rule as `readOnlyDirectories` applies: Seatbelt matches the canonical\n * path, so an unresolved one silently never matches.\n */\nfunction canonicalSocketPath(path: string): string {\n\tconst directory = dirname(resolve(path));\n\ttry {\n\t\treturn join(realpathSync(directory), basename(path));\n\t} catch {\n\t\treturn join(directory, basename(path));\n\t}\n}\n\n/** Seatbelt refuses through the blocked call's own errno text, not a wrapper message. */\nconst DENIAL_EVIDENCE = /Operation not permitted|Permission denied|EPERM|EACCES/i;\n\nfunction classifySandboxFailure(stderr: string): \"filesystem\" | \"network\" | \"unknown\" | undefined {\n\t// Confirmed empirically (2b.5 prototype): unlike Linux's bwrap, macOS Seatbelt\n\t// denials surface through the launched command's own generic \"Operation not\n\t// permitted\"/EPERM text for filesystem AND network refusals alike -- there is no\n\t// macOS-specific phrase equivalent to Linux's distinct \"Network is unreachable\".\n\t// Only sandbox-exec's own wrapper-level exec refusal (a sign our own profile\n\t// forgot to allow a binary, not a policy decision) is unambiguous from stderr\n\t// text. Anything else that still shows a refusal stays \"unknown\";\n\t// network-proxy.ts's own precise per-request recording is the reliable \"network\"\n\t// signal on this platform.\n\tif (/execvp\\(\\) of .* failed: Operation not permitted/i.test(stderr)) return \"filesystem\";\n\tif (DENIAL_EVIDENCE.test(stderr)) return \"unknown\";\n\t// No refusal in evidence. The coarseness above is about telling filesystem from\n\t// network, not about telling a denial from an ordinary failure -- a non-zero exit\n\t// on its own says nothing about the boundary, and reporting it as a violation\n\t// sends the reader looking for a refusal that never happened.\n\treturn undefined;\n}\n\n/**\n * macOS's platform adapter. Seatbelt (`sandbox-exec`) allows reads broadly except\n * the invoking account's home directory, denies writes outside the workspace, and\n * denies network outside the sandbox proxy's exact loopback port -- narrower than\n * the wildcard `localhost:*` pattern some other sandboxes use. This mirrors Linux's\n * actual posture (`--ro-bind / /` plus a hidden `/home`) rather than the narrower\n * read-allowlist this design started with, which broke ordinary process startup.\n */\nexport function createMacosSandboxBackend(options?: MacosSandboxBackendOptions): SandboxBackend {\n\tconst platform = options?.platform ?? process.platform;\n\tif (platform !== \"darwin\") {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"OS sandbox is supported on macOS only.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"macOS sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\tconst hasCommand = options?.commandExists ?? sandboxExecExists;\n\tconst violationStore = options?.violationStore;\n\tlet proxy: SandboxNetworkProxy | undefined;\n\tlet handoff: TerminalHandoff | undefined;\n\tlet gitCredentialProxy: GitCredentialProxy | undefined;\n\tlet gitCredentialDirectory: string | undefined;\n\tlet escalationProxy: Awaited<ReturnType<typeof createCommandEscalationProxy>> | undefined;\n\tlet escalationDirectory: string | undefined;\n\tif (!hasCommand(\"sandbox-exec\")) {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"sandbox-exec (Seatbelt) is required for OS sandboxing.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"macOS sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\treturn {\n\t\tstatus: { kind: \"enforced\" },\n\t\tasync launch(launch: SandboxLaunch): Promise<number> {\n\t\t\t// Same reasoning as readOnlyDirectories: on macOS the workspace itself can\n\t\t\t// be reached through a symlink (e.g. os.tmpdir()'s /var -> /private/var),\n\t\t\t// and Seatbelt's subpath match is against the canonical path, not the one\n\t\t\t// the caller happened to spell. Resolve once, use everywhere below.\n\t\t\tconst workspace = realpathSync(launch.policy.workspace);\n\t\t\tconst writableRoots = launch.policy.additionalWritableRoots;\n\t\t\tconst stateDirectory = join(workspace, \".apex-code\", \"sandbox-state\");\n\t\t\tmkdirSync(stateDirectory, { recursive: true });\n\t\t\tconst supervisorStateDirectory = launch.supervisorStateDirectory;\n\t\t\tmkdirSync(supervisorStateDirectory, { recursive: true, mode: 0o700 });\n\t\t\tconst acknowledgementPath = join(stateDirectory, \"terminal-handoff-ack\");\n\n\t\t\tconst violationCountBeforeLaunch = violationStore?.totalCount ?? 0;\n\n\t\t\thandoff = createTerminalHandoff(supervisorStateDirectory, { acknowledgementPath });\n\t\t\t// Both channels are AF_UNIX servers, which Windows has not got. Nothing is lost\n\t\t\t// by skipping them there: ADR 0005 leaves Windows unsupported and this backend\n\t\t\t// reports `unavailable` on any real Windows host. They are reachable here only\n\t\t\t// because the violation-attribution tests run this body off-macOS on purpose,\n\t\t\t// with `spawnChild` injected, and those must keep working on every runner.\n\t\t\tconst unixSocketsAvailable = process.platform !== \"win32\";\n\t\t\tconst gitCredentialPaths = unixSocketsAvailable ? resolveGitCredentialChannelPaths() : undefined;\n\t\t\tgitCredentialDirectory = gitCredentialPaths?.hostSocketDirectory;\n\t\t\tproxy = await createSandboxNetworkProxy({\n\t\t\t\ttcpHost: \"127.0.0.1\",\n\t\t\t\tallowedHosts: launch.policy.allowedHosts,\n\t\t\t\tviolationStore,\n\t\t\t\t// Undefined without a terminal, which leaves the proxy's own\n\t\t\t\t// deny-without-asking path in place for headless, print, JSON, and RPC.\n\t\t\t\trequestApproval: createHostApprover({ handoff }),\n\t\t\t});\n\t\t\t// After the network proxy, because reachability is its answer to give.\n\t\t\tgitCredentialProxy =\n\t\t\t\tgitCredentialPaths &&\n\t\t\t\t(await createGitCredentialProxy({\n\t\t\t\t\tsocketPath: gitCredentialPaths.hostSocketPath,\n\t\t\t\t\tisHostAllowed: (host) => proxy?.isHostReachable(host) ?? false,\n\t\t\t\t\trequestRelease: createCredentialReleaser({ handoff }),\n\t\t\t\t\t// The supervisor's own environment: this runs outside the boundary and must\n\t\t\t\t\t// resolve against the real host home the child cannot see.\n\t\t\t\t\tfillCredential: (request) => fillHostGitCredential(request, { environment: process.env }),\n\t\t\t\t\tviolationStore,\n\t\t\t\t}));\n\t\t\twriteGitCredentialHelper(supervisorStateDirectory);\n\n\t\t\tconst escalationPaths = unixSocketsAvailable ? resolveCommandEscalationChannelPaths() : undefined;\n\t\t\tescalationDirectory = escalationPaths?.hostSocketDirectory;\n\t\t\t// Seatbelt cannot remap a path, so the child reaches this socket at its host\n\t\t\t// path, exactly as it reaches the credential channel.\n\t\t\tescalationProxy =\n\t\t\t\tescalationPaths &&\n\t\t\t\t(await createCommandEscalationProxy({\n\t\t\t\t\tsocketPath: escalationPaths.hostSocketPath,\n\t\t\t\t\trequestApproval: options?.requestCommandEscalation ?? createCommandEscalationApprover({ handoff }),\n\t\t\t\t\tviolationStore,\n\t\t\t\t\trunEscalated: (request) =>\n\t\t\t\t\t\trunEscalatedCommand({\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\tlaunch,\n\t\t\t\t\t\t\tworkspace,\n\t\t\t\t\t\t\tsupervisorStateDirectory,\n\t\t\t\t\t\t\tspawnSandboxExec: options?.spawnChild ?? spawn,\n\t\t\t\t\t\t}),\n\t\t\t\t}));\n\t\t\tconst proxyPort = proxy.port as number;\n\n\t\t\t// Seatbelt cannot remap a path, so the child connects to the channel socket at\n\t\t\t// its host path, and the profile allows outbound to exactly that one socket.\n\t\t\tconst credentialChannelSocket = launch.credentialChannel\n\t\t\t\t? canonicalSocketPath(launch.credentialChannel.hostSocketPath)\n\t\t\t\t: undefined;\n\n\t\t\tconst readOnlyDirs = readOnlyDirectories([\n\t\t\t\tprocess.execPath,\n\t\t\t\tlaunch.command,\n\t\t\t\t...(launch.readOnlyPaths ?? []),\n\t\t\t\tsupervisorStateDirectory,\n\t\t\t]);\n\t\t\t// Seatbelt denies the whole home directory before re-allowing named paths,\n\t\t\t// so a `.ts` extension loaded inside the sandbox fails on its tsconfig\n\t\t\t// rather than on anything the extension does. Empty off a source runtime.\n\t\t\tconst readOnlyFiles = [...(launch.readOnlyFiles ?? []), ...typeScriptSourceConfigFiles()].map((path) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn realpathSync(path);\n\t\t\t\t} catch {\n\t\t\t\t\treturn resolve(path);\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst terminalSizePath = join(supervisorStateDirectory, \"terminal-size\");\n\t\t\tconst stopPublishingTerminalSize = publishTerminalSize(terminalSizePath);\n\t\t\tlet userHome: string | undefined;\n\t\t\ttry {\n\t\t\t\tuserHome = realpathSync(homedir());\n\t\t\t} catch {\n\t\t\t\tuserHome = homedir();\n\t\t\t}\n\n\t\t\tconst profileLines = [\n\t\t\t\t\"(version 1)\",\n\t\t\t\t// bsd.sb's baseline is broader than Linux's opt-in bind-mount model --\n\t\t\t\t// a real gap this design accepts, documented in the spec's fourth\n\t\t\t\t// amendment -- but it is the only combination the 2b.5 prototype found\n\t\t\t\t// that lets a normal Unix child process (dyld, /bin/sh) actually start.\n\t\t\t\t'(import \"bsd.sb\")',\n\t\t\t\t\"(allow process-exec*)\",\n\t\t\t\t// Separate from process-exec* -- confirmed empirically: a shell that\n\t\t\t\t// forks a child shell (e.g. `sh -c 'a' && sh -c 'b'`) failed with\n\t\t\t\t// \"fork: Operation not permitted\" without this, even with exec allowed.\n\t\t\t\t\"(allow process-fork)\",\n\t\t\t\t// Matches Linux's actual posture (bwrap's `--ro-bind / /`): broad read,\n\t\t\t\t// narrow write. A read-only allowlist alone (this design's first cut)\n\t\t\t\t// breaks ordinary process startup -- confirmed empirically, getcwd()\n\t\t\t\t// itself needs to traverse the workspace's ancestor directories, which\n\t\t\t\t// no read rule scoped only to the workspace or runtime paths covers.\n\t\t\t\t\"(allow file-read*)\",\n\t\t\t\t'(deny file-read* (subpath (param \"USER_HOME\")))',\n\t\t\t\t...readOnlyDirs.map((_, index) => `(allow file-read* (subpath (param \"RO_${index}\")))`),\n\t\t\t\t...readOnlyFiles.map((_, index) => `(allow file-read* (subpath (param \"RO_FILE_${index}\")))`),\n\t\t\t\t'(allow file-read* (subpath (param \"WORKSPACE\")))',\n\t\t\t\t\"(deny file-write*)\",\n\t\t\t\t'(allow file-write* (subpath (param \"WORKSPACE\")))',\n\t\t\t\t// Each extra root is allowed exactly as the workspace is, and only ever from\n\t\t\t\t// an argv-parsed flag: a repository that could name its own writable root\n\t\t\t\t// would be granting itself authority (ADR 0016).\n\t\t\t\t...writableRoots.map((_, index) => `(allow file-read* (subpath (param \"RW_${index}\")))`),\n\t\t\t\t...writableRoots.map((_, index) => `(allow file-write* (subpath (param \"RW_${index}\")))`),\n\t\t\t\t\"(deny network*)\",\n\t\t\t\t'(allow network-outbound (remote ip (param \"PROXY_ADDR\")))',\n\t\t\t\t...(credentialChannelSocket\n\t\t\t\t\t? [`(allow network-outbound (remote unix-socket (literal \"${credentialChannelSocket}\")))`]\n\t\t\t\t\t: []),\n\t\t\t\t...(gitCredentialPaths\n\t\t\t\t\t? [`(allow network-outbound (remote unix-socket (literal \"${gitCredentialPaths.hostSocketPath}\")))`]\n\t\t\t\t\t: []),\n\t\t\t\t...(escalationPaths\n\t\t\t\t\t? [`(allow network-outbound (remote unix-socket (literal \"${escalationPaths.hostSocketPath}\")))`]\n\t\t\t\t\t: []),\n\t\t\t];\n\t\t\tconst profilePath = join(supervisorStateDirectory, \"profile.sb\");\n\t\t\twriteFileSync(profilePath, profileLines.join(\"\\n\"));\n\n\t\t\tconst params: string[] = [\"-D\", `USER_HOME=${userHome}`];\n\t\t\treadOnlyDirs.forEach((dir, index) => {\n\t\t\t\tparams.push(\"-D\", `RO_${index}=${dir}`);\n\t\t\t});\n\t\t\treadOnlyFiles.forEach((file, index) => {\n\t\t\t\tparams.push(\"-D\", `RO_FILE_${index}=${file}`);\n\t\t\t});\n\t\t\tparams.push(\"-D\", `WORKSPACE=${workspace}`);\n\t\t\twritableRoots.forEach((root, index) => {\n\t\t\t\tparams.push(\"-D\", `RW_${index}=${root}`);\n\t\t\t});\n\t\t\tparams.push(\"-D\", `PROXY_ADDR=localhost:${proxyPort}`);\n\n\t\t\tconst spawnSandboxExec = options?.spawnChild ?? spawn;\n\t\t\tconst child = spawnSandboxExec(\n\t\t\t\t\"sandbox-exec\",\n\t\t\t\t[\"-f\", profilePath, ...params, \"--\", launch.command, ...launch.args],\n\t\t\t\t{\n\t\t\t\t\tcwd: workspace,\n\t\t\t\t\tenv: {\n\t\t\t\t\t\t...launch.environment,\n\t\t\t\t\t\tHOME: launch.environment?.HOME ?? stateDirectory,\n\t\t\t\t\t\tTMPDIR: launch.environment?.TMPDIR ?? stateDirectory,\n\t\t\t\t\t\tHTTP_PROXY: `http://127.0.0.1:${proxyPort}`,\n\t\t\t\t\t\tHTTPS_PROXY: `http://127.0.0.1:${proxyPort}`,\n\t\t\t\t\t\t[TERMINAL_HANDOFF_PATH_VARIABLE]: supervisorStateDirectory,\n\t\t\t\t\t\t[TERMINAL_HANDOFF_ACK_PATH_VARIABLE]: acknowledgementPath,\n\t\t\t\t\t\t[TERMINAL_SIZE_PATH_VARIABLE]: terminalSizePath,\n\t\t\t\t\t\t...(gitCredentialPaths\n\t\t\t\t\t\t\t? { [GIT_CREDENTIAL_SOCKET_VARIABLE]: gitCredentialPaths.childSocketPath }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t...(escalationPaths ? { [COMMAND_ESCALATION_SOCKET_VARIABLE]: escalationPaths.childSocketPath } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tstdio: [\"inherit\", \"inherit\", \"pipe\"],\n\t\t\t\t},\n\t\t\t);\n\t\t\tlet stderr = \"\";\n\t\t\tchild.stderr?.setEncoding(\"utf8\");\n\t\t\tchild.stderr?.on(\"data\", (chunk: string) => {\n\t\t\t\tstderr += chunk;\n\t\t\t\tprocess.stderr.write(chunk);\n\t\t\t});\n\t\t\tconst exitCode = await waitForExit(child);\n\t\t\tstopPublishingTerminalSize();\n\t\t\tawait proxy?.close();\n\t\t\tconst proxyAlreadyRecordedAViolation = (violationStore?.totalCount ?? 0) > violationCountBeforeLaunch;\n\t\t\tconst kind = exitCode !== 0 && !proxyAlreadyRecordedAViolation ? classifySandboxFailure(stderr) : undefined;\n\t\t\tif (kind) {\n\t\t\t\tviolationStore?.add({\n\t\t\t\t\tkind,\n\t\t\t\t\tcommand: [launch.command, ...launch.args].join(\" \"),\n\t\t\t\t\tdetail: stderr.trim(),\n\t\t\t\t\ttimestamp: new Date(),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn exitCode;\n\t\t},\n\t\tasync close() {\n\t\t\thandoff?.stop();\n\t\t\tawait escalationProxy?.close();\n\t\t\tif (escalationDirectory) rmSync(escalationDirectory, { force: true, recursive: true });\n\t\t\tawait gitCredentialProxy?.close();\n\t\t\tif (gitCredentialDirectory) rmSync(gitCredentialDirectory, { force: true, recursive: true });\n\t\t\tawait proxy?.close();\n\t\t},\n\t};\n}\n\nasync function runEscalatedCommand(options: {\n\trequest: CommandEscalationRequest;\n\tlaunch: SandboxLaunch;\n\tworkspace: string;\n\tsupervisorStateDirectory: string;\n\tspawnSandboxExec: typeof spawn;\n}): Promise<CommandEscalationResult> {\n\tconst profilePath = join(options.supervisorStateDirectory, `escalation-${process.pid}-${Date.now()}.sb`);\n\tconst writableRoots = [...options.launch.policy.additionalWritableRoots, options.request.writableRoot].map((root) =>\n\t\tresolve(root),\n\t);\n\twriteFileSync(\n\t\tprofilePath,\n\t\t[\n\t\t\t\"(version 1)\",\n\t\t\t'(import \"bsd.sb\")',\n\t\t\t\"(allow process-exec*)\",\n\t\t\t\"(allow process-fork)\",\n\t\t\t\"(allow file-read*)\",\n\t\t\t\"(deny file-write*)\",\n\t\t\t`(allow file-write* (subpath \"${seatbeltParameter(options.workspace)}\"))`,\n\t\t\t...writableRoots.map((root) => `(allow file-write* (subpath \"${seatbeltParameter(root)}\"))`),\n\t\t\t\"(deny network*)\",\n\t\t].join(\"\\n\"),\n\t\t{ mode: 0o600 },\n\t);\n\ttry {\n\t\tconst child = options.spawnSandboxExec(\n\t\t\t\"sandbox-exec\",\n\t\t\t[\"-f\", profilePath, \"--\", \"/bin/sh\", \"-c\", options.request.command],\n\t\t\t{\n\t\t\t\tcwd: options.workspace,\n\t\t\t\tenv: {\n\t\t\t\t\tPATH: options.launch.environment?.PATH,\n\t\t\t\t\tHOME: options.launch.environment?.HOME,\n\t\t\t\t\tTMPDIR: options.launch.environment?.TMPDIR,\n\t\t\t\t},\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t},\n\t\t);\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout?.setEncoding(\"utf8\");\n\t\tchild.stdout?.on(\"data\", (chunk: string) => {\n\t\t\tstdout = appendBounded(stdout, chunk);\n\t\t});\n\t\tchild.stderr?.setEncoding(\"utf8\");\n\t\tchild.stderr?.on(\"data\", (chunk: string) => {\n\t\t\tstderr = appendBounded(stderr, chunk);\n\t\t});\n\t\tconst code = await new Promise<number>((resolveCode) => {\n\t\t\tchild.once(\"error\", () => resolveCode(1));\n\t\t\tchild.once(\"exit\", (exitCode) => resolveCode(exitCode ?? 1));\n\t\t});\n\t\treturn { code, stdout, stderr };\n\t} finally {\n\t\trmSync(profilePath, { force: true });\n\t}\n}\n"]}
|
|
@@ -4,11 +4,30 @@ export interface SandboxNetworkProxy {
|
|
|
4
4
|
readonly server: http.Server;
|
|
5
5
|
/** Set only in TCP-listen mode (`tcpHost`); the ephemeral port the proxy bound. */
|
|
6
6
|
readonly port?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Whether this session may reach `hostname` on any port, by configuration or by a
|
|
9
|
+
* grant the human made this session.
|
|
10
|
+
*
|
|
11
|
+
* Exposed so the git credential channel can ask the one component that actually knows,
|
|
12
|
+
* rather than keeping a second copy of the allowlist that would drift from this one the
|
|
13
|
+
* first time a host is approved at runtime.
|
|
14
|
+
*/
|
|
15
|
+
isHostReachable(hostname: string): boolean;
|
|
7
16
|
close(): Promise<void>;
|
|
8
17
|
}
|
|
9
18
|
export declare function createSandboxNetworkProxy(options: {
|
|
10
19
|
allowedHosts: readonly string[];
|
|
11
20
|
violationStore?: SandboxViolationStore;
|
|
21
|
+
/**
|
|
22
|
+
* Ask a human whether to permit one refused host for the rest of this session.
|
|
23
|
+
*
|
|
24
|
+
* Supplied by the supervisor and never by the child: per ADR 0023, an approval
|
|
25
|
+
* asserted from inside the boundary would be indistinguishable from one forged by
|
|
26
|
+
* the code the boundary exists to contain. Absent means deny without asking, which
|
|
27
|
+
* is exactly today's behaviour and what ADR 0005 requires of headless, print,
|
|
28
|
+
* JSON, and RPC modes.
|
|
29
|
+
*/
|
|
30
|
+
requestApproval?: (hostname: string, port: number) => Promise<boolean>;
|
|
12
31
|
} & ({
|
|
13
32
|
socketPath: string;
|
|
14
33
|
tcpHost?: undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network-proxy.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/network-proxy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAgB,yBAAyB,CACxC,OAAO,EAAE;IACR,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,cAAc,CAAC,EAAE,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"network-proxy.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/network-proxy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAgB,yBAAyB,CACxC,OAAO,EAAE;IACR,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE,GAAG,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,UAAU,CAAC,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7F,OAAO,CAAC,mBAAmB,CAAC,CA8J9B","sourcesContent":["import { existsSync, unlinkSync } from \"node:fs\";\nimport * as http from \"node:http\";\nimport * as net from \"node:net\";\nimport type { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface SandboxNetworkProxy {\n\treadonly server: http.Server;\n\t/** Set only in TCP-listen mode (`tcpHost`); the ephemeral port the proxy bound. */\n\treadonly port?: number;\n\t/**\n\t * Whether this session may reach `hostname` on any port, by configuration or by a\n\t * grant the human made this session.\n\t *\n\t * Exposed so the git credential channel can ask the one component that actually knows,\n\t * rather than keeping a second copy of the allowlist that would drift from this one the\n\t * first time a host is approved at runtime.\n\t */\n\tisHostReachable(hostname: string): boolean;\n\tclose(): Promise<void>;\n}\n\nexport function createSandboxNetworkProxy(\n\toptions: {\n\t\tallowedHosts: readonly string[];\n\t\tviolationStore?: SandboxViolationStore;\n\t\t/**\n\t\t * Ask a human whether to permit one refused host for the rest of this session.\n\t\t *\n\t\t * Supplied by the supervisor and never by the child: per ADR 0023, an approval\n\t\t * asserted from inside the boundary would be indistinguishable from one forged by\n\t\t * the code the boundary exists to contain. Absent means deny without asking, which\n\t\t * is exactly today's behaviour and what ADR 0005 requires of headless, print,\n\t\t * JSON, and RPC modes.\n\t\t */\n\t\trequestApproval?: (hostname: string, port: number) => Promise<boolean>;\n\t} & ({ socketPath: string; tcpHost?: undefined } | { socketPath?: undefined; tcpHost: string }),\n): Promise<SandboxNetworkProxy> {\n\t/** Hosts a human permitted this session. Never persisted; a durable entry is a settings edit. */\n\tconst granted = new Set<string>();\n\t/**\n\t * Approvals currently awaiting an answer, keyed the same way a grant is.\n\t *\n\t * A real prompt takes as long as a human takes to read it, and a refused host usually\n\t * arrives as several near-simultaneous connections. Without this, each one raises its\n\t * own prompt for the same question.\n\t */\n\tconst pending = new Map<string, Promise<boolean>>();\n\n\tfunction isHostReachable(hostname: string): boolean {\n\t\tif (options.allowedHosts.some((entry) => entry === hostname || entry.startsWith(`${hostname}:`))) return true;\n\t\tfor (const grant of granted) {\n\t\t\tif (grant === hostname || grant.startsWith(`${hostname}:`)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tfunction isAllowed(hostname: string, port: number): boolean {\n\t\treturn (\n\t\t\toptions.allowedHosts.includes(hostname) ||\n\t\t\toptions.allowedHosts.includes(`${hostname}:${port}`) ||\n\t\t\tgranted.has(`${hostname}:${port}`)\n\t\t);\n\t}\n\n\tasync function approve(hostname: string, port: number): Promise<boolean> {\n\t\tconst key = `${hostname}:${port}`;\n\t\tif (granted.has(key)) return true;\n\t\tconst inFlight = pending.get(key);\n\t\tif (inFlight) return inFlight;\n\t\t// Declining is deliberately not remembered. A second attempt asks again, because a\n\t\t// cached \"no\" would be indistinguishable to the user from the boundary having\n\t\t// silently stopped asking.\n\t\tconst question = (async () => {\n\t\t\ttry {\n\t\t\t\tconst approved = await (options.requestApproval as (h: string, p: number) => Promise<boolean>)(\n\t\t\t\t\thostname,\n\t\t\t\t\tport,\n\t\t\t\t);\n\t\t\t\tif (approved) granted.add(key);\n\t\t\t\treturn approved;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t} finally {\n\t\t\t\tpending.delete(key);\n\t\t\t}\n\t\t})();\n\t\tpending.set(key, question);\n\t\treturn question;\n\t}\n\n\treturn new Promise((resolve, reject) => {\n\t\tconst server = http.createServer((_req, res) => {\n\t\t\tres.writeHead(405, { \"Content-Type\": \"text/plain\" });\n\t\t\tres.end(\"Method Not Allowed\\n\");\n\t\t});\n\n\t\tserver.on(\"connect\", (req, clientSocket, head) => {\n\t\t\tconst match = req.url?.match(/^([^:]+):(\\d+)$/);\n\t\t\tif (!match) {\n\t\t\t\tclientSocket.destroy();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst hostname = match[1];\n\t\t\tconst port = Number.parseInt(match[2], 10);\n\n\t\t\tfunction refuse(): void {\n\t\t\t\tif (!clientSocket.destroyed) {\n\t\t\t\t\tclientSocket.write(\"HTTP/1.1 403 Forbidden\\r\\n\\r\\n\");\n\t\t\t\t\tclientSocket.destroy();\n\t\t\t\t}\n\t\t\t\toptions.violationStore?.add({\n\t\t\t\t\tkind: \"network\",\n\t\t\t\t\tcommand: `CONNECT ${hostname}:${port}`,\n\t\t\t\t\tdetail: `Host ${hostname} refused by allowlist policy.`,\n\t\t\t\t\ttimestamp: new Date(),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (!isAllowed(hostname, port)) {\n\t\t\t\tif (!options.requestApproval) {\n\t\t\t\t\trefuse();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// The client is left waiting on its CONNECT while the human reads the\n\t\t\t\t// prompt, which is what makes an approved host resume the original request\n\t\t\t\t// instead of surfacing as a failure the caller has to retry.\n\t\t\t\tvoid approve(hostname, port).then((approved) => {\n\t\t\t\t\tif (approved) tunnel();\n\t\t\t\t\telse refuse();\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttunnel();\n\n\t\t\tfunction tunnel(): void {\n\t\t\t\tif (clientSocket.destroyed) return;\n\t\t\t\tconst serverSocket = net.connect(Number(port) || 443, hostname, () => {\n\t\t\t\t\tclientSocket.write(\"HTTP/1.1 200 Connection Established\\r\\n\\r\\n\");\n\t\t\t\t\tserverSocket.write(head);\n\t\t\t\t\tserverSocket.pipe(clientSocket);\n\t\t\t\t\tclientSocket.pipe(serverSocket);\n\t\t\t\t});\n\n\t\t\t\tserverSocket.on(\"error\", () => {\n\t\t\t\t\tif (!clientSocket.destroyed) {\n\t\t\t\t\t\tclientSocket.write(`HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n`);\n\t\t\t\t\t\tclientSocket.destroy();\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tclientSocket.on(\"error\", () => {\n\t\t\t\t\tserverSocket.destroy();\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tserver.on(\"error\", reject);\n\n\t\tif (options.tcpHost !== undefined) {\n\t\t\t// macOS's Seatbelt can permit a specific localhost port directly, so the\n\t\t\t// proxy listens on loopback TCP there instead of a Unix domain socket --\n\t\t\t// no in-child relay process is needed the way Linux's `--unshare-net`\n\t\t\t// requires one.\n\t\t\tserver.listen(0, options.tcpHost, () => {\n\t\t\t\tserver.off(\"error\", reject);\n\t\t\t\tconst address = server.address();\n\t\t\t\tconst port = typeof address === \"object\" && address !== null ? address.port : undefined;\n\t\t\t\tresolve({\n\t\t\t\t\tserver,\n\t\t\t\t\tport,\n\t\t\t\t\tisHostReachable,\n\t\t\t\t\tclose: () => new Promise((r) => server.close(() => r())),\n\t\t\t\t});\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// A prior sandboxed launch that never called close() (a crash, a SIGKILL)\n\t\t// leaves a stale socket file behind; Node's listen() refuses to bind over\n\t\t// one even though nothing is listening on it.\n\t\tif (existsSync(options.socketPath)) {\n\t\t\tunlinkSync(options.socketPath);\n\t\t}\n\n\t\tserver.listen(options.socketPath, () => {\n\t\t\tserver.off(\"error\", reject);\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tisHostReachable,\n\t\t\t\tclose: () => new Promise((r) => server.close(() => r())),\n\t\t\t});\n\t\t});\n\t});\n}\n"]}
|