apex-code 0.0.1-alpha.1 → 0.0.1-alpha.11
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 +482 -0
- package/README.md +75 -680
- package/dist/cli/args.d.ts +21 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +119 -15
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/auth-command.d.ts.map +1 -1
- package/dist/cli/auth-command.js +3 -3
- package/dist/cli/auth-command.js.map +1 -1
- package/dist/cli/concurrent-session.d.ts +3 -0
- package/dist/cli/concurrent-session.d.ts.map +1 -0
- package/dist/cli/concurrent-session.js +13 -0
- package/dist/cli/concurrent-session.js.map +1 -0
- package/dist/cli/cost-command.d.ts +19 -0
- package/dist/cli/cost-command.d.ts.map +1 -0
- package/dist/cli/cost-command.js +107 -0
- package/dist/cli/cost-command.js.map +1 -0
- 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/startup-ui.d.ts +1 -1
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +1 -2
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +99 -16
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +5 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +58 -24
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +2 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +28 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +140 -4
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +147 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +440 -123
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +4 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +17 -14
- 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 +18 -10
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/checkpoints/git-checkpoints.d.ts +33 -0
- package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
- package/dist/core/checkpoints/git-checkpoints.js +230 -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 +29 -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 +3 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +4 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +41 -32
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/context/deferred-schemas.d.ts +92 -0
- package/dist/core/context/deferred-schemas.d.ts.map +1 -0
- package/dist/core/context/deferred-schemas.js +74 -0
- package/dist/core/context/deferred-schemas.js.map +1 -0
- package/dist/core/context/eviction.d.ts +47 -0
- package/dist/core/context/eviction.d.ts.map +1 -0
- package/dist/core/context/eviction.js +83 -0
- package/dist/core/context/eviction.js.map +1 -0
- package/dist/core/context/pipeline.d.ts +90 -0
- package/dist/core/context/pipeline.d.ts.map +1 -0
- package/dist/core/context/pipeline.js +109 -0
- package/dist/core/context/pipeline.js.map +1 -0
- package/dist/core/credential-failover.d.ts +30 -0
- package/dist/core/credential-failover.d.ts.map +1 -0
- package/dist/core/credential-failover.js +46 -0
- package/dist/core/credential-failover.js.map +1 -0
- package/dist/core/credential-pool.d.ts +60 -0
- package/dist/core/credential-pool.d.ts.map +1 -0
- package/dist/core/credential-pool.js +84 -0
- package/dist/core/credential-pool.js.map +1 -0
- 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/agents.d.ts +15 -0
- package/dist/core/delegation/agents.d.ts.map +1 -0
- package/dist/core/delegation/agents.js +55 -0
- package/dist/core/delegation/agents.js.map +1 -0
- package/dist/core/delegation/ceiling.d.ts +29 -0
- package/dist/core/delegation/ceiling.d.ts.map +1 -0
- package/dist/core/delegation/ceiling.js +34 -0
- package/dist/core/delegation/ceiling.js.map +1 -0
- package/dist/core/delegation/runtime.d.ts +94 -0
- package/dist/core/delegation/runtime.d.ts.map +1 -0
- package/dist/core/delegation/runtime.js +117 -0
- package/dist/core/delegation/runtime.js.map +1 -0
- package/dist/core/durable-state/daemon.d.ts +37 -0
- package/dist/core/durable-state/daemon.d.ts.map +1 -0
- package/dist/core/durable-state/daemon.js +62 -0
- package/dist/core/durable-state/daemon.js.map +1 -0
- package/dist/core/durable-state/provenance.d.ts +7 -0
- package/dist/core/durable-state/provenance.d.ts.map +1 -0
- package/dist/core/durable-state/provenance.js +24 -0
- package/dist/core/durable-state/provenance.js.map +1 -0
- package/dist/core/durable-state/sqlite.d.ts +84 -0
- package/dist/core/durable-state/sqlite.d.ts.map +1 -0
- package/dist/core/durable-state/sqlite.js +355 -0
- package/dist/core/durable-state/sqlite.js.map +1 -0
- package/dist/core/environment.d.ts +12 -0
- package/dist/core/environment.d.ts.map +1 -0
- package/dist/core/environment.js +69 -0
- package/dist/core/environment.js.map +1 -0
- package/dist/core/evidence-policy.d.ts +17 -0
- package/dist/core/evidence-policy.d.ts.map +1 -0
- package/dist/core/evidence-policy.js +25 -0
- package/dist/core/evidence-policy.js.map +1 -0
- package/dist/core/evidence.d.ts +16 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +51 -0
- package/dist/core/evidence.js.map +1 -0
- 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/experimental.d.ts +4 -0
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +6 -1
- package/dist/core/experimental.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 +122 -49
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +44 -9
- 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/http-dispatcher.d.ts +1 -2
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -17
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/http-idle-timeout.d.ts +12 -0
- package/dist/core/http-idle-timeout.d.ts.map +1 -0
- package/dist/core/http-idle-timeout.js +27 -0
- package/dist/core/http-idle-timeout.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +38 -13
- 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/lsp/client.d.ts +78 -0
- package/dist/core/lsp/client.d.ts.map +1 -0
- package/dist/core/lsp/client.js +494 -0
- package/dist/core/lsp/client.js.map +1 -0
- package/dist/core/lsp/diagnostics.d.ts +47 -0
- package/dist/core/lsp/diagnostics.d.ts.map +1 -0
- package/dist/core/lsp/diagnostics.js +224 -0
- package/dist/core/lsp/diagnostics.js.map +1 -0
- package/dist/core/lsp/pool.d.ts +48 -0
- package/dist/core/lsp/pool.d.ts.map +1 -0
- package/dist/core/lsp/pool.js +150 -0
- package/dist/core/lsp/pool.js.map +1 -0
- package/dist/core/lsp/registry.d.ts +49 -0
- package/dist/core/lsp/registry.d.ts.map +1 -0
- package/dist/core/lsp/registry.js +215 -0
- package/dist/core/lsp/registry.js.map +1 -0
- package/dist/core/lsp/status.d.ts +23 -0
- package/dist/core/lsp/status.d.ts.map +1 -0
- package/dist/core/lsp/status.js +35 -0
- package/dist/core/lsp/status.js.map +1 -0
- package/dist/core/lsp/sync.d.ts +13 -0
- package/dist/core/lsp/sync.d.ts.map +1 -0
- package/dist/core/lsp/sync.js +71 -0
- package/dist/core/lsp/sync.js.map +1 -0
- 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 +152 -0
- package/dist/core/mcp/config.js.map +1 -0
- package/dist/core/mcp/connector.d.ts +10 -0
- package/dist/core/mcp/connector.d.ts.map +1 -0
- package/dist/core/mcp/connector.js +70 -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 +165 -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/runtime.d.ts +18 -0
- package/dist/core/mcp/runtime.d.ts.map +1 -0
- package/dist/core/mcp/runtime.js +35 -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 +98 -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 +18 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +25 -6
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts +21 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +73 -8
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +73 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +225 -8
- package/dist/core/model-runtime.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/observability/aggregate.d.ts +21 -0
- package/dist/core/observability/aggregate.d.ts.map +1 -0
- package/dist/core/observability/aggregate.js +36 -0
- package/dist/core/observability/aggregate.js.map +1 -0
- package/dist/core/observability/otlp.d.ts +66 -0
- package/dist/core/observability/otlp.d.ts.map +1 -0
- package/dist/core/observability/otlp.js +95 -0
- package/dist/core/observability/otlp.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +40 -16
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/permissions/gate.d.ts +42 -0
- package/dist/core/permissions/gate.d.ts.map +1 -0
- package/dist/core/permissions/gate.js +72 -0
- package/dist/core/permissions/gate.js.map +1 -0
- package/dist/core/permissions/modes.d.ts +18 -0
- package/dist/core/permissions/modes.d.ts.map +1 -0
- package/dist/core/permissions/modes.js +57 -0
- package/dist/core/permissions/modes.js.map +1 -0
- package/dist/core/permissions/responder.d.ts +30 -0
- package/dist/core/permissions/responder.d.ts.map +1 -0
- package/dist/core/permissions/responder.js +32 -0
- package/dist/core/permissions/responder.js.map +1 -0
- package/dist/core/permissions/rules.d.ts +34 -0
- package/dist/core/permissions/rules.d.ts.map +1 -0
- package/dist/core/permissions/rules.js +74 -0
- package/dist/core/permissions/rules.js.map +1 -0
- package/dist/core/permissions/startup.d.ts +45 -0
- package/dist/core/permissions/startup.d.ts.map +1 -0
- package/dist/core/permissions/startup.js +63 -0
- package/dist/core/permissions/startup.js.map +1 -0
- package/dist/core/permissions/store.d.ts +114 -0
- package/dist/core/permissions/store.d.ts.map +1 -0
- package/dist/core/permissions/store.js +229 -0
- package/dist/core/permissions/store.js.map +1 -0
- 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/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js +1 -1
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/provider-attribution.d.ts.map +1 -1
- package/dist/core/provider-attribution.js +6 -7
- package/dist/core/provider-attribution.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +2 -2
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +22 -17
- 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 +52 -0
- package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
- package/dist/core/sandbox/bwrap-arguments.js +86 -0
- package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
- package/dist/core/sandbox/child-entry.d.ts +2 -0
- package/dist/core/sandbox/child-entry.d.ts.map +1 -0
- package/dist/core/sandbox/child-entry.js +22 -0
- package/dist/core/sandbox/child-entry.js.map +1 -0
- package/dist/core/sandbox/cli-launch.d.ts +88 -0
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -0
- package/dist/core/sandbox/cli-launch.js +272 -0
- package/dist/core/sandbox/cli-launch.js.map +1 -0
- package/dist/core/sandbox/cli-supervisor.d.ts +30 -0
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -0
- package/dist/core/sandbox/cli-supervisor.js +123 -0
- package/dist/core/sandbox/cli-supervisor.js.map +1 -0
- package/dist/core/sandbox/default-hosts.d.ts +28 -0
- package/dist/core/sandbox/default-hosts.d.ts.map +1 -0
- package/dist/core/sandbox/default-hosts.js +69 -0
- package/dist/core/sandbox/default-hosts.js.map +1 -0
- 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 +46 -0
- package/dist/core/sandbox/linux-backend.d.ts.map +1 -0
- package/dist/core/sandbox/linux-backend.js +304 -0
- package/dist/core/sandbox/linux-backend.js.map +1 -0
- package/dist/core/sandbox/macos-backend.d.ts +28 -0
- package/dist/core/sandbox/macos-backend.d.ts.map +1 -0
- package/dist/core/sandbox/macos-backend.js +313 -0
- package/dist/core/sandbox/macos-backend.js.map +1 -0
- package/dist/core/sandbox/network-proxy.d.ts +38 -0
- package/dist/core/sandbox/network-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/network-proxy.js +153 -0
- package/dist/core/sandbox/network-proxy.js.map +1 -0
- package/dist/core/sandbox/network-refusal.d.ts +24 -0
- package/dist/core/sandbox/network-refusal.d.ts.map +1 -0
- package/dist/core/sandbox/network-refusal.js +72 -0
- package/dist/core/sandbox/network-refusal.js.map +1 -0
- package/dist/core/sandbox/policy.d.ts +40 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -0
- package/dist/core/sandbox/policy.js +55 -0
- package/dist/core/sandbox/policy.js.map +1 -0
- 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-client.d.ts +20 -0
- package/dist/core/sandbox/rpc/credential-client.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-client.js +170 -0
- package/dist/core/sandbox/rpc/credential-client.js.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts +44 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.js +287 -0
- package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -0
- 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 +40 -0
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js +152 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +48 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.js +142 -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 +51 -0
- package/dist/core/sandbox/supervisor.d.ts.map +1 -0
- package/dist/core/sandbox/supervisor.js +18 -0
- package/dist/core/sandbox/supervisor.js.map +1 -0
- package/dist/core/sandbox/terminal-handoff.d.ts +45 -0
- package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-handoff.js +176 -0
- package/dist/core/sandbox/terminal-handoff.js.map +1 -0
- package/dist/core/sandbox/terminal-size.d.ts +40 -0
- package/dist/core/sandbox/terminal-size.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-size.js +105 -0
- package/dist/core/sandbox/terminal-size.js.map +1 -0
- package/dist/core/sandbox/violations.d.ts +23 -0
- package/dist/core/sandbox/violations.d.ts.map +1 -0
- package/dist/core/sandbox/violations.js +30 -0
- package/dist/core/sandbox/violations.js.map +1 -0
- package/dist/core/sdk.d.ts +32 -41
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +174 -6
- 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-lease.d.ts +31 -0
- package/dist/core/session-lease.d.ts.map +1 -0
- package/dist/core/session-lease.js +138 -0
- package/dist/core/session-lease.js.map +1 -0
- package/dist/core/session-manager.d.ts +58 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +92 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-share.d.ts +31 -0
- package/dist/core/session-share.d.ts.map +1 -0
- package/dist/core/session-share.js +45 -0
- package/dist/core/session-share.js.map +1 -0
- 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 +99 -9
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +141 -36
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +19 -7
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +128 -52
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +6 -2
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -2
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +59 -68
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/timings.d.ts +1 -1
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +3 -2
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/ask-user.d.ts +25 -0
- package/dist/core/tools/ask-user.d.ts.map +1 -0
- package/dist/core/tools/ask-user.js +49 -0
- package/dist/core/tools/ask-user.js.map +1 -0
- package/dist/core/tools/bash-command-segments.d.ts +22 -0
- package/dist/core/tools/bash-command-segments.d.ts.map +1 -0
- package/dist/core/tools/bash-command-segments.js +128 -0
- package/dist/core/tools/bash-command-segments.js.map +1 -0
- package/dist/core/tools/bash.d.ts +40 -4
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +183 -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 +185 -0
- package/dist/core/tools/contract.d.ts.map +1 -0
- package/dist/core/tools/contract.js +50 -0
- package/dist/core/tools/contract.js.map +1 -0
- package/dist/core/tools/delegate.d.ts +38 -0
- package/dist/core/tools/delegate.d.ts.map +1 -0
- package/dist/core/tools/delegate.js +90 -0
- package/dist/core/tools/delegate.js.map +1 -0
- package/dist/core/tools/diagnostics.d.ts +39 -0
- package/dist/core/tools/diagnostics.d.ts.map +1 -0
- package/dist/core/tools/diagnostics.js +62 -0
- package/dist/core/tools/diagnostics.js.map +1 -0
- package/dist/core/tools/edit-diff.d.ts +0 -5
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +2 -5
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +12 -2
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +61 -10
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +2 -2
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +13 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +4 -2
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +47 -10
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +60 -6
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +148 -2
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +4 -2
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +17 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/lsp.d.ts +62 -0
- package/dist/core/tools/lsp.d.ts.map +1 -0
- package/dist/core/tools/lsp.js +216 -0
- package/dist/core/tools/lsp.js.map +1 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +1 -1
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/path-permission.d.ts +18 -0
- package/dist/core/tools/path-permission.d.ts.map +1 -0
- package/dist/core/tools/path-permission.js +28 -0
- package/dist/core/tools/path-permission.js.map +1 -0
- package/dist/core/tools/plan-present.d.ts +27 -0
- package/dist/core/tools/plan-present.d.ts.map +1 -0
- package/dist/core/tools/plan-present.js +55 -0
- package/dist/core/tools/plan-present.js.map +1 -0
- 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 +2 -2
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +14 -0
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/skill-search.d.ts +32 -0
- package/dist/core/tools/skill-search.d.ts.map +1 -0
- package/dist/core/tools/skill-search.js +56 -0
- package/dist/core/tools/skill-search.js.map +1 -0
- package/dist/core/tools/test.d.ts +34 -0
- package/dist/core/tools/test.d.ts.map +1 -0
- package/dist/core/tools/test.js +75 -0
- package/dist/core/tools/test.js.map +1 -0
- package/dist/core/tools/todo-write.d.ts +31 -0
- package/dist/core/tools/todo-write.d.ts.map +1 -0
- package/dist/core/tools/todo-write.js +51 -0
- package/dist/core/tools/todo-write.js.map +1 -0
- package/dist/core/tools/tool-definition-wrapper.d.ts +13 -2
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +11 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/tools/tool-schema.d.ts +22 -0
- package/dist/core/tools/tool-schema.d.ts.map +1 -0
- package/dist/core/tools/tool-schema.js +45 -0
- package/dist/core/tools/tool-schema.js.map +1 -0
- package/dist/core/tools/web-fetch.d.ts +26 -0
- package/dist/core/tools/web-fetch.d.ts.map +1 -0
- package/dist/core/tools/web-fetch.js +62 -0
- package/dist/core/tools/web-fetch.js.map +1 -0
- package/dist/core/tools/web-search-exa.d.ts +33 -0
- package/dist/core/tools/web-search-exa.d.ts.map +1 -0
- package/dist/core/tools/web-search-exa.js +128 -0
- package/dist/core/tools/web-search-exa.js.map +1 -0
- package/dist/core/tools/web-search.d.ts +26 -0
- package/dist/core/tools/web-search.d.ts.map +1 -0
- package/dist/core/tools/web-search.js +53 -0
- package/dist/core/tools/web-search.js.map +1 -0
- package/dist/core/tools/write.d.ts +16 -2
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +48 -2
- 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/usage-performance-store.d.ts +56 -0
- package/dist/core/usage-performance-store.d.ts.map +1 -0
- package/dist/core/usage-performance-store.js +85 -0
- package/dist/core/usage-performance-store.js.map +1 -0
- package/dist/core/web-search-provider.d.ts +64 -0
- package/dist/core/web-search-provider.d.ts.map +1 -0
- package/dist/core/web-search-provider.js +149 -0
- package/dist/core/web-search-provider.js.map +1 -0
- 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.map +1 -1
- package/dist/extensions/llama/provider.js +6 -2
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +9 -6
- 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 +5 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +87 -22
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +6 -5
- package/dist/migrations.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/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +9 -6
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +72 -1
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +338 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts +7 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +11 -2
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +4 -0
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +24 -6
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -4
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +17 -41
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/first-use-hints.d.ts +8 -0
- package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -0
- package/dist/modes/interactive/components/first-use-hints.js +22 -0
- package/dist/modes/interactive/components/first-use-hints.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +31 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +205 -24
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +2 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +2 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +31 -9
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +236 -96
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +25 -5
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +226 -58
- 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/splash-header.d.ts +66 -0
- package/dist/modes/interactive/components/splash-header.d.ts.map +1 -0
- package/dist/modes/interactive/components/splash-header.js +180 -0
- package/dist/modes/interactive/components/splash-header.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 +28 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +144 -35
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-panel.d.ts +30 -0
- package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-panel.js +130 -0
- package/dist/modes/interactive/components/tool-panel.js.map +1 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +60 -10
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +90 -5
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +729 -206
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
- package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
- package/dist/modes/interactive/model-catalog-refresh.js +34 -0
- package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
- package/dist/modes/interactive/theme/apex.json +93 -0
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +3 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +25 -11
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +11 -3
- package/dist/modes/interactive/theme/theme.d.ts +14 -3
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +65 -10
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +18 -12
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +22 -5
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -1
- package/dist/modes/rpc/rpc-mode.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 +210 -19
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/rpc-entry.d.ts.map +1 -1
- package/dist/rpc-entry.js +2 -1
- package/dist/rpc-entry.js.map +1 -1
- package/dist/testing/replay/metrics.d.ts +24 -0
- package/dist/testing/replay/metrics.d.ts.map +1 -0
- package/dist/testing/replay/metrics.js +35 -0
- package/dist/testing/replay/metrics.js.map +1 -0
- package/dist/testing/replay/recorded-provider.d.ts +10 -0
- package/dist/testing/replay/recorded-provider.d.ts.map +1 -0
- package/dist/testing/replay/recorded-provider.js +108 -0
- package/dist/testing/replay/recorded-provider.js.map +1 -0
- package/dist/testing/replay/runner.d.ts +56 -0
- package/dist/testing/replay/runner.d.ts.map +1 -0
- package/dist/testing/replay/runner.js +406 -0
- package/dist/testing/replay/runner.js.map +1 -0
- package/dist/themes/apex-logo.d.ts +41 -0
- package/dist/themes/apex-logo.d.ts.map +1 -0
- package/dist/themes/apex-logo.js +35 -0
- package/dist/themes/apex-logo.js.map +1 -0
- package/dist/utils/apex-code-user-agent.d.ts +2 -0
- package/dist/utils/apex-code-user-agent.d.ts.map +1 -0
- package/dist/utils/apex-code-user-agent.js +5 -0
- package/dist/utils/apex-code-user-agent.js.map +1 -0
- package/dist/utils/changelog.d.ts +7 -2
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +82 -13
- package/dist/utils/changelog.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.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 +17 -5
- 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/tools-manager.d.ts +70 -2
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +280 -124
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +7 -5
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +27 -14
- package/dist/utils/version-check.js.map +1 -1
- package/dist/utils/windows-self-update.d.ts.map +1 -1
- package/dist/utils/windows-self-update.js +1 -1
- package/dist/utils/windows-self-update.js.map +1 -1
- package/docs/compaction.md +41 -24
- package/docs/containerization.md +24 -21
- package/docs/custom-provider.md +15 -12
- package/docs/development.md +11 -11
- package/docs/environment-variables.md +37 -77
- package/docs/extensions.md +68 -54
- package/docs/index.md +12 -24
- package/docs/json.md +18 -11
- package/docs/keybindings.md +18 -12
- package/docs/llama-cpp.md +9 -7
- package/docs/models.md +17 -11
- package/docs/packages.md +47 -43
- package/docs/prompt-templates.md +3 -3
- package/docs/providers.md +10 -10
- package/docs/quickstart.md +49 -32
- package/docs/rpc.md +35 -18
- package/docs/sdk.md +20 -6
- package/docs/security.md +18 -18
- package/docs/session-format.md +9 -9
- package/docs/sessions.md +13 -13
- package/docs/settings.md +83 -16
- package/docs/shell-aliases.md +1 -1
- package/docs/skills.md +66 -15
- package/docs/terminal-setup.md +44 -19
- package/docs/termux.md +4 -4
- package/docs/themes.md +32 -11
- package/docs/tmux.md +4 -4
- package/docs/tui.md +5 -5
- package/docs/usage.md +61 -53
- package/docs/windows.md +24 -2
- package/examples/extensions/border-status-editor.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +2 -2
- 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/notify.ts +3 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +3 -1
- package/examples/extensions/subagent/agents.ts +40 -9
- package/examples/extensions/subagent/index.ts +27 -4
- 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 +435 -452
- package/package.json +17 -18
- package/dist/core/telemetry.d.ts +0 -3
- package/dist/core/telemetry.d.ts.map +0 -1
- package/dist/core/telemetry.js +0 -9
- package/dist/core/telemetry.js.map +0 -1
- 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 -104
- package/dist/server/create-harness.js.map +0 -1
- package/dist/utils/pi-user-agent.d.ts +0 -2
- package/dist/utils/pi-user-agent.d.ts.map +0 -1
- package/dist/utils/pi-user-agent.js +0 -5
- package/dist/utils/pi-user-agent.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-refusal.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/network-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwBH;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK7F;AASD;;;;;GAKG;AACH,wBAAgB,oCAAoC,IAAI,IAAI,CAW3D","sourcesContent":["/**\n * Turn a sandbox allowlist refusal into a message the user can act on.\n *\n * The child reaches the network only through the supervisor's CONNECT proxy, which\n * answers 403 for any host absent from `network.allowedHosts`. undici reports that as\n * `Proxy response (403) !== 200 when HTTP Tunneling`, but fetch surfaces only the\n * generic `fetch failed` at the top of the cause chain — so the caller sees a network\n * error with no host, no reason, and no remedy. Recovering the detail here is what\n * separates \"something is broken\" from \"this host is not on the allowlist\".\n */\n\n/** undici's own wording for a tunnel the proxy declined to open. */\nconst TUNNEL_REFUSAL = /Proxy response \\((\\d+)\\) !== 200 when HTTP Tunneling/;\n\nconst MAXIMUM_CAUSE_DEPTH = 8;\n\nfunction findTunnelRefusal(error: unknown): boolean {\n\tlet current: unknown = error;\n\tfor (let depth = 0; depth < MAXIMUM_CAUSE_DEPTH && current instanceof Error; depth++) {\n\t\tif (TUNNEL_REFUSAL.test(current.message)) return true;\n\t\tcurrent = current.cause;\n\t}\n\treturn false;\n}\n\nfunction hostFromUrl(url: string): string | undefined {\n\ttry {\n\t\treturn new URL(url).host;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Describe a refused request, or return undefined when the failure was something else.\n * Only the sandboxed child installs this, so attributing the refusal to the sandbox\n * allowlist is accurate rather than a guess about whose proxy answered.\n */\nexport function describeSandboxNetworkRefusal(error: unknown, url: string): string | undefined {\n\tif (!findTunnelRefusal(error)) return undefined;\n\tconst host = hostFromUrl(url);\n\tconst subject = host ? `Host ${host} is` : \"That host is\";\n\treturn `${subject} not on the sandbox network allowlist, so the request was refused. Add it to \"network.allowedHosts\" in your global Apex Code settings to permit it.`;\n}\n\nfunction requestUrl(input: unknown): string {\n\tif (typeof input === \"string\") return input;\n\tif (input instanceof URL) return input.href;\n\tif (input instanceof Request) return input.url;\n\treturn \"\";\n}\n\n/**\n * Wrap global fetch so every caller — model providers, the version check, catalogs —\n * reports a refusal in the same actionable terms without each having to know the\n * sandbox exists. The original error is preserved as `cause`, and anything that is not\n * a refusal is rethrown untouched so retry and abort handling behave exactly as before.\n */\nexport function installSandboxNetworkRefusalMessages(): void {\n\tconst wrapped = globalThis.fetch;\n\tglobalThis.fetch = async function fetchWithRefusalDetail(input, init) {\n\t\ttry {\n\t\t\treturn await wrapped(input, init);\n\t\t} catch (error) {\n\t\t\tconst description = describeSandboxNetworkRefusal(error, requestUrl(input));\n\t\t\tif (!description) throw error;\n\t\t\tthrow new Error(description, { cause: error });\n\t\t}\n\t} as typeof globalThis.fetch;\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn a sandbox allowlist refusal into a message the user can act on.
|
|
3
|
+
*
|
|
4
|
+
* The child reaches the network only through the supervisor's CONNECT proxy, which
|
|
5
|
+
* answers 403 for any host absent from `network.allowedHosts`. undici reports that as
|
|
6
|
+
* `Proxy response (403) !== 200 when HTTP Tunneling`, but fetch surfaces only the
|
|
7
|
+
* generic `fetch failed` at the top of the cause chain — so the caller sees a network
|
|
8
|
+
* error with no host, no reason, and no remedy. Recovering the detail here is what
|
|
9
|
+
* separates "something is broken" from "this host is not on the allowlist".
|
|
10
|
+
*/
|
|
11
|
+
/** undici's own wording for a tunnel the proxy declined to open. */
|
|
12
|
+
const TUNNEL_REFUSAL = /Proxy response \((\d+)\) !== 200 when HTTP Tunneling/;
|
|
13
|
+
const MAXIMUM_CAUSE_DEPTH = 8;
|
|
14
|
+
function findTunnelRefusal(error) {
|
|
15
|
+
let current = error;
|
|
16
|
+
for (let depth = 0; depth < MAXIMUM_CAUSE_DEPTH && current instanceof Error; depth++) {
|
|
17
|
+
if (TUNNEL_REFUSAL.test(current.message))
|
|
18
|
+
return true;
|
|
19
|
+
current = current.cause;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
function hostFromUrl(url) {
|
|
24
|
+
try {
|
|
25
|
+
return new URL(url).host;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Describe a refused request, or return undefined when the failure was something else.
|
|
33
|
+
* Only the sandboxed child installs this, so attributing the refusal to the sandbox
|
|
34
|
+
* allowlist is accurate rather than a guess about whose proxy answered.
|
|
35
|
+
*/
|
|
36
|
+
export function describeSandboxNetworkRefusal(error, url) {
|
|
37
|
+
if (!findTunnelRefusal(error))
|
|
38
|
+
return undefined;
|
|
39
|
+
const host = hostFromUrl(url);
|
|
40
|
+
const subject = host ? `Host ${host} is` : "That host is";
|
|
41
|
+
return `${subject} not on the sandbox network allowlist, so the request was refused. Add it to "network.allowedHosts" in your global Apex Code settings to permit it.`;
|
|
42
|
+
}
|
|
43
|
+
function requestUrl(input) {
|
|
44
|
+
if (typeof input === "string")
|
|
45
|
+
return input;
|
|
46
|
+
if (input instanceof URL)
|
|
47
|
+
return input.href;
|
|
48
|
+
if (input instanceof Request)
|
|
49
|
+
return input.url;
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Wrap global fetch so every caller — model providers, the version check, catalogs —
|
|
54
|
+
* reports a refusal in the same actionable terms without each having to know the
|
|
55
|
+
* sandbox exists. The original error is preserved as `cause`, and anything that is not
|
|
56
|
+
* a refusal is rethrown untouched so retry and abort handling behave exactly as before.
|
|
57
|
+
*/
|
|
58
|
+
export function installSandboxNetworkRefusalMessages() {
|
|
59
|
+
const wrapped = globalThis.fetch;
|
|
60
|
+
globalThis.fetch = async function fetchWithRefusalDetail(input, init) {
|
|
61
|
+
try {
|
|
62
|
+
return await wrapped(input, init);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const description = describeSandboxNetworkRefusal(error, requestUrl(input));
|
|
66
|
+
if (!description)
|
|
67
|
+
throw error;
|
|
68
|
+
throw new Error(description, { cause: error });
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=network-refusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-refusal.js","sourceRoot":"","sources":["../../../src/core/sandbox/network-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,oEAAoE;AACpE,MAAM,cAAc,GAAG,sDAAsD,CAAC;AAE9E,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,SAAS,iBAAiB,CAAC,KAAc,EAAW;IACnD,IAAI,OAAO,GAAY,KAAK,CAAC;IAC7B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,mBAAmB,IAAI,OAAO,YAAY,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;QACtF,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QACtD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;IACzB,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED,SAAS,WAAW,CAAC,GAAW,EAAsB;IACrD,IAAI,CAAC;QACJ,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AAAA,CACD;AAED;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,KAAc,EAAE,GAAW,EAAsB;IAC9F,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC;IAC1D,OAAO,GAAG,OAAO,qJAAqJ,CAAC;AAAA,CACvK;AAED,SAAS,UAAU,CAAC,KAAc,EAAU;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,YAAY,GAAG;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IAC5C,IAAI,KAAK,YAAY,OAAO;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC;IAC/C,OAAO,EAAE,CAAC;AAAA,CACV;AAED;;;;;GAKG;AACH,MAAM,UAAU,oCAAoC,GAAS;IAC5D,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC;IACjC,UAAU,CAAC,KAAK,GAAG,KAAK,UAAU,sBAAsB,CAAC,KAAK,EAAE,IAAI,EAAE;QACrE,IAAI,CAAC;YACJ,OAAO,MAAM,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,WAAW,GAAG,6BAA6B,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;YAC5E,IAAI,CAAC,WAAW;gBAAE,MAAM,KAAK,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,CAAC;IAAA,CAC0B,CAAC;AAAA,CAC7B","sourcesContent":["/**\n * Turn a sandbox allowlist refusal into a message the user can act on.\n *\n * The child reaches the network only through the supervisor's CONNECT proxy, which\n * answers 403 for any host absent from `network.allowedHosts`. undici reports that as\n * `Proxy response (403) !== 200 when HTTP Tunneling`, but fetch surfaces only the\n * generic `fetch failed` at the top of the cause chain — so the caller sees a network\n * error with no host, no reason, and no remedy. Recovering the detail here is what\n * separates \"something is broken\" from \"this host is not on the allowlist\".\n */\n\n/** undici's own wording for a tunnel the proxy declined to open. */\nconst TUNNEL_REFUSAL = /Proxy response \\((\\d+)\\) !== 200 when HTTP Tunneling/;\n\nconst MAXIMUM_CAUSE_DEPTH = 8;\n\nfunction findTunnelRefusal(error: unknown): boolean {\n\tlet current: unknown = error;\n\tfor (let depth = 0; depth < MAXIMUM_CAUSE_DEPTH && current instanceof Error; depth++) {\n\t\tif (TUNNEL_REFUSAL.test(current.message)) return true;\n\t\tcurrent = current.cause;\n\t}\n\treturn false;\n}\n\nfunction hostFromUrl(url: string): string | undefined {\n\ttry {\n\t\treturn new URL(url).host;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Describe a refused request, or return undefined when the failure was something else.\n * Only the sandboxed child installs this, so attributing the refusal to the sandbox\n * allowlist is accurate rather than a guess about whose proxy answered.\n */\nexport function describeSandboxNetworkRefusal(error: unknown, url: string): string | undefined {\n\tif (!findTunnelRefusal(error)) return undefined;\n\tconst host = hostFromUrl(url);\n\tconst subject = host ? `Host ${host} is` : \"That host is\";\n\treturn `${subject} not on the sandbox network allowlist, so the request was refused. Add it to \"network.allowedHosts\" in your global Apex Code settings to permit it.`;\n}\n\nfunction requestUrl(input: unknown): string {\n\tif (typeof input === \"string\") return input;\n\tif (input instanceof URL) return input.href;\n\tif (input instanceof Request) return input.url;\n\treturn \"\";\n}\n\n/**\n * Wrap global fetch so every caller — model providers, the version check, catalogs —\n * reports a refusal in the same actionable terms without each having to know the\n * sandbox exists. The original error is preserved as `cause`, and anything that is not\n * a refusal is rethrown untouched so retry and abort handling behave exactly as before.\n */\nexport function installSandboxNetworkRefusalMessages(): void {\n\tconst wrapped = globalThis.fetch;\n\tglobalThis.fetch = async function fetchWithRefusalDetail(input, init) {\n\t\ttry {\n\t\t\treturn await wrapped(input, init);\n\t\t} catch (error) {\n\t\t\tconst description = describeSandboxNetworkRefusal(error, requestUrl(input));\n\t\t\tif (!description) throw error;\n\t\t\tthrow new Error(description, { cause: error });\n\t\t}\n\t} as typeof globalThis.fetch;\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface SandboxPolicy {
|
|
2
|
+
/**
|
|
3
|
+
* The one directory the session is anchored to. State, sessions, and the concurrency
|
|
4
|
+
* lease all live under it, and the child starts here, so it stays singular even when
|
|
5
|
+
* more roots are writable.
|
|
6
|
+
*/
|
|
7
|
+
readonly workspace: string;
|
|
8
|
+
readonly allowedHosts: readonly string[];
|
|
9
|
+
/** Further writable directories, named on the command line and validated the same way. */
|
|
10
|
+
readonly additionalWritableRoots: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export type SandboxPolicyResult = {
|
|
13
|
+
readonly kind: "valid";
|
|
14
|
+
readonly policy: SandboxPolicy;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: "invalid";
|
|
17
|
+
readonly reason: string;
|
|
18
|
+
};
|
|
19
|
+
export type SandboxStatus = {
|
|
20
|
+
readonly kind: "enforced";
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: "unavailable";
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
};
|
|
25
|
+
export declare class SandboxUnavailableError extends Error {
|
|
26
|
+
constructor(reason: string);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validate the single writable boundary before any platform backend runs. We accept
|
|
30
|
+
* no relative, missing, or non-directory workspace because a backend must never
|
|
31
|
+
* silently broaden an ambiguous path into host authority.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createSandboxPolicy(options: {
|
|
34
|
+
workspace: string;
|
|
35
|
+
allowedHosts?: readonly string[];
|
|
36
|
+
additionalWritableRoots?: readonly string[];
|
|
37
|
+
}): SandboxPolicyResult;
|
|
38
|
+
/** Throws rather than returning an executable fallback when OS enforcement is absent. */
|
|
39
|
+
export declare function requireSandboxEnforcement(status: SandboxStatus): void;
|
|
40
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,0FAA0F;IAC1F,QAAQ,CAAC,uBAAuB,EAAE,SAAS,MAAM,EAAE,CAAC;CACpD;AAED,MAAM,MAAM,mBAAmB,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtH,qBAAa,uBAAwB,SAAQ,KAAK;IACjD,YAAY,MAAM,EAAE,MAAM,EAGzB;CACD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,uBAAuB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C,GAAG,mBAAmB,CAetB;AAsBD,yFAAyF;AACzF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAGrE","sourcesContent":["import { existsSync, realpathSync, statSync } from \"node:fs\";\nimport { isAbsolute } from \"node:path\";\n\nexport interface SandboxPolicy {\n\t/**\n\t * The one directory the session is anchored to. State, sessions, and the concurrency\n\t * lease all live under it, and the child starts here, so it stays singular even when\n\t * more roots are writable.\n\t */\n\treadonly workspace: string;\n\treadonly allowedHosts: readonly string[];\n\t/** Further writable directories, named on the command line and validated the same way. */\n\treadonly additionalWritableRoots: readonly string[];\n}\n\nexport type SandboxPolicyResult =\n\t| { readonly kind: \"valid\"; readonly policy: SandboxPolicy }\n\t| { readonly kind: \"invalid\"; readonly reason: string };\n\nexport type SandboxStatus = { readonly kind: \"enforced\" } | { readonly kind: \"unavailable\"; readonly reason: string };\n\nexport class SandboxUnavailableError extends Error {\n\tconstructor(reason: string) {\n\t\tsuper(`OS sandbox is not enforcing this agent session: ${reason}`);\n\t\tthis.name = \"SandboxUnavailableError\";\n\t}\n}\n\n/**\n * Validate the single writable boundary before any platform backend runs. We accept\n * no relative, missing, or non-directory workspace because a backend must never\n * silently broaden an ambiguous path into host authority.\n */\nexport function createSandboxPolicy(options: {\n\tworkspace: string;\n\tallowedHosts?: readonly string[];\n\tadditionalWritableRoots?: readonly string[];\n}): SandboxPolicyResult {\n\tconst workspace = validateRoot(options.workspace, \"workspace\");\n\tif (typeof workspace !== \"string\") return workspace;\n\n\tconst additionalWritableRoots: string[] = [];\n\tfor (const candidate of options.additionalWritableRoots ?? []) {\n\t\tconst resolved = validateRoot(candidate, \"writable root\");\n\t\tif (typeof resolved !== \"string\") return resolved;\n\t\tadditionalWritableRoots.push(resolved);\n\t}\n\n\treturn {\n\t\tkind: \"valid\",\n\t\tpolicy: { workspace, allowedHosts: options.allowedHosts ?? [], additionalWritableRoots },\n\t};\n}\n\n/**\n * Resolve one writable root, or explain why it cannot be one.\n *\n * Every root goes through the same check for the same reason the workspace always did: a\n * backend must never silently broaden an ambiguous path into host authority, and an extra\n * root named on the command line is no less capable of doing that than the first one.\n */\nfunction validateRoot(candidate: string, label: string): string | { kind: \"invalid\"; reason: string } {\n\tif (!isAbsolute(candidate)) {\n\t\treturn { kind: \"invalid\", reason: `Sandbox ${label} must be an absolute path: ${candidate}` };\n\t}\n\tif (!existsSync(candidate)) {\n\t\treturn { kind: \"invalid\", reason: `Sandbox ${label} does not exist: ${candidate}` };\n\t}\n\tif (!statSync(candidate).isDirectory()) {\n\t\treturn { kind: \"invalid\", reason: `Sandbox ${label} is not a directory: ${candidate}` };\n\t}\n\treturn realpathSync(candidate);\n}\n\n/** Throws rather than returning an executable fallback when OS enforcement is absent. */\nexport function requireSandboxEnforcement(status: SandboxStatus): void {\n\tif (status.kind === \"enforced\") return;\n\tthrow new SandboxUnavailableError(status.reason);\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
export class SandboxUnavailableError extends Error {
|
|
4
|
+
constructor(reason) {
|
|
5
|
+
super(`OS sandbox is not enforcing this agent session: ${reason}`);
|
|
6
|
+
this.name = "SandboxUnavailableError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Validate the single writable boundary before any platform backend runs. We accept
|
|
11
|
+
* no relative, missing, or non-directory workspace because a backend must never
|
|
12
|
+
* silently broaden an ambiguous path into host authority.
|
|
13
|
+
*/
|
|
14
|
+
export function createSandboxPolicy(options) {
|
|
15
|
+
const workspace = validateRoot(options.workspace, "workspace");
|
|
16
|
+
if (typeof workspace !== "string")
|
|
17
|
+
return workspace;
|
|
18
|
+
const additionalWritableRoots = [];
|
|
19
|
+
for (const candidate of options.additionalWritableRoots ?? []) {
|
|
20
|
+
const resolved = validateRoot(candidate, "writable root");
|
|
21
|
+
if (typeof resolved !== "string")
|
|
22
|
+
return resolved;
|
|
23
|
+
additionalWritableRoots.push(resolved);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
kind: "valid",
|
|
27
|
+
policy: { workspace, allowedHosts: options.allowedHosts ?? [], additionalWritableRoots },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve one writable root, or explain why it cannot be one.
|
|
32
|
+
*
|
|
33
|
+
* Every root goes through the same check for the same reason the workspace always did: a
|
|
34
|
+
* backend must never silently broaden an ambiguous path into host authority, and an extra
|
|
35
|
+
* root named on the command line is no less capable of doing that than the first one.
|
|
36
|
+
*/
|
|
37
|
+
function validateRoot(candidate, label) {
|
|
38
|
+
if (!isAbsolute(candidate)) {
|
|
39
|
+
return { kind: "invalid", reason: `Sandbox ${label} must be an absolute path: ${candidate}` };
|
|
40
|
+
}
|
|
41
|
+
if (!existsSync(candidate)) {
|
|
42
|
+
return { kind: "invalid", reason: `Sandbox ${label} does not exist: ${candidate}` };
|
|
43
|
+
}
|
|
44
|
+
if (!statSync(candidate).isDirectory()) {
|
|
45
|
+
return { kind: "invalid", reason: `Sandbox ${label} is not a directory: ${candidate}` };
|
|
46
|
+
}
|
|
47
|
+
return realpathSync(candidate);
|
|
48
|
+
}
|
|
49
|
+
/** Throws rather than returning an executable fallback when OS enforcement is absent. */
|
|
50
|
+
export function requireSandboxEnforcement(status) {
|
|
51
|
+
if (status.kind === "enforced")
|
|
52
|
+
return;
|
|
53
|
+
throw new SandboxUnavailableError(status.reason);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAoBvC,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACjD,YAAY,MAAc,EAAE;QAC3B,KAAK,CAAC,mDAAmD,MAAM,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IAAA,CACtC;CACD;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAInC,EAAuB;IACvB,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAC/D,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAEpD,MAAM,uBAAuB,GAAa,EAAE,CAAC;IAC7C,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,uBAAuB,IAAI,EAAE,EAAE,CAAC;QAC/D,MAAM,QAAQ,GAAG,YAAY,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QAC1D,IAAI,OAAO,QAAQ,KAAK,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAClD,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,OAAO;QACN,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,EAAE,EAAE,uBAAuB,EAAE;KACxF,CAAC;AAAA,CACF;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,SAAiB,EAAE,KAAa,EAAgD;IACrG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,KAAK,8BAA8B,SAAS,EAAE,EAAE,CAAC;IAC/F,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,KAAK,oBAAoB,SAAS,EAAE,EAAE,CAAC;IACrF,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,KAAK,wBAAwB,SAAS,EAAE,EAAE,CAAC;IACzF,CAAC;IACD,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;AAAA,CAC/B;AAED,yFAAyF;AACzF,MAAM,UAAU,yBAAyB,CAAC,MAAqB,EAAQ;IACtE,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO;IACvC,MAAM,IAAI,uBAAuB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAAA,CACjD","sourcesContent":["import { existsSync, realpathSync, statSync } from \"node:fs\";\nimport { isAbsolute } from \"node:path\";\n\nexport interface SandboxPolicy {\n\t/**\n\t * The one directory the session is anchored to. State, sessions, and the concurrency\n\t * lease all live under it, and the child starts here, so it stays singular even when\n\t * more roots are writable.\n\t */\n\treadonly workspace: string;\n\treadonly allowedHosts: readonly string[];\n\t/** Further writable directories, named on the command line and validated the same way. */\n\treadonly additionalWritableRoots: readonly string[];\n}\n\nexport type SandboxPolicyResult =\n\t| { readonly kind: \"valid\"; readonly policy: SandboxPolicy }\n\t| { readonly kind: \"invalid\"; readonly reason: string };\n\nexport type SandboxStatus = { readonly kind: \"enforced\" } | { readonly kind: \"unavailable\"; readonly reason: string };\n\nexport class SandboxUnavailableError extends Error {\n\tconstructor(reason: string) {\n\t\tsuper(`OS sandbox is not enforcing this agent session: ${reason}`);\n\t\tthis.name = \"SandboxUnavailableError\";\n\t}\n}\n\n/**\n * Validate the single writable boundary before any platform backend runs. We accept\n * no relative, missing, or non-directory workspace because a backend must never\n * silently broaden an ambiguous path into host authority.\n */\nexport function createSandboxPolicy(options: {\n\tworkspace: string;\n\tallowedHosts?: readonly string[];\n\tadditionalWritableRoots?: readonly string[];\n}): SandboxPolicyResult {\n\tconst workspace = validateRoot(options.workspace, \"workspace\");\n\tif (typeof workspace !== \"string\") return workspace;\n\n\tconst additionalWritableRoots: string[] = [];\n\tfor (const candidate of options.additionalWritableRoots ?? []) {\n\t\tconst resolved = validateRoot(candidate, \"writable root\");\n\t\tif (typeof resolved !== \"string\") return resolved;\n\t\tadditionalWritableRoots.push(resolved);\n\t}\n\n\treturn {\n\t\tkind: \"valid\",\n\t\tpolicy: { workspace, allowedHosts: options.allowedHosts ?? [], additionalWritableRoots },\n\t};\n}\n\n/**\n * Resolve one writable root, or explain why it cannot be one.\n *\n * Every root goes through the same check for the same reason the workspace always did: a\n * backend must never silently broaden an ambiguous path into host authority, and an extra\n * root named on the command line is no less capable of doing that than the first one.\n */\nfunction validateRoot(candidate: string, label: string): string | { kind: \"invalid\"; reason: string } {\n\tif (!isAbsolute(candidate)) {\n\t\treturn { kind: \"invalid\", reason: `Sandbox ${label} must be an absolute path: ${candidate}` };\n\t}\n\tif (!existsSync(candidate)) {\n\t\treturn { kind: \"invalid\", reason: `Sandbox ${label} does not exist: ${candidate}` };\n\t}\n\tif (!statSync(candidate).isDirectory()) {\n\t\treturn { kind: \"invalid\", reason: `Sandbox ${label} is not a directory: ${candidate}` };\n\t}\n\treturn realpathSync(candidate);\n}\n\n/** Throws rather than returning an executable fallback when OS enforcement is absent. */\nexport function requireSandboxEnforcement(status: SandboxStatus): void {\n\tif (status.kind === \"enforced\") return;\n\tthrow new SandboxUnavailableError(status.reason);\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A named, saved combination of what the OS boundary permits.
|
|
3
|
+
*
|
|
4
|
+
* Apex already has a permission rule model with five modes and eight-source precedence
|
|
5
|
+
* (ADR 0004). That governs the tool gate at `beforeToolCall`, inside the child. This
|
|
6
|
+
* governs the supervisor's launch contract, outside it. They are deliberately separate
|
|
7
|
+
* surfaces, and conflating them is exactly the misreading that makes `bypassPermissions`
|
|
8
|
+
* look like a sandbox escape -- so a profile here carries only boundary inputs and has no
|
|
9
|
+
* way to express a tool-gate mode.
|
|
10
|
+
*
|
|
11
|
+
* It also has no way to express "no boundary". That decision stays on the command line
|
|
12
|
+
* where a human has to type it, per ADR 0005's 2026-08-28 amendment; a profile can widen
|
|
13
|
+
* what is reachable and writable, never remove the boundary that constrains them.
|
|
14
|
+
*
|
|
15
|
+
* Resolved from global settings only, with `projectTrusted: false`, for the reason ADR
|
|
16
|
+
* 0016 gives for every supervisor policy input: a repository that could define the
|
|
17
|
+
* boundary it runs under would be granting itself authority.
|
|
18
|
+
*/
|
|
19
|
+
export interface SandboxProfile {
|
|
20
|
+
readonly allowedHosts: readonly string[];
|
|
21
|
+
readonly additionalWritableRoots: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
/** Resolve one named profile, or undefined when the name is absent or was never given. */
|
|
24
|
+
export declare function resolveSandboxProfile(name: string | undefined, cwd: string, agentDir: string): SandboxProfile | undefined;
|
|
25
|
+
//# sourceMappingURL=profiles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/profiles.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,uBAAuB,EAAE,SAAS,MAAM,EAAE,CAAC;CACpD;AAMD,0FAA0F;AAC1F,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACd,cAAc,GAAG,SAAS,CAc5B","sourcesContent":["import { SettingsManager } from \"../settings-manager.ts\";\n\n/**\n * A named, saved combination of what the OS boundary permits.\n *\n * Apex already has a permission rule model with five modes and eight-source precedence\n * (ADR 0004). That governs the tool gate at `beforeToolCall`, inside the child. This\n * governs the supervisor's launch contract, outside it. They are deliberately separate\n * surfaces, and conflating them is exactly the misreading that makes `bypassPermissions`\n * look like a sandbox escape -- so a profile here carries only boundary inputs and has no\n * way to express a tool-gate mode.\n *\n * It also has no way to express \"no boundary\". That decision stays on the command line\n * where a human has to type it, per ADR 0005's 2026-08-28 amendment; a profile can widen\n * what is reachable and writable, never remove the boundary that constrains them.\n *\n * Resolved from global settings only, with `projectTrusted: false`, for the reason ADR\n * 0016 gives for every supervisor policy input: a repository that could define the\n * boundary it runs under would be granting itself authority.\n */\nexport interface SandboxProfile {\n\treadonly allowedHosts: readonly string[];\n\treadonly additionalWritableRoots: readonly string[];\n}\n\nfunction stringArray(value: unknown): string[] {\n\treturn Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === \"string\") : [];\n}\n\n/** Resolve one named profile, or undefined when the name is absent or was never given. */\nexport function resolveSandboxProfile(\n\tname: string | undefined,\n\tcwd: string,\n\tagentDir: string,\n): SandboxProfile | undefined {\n\tif (!name) return undefined;\n\tconst settings = SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tconst profiles = settings.getSandboxProfiles();\n\tconst profile = profiles?.[name];\n\tif (!profile || typeof profile !== \"object\") return undefined;\n\tconst record = profile as Record<string, unknown>;\n\t// Only these two keys are read. An unknown key in a profile is ignored rather than\n\t// rejected, so a profile written for a later version does not break this one, and a\n\t// key this version does not understand can never take effect by accident.\n\treturn {\n\t\tallowedHosts: stringArray(record.allowedHosts),\n\t\tadditionalWritableRoots: stringArray(record.additionalWritableRoots),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SettingsManager } from "../settings-manager.js";
|
|
2
|
+
function stringArray(value) {
|
|
3
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
4
|
+
}
|
|
5
|
+
/** Resolve one named profile, or undefined when the name is absent or was never given. */
|
|
6
|
+
export function resolveSandboxProfile(name, cwd, agentDir) {
|
|
7
|
+
if (!name)
|
|
8
|
+
return undefined;
|
|
9
|
+
const settings = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
|
|
10
|
+
const profiles = settings.getSandboxProfiles();
|
|
11
|
+
const profile = profiles?.[name];
|
|
12
|
+
if (!profile || typeof profile !== "object")
|
|
13
|
+
return undefined;
|
|
14
|
+
const record = profile;
|
|
15
|
+
// Only these two keys are read. An unknown key in a profile is ignored rather than
|
|
16
|
+
// rejected, so a profile written for a later version does not break this one, and a
|
|
17
|
+
// key this version does not understand can never take effect by accident.
|
|
18
|
+
return {
|
|
19
|
+
allowedHosts: stringArray(record.allowedHosts),
|
|
20
|
+
additionalWritableRoots: stringArray(record.additionalWritableRoots),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=profiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../../src/core/sandbox/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAyBzD,SAAS,WAAW,CAAC,KAAc,EAAY;IAC9C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAAA,CACvG;AAED,0FAA0F;AAC1F,MAAM,UAAU,qBAAqB,CACpC,IAAwB,EACxB,GAAW,EACX,QAAgB,EACa;IAC7B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;IAClF,MAAM,QAAQ,GAAG,QAAQ,CAAC,kBAAkB,EAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC9D,MAAM,MAAM,GAAG,OAAkC,CAAC;IAClD,mFAAmF;IACnF,oFAAoF;IACpF,0EAA0E;IAC1E,OAAO;QACN,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,YAAY,CAAC;QAC9C,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAAC,uBAAuB,CAAC;KACpE,CAAC;AAAA,CACF","sourcesContent":["import { SettingsManager } from \"../settings-manager.ts\";\n\n/**\n * A named, saved combination of what the OS boundary permits.\n *\n * Apex already has a permission rule model with five modes and eight-source precedence\n * (ADR 0004). That governs the tool gate at `beforeToolCall`, inside the child. This\n * governs the supervisor's launch contract, outside it. They are deliberately separate\n * surfaces, and conflating them is exactly the misreading that makes `bypassPermissions`\n * look like a sandbox escape -- so a profile here carries only boundary inputs and has no\n * way to express a tool-gate mode.\n *\n * It also has no way to express \"no boundary\". That decision stays on the command line\n * where a human has to type it, per ADR 0005's 2026-08-28 amendment; a profile can widen\n * what is reachable and writable, never remove the boundary that constrains them.\n *\n * Resolved from global settings only, with `projectTrusted: false`, for the reason ADR\n * 0016 gives for every supervisor policy input: a repository that could define the\n * boundary it runs under would be granting itself authority.\n */\nexport interface SandboxProfile {\n\treadonly allowedHosts: readonly string[];\n\treadonly additionalWritableRoots: readonly string[];\n}\n\nfunction stringArray(value: unknown): string[] {\n\treturn Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === \"string\") : [];\n}\n\n/** Resolve one named profile, or undefined when the name is absent or was never given. */\nexport function resolveSandboxProfile(\n\tname: string | undefined,\n\tcwd: string,\n\tagentDir: string,\n): SandboxProfile | undefined {\n\tif (!name) return undefined;\n\tconst settings = SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tconst profiles = settings.getSandboxProfiles();\n\tconst profile = profiles?.[name];\n\tif (!profile || typeof profile !== \"object\") return undefined;\n\tconst record = profile as Record<string, unknown>;\n\t// Only these two keys are read. An unknown key in a profile is ignored rather than\n\t// rejected, so a profile written for a later version does not break this one, and a\n\t// key this version does not understand can never take effect by accident.\n\treturn {\n\t\tallowedHosts: stringArray(record.allowedHosts),\n\t\tadditionalWritableRoots: stringArray(record.additionalWritableRoots),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** The absolute path a sandbox filesystem refusal named, if the output named one. */
|
|
2
|
+
export declare function extractRefusedPath(output: string): string | undefined;
|
|
3
|
+
/** True when the output looks like the boundary refused a write rather than the command failing. */
|
|
4
|
+
export declare function looksLikeSandboxRefusal(output: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* The directory an escalation should make writable for a refused path.
|
|
7
|
+
*
|
|
8
|
+
* The parent, not the file: the command was creating something that does not exist, and a
|
|
9
|
+
* bind of a non-existent path is not a mount bwrap can make.
|
|
10
|
+
*/
|
|
11
|
+
export declare function escalationRootFor(refusedPath: string): string;
|
|
12
|
+
export interface EscalationOutcome {
|
|
13
|
+
readonly code: number;
|
|
14
|
+
readonly stdout: string;
|
|
15
|
+
readonly stderr: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Ask the supervisor to run one command with one extra writable root.
|
|
19
|
+
*
|
|
20
|
+
* Resolves undefined for every refusal and every failure alike, because the caller's
|
|
21
|
+
* behaviour is identical in both cases: report the original error. Only a command that
|
|
22
|
+
* actually ran returns anything.
|
|
23
|
+
*/
|
|
24
|
+
export declare function requestCommandEscalation(request: {
|
|
25
|
+
command: string;
|
|
26
|
+
writableRoot: string;
|
|
27
|
+
}, socketPath?: string | undefined): Promise<EscalationOutcome | undefined>;
|
|
28
|
+
//# sourceMappingURL=command-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-client.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/command-client.ts"],"names":[],"mappings":"AAsBA,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOrE;AAED,oGAAoG;AACpG,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACvC,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EAClD,UAAU,qBAAkD,GAC1D,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAgCxC","sourcesContent":["import * as net from \"node:net\";\nimport { dirname, isAbsolute } from \"node:path\";\nimport { COMMAND_ESCALATION_SOCKET_VARIABLE } from \"./command-proxy.ts\";\n\n/**\n * The child's side of per-command escalation.\n *\n * The path this extracts is a guess, and it is allowed to be, because nothing safe rests\n * on it. The supervisor shows the human the exact command and the exact root before\n * anything runs, so a bad guess is refused by someone reading it rather than silently\n * granted. What the guess buys is not having to make the user work out which directory to\n * name; when it finds nothing, no escalation is offered and the command fails as it did\n * before.\n */\n\n/** The wordings the kernel and the shell actually produce for a mount refusal. */\nconst REFUSAL_PATTERNS: readonly RegExp[] = [\n\t/cannot (?:create|touch|remove|open) ['\"]?([^'\":]+)['\"]?: (?:Read-only file system|Permission denied)/i,\n\t/^[^:]*: ?([/][^\\s:]+): (?:Operation not permitted|Permission denied|Read-only file system)/im,\n\t/(?:EROFS|EACCES|EPERM)[^'\"]*['\"]([/][^'\"]+)['\"]/,\n];\n\n/** The absolute path a sandbox filesystem refusal named, if the output named one. */\nexport function extractRefusedPath(output: string): string | undefined {\n\tfor (const pattern of REFUSAL_PATTERNS) {\n\t\tconst match = pattern.exec(output);\n\t\tconst candidate = match?.[1]?.trim();\n\t\tif (candidate && isAbsolute(candidate)) return candidate;\n\t}\n\treturn undefined;\n}\n\n/** True when the output looks like the boundary refused a write rather than the command failing. */\nexport function looksLikeSandboxRefusal(output: string): boolean {\n\treturn /Read-only file system|Operation not permitted|Permission denied/i.test(output);\n}\n\n/**\n * The directory an escalation should make writable for a refused path.\n *\n * The parent, not the file: the command was creating something that does not exist, and a\n * bind of a non-existent path is not a mount bwrap can make.\n */\nexport function escalationRootFor(refusedPath: string): string {\n\treturn dirname(refusedPath);\n}\n\nexport interface EscalationOutcome {\n\treadonly code: number;\n\treadonly stdout: string;\n\treadonly stderr: string;\n}\n\n/**\n * Ask the supervisor to run one command with one extra writable root.\n *\n * Resolves undefined for every refusal and every failure alike, because the caller's\n * behaviour is identical in both cases: report the original error. Only a command that\n * actually ran returns anything.\n */\nexport function requestCommandEscalation(\n\trequest: { command: string; writableRoot: string },\n\tsocketPath = process.env[COMMAND_ESCALATION_SOCKET_VARIABLE],\n): Promise<EscalationOutcome | undefined> {\n\tif (!socketPath) return Promise.resolve(undefined);\n\treturn new Promise((resolve) => {\n\t\tconst socket = net.connect(socketPath);\n\t\tlet buffer = \"\";\n\t\tconst giveUp = () => {\n\t\t\tsocket.destroy();\n\t\t\tresolve(undefined);\n\t\t};\n\t\tsocket.on(\"connect\", () => socket.write(`${JSON.stringify({ op: \"run\", ...request })}\\n`));\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\tbuffer += chunk.toString();\n\t\t\tconst newline = buffer.indexOf(\"\\n\");\n\t\t\tif (newline < 0) return;\n\t\t\tsocket.destroy();\n\t\t\ttry {\n\t\t\t\tconst response = JSON.parse(buffer.slice(0, newline)) as Record<string, unknown>;\n\t\t\t\tif (response.ok === true && typeof response.code === \"number\") {\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcode: response.code,\n\t\t\t\t\t\tstdout: typeof response.stdout === \"string\" ? response.stdout : \"\",\n\t\t\t\t\t\tstderr: typeof response.stderr === \"string\" ? response.stderr : \"\",\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Fall through to the same undefined a refusal produces.\n\t\t\t}\n\t\t\tresolve(undefined);\n\t\t});\n\t\tsocket.on(\"error\", giveUp);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as net from "node:net";
|
|
2
|
+
import { dirname, isAbsolute } from "node:path";
|
|
3
|
+
import { COMMAND_ESCALATION_SOCKET_VARIABLE } from "./command-proxy.js";
|
|
4
|
+
/**
|
|
5
|
+
* The child's side of per-command escalation.
|
|
6
|
+
*
|
|
7
|
+
* The path this extracts is a guess, and it is allowed to be, because nothing safe rests
|
|
8
|
+
* on it. The supervisor shows the human the exact command and the exact root before
|
|
9
|
+
* anything runs, so a bad guess is refused by someone reading it rather than silently
|
|
10
|
+
* granted. What the guess buys is not having to make the user work out which directory to
|
|
11
|
+
* name; when it finds nothing, no escalation is offered and the command fails as it did
|
|
12
|
+
* before.
|
|
13
|
+
*/
|
|
14
|
+
/** The wordings the kernel and the shell actually produce for a mount refusal. */
|
|
15
|
+
const REFUSAL_PATTERNS = [
|
|
16
|
+
/cannot (?:create|touch|remove|open) ['"]?([^'":]+)['"]?: (?:Read-only file system|Permission denied)/i,
|
|
17
|
+
/^[^:]*: ?([/][^\s:]+): (?:Operation not permitted|Permission denied|Read-only file system)/im,
|
|
18
|
+
/(?:EROFS|EACCES|EPERM)[^'"]*['"]([/][^'"]+)['"]/,
|
|
19
|
+
];
|
|
20
|
+
/** The absolute path a sandbox filesystem refusal named, if the output named one. */
|
|
21
|
+
export function extractRefusedPath(output) {
|
|
22
|
+
for (const pattern of REFUSAL_PATTERNS) {
|
|
23
|
+
const match = pattern.exec(output);
|
|
24
|
+
const candidate = match?.[1]?.trim();
|
|
25
|
+
if (candidate && isAbsolute(candidate))
|
|
26
|
+
return candidate;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/** True when the output looks like the boundary refused a write rather than the command failing. */
|
|
31
|
+
export function looksLikeSandboxRefusal(output) {
|
|
32
|
+
return /Read-only file system|Operation not permitted|Permission denied/i.test(output);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The directory an escalation should make writable for a refused path.
|
|
36
|
+
*
|
|
37
|
+
* The parent, not the file: the command was creating something that does not exist, and a
|
|
38
|
+
* bind of a non-existent path is not a mount bwrap can make.
|
|
39
|
+
*/
|
|
40
|
+
export function escalationRootFor(refusedPath) {
|
|
41
|
+
return dirname(refusedPath);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Ask the supervisor to run one command with one extra writable root.
|
|
45
|
+
*
|
|
46
|
+
* Resolves undefined for every refusal and every failure alike, because the caller's
|
|
47
|
+
* behaviour is identical in both cases: report the original error. Only a command that
|
|
48
|
+
* actually ran returns anything.
|
|
49
|
+
*/
|
|
50
|
+
export function requestCommandEscalation(request, socketPath = process.env[COMMAND_ESCALATION_SOCKET_VARIABLE]) {
|
|
51
|
+
if (!socketPath)
|
|
52
|
+
return Promise.resolve(undefined);
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const socket = net.connect(socketPath);
|
|
55
|
+
let buffer = "";
|
|
56
|
+
const giveUp = () => {
|
|
57
|
+
socket.destroy();
|
|
58
|
+
resolve(undefined);
|
|
59
|
+
};
|
|
60
|
+
socket.on("connect", () => socket.write(`${JSON.stringify({ op: "run", ...request })}\n`));
|
|
61
|
+
socket.on("data", (chunk) => {
|
|
62
|
+
buffer += chunk.toString();
|
|
63
|
+
const newline = buffer.indexOf("\n");
|
|
64
|
+
if (newline < 0)
|
|
65
|
+
return;
|
|
66
|
+
socket.destroy();
|
|
67
|
+
try {
|
|
68
|
+
const response = JSON.parse(buffer.slice(0, newline));
|
|
69
|
+
if (response.ok === true && typeof response.code === "number") {
|
|
70
|
+
resolve({
|
|
71
|
+
code: response.code,
|
|
72
|
+
stdout: typeof response.stdout === "string" ? response.stdout : "",
|
|
73
|
+
stderr: typeof response.stderr === "string" ? response.stderr : "",
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Fall through to the same undefined a refusal produces.
|
|
80
|
+
}
|
|
81
|
+
resolve(undefined);
|
|
82
|
+
});
|
|
83
|
+
socket.on("error", giveUp);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=command-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-client.js","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/command-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,kCAAkC,EAAE,MAAM,oBAAoB,CAAC;AAExE;;;;;;;;;GASG;AAEH,kFAAkF;AAClF,MAAM,gBAAgB,GAAsB;IAC3C,uGAAuG;IACvG,8FAA8F;IAC9F,iDAAiD;CACjD,CAAC;AAEF,qFAAqF;AACrF,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAsB;IACtE,KAAK,MAAM,OAAO,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACrC,IAAI,SAAS,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC1D,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,oGAAoG;AACpG,MAAM,UAAU,uBAAuB,CAAC,MAAc,EAAW;IAChE,OAAO,kEAAkE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAAA,CACvF;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAmB,EAAU;IAC9D,OAAO,OAAO,CAAC,WAAW,CAAC,CAAC;AAAA,CAC5B;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACvC,OAAkD,EAClD,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,EACnB;IACzC,IAAI,CAAC,UAAU;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACnD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvC,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC;YACpB,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,SAAS,CAAC,CAAC;QAAA,CACnB,CAAC;QACF,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3F,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,OAAO,GAAG,CAAC;gBAAE,OAAO;YACxB,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC;gBACJ,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAA4B,CAAC;gBACjF,IAAI,QAAQ,CAAC,EAAE,KAAK,IAAI,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC/D,OAAO,CAAC;wBACP,IAAI,EAAE,QAAQ,CAAC,IAAI;wBACnB,MAAM,EAAE,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;wBAClE,MAAM,EAAE,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;qBAClE,CAAC,CAAC;oBACH,OAAO;gBACR,CAAC;YACF,CAAC;YAAC,MAAM,CAAC;gBACR,yDAAyD;YAC1D,CAAC;YACD,OAAO,CAAC,SAAS,CAAC,CAAC;QAAA,CACnB,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAAA,CAC3B,CAAC,CAAC;AAAA,CACH","sourcesContent":["import * as net from \"node:net\";\nimport { dirname, isAbsolute } from \"node:path\";\nimport { COMMAND_ESCALATION_SOCKET_VARIABLE } from \"./command-proxy.ts\";\n\n/**\n * The child's side of per-command escalation.\n *\n * The path this extracts is a guess, and it is allowed to be, because nothing safe rests\n * on it. The supervisor shows the human the exact command and the exact root before\n * anything runs, so a bad guess is refused by someone reading it rather than silently\n * granted. What the guess buys is not having to make the user work out which directory to\n * name; when it finds nothing, no escalation is offered and the command fails as it did\n * before.\n */\n\n/** The wordings the kernel and the shell actually produce for a mount refusal. */\nconst REFUSAL_PATTERNS: readonly RegExp[] = [\n\t/cannot (?:create|touch|remove|open) ['\"]?([^'\":]+)['\"]?: (?:Read-only file system|Permission denied)/i,\n\t/^[^:]*: ?([/][^\\s:]+): (?:Operation not permitted|Permission denied|Read-only file system)/im,\n\t/(?:EROFS|EACCES|EPERM)[^'\"]*['\"]([/][^'\"]+)['\"]/,\n];\n\n/** The absolute path a sandbox filesystem refusal named, if the output named one. */\nexport function extractRefusedPath(output: string): string | undefined {\n\tfor (const pattern of REFUSAL_PATTERNS) {\n\t\tconst match = pattern.exec(output);\n\t\tconst candidate = match?.[1]?.trim();\n\t\tif (candidate && isAbsolute(candidate)) return candidate;\n\t}\n\treturn undefined;\n}\n\n/** True when the output looks like the boundary refused a write rather than the command failing. */\nexport function looksLikeSandboxRefusal(output: string): boolean {\n\treturn /Read-only file system|Operation not permitted|Permission denied/i.test(output);\n}\n\n/**\n * The directory an escalation should make writable for a refused path.\n *\n * The parent, not the file: the command was creating something that does not exist, and a\n * bind of a non-existent path is not a mount bwrap can make.\n */\nexport function escalationRootFor(refusedPath: string): string {\n\treturn dirname(refusedPath);\n}\n\nexport interface EscalationOutcome {\n\treadonly code: number;\n\treadonly stdout: string;\n\treadonly stderr: string;\n}\n\n/**\n * Ask the supervisor to run one command with one extra writable root.\n *\n * Resolves undefined for every refusal and every failure alike, because the caller's\n * behaviour is identical in both cases: report the original error. Only a command that\n * actually ran returns anything.\n */\nexport function requestCommandEscalation(\n\trequest: { command: string; writableRoot: string },\n\tsocketPath = process.env[COMMAND_ESCALATION_SOCKET_VARIABLE],\n): Promise<EscalationOutcome | undefined> {\n\tif (!socketPath) return Promise.resolve(undefined);\n\treturn new Promise((resolve) => {\n\t\tconst socket = net.connect(socketPath);\n\t\tlet buffer = \"\";\n\t\tconst giveUp = () => {\n\t\t\tsocket.destroy();\n\t\t\tresolve(undefined);\n\t\t};\n\t\tsocket.on(\"connect\", () => socket.write(`${JSON.stringify({ op: \"run\", ...request })}\\n`));\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\tbuffer += chunk.toString();\n\t\t\tconst newline = buffer.indexOf(\"\\n\");\n\t\t\tif (newline < 0) return;\n\t\t\tsocket.destroy();\n\t\t\ttry {\n\t\t\t\tconst response = JSON.parse(buffer.slice(0, newline)) as Record<string, unknown>;\n\t\t\t\tif (response.ok === true && typeof response.code === \"number\") {\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcode: response.code,\n\t\t\t\t\t\tstdout: typeof response.stdout === \"string\" ? response.stdout : \"\",\n\t\t\t\t\t\tstderr: typeof response.stderr === \"string\" ? response.stderr : \"\",\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Fall through to the same undefined a refusal produces.\n\t\t\t}\n\t\t\tresolve(undefined);\n\t\t});\n\t\tsocket.on(\"error\", giveUp);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SandboxViolationStore } from "../violations.ts";
|
|
2
|
+
/** Env var naming the child-side socket. */
|
|
3
|
+
export declare const COMMAND_ESCALATION_SOCKET_VARIABLE = "APEX_COMMAND_ESCALATION_PATH";
|
|
4
|
+
export interface CommandEscalationRequest {
|
|
5
|
+
readonly command: string;
|
|
6
|
+
/** The one directory the command needs writable that it does not already have. */
|
|
7
|
+
readonly writableRoot: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CommandEscalationResult {
|
|
10
|
+
readonly code: number;
|
|
11
|
+
readonly stdout: string;
|
|
12
|
+
readonly stderr: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CommandEscalationChannelPaths {
|
|
15
|
+
readonly hostSocketDirectory: string;
|
|
16
|
+
readonly hostSocketPath: string;
|
|
17
|
+
readonly childSocketPath: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function resolveCommandEscalationChannelPaths(): CommandEscalationChannelPaths;
|
|
20
|
+
/**
|
|
21
|
+
* Run one approved command in a second, differently-mounted child.
|
|
22
|
+
*
|
|
23
|
+
* A refused write is refused by the kernel inside a namespace whose mounts are fixed for
|
|
24
|
+
* its lifetime. There is no in-place equivalent of holding a CONNECT open while a human
|
|
25
|
+
* decides: the syscall has already failed, and nothing inside the namespace can widen it.
|
|
26
|
+
* So the escalation happens where the authority is, outside the boundary, by starting a
|
|
27
|
+
* separate child for that one command.
|
|
28
|
+
*
|
|
29
|
+
* The original child's namespace is never modified, which is what makes this sound rather
|
|
30
|
+
* than a hole. Approving one command grants nothing to the session it came from; the
|
|
31
|
+
* session's own next attempt at the same operation is refused exactly as before.
|
|
32
|
+
*
|
|
33
|
+
* Approval is the supervisor's, per ADR 0023, for the reason that ADR gives: this socket
|
|
34
|
+
* has no peer authentication, so a request arriving on it may have come from anything
|
|
35
|
+
* inside the boundary. Without an approver the channel refuses, which is what keeps
|
|
36
|
+
* headless, print, JSON, and RPC modes at ADR 0005's deny.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createCommandEscalationProxy(options: {
|
|
39
|
+
socketPath: string;
|
|
40
|
+
/** Ask a human. Absent means refuse without asking. */
|
|
41
|
+
requestApproval?: (request: CommandEscalationRequest) => Promise<boolean>;
|
|
42
|
+
/** Start the second child. Supplied by the platform backend, which owns mount shape. */
|
|
43
|
+
runEscalated: (request: CommandEscalationRequest) => Promise<CommandEscalationResult>;
|
|
44
|
+
violationStore?: SandboxViolationStore;
|
|
45
|
+
}): Promise<{
|
|
46
|
+
close(): Promise<void>;
|
|
47
|
+
}>;
|
|
48
|
+
//# sourceMappingURL=command-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-proxy.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/command-proxy.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAO9D,4CAA4C;AAC5C,eAAO,MAAM,kCAAkC,iCAAiC,CAAC;AAEjF,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACjC;AAED,wBAAgB,oCAAoC,IAAI,6BAA6B,CAWpF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1E,wFAAwF;IACxF,YAAY,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACtF,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACvC,GAAG,OAAO,CAAC;IAAE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CA2EtC","sourcesContent":["import { chmodSync, existsSync, mkdtempSync, unlinkSync } from \"node:fs\";\nimport * as net from \"node:net\";\nimport { join } from \"node:path\";\nimport { supervisorTempDirectory } from \"../supervisor-temp.ts\";\nimport type { SandboxViolationStore } from \"../violations.ts\";\nimport { FrameReader, isRequestObject, writeFrame } from \"./framing.ts\";\n\n/** AF_UNIX `sun_path` is 108 bytes on Linux, including the terminating NUL. */\nconst SUN_PATH_LIMIT = 108;\nconst MAX_CONNECTIONS = 8;\n\n/** Env var naming the child-side socket. */\nexport const COMMAND_ESCALATION_SOCKET_VARIABLE = \"APEX_COMMAND_ESCALATION_PATH\";\n\nexport interface CommandEscalationRequest {\n\treadonly command: string;\n\t/** The one directory the command needs writable that it does not already have. */\n\treadonly writableRoot: string;\n}\n\nexport interface CommandEscalationResult {\n\treadonly code: number;\n\treadonly stdout: string;\n\treadonly stderr: string;\n}\n\nexport interface CommandEscalationChannelPaths {\n\treadonly hostSocketDirectory: string;\n\treadonly hostSocketPath: string;\n\treadonly childSocketPath: string;\n}\n\nexport function resolveCommandEscalationChannelPaths(): CommandEscalationChannelPaths {\n\tconst hostSocketDirectory = mkdtempSync(join(supervisorTempDirectory(), `apex-escalate-${process.pid}-`), {\n\t\tencoding: \"utf8\",\n\t});\n\tchmodSync(hostSocketDirectory, 0o700);\n\tconst hostSocketPath = join(hostSocketDirectory, \"channel.sock\");\n\tif (Buffer.byteLength(hostSocketPath) + 1 > SUN_PATH_LIMIT) {\n\t\tthrow new Error(\"Command escalation channel path exceeds the Unix socket path limit.\");\n\t}\n\tconst childSocketPath = process.platform === \"linux\" ? \"/home/apex-command-escalation.sock\" : hostSocketPath;\n\treturn { hostSocketDirectory, hostSocketPath, childSocketPath };\n}\n\n/**\n * Run one approved command in a second, differently-mounted child.\n *\n * A refused write is refused by the kernel inside a namespace whose mounts are fixed for\n * its lifetime. There is no in-place equivalent of holding a CONNECT open while a human\n * decides: the syscall has already failed, and nothing inside the namespace can widen it.\n * So the escalation happens where the authority is, outside the boundary, by starting a\n * separate child for that one command.\n *\n * The original child's namespace is never modified, which is what makes this sound rather\n * than a hole. Approving one command grants nothing to the session it came from; the\n * session's own next attempt at the same operation is refused exactly as before.\n *\n * Approval is the supervisor's, per ADR 0023, for the reason that ADR gives: this socket\n * has no peer authentication, so a request arriving on it may have come from anything\n * inside the boundary. Without an approver the channel refuses, which is what keeps\n * headless, print, JSON, and RPC modes at ADR 0005's deny.\n */\nexport function createCommandEscalationProxy(options: {\n\tsocketPath: string;\n\t/** Ask a human. Absent means refuse without asking. */\n\trequestApproval?: (request: CommandEscalationRequest) => Promise<boolean>;\n\t/** Start the second child. Supplied by the platform backend, which owns mount shape. */\n\trunEscalated: (request: CommandEscalationRequest) => Promise<CommandEscalationResult>;\n\tviolationStore?: SandboxViolationStore;\n}): Promise<{ close(): Promise<void> }> {\n\tfunction audit(detail: string, command: string): void {\n\t\toptions.violationStore?.add({ kind: \"filesystem\", command, detail, timestamp: new Date() });\n\t}\n\n\tasync function answer(request: unknown): Promise<object> {\n\t\tif (!isRequestObject(request)) return { ok: false, error: \"Malformed request.\" };\n\t\tif (request.op !== \"run\") return { ok: false, error: \"Only command escalation is served here.\" };\n\t\tconst command = typeof request.command === \"string\" ? request.command : \"\";\n\t\tconst writableRoot = typeof request.writableRoot === \"string\" ? request.writableRoot : \"\";\n\t\tif (!command || !writableRoot) return { ok: false, error: \"Request named no command or no root.\" };\n\n\t\t// Every request is asked about individually. There is no session-scoped grant here,\n\t\t// unlike a host or a credential: a command is not a stable subject to remember, and\n\t\t// remembering one would silently cover the next command that named the same root.\n\t\tif (!options.requestApproval) {\n\t\t\taudit(`Refused an escalation with no way to ask: ${command}`, command);\n\t\t\treturn { ok: false, error: \"Escalation is unavailable without a terminal.\" };\n\t\t}\n\t\tlet approved = false;\n\t\ttry {\n\t\t\tapproved = await options.requestApproval({ command, writableRoot });\n\t\t} catch {\n\t\t\tapproved = false;\n\t\t}\n\t\tif (!approved) {\n\t\t\taudit(`Escalation refused for ${writableRoot}`, command);\n\t\t\treturn { ok: false, error: \"Escalation was refused.\" };\n\t\t}\n\t\tconst result = await options.runEscalated({ command, writableRoot });\n\t\taudit(`Escalated with ${writableRoot} writable, exit ${result.code}`, command);\n\t\treturn { ok: true, code: result.code, stdout: result.stdout, stderr: result.stderr };\n\t}\n\n\treturn new Promise((resolve, reject) => {\n\t\tif (existsSync(options.socketPath)) unlinkSync(options.socketPath);\n\t\tconst sockets = new Set<net.Socket>();\n\t\tconst server = net.createServer((socket) => {\n\t\t\tif (sockets.size >= MAX_CONNECTIONS) {\n\t\t\t\tsocket.destroy();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsockets.add(socket);\n\t\t\tsocket.on(\"close\", () => sockets.delete(socket));\n\t\t\tsocket.on(\"error\", () => socket.destroy());\n\t\t\tconst reader = new FrameReader(socket, \"Command escalation channel\", (detail) =>\n\t\t\t\taudit(`Refused: ${detail}`, \"(protocol)\"),\n\t\t\t);\n\t\t\tvoid (async () => {\n\t\t\t\ttry {\n\t\t\t\t\twriteFrame(socket, await answer(await reader.next()));\n\t\t\t\t} catch {\n\t\t\t\t\t// A disconnected or malformed client is the reader's business.\n\t\t\t\t} finally {\n\t\t\t\t\tsocket.end();\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t\tserver.on(\"error\", reject);\n\t\tserver.listen(options.socketPath, () => {\n\t\t\tserver.off(\"error\", reject);\n\t\t\ttry {\n\t\t\t\tchmodSync(options.socketPath, 0o600);\n\t\t\t} catch {\n\t\t\t\t// The containing directory is already 0700; this is defence in depth.\n\t\t\t}\n\t\t\tresolve({\n\t\t\t\tclose: () =>\n\t\t\t\t\tnew Promise((closed) => {\n\t\t\t\t\t\tfor (const socket of sockets) socket.destroy();\n\t\t\t\t\t\tserver.close(() => closed());\n\t\t\t\t\t}),\n\t\t\t});\n\t\t});\n\t});\n}\n"]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdtempSync, unlinkSync } from "node:fs";
|
|
2
|
+
import * as net from "node:net";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { supervisorTempDirectory } from "../supervisor-temp.js";
|
|
5
|
+
import { FrameReader, isRequestObject, writeFrame } from "./framing.js";
|
|
6
|
+
/** AF_UNIX `sun_path` is 108 bytes on Linux, including the terminating NUL. */
|
|
7
|
+
const SUN_PATH_LIMIT = 108;
|
|
8
|
+
const MAX_CONNECTIONS = 8;
|
|
9
|
+
/** Env var naming the child-side socket. */
|
|
10
|
+
export const COMMAND_ESCALATION_SOCKET_VARIABLE = "APEX_COMMAND_ESCALATION_PATH";
|
|
11
|
+
export function resolveCommandEscalationChannelPaths() {
|
|
12
|
+
const hostSocketDirectory = mkdtempSync(join(supervisorTempDirectory(), `apex-escalate-${process.pid}-`), {
|
|
13
|
+
encoding: "utf8",
|
|
14
|
+
});
|
|
15
|
+
chmodSync(hostSocketDirectory, 0o700);
|
|
16
|
+
const hostSocketPath = join(hostSocketDirectory, "channel.sock");
|
|
17
|
+
if (Buffer.byteLength(hostSocketPath) + 1 > SUN_PATH_LIMIT) {
|
|
18
|
+
throw new Error("Command escalation channel path exceeds the Unix socket path limit.");
|
|
19
|
+
}
|
|
20
|
+
const childSocketPath = process.platform === "linux" ? "/home/apex-command-escalation.sock" : hostSocketPath;
|
|
21
|
+
return { hostSocketDirectory, hostSocketPath, childSocketPath };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run one approved command in a second, differently-mounted child.
|
|
25
|
+
*
|
|
26
|
+
* A refused write is refused by the kernel inside a namespace whose mounts are fixed for
|
|
27
|
+
* its lifetime. There is no in-place equivalent of holding a CONNECT open while a human
|
|
28
|
+
* decides: the syscall has already failed, and nothing inside the namespace can widen it.
|
|
29
|
+
* So the escalation happens where the authority is, outside the boundary, by starting a
|
|
30
|
+
* separate child for that one command.
|
|
31
|
+
*
|
|
32
|
+
* The original child's namespace is never modified, which is what makes this sound rather
|
|
33
|
+
* than a hole. Approving one command grants nothing to the session it came from; the
|
|
34
|
+
* session's own next attempt at the same operation is refused exactly as before.
|
|
35
|
+
*
|
|
36
|
+
* Approval is the supervisor's, per ADR 0023, for the reason that ADR gives: this socket
|
|
37
|
+
* has no peer authentication, so a request arriving on it may have come from anything
|
|
38
|
+
* inside the boundary. Without an approver the channel refuses, which is what keeps
|
|
39
|
+
* headless, print, JSON, and RPC modes at ADR 0005's deny.
|
|
40
|
+
*/
|
|
41
|
+
export function createCommandEscalationProxy(options) {
|
|
42
|
+
function audit(detail, command) {
|
|
43
|
+
options.violationStore?.add({ kind: "filesystem", command, detail, timestamp: new Date() });
|
|
44
|
+
}
|
|
45
|
+
async function answer(request) {
|
|
46
|
+
if (!isRequestObject(request))
|
|
47
|
+
return { ok: false, error: "Malformed request." };
|
|
48
|
+
if (request.op !== "run")
|
|
49
|
+
return { ok: false, error: "Only command escalation is served here." };
|
|
50
|
+
const command = typeof request.command === "string" ? request.command : "";
|
|
51
|
+
const writableRoot = typeof request.writableRoot === "string" ? request.writableRoot : "";
|
|
52
|
+
if (!command || !writableRoot)
|
|
53
|
+
return { ok: false, error: "Request named no command or no root." };
|
|
54
|
+
// Every request is asked about individually. There is no session-scoped grant here,
|
|
55
|
+
// unlike a host or a credential: a command is not a stable subject to remember, and
|
|
56
|
+
// remembering one would silently cover the next command that named the same root.
|
|
57
|
+
if (!options.requestApproval) {
|
|
58
|
+
audit(`Refused an escalation with no way to ask: ${command}`, command);
|
|
59
|
+
return { ok: false, error: "Escalation is unavailable without a terminal." };
|
|
60
|
+
}
|
|
61
|
+
let approved = false;
|
|
62
|
+
try {
|
|
63
|
+
approved = await options.requestApproval({ command, writableRoot });
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
approved = false;
|
|
67
|
+
}
|
|
68
|
+
if (!approved) {
|
|
69
|
+
audit(`Escalation refused for ${writableRoot}`, command);
|
|
70
|
+
return { ok: false, error: "Escalation was refused." };
|
|
71
|
+
}
|
|
72
|
+
const result = await options.runEscalated({ command, writableRoot });
|
|
73
|
+
audit(`Escalated with ${writableRoot} writable, exit ${result.code}`, command);
|
|
74
|
+
return { ok: true, code: result.code, stdout: result.stdout, stderr: result.stderr };
|
|
75
|
+
}
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
if (existsSync(options.socketPath))
|
|
78
|
+
unlinkSync(options.socketPath);
|
|
79
|
+
const sockets = new Set();
|
|
80
|
+
const server = net.createServer((socket) => {
|
|
81
|
+
if (sockets.size >= MAX_CONNECTIONS) {
|
|
82
|
+
socket.destroy();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
sockets.add(socket);
|
|
86
|
+
socket.on("close", () => sockets.delete(socket));
|
|
87
|
+
socket.on("error", () => socket.destroy());
|
|
88
|
+
const reader = new FrameReader(socket, "Command escalation channel", (detail) => audit(`Refused: ${detail}`, "(protocol)"));
|
|
89
|
+
void (async () => {
|
|
90
|
+
try {
|
|
91
|
+
writeFrame(socket, await answer(await reader.next()));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// A disconnected or malformed client is the reader's business.
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
socket.end();
|
|
98
|
+
}
|
|
99
|
+
})();
|
|
100
|
+
});
|
|
101
|
+
server.on("error", reject);
|
|
102
|
+
server.listen(options.socketPath, () => {
|
|
103
|
+
server.off("error", reject);
|
|
104
|
+
try {
|
|
105
|
+
chmodSync(options.socketPath, 0o600);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// The containing directory is already 0700; this is defence in depth.
|
|
109
|
+
}
|
|
110
|
+
resolve({
|
|
111
|
+
close: () => new Promise((closed) => {
|
|
112
|
+
for (const socket of sockets)
|
|
113
|
+
socket.destroy();
|
|
114
|
+
server.close(() => closed());
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=command-proxy.js.map
|