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":"command-proxy.js","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/command-proxy.ts"],"names":[],"mappings":"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","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,20 @@
|
|
|
1
|
+
/** Child-side credential store for sandboxed sessions. */
|
|
2
|
+
import type { AuthOperationOptions, Credential, CredentialInfo, CredentialStore } from "@earendil-works/pi-ai";
|
|
3
|
+
export declare const CREDENTIAL_CHANNEL_SOCKET_ENV = "APEX_CREDENTIAL_PROXY_PATH";
|
|
4
|
+
export declare class SandboxAuthStorage implements CredentialStore {
|
|
5
|
+
private readonly authPath;
|
|
6
|
+
private readonly socketPath;
|
|
7
|
+
private readOnly;
|
|
8
|
+
constructor(options: {
|
|
9
|
+
socketPath: string;
|
|
10
|
+
authPath?: string;
|
|
11
|
+
});
|
|
12
|
+
read(providerId: string, options?: AuthOperationOptions): Promise<Credential | undefined>;
|
|
13
|
+
list(options?: AuthOperationOptions): Promise<readonly CredentialInfo[]>;
|
|
14
|
+
modify(providerId: string, fn: (current: Credential | undefined) => Promise<Credential | undefined>, options?: AuthOperationOptions): Promise<Credential | undefined>;
|
|
15
|
+
delete(providerId: string, options?: AuthOperationOptions): Promise<void>;
|
|
16
|
+
private invalidateReadView;
|
|
17
|
+
private transaction;
|
|
18
|
+
}
|
|
19
|
+
export declare function createSandboxCredentialStore(env?: NodeJS.ProcessEnv): CredentialStore | undefined;
|
|
20
|
+
//# sourceMappingURL=credential-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-client.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/credential-client.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAG1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAI/G,eAAO,MAAM,6BAA6B,+BAA+B,CAAC;AA4C1E,qBAAa,kBAAmB,YAAW,eAAe;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAsB;IAEtC,YAAY,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAI7D;IAEK,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAE9F;IAEK,IAAI,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC,CAE7E;IAEK,MAAM,CACX,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,KAAK,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,EACxE,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAmBjC;IAEK,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9E;IAED,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,WAAW;CAqGnB;AAED,wBAAgB,4BAA4B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,eAAe,GAAG,SAAS,CAI9G","sourcesContent":["/** Child-side credential store for sandboxed sessions. */\n\nimport * as net from \"node:net\";\nimport type { AuthOperationOptions, Credential, CredentialInfo, CredentialStore } from \"@earendil-works/pi-ai\";\nimport { getAuthPath } from \"../../../config.ts\";\nimport { ReadOnlyAuthStorage } from \"../../auth-storage.ts\";\n\nexport const CREDENTIAL_CHANNEL_SOCKET_ENV = \"APEX_CREDENTIAL_PROXY_PATH\";\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst MAX_RESPONSE_BYTES = 64 * 1024;\n\ninterface CurrentFrame {\n\treadonly type?: unknown;\n\treadonly credential?: unknown;\n}\n\ninterface ResultFrame {\n\treadonly ok?: unknown;\n\treadonly credential?: unknown;\n\treadonly error?: unknown;\n}\n\nfunction isCredential(value: unknown): value is Credential {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n\tconst credential = value as Record<string, unknown>;\n\tif (credential.type === \"api_key\") {\n\t\treturn (\n\t\t\t(credential.key === undefined || typeof credential.key === \"string\") &&\n\t\t\t(credential.env === undefined ||\n\t\t\t\t(typeof credential.env === \"object\" &&\n\t\t\t\t\tcredential.env !== null &&\n\t\t\t\t\t!Array.isArray(credential.env) &&\n\t\t\t\t\tObject.values(credential.env).every((entry) => typeof entry === \"string\")))\n\t\t);\n\t}\n\treturn (\n\t\tcredential.type === \"oauth\" &&\n\t\ttypeof credential.access === \"string\" &&\n\t\ttypeof credential.refresh === \"string\" &&\n\t\ttypeof credential.expires === \"number\" &&\n\t\tNumber.isFinite(credential.expires)\n\t);\n}\n\nfunction parseCurrentCredential(frame: CurrentFrame): Credential | undefined {\n\tif (frame.credential === undefined) return undefined;\n\tif (!isCredential(frame.credential))\n\t\tthrow new Error(\"The credential channel returned an invalid current credential.\");\n\treturn frame.credential;\n}\n\nexport class SandboxAuthStorage implements CredentialStore {\n\tprivate readonly authPath: string;\n\tprivate readonly socketPath: string;\n\tprivate readOnly: ReadOnlyAuthStorage;\n\n\tconstructor(options: { socketPath: string; authPath?: string }) {\n\t\tthis.socketPath = options.socketPath;\n\t\tthis.authPath = options.authPath ?? getAuthPath();\n\t\tthis.readOnly = new ReadOnlyAuthStorage(this.authPath, { resolveCommands: true });\n\t}\n\n\tasync read(providerId: string, options?: AuthOperationOptions): Promise<Credential | undefined> {\n\t\treturn this.readOnly.read(providerId, options);\n\t}\n\n\tasync list(options?: AuthOperationOptions): Promise<readonly CredentialInfo[]> {\n\t\treturn this.readOnly.list(options);\n\t}\n\n\tasync modify(\n\t\tproviderId: string,\n\t\tfn: (current: Credential | undefined) => Promise<Credential | undefined>,\n\t\toptions?: AuthOperationOptions,\n\t): Promise<Credential | undefined> {\n\t\tconst result = await this.transaction(\n\t\t\t{ action: \"modify\", providerId },\n\t\t\tasync (frame, send) => {\n\t\t\t\tif (frame.type !== \"current\") throw new Error(\"The credential channel returned an unexpected response.\");\n\t\t\t\tconst current = parseCurrentCredential(frame);\n\t\t\t\tlet next: Credential | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tnext = await fn(current);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tsend({ action: \"abort\" });\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tsend(next === undefined ? { action: \"no_change\" } : { action: \"commit\", credential: next });\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t\tthis.invalidateReadView();\n\t\treturn result;\n\t}\n\n\tasync delete(providerId: string, options?: AuthOperationOptions): Promise<void> {\n\t\tawait this.transaction({ action: \"delete\", providerId }, undefined, options);\n\t\tthis.invalidateReadView();\n\t}\n\n\tprivate invalidateReadView(): void {\n\t\tthis.readOnly = new ReadOnlyAuthStorage(this.authPath, { resolveCommands: true });\n\t}\n\n\tprivate transaction(\n\t\tinitial: object,\n\t\tonCurrent: ((frame: CurrentFrame, send: (frame: object) => void) => Promise<void>) | undefined,\n\t\toptions?: AuthOperationOptions,\n\t): Promise<Credential | undefined> {\n\t\toptions?.signal?.throwIfAborted();\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst client = net.connect(this.socketPath);\n\t\t\tlet settled = false;\n\t\t\tlet buffer = Buffer.alloc(0);\n\t\t\tlet handling = Promise.resolve();\n\t\t\tconst timeout = setTimeout(\n\t\t\t\t() => finish(new Error(\"Timed out waiting for the credential channel to answer.\")),\n\t\t\t\tREQUEST_TIMEOUT_MS,\n\t\t\t);\n\n\t\t\tconst finish = (error: Error | undefined, result?: Credential): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\toptions?.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tclient.destroy();\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve(result);\n\t\t\t};\n\t\t\tconst onAbort = (): void => finish(new Error(\"Operation aborted.\"));\n\t\t\tconst send = (frame: object): void => {\n\t\t\t\tif (!client.destroyed) client.write(`${JSON.stringify(frame)}\\n`);\n\t\t\t};\n\t\t\tconst handleFrame = async (frame: unknown): Promise<void> => {\n\t\t\t\tif (typeof frame !== \"object\" || frame === null || Array.isArray(frame)) {\n\t\t\t\t\tfinish(new Error(\"The credential channel returned a malformed response.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst response = frame as CurrentFrame & ResultFrame;\n\t\t\t\tif (response.type === \"current\") {\n\t\t\t\t\tif (!onCurrent) throw new Error(\"The credential channel returned an unexpected current value.\");\n\t\t\t\t\tawait onCurrent(response, send);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tif (response.credential !== undefined && !isCredential(response.credential)) {\n\t\t\t\t\t\tthrow new Error(\"The credential channel returned an invalid credential.\");\n\t\t\t\t\t}\n\t\t\t\t\tfinish(undefined, response.credential as Credential | undefined);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfinish(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\ttypeof response.error === \"string\" ? response.error : \"The credential channel refused the mutation.\",\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t};\n\n\t\t\toptions?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\tclient.on(\"connect\", () => send(initial));\n\t\t\tclient.on(\"data\", (chunk: Buffer) => {\n\t\t\t\tbuffer = Buffer.concat([buffer, chunk]);\n\t\t\t\tif (buffer.length > MAX_RESPONSE_BYTES && buffer.indexOf(0x0a) < 0) {\n\t\t\t\t\tfinish(new Error(\"The credential channel response is too large.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet newline = buffer.indexOf(0x0a);\n\t\t\t\twhile (newline >= 0) {\n\t\t\t\t\tif (newline > MAX_RESPONSE_BYTES) {\n\t\t\t\t\t\tfinish(new Error(\"The credential channel response is too large.\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst raw = buffer.subarray(0, newline);\n\t\t\t\t\tbuffer = buffer.subarray(newline + 1);\n\t\t\t\t\tlet frame: unknown;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tframe = JSON.parse(raw.toString(\"utf8\")) as unknown;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tfinish(new Error(\"The credential channel returned a malformed response.\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\thandling = handling\n\t\t\t\t\t\t.then(() => handleFrame(frame))\n\t\t\t\t\t\t.catch((error: unknown) => {\n\t\t\t\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t\t\t});\n\t\t\t\t\tnewline = buffer.indexOf(0x0a);\n\t\t\t\t}\n\t\t\t\tif (buffer.length > MAX_RESPONSE_BYTES) {\n\t\t\t\t\tfinish(new Error(\"The credential channel response is too large.\"));\n\t\t\t\t}\n\t\t\t});\n\t\t\tclient.on(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\t\tif (error.code === \"EACCES\" || error.code === \"ENOENT\" || error.code === \"ECONNREFUSED\") {\n\t\t\t\t\tfinish(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`The sandbox credential channel is unavailable (${error.code}); this session cannot modify credentials.`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfinish(new Error(`The sandbox credential channel failed: ${error.message}`));\n\t\t\t});\n\t\t});\n\t}\n}\n\nexport function createSandboxCredentialStore(env: NodeJS.ProcessEnv = process.env): CredentialStore | undefined {\n\tconst socketPath = env[CREDENTIAL_CHANNEL_SOCKET_ENV];\n\tif (!socketPath) return undefined;\n\treturn new SandboxAuthStorage({ socketPath });\n}\n"]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/** Child-side credential store for sandboxed sessions. */
|
|
2
|
+
import * as net from "node:net";
|
|
3
|
+
import { getAuthPath } from "../../../config.js";
|
|
4
|
+
import { ReadOnlyAuthStorage } from "../../auth-storage.js";
|
|
5
|
+
export const CREDENTIAL_CHANNEL_SOCKET_ENV = "APEX_CREDENTIAL_PROXY_PATH";
|
|
6
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
7
|
+
const MAX_RESPONSE_BYTES = 64 * 1024;
|
|
8
|
+
function isCredential(value) {
|
|
9
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
10
|
+
return false;
|
|
11
|
+
const credential = value;
|
|
12
|
+
if (credential.type === "api_key") {
|
|
13
|
+
return ((credential.key === undefined || typeof credential.key === "string") &&
|
|
14
|
+
(credential.env === undefined ||
|
|
15
|
+
(typeof credential.env === "object" &&
|
|
16
|
+
credential.env !== null &&
|
|
17
|
+
!Array.isArray(credential.env) &&
|
|
18
|
+
Object.values(credential.env).every((entry) => typeof entry === "string"))));
|
|
19
|
+
}
|
|
20
|
+
return (credential.type === "oauth" &&
|
|
21
|
+
typeof credential.access === "string" &&
|
|
22
|
+
typeof credential.refresh === "string" &&
|
|
23
|
+
typeof credential.expires === "number" &&
|
|
24
|
+
Number.isFinite(credential.expires));
|
|
25
|
+
}
|
|
26
|
+
function parseCurrentCredential(frame) {
|
|
27
|
+
if (frame.credential === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (!isCredential(frame.credential))
|
|
30
|
+
throw new Error("The credential channel returned an invalid current credential.");
|
|
31
|
+
return frame.credential;
|
|
32
|
+
}
|
|
33
|
+
export class SandboxAuthStorage {
|
|
34
|
+
authPath;
|
|
35
|
+
socketPath;
|
|
36
|
+
readOnly;
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.socketPath = options.socketPath;
|
|
39
|
+
this.authPath = options.authPath ?? getAuthPath();
|
|
40
|
+
this.readOnly = new ReadOnlyAuthStorage(this.authPath, { resolveCommands: true });
|
|
41
|
+
}
|
|
42
|
+
async read(providerId, options) {
|
|
43
|
+
return this.readOnly.read(providerId, options);
|
|
44
|
+
}
|
|
45
|
+
async list(options) {
|
|
46
|
+
return this.readOnly.list(options);
|
|
47
|
+
}
|
|
48
|
+
async modify(providerId, fn, options) {
|
|
49
|
+
const result = await this.transaction({ action: "modify", providerId }, async (frame, send) => {
|
|
50
|
+
if (frame.type !== "current")
|
|
51
|
+
throw new Error("The credential channel returned an unexpected response.");
|
|
52
|
+
const current = parseCurrentCredential(frame);
|
|
53
|
+
let next;
|
|
54
|
+
try {
|
|
55
|
+
next = await fn(current);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
send({ action: "abort" });
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
send(next === undefined ? { action: "no_change" } : { action: "commit", credential: next });
|
|
62
|
+
}, options);
|
|
63
|
+
this.invalidateReadView();
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
async delete(providerId, options) {
|
|
67
|
+
await this.transaction({ action: "delete", providerId }, undefined, options);
|
|
68
|
+
this.invalidateReadView();
|
|
69
|
+
}
|
|
70
|
+
invalidateReadView() {
|
|
71
|
+
this.readOnly = new ReadOnlyAuthStorage(this.authPath, { resolveCommands: true });
|
|
72
|
+
}
|
|
73
|
+
transaction(initial, onCurrent, options) {
|
|
74
|
+
options?.signal?.throwIfAborted();
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const client = net.connect(this.socketPath);
|
|
77
|
+
let settled = false;
|
|
78
|
+
let buffer = Buffer.alloc(0);
|
|
79
|
+
let handling = Promise.resolve();
|
|
80
|
+
const timeout = setTimeout(() => finish(new Error("Timed out waiting for the credential channel to answer.")), REQUEST_TIMEOUT_MS);
|
|
81
|
+
const finish = (error, result) => {
|
|
82
|
+
if (settled)
|
|
83
|
+
return;
|
|
84
|
+
settled = true;
|
|
85
|
+
clearTimeout(timeout);
|
|
86
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
87
|
+
client.destroy();
|
|
88
|
+
if (error)
|
|
89
|
+
reject(error);
|
|
90
|
+
else
|
|
91
|
+
resolve(result);
|
|
92
|
+
};
|
|
93
|
+
const onAbort = () => finish(new Error("Operation aborted."));
|
|
94
|
+
const send = (frame) => {
|
|
95
|
+
if (!client.destroyed)
|
|
96
|
+
client.write(`${JSON.stringify(frame)}\n`);
|
|
97
|
+
};
|
|
98
|
+
const handleFrame = async (frame) => {
|
|
99
|
+
if (typeof frame !== "object" || frame === null || Array.isArray(frame)) {
|
|
100
|
+
finish(new Error("The credential channel returned a malformed response."));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const response = frame;
|
|
104
|
+
if (response.type === "current") {
|
|
105
|
+
if (!onCurrent)
|
|
106
|
+
throw new Error("The credential channel returned an unexpected current value.");
|
|
107
|
+
await onCurrent(response, send);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (response.ok === true) {
|
|
111
|
+
if (response.credential !== undefined && !isCredential(response.credential)) {
|
|
112
|
+
throw new Error("The credential channel returned an invalid credential.");
|
|
113
|
+
}
|
|
114
|
+
finish(undefined, response.credential);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
finish(new Error(typeof response.error === "string" ? response.error : "The credential channel refused the mutation."));
|
|
118
|
+
};
|
|
119
|
+
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
120
|
+
client.on("connect", () => send(initial));
|
|
121
|
+
client.on("data", (chunk) => {
|
|
122
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
123
|
+
if (buffer.length > MAX_RESPONSE_BYTES && buffer.indexOf(0x0a) < 0) {
|
|
124
|
+
finish(new Error("The credential channel response is too large."));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let newline = buffer.indexOf(0x0a);
|
|
128
|
+
while (newline >= 0) {
|
|
129
|
+
if (newline > MAX_RESPONSE_BYTES) {
|
|
130
|
+
finish(new Error("The credential channel response is too large."));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const raw = buffer.subarray(0, newline);
|
|
134
|
+
buffer = buffer.subarray(newline + 1);
|
|
135
|
+
let frame;
|
|
136
|
+
try {
|
|
137
|
+
frame = JSON.parse(raw.toString("utf8"));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
finish(new Error("The credential channel returned a malformed response."));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
handling = handling
|
|
144
|
+
.then(() => handleFrame(frame))
|
|
145
|
+
.catch((error) => {
|
|
146
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
147
|
+
});
|
|
148
|
+
newline = buffer.indexOf(0x0a);
|
|
149
|
+
}
|
|
150
|
+
if (buffer.length > MAX_RESPONSE_BYTES) {
|
|
151
|
+
finish(new Error("The credential channel response is too large."));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
client.on("error", (error) => {
|
|
155
|
+
if (error.code === "EACCES" || error.code === "ENOENT" || error.code === "ECONNREFUSED") {
|
|
156
|
+
finish(new Error(`The sandbox credential channel is unavailable (${error.code}); this session cannot modify credentials.`));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
finish(new Error(`The sandbox credential channel failed: ${error.message}`));
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export function createSandboxCredentialStore(env = process.env) {
|
|
165
|
+
const socketPath = env[CREDENTIAL_CHANNEL_SOCKET_ENV];
|
|
166
|
+
if (!socketPath)
|
|
167
|
+
return undefined;
|
|
168
|
+
return new SandboxAuthStorage({ socketPath });
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=credential-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-client.js","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/credential-client.ts"],"names":[],"mappings":"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","sourcesContent":["/** Child-side credential store for sandboxed sessions. */\n\nimport * as net from \"node:net\";\nimport type { AuthOperationOptions, Credential, CredentialInfo, CredentialStore } from \"@earendil-works/pi-ai\";\nimport { getAuthPath } from \"../../../config.ts\";\nimport { ReadOnlyAuthStorage } from \"../../auth-storage.ts\";\n\nexport const CREDENTIAL_CHANNEL_SOCKET_ENV = \"APEX_CREDENTIAL_PROXY_PATH\";\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst MAX_RESPONSE_BYTES = 64 * 1024;\n\ninterface CurrentFrame {\n\treadonly type?: unknown;\n\treadonly credential?: unknown;\n}\n\ninterface ResultFrame {\n\treadonly ok?: unknown;\n\treadonly credential?: unknown;\n\treadonly error?: unknown;\n}\n\nfunction isCredential(value: unknown): value is Credential {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n\tconst credential = value as Record<string, unknown>;\n\tif (credential.type === \"api_key\") {\n\t\treturn (\n\t\t\t(credential.key === undefined || typeof credential.key === \"string\") &&\n\t\t\t(credential.env === undefined ||\n\t\t\t\t(typeof credential.env === \"object\" &&\n\t\t\t\t\tcredential.env !== null &&\n\t\t\t\t\t!Array.isArray(credential.env) &&\n\t\t\t\t\tObject.values(credential.env).every((entry) => typeof entry === \"string\")))\n\t\t);\n\t}\n\treturn (\n\t\tcredential.type === \"oauth\" &&\n\t\ttypeof credential.access === \"string\" &&\n\t\ttypeof credential.refresh === \"string\" &&\n\t\ttypeof credential.expires === \"number\" &&\n\t\tNumber.isFinite(credential.expires)\n\t);\n}\n\nfunction parseCurrentCredential(frame: CurrentFrame): Credential | undefined {\n\tif (frame.credential === undefined) return undefined;\n\tif (!isCredential(frame.credential))\n\t\tthrow new Error(\"The credential channel returned an invalid current credential.\");\n\treturn frame.credential;\n}\n\nexport class SandboxAuthStorage implements CredentialStore {\n\tprivate readonly authPath: string;\n\tprivate readonly socketPath: string;\n\tprivate readOnly: ReadOnlyAuthStorage;\n\n\tconstructor(options: { socketPath: string; authPath?: string }) {\n\t\tthis.socketPath = options.socketPath;\n\t\tthis.authPath = options.authPath ?? getAuthPath();\n\t\tthis.readOnly = new ReadOnlyAuthStorage(this.authPath, { resolveCommands: true });\n\t}\n\n\tasync read(providerId: string, options?: AuthOperationOptions): Promise<Credential | undefined> {\n\t\treturn this.readOnly.read(providerId, options);\n\t}\n\n\tasync list(options?: AuthOperationOptions): Promise<readonly CredentialInfo[]> {\n\t\treturn this.readOnly.list(options);\n\t}\n\n\tasync modify(\n\t\tproviderId: string,\n\t\tfn: (current: Credential | undefined) => Promise<Credential | undefined>,\n\t\toptions?: AuthOperationOptions,\n\t): Promise<Credential | undefined> {\n\t\tconst result = await this.transaction(\n\t\t\t{ action: \"modify\", providerId },\n\t\t\tasync (frame, send) => {\n\t\t\t\tif (frame.type !== \"current\") throw new Error(\"The credential channel returned an unexpected response.\");\n\t\t\t\tconst current = parseCurrentCredential(frame);\n\t\t\t\tlet next: Credential | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tnext = await fn(current);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tsend({ action: \"abort\" });\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tsend(next === undefined ? { action: \"no_change\" } : { action: \"commit\", credential: next });\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t\tthis.invalidateReadView();\n\t\treturn result;\n\t}\n\n\tasync delete(providerId: string, options?: AuthOperationOptions): Promise<void> {\n\t\tawait this.transaction({ action: \"delete\", providerId }, undefined, options);\n\t\tthis.invalidateReadView();\n\t}\n\n\tprivate invalidateReadView(): void {\n\t\tthis.readOnly = new ReadOnlyAuthStorage(this.authPath, { resolveCommands: true });\n\t}\n\n\tprivate transaction(\n\t\tinitial: object,\n\t\tonCurrent: ((frame: CurrentFrame, send: (frame: object) => void) => Promise<void>) | undefined,\n\t\toptions?: AuthOperationOptions,\n\t): Promise<Credential | undefined> {\n\t\toptions?.signal?.throwIfAborted();\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst client = net.connect(this.socketPath);\n\t\t\tlet settled = false;\n\t\t\tlet buffer = Buffer.alloc(0);\n\t\t\tlet handling = Promise.resolve();\n\t\t\tconst timeout = setTimeout(\n\t\t\t\t() => finish(new Error(\"Timed out waiting for the credential channel to answer.\")),\n\t\t\t\tREQUEST_TIMEOUT_MS,\n\t\t\t);\n\n\t\t\tconst finish = (error: Error | undefined, result?: Credential): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\toptions?.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tclient.destroy();\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve(result);\n\t\t\t};\n\t\t\tconst onAbort = (): void => finish(new Error(\"Operation aborted.\"));\n\t\t\tconst send = (frame: object): void => {\n\t\t\t\tif (!client.destroyed) client.write(`${JSON.stringify(frame)}\\n`);\n\t\t\t};\n\t\t\tconst handleFrame = async (frame: unknown): Promise<void> => {\n\t\t\t\tif (typeof frame !== \"object\" || frame === null || Array.isArray(frame)) {\n\t\t\t\t\tfinish(new Error(\"The credential channel returned a malformed response.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst response = frame as CurrentFrame & ResultFrame;\n\t\t\t\tif (response.type === \"current\") {\n\t\t\t\t\tif (!onCurrent) throw new Error(\"The credential channel returned an unexpected current value.\");\n\t\t\t\t\tawait onCurrent(response, send);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tif (response.credential !== undefined && !isCredential(response.credential)) {\n\t\t\t\t\t\tthrow new Error(\"The credential channel returned an invalid credential.\");\n\t\t\t\t\t}\n\t\t\t\t\tfinish(undefined, response.credential as Credential | undefined);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfinish(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\ttypeof response.error === \"string\" ? response.error : \"The credential channel refused the mutation.\",\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t};\n\n\t\t\toptions?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\tclient.on(\"connect\", () => send(initial));\n\t\t\tclient.on(\"data\", (chunk: Buffer) => {\n\t\t\t\tbuffer = Buffer.concat([buffer, chunk]);\n\t\t\t\tif (buffer.length > MAX_RESPONSE_BYTES && buffer.indexOf(0x0a) < 0) {\n\t\t\t\t\tfinish(new Error(\"The credential channel response is too large.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet newline = buffer.indexOf(0x0a);\n\t\t\t\twhile (newline >= 0) {\n\t\t\t\t\tif (newline > MAX_RESPONSE_BYTES) {\n\t\t\t\t\t\tfinish(new Error(\"The credential channel response is too large.\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst raw = buffer.subarray(0, newline);\n\t\t\t\t\tbuffer = buffer.subarray(newline + 1);\n\t\t\t\t\tlet frame: unknown;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tframe = JSON.parse(raw.toString(\"utf8\")) as unknown;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tfinish(new Error(\"The credential channel returned a malformed response.\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\thandling = handling\n\t\t\t\t\t\t.then(() => handleFrame(frame))\n\t\t\t\t\t\t.catch((error: unknown) => {\n\t\t\t\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t\t\t});\n\t\t\t\t\tnewline = buffer.indexOf(0x0a);\n\t\t\t\t}\n\t\t\t\tif (buffer.length > MAX_RESPONSE_BYTES) {\n\t\t\t\t\tfinish(new Error(\"The credential channel response is too large.\"));\n\t\t\t\t}\n\t\t\t});\n\t\t\tclient.on(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\t\tif (error.code === \"EACCES\" || error.code === \"ENOENT\" || error.code === \"ECONNREFUSED\") {\n\t\t\t\t\tfinish(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`The sandbox credential channel is unavailable (${error.code}); this session cannot modify credentials.`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfinish(new Error(`The sandbox credential channel failed: ${error.message}`));\n\t\t\t});\n\t\t});\n\t}\n}\n\nexport function createSandboxCredentialStore(env: NodeJS.ProcessEnv = process.env): CredentialStore | undefined {\n\tconst socketPath = env[CREDENTIAL_CHANNEL_SOCKET_ENV];\n\tif (!socketPath) return undefined;\n\treturn new SandboxAuthStorage({ socketPath });\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supervisor-owned credential mutation service for sandboxed sessions.
|
|
3
|
+
*
|
|
4
|
+
* Reads stay on the read-only auth.json projection. Mutations cross this Unix socket
|
|
5
|
+
* and execute under the host AuthStorage lock. The child can therefore implement the
|
|
6
|
+
* CredentialStore serialized read-modify-write contract without gaining filesystem
|
|
7
|
+
* write access to the credential file.
|
|
8
|
+
*/
|
|
9
|
+
import type { Credential } from "@earendil-works/pi-ai";
|
|
10
|
+
import type { SandboxViolationStore } from "../violations.ts";
|
|
11
|
+
export interface CredentialChannelPaths {
|
|
12
|
+
readonly hostSocketDirectory: string;
|
|
13
|
+
readonly hostSocketPath: string;
|
|
14
|
+
readonly childSocketPath: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Allocate the endpoint beneath a private fixed-root directory.
|
|
18
|
+
*
|
|
19
|
+
* Do not honor TMPDIR here. It can point inside the workspace on macOS, where the
|
|
20
|
+
* sandbox may write it. A fresh 0700 directory under /tmp also removes the
|
|
21
|
+
* listen-before-chmod authorization race: no other account can reach the socket while
|
|
22
|
+
* Node applies its initial umask-derived mode.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveCredentialChannelPaths(): CredentialChannelPaths;
|
|
25
|
+
export interface CredentialProxy {
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export interface CredentialConfigReference {
|
|
29
|
+
readonly field: string;
|
|
30
|
+
readonly kind: "command" | "environment";
|
|
31
|
+
}
|
|
32
|
+
/** Find a config-value reference without recursively walking attacker-controlled input. */
|
|
33
|
+
export declare function findCredentialConfigReference(credential: unknown): CredentialConfigReference | undefined;
|
|
34
|
+
/** Runtime validation at the untrusted child-to-host boundary. */
|
|
35
|
+
export declare function isCredential(value: unknown): value is Credential;
|
|
36
|
+
/** Serve serialized credential mutations on one private Unix socket. */
|
|
37
|
+
export declare function createCredentialProxy(options: {
|
|
38
|
+
authPath: string;
|
|
39
|
+
violationStore?: SandboxViolationStore;
|
|
40
|
+
socketPath: string;
|
|
41
|
+
/** Private directory allocated by resolveCredentialChannelPaths; removed on close. */
|
|
42
|
+
cleanupDirectory?: string;
|
|
43
|
+
}): Promise<CredentialProxy>;
|
|
44
|
+
//# sourceMappingURL=credential-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-proxy.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/credential-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AA0C9D,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,IAAI,sBAAsB,CAWtE;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,aAAa,CAAC;CACzC;AAED,2FAA2F;AAC3F,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,OAAO,GAAG,yBAAyB,GAAG,SAAS,CA2BxG;AAWD,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAgBhE;AA8BD,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,eAAe,CAAC,CAoK3B","sourcesContent":["/**\n * Supervisor-owned credential mutation service for sandboxed sessions.\n *\n * Reads stay on the read-only auth.json projection. Mutations cross this Unix socket\n * and execute under the host AuthStorage lock. The child can therefore implement the\n * CredentialStore serialized read-modify-write contract without gaining filesystem\n * write access to the credential file.\n */\n\nimport { chmodSync, lstatSync, mkdtempSync, readdirSync, rmSync } from \"node:fs\";\nimport * as net from \"node:net\";\nimport { join } from \"node:path\";\nimport type { Credential } from \"@earendil-works/pi-ai\";\nimport { AuthStorage } from \"../../auth-storage.ts\";\nimport { getConfigValueEnvVarNames, isCommandConfigValue } from \"../../resolve-config-value.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;\n/** Credentials are small; reject larger frames before UTF-8 decoding or JSON parsing. */\nconst MAX_CONNECTIONS = 32;\nconst CONNECTION_IDLE_TIMEOUT_MS = 30_000;\n\nfunction processIsAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\n/** Reclaim only same-user directories whose encoded supervisor PID is dead. */\nfunction reclaimStaleCredentialChannelDirectories(): void {\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(\"/tmp\");\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const entry of entries) {\n\t\tconst match = /^apex-cred-(\\d+)-[A-Za-z0-9_-]+$/.exec(entry);\n\t\tif (!match) continue;\n\t\tconst pid = Number(match[1]);\n\t\tif (!Number.isSafeInteger(pid) || pid < 1 || processIsAlive(pid)) continue;\n\t\tconst path = join(\"/tmp\", entry);\n\t\ttry {\n\t\t\tconst stat = lstatSync(path);\n\t\t\tif (!stat.isDirectory() || stat.uid !== process.getuid?.()) continue;\n\t\t\trmSync(path, { force: true, recursive: true });\n\t\t} catch {\n\t\t\t// Another launch may have reclaimed it first.\n\t\t}\n\t}\n}\n\nexport interface CredentialChannelPaths {\n\treadonly hostSocketDirectory: string;\n\treadonly hostSocketPath: string;\n\treadonly childSocketPath: string;\n}\n\n/**\n * Allocate the endpoint beneath a private fixed-root directory.\n *\n * Do not honor TMPDIR here. It can point inside the workspace on macOS, where the\n * sandbox may write it. A fresh 0700 directory under /tmp also removes the\n * listen-before-chmod authorization race: no other account can reach the socket while\n * Node applies its initial umask-derived mode.\n */\nexport function resolveCredentialChannelPaths(): CredentialChannelPaths {\n\treclaimStaleCredentialChannelDirectories();\n\tconst hostSocketDirectory = mkdtempSync(`/tmp/apex-cred-${process.pid}-`, { encoding: \"utf8\" });\n\tchmodSync(hostSocketDirectory, 0o700);\n\tconst hostSocketPath = join(hostSocketDirectory, \"channel.sock\");\n\tif (Buffer.byteLength(hostSocketPath) + 1 > SUN_PATH_LIMIT) {\n\t\trmSync(hostSocketDirectory, { force: true, recursive: true });\n\t\tthrow new Error(\"Credential channel path exceeds the Unix socket path limit.\");\n\t}\n\tconst childSocketPath = process.platform === \"linux\" ? \"/home/apex-credential-channel.sock\" : hostSocketPath;\n\treturn { hostSocketDirectory, hostSocketPath, childSocketPath };\n}\n\nexport interface CredentialProxy {\n\tclose(): Promise<void>;\n}\n\nexport interface CredentialConfigReference {\n\treadonly field: string;\n\treadonly kind: \"command\" | \"environment\";\n}\n\n/** Find a config-value reference without recursively walking attacker-controlled input. */\nexport function findCredentialConfigReference(credential: unknown): CredentialConfigReference | undefined {\n\tconst pending: Array<{ value: unknown; field: string }> = [{ value: credential, field: \"\" }];\n\twhile (pending.length > 0) {\n\t\tconst current = pending.pop();\n\t\tif (!current) break;\n\t\tif (typeof current.value === \"string\") {\n\t\t\tif (isCommandConfigValue(current.value)) return { field: current.field, kind: \"command\" };\n\t\t\tif (getConfigValueEnvVarNames(current.value).length > 0) {\n\t\t\t\treturn { field: current.field, kind: \"environment\" };\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(current.value)) {\n\t\t\tfor (let index = current.value.length - 1; index >= 0; index--) {\n\t\t\t\tpending.push({ value: current.value[index], field: `${current.field}[${index}]` });\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (current.value !== null && typeof current.value === \"object\") {\n\t\t\tconst entries = Object.entries(current.value);\n\t\t\tfor (let index = entries.length - 1; index >= 0; index--) {\n\t\t\t\tconst [key, value] = entries[index]!;\n\t\t\t\tpending.push({ value, field: current.field ? `${current.field}.${key}` : key });\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction isStringRecord(value: unknown): value is Record<string, string> {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t!Array.isArray(value) &&\n\t\tObject.values(value).every((entry) => typeof entry === \"string\")\n\t);\n}\n\n/** Runtime validation at the untrusted child-to-host boundary. */\nexport function isCredential(value: unknown): value is Credential {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n\tconst credential = value as Record<string, unknown>;\n\tif (credential.type === \"api_key\") {\n\t\treturn (\n\t\t\t(credential.key === undefined || typeof credential.key === \"string\") &&\n\t\t\t(credential.env === undefined || isStringRecord(credential.env))\n\t\t);\n\t}\n\treturn (\n\t\tcredential.type === \"oauth\" &&\n\t\ttypeof credential.access === \"string\" &&\n\t\ttypeof credential.refresh === \"string\" &&\n\t\ttypeof credential.expires === \"number\" &&\n\t\tNumber.isFinite(credential.expires)\n\t);\n}\n\nfunction referenceRefusal(reference: CredentialConfigReference): string {\n\treturn (\n\t\t`Credential values written from a sandboxed session must be literal secrets: field ` +\n\t\t`\"${reference.field}\" contains a ${reference.kind} reference. To store a reference, ` +\n\t\t`edit the credential file on the host directly or use the provider environment variable.`\n\t);\n}\n\ninterface StartRequest {\n\treadonly action?: unknown;\n\treadonly providerId?: unknown;\n}\n\ninterface CommitRequest {\n\treadonly action?: unknown;\n\treadonly credential?: unknown;\n}\n\nclass ChannelRefusal extends Error {\n\treadonly command: string;\n\n\tconstructor(command: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"ChannelRefusal\";\n\t\tthis.command = command;\n\t}\n}\n\n/** Serve serialized credential mutations on one private Unix socket. */\nexport function createCredentialProxy(options: {\n\tauthPath: string;\n\tviolationStore?: SandboxViolationStore;\n\tsocketPath: string;\n\t/** Private directory allocated by resolveCredentialChannelPaths; removed on close. */\n\tcleanupDirectory?: string;\n}): Promise<CredentialProxy> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst authStorage = AuthStorage.create(options.authPath);\n\t\tconst sockets = new Set<net.Socket>();\n\t\tconst operations = new Set<Promise<void>>();\n\t\tlet settled = false;\n\t\tlet closing: Promise<void> | undefined;\n\n\t\tconst audit = (command: string, detail: string): void => {\n\t\t\toptions.violationStore?.add({ kind: \"unknown\", command, detail, timestamp: new Date() });\n\t\t};\n\t\tconst refuse = (socket: net.Socket, command: string, error: string, detail = error): void => {\n\t\t\taudit(command, `Refused: ${detail}`);\n\t\t\twriteFrame(socket, { ok: false, error });\n\t\t};\n\n\t\tconst handleConnection = async (socket: net.Socket, reader: FrameReader, signal: AbortSignal): Promise<void> => {\n\t\t\tconst raw = await reader.next();\n\t\t\tif (!isRequestObject(raw)) {\n\t\t\t\trefuse(socket, \"credential-channel\", \"Invalid request: expected an object.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst request = raw as StartRequest;\n\t\t\tconst providerId = request.providerId;\n\t\t\tif (\n\t\t\t\ttypeof providerId !== \"string\" ||\n\t\t\t\tproviderId.length === 0 ||\n\t\t\t\tBuffer.byteLength(providerId, \"utf8\") > 256 ||\n\t\t\t\t/[\\u0000-\\u001f\\u007f-\\u009f\\u202a-\\u202e\\u2066-\\u2069]/u.test(providerId)\n\t\t\t) {\n\t\t\t\trefuse(\n\t\t\t\t\tsocket,\n\t\t\t\t\t\"credential-channel\",\n\t\t\t\t\t\"Invalid request: providerId contains unsafe characters or is too long.\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (request.action === \"delete\") {\n\t\t\t\tawait authStorage.delete(providerId, { signal });\n\t\t\t\taudit(`credential-delete ${providerId}`, \"Accepted: credential deleted through the sandbox channel.\");\n\t\t\t\twriteFrame(socket, { ok: true });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (request.action !== \"modify\") {\n\t\t\t\trefuse(socket, \"credential-channel\", 'Invalid request: action must be \"modify\" or \"delete\".');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet changed = false;\n\t\t\tconst result = await authStorage.modify(\n\t\t\t\tproviderId,\n\t\t\t\tasync (current) => {\n\t\t\t\t\twriteFrame(socket, { type: \"current\", ...(current === undefined ? {} : { credential: current }) });\n\t\t\t\t\tconst rawCommit = await reader.next();\n\t\t\t\t\tif (!isRequestObject(rawCommit)) {\n\t\t\t\t\t\tthrow new ChannelRefusal(\n\t\t\t\t\t\t\t`credential-write ${providerId}`,\n\t\t\t\t\t\t\t\"Invalid modify response: expected an object.\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tconst commit = rawCommit as CommitRequest;\n\t\t\t\t\tif (commit.action === \"abort\") {\n\t\t\t\t\t\tthrow new ChannelRefusal(\n\t\t\t\t\t\t\t`credential-write ${providerId}`,\n\t\t\t\t\t\t\t\"Credential modification was aborted by the client.\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (commit.action === \"no_change\") return undefined;\n\t\t\t\t\tif (commit.action !== \"commit\") {\n\t\t\t\t\t\tthrow new ChannelRefusal(`credential-write ${providerId}`, \"Invalid modify response action.\");\n\t\t\t\t\t}\n\t\t\t\t\tif (!isCredential(commit.credential)) {\n\t\t\t\t\t\tthrow new ChannelRefusal(`credential-write ${providerId}`, \"Invalid credential shape.\");\n\t\t\t\t\t}\n\t\t\t\t\tconst reference = findCredentialConfigReference(commit.credential);\n\t\t\t\t\tif (reference) {\n\t\t\t\t\t\tthrow new ChannelRefusal(`credential-write ${providerId}`, referenceRefusal(reference));\n\t\t\t\t\t}\n\t\t\t\t\tchanged = true;\n\t\t\t\t\treturn commit.credential;\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t\tif (changed) {\n\t\t\t\taudit(`credential-write ${providerId}`, \"Accepted: credential written through the sandbox channel.\");\n\t\t\t}\n\t\t\twriteFrame(socket, { ok: true, ...(result === undefined ? {} : { credential: result }) });\n\t\t};\n\n\t\tconst server = net.createServer((socket) => {\n\t\t\tif (sockets.size >= MAX_CONNECTIONS) {\n\t\t\t\trefuse(socket, \"credential-channel\", \"Credential channel connection limit reached.\");\n\t\t\t\tsocket.end();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsockets.add(socket);\n\t\t\tconst controller = new AbortController();\n\t\t\tsocket.setTimeout(CONNECTION_IDLE_TIMEOUT_MS, () => {\n\t\t\t\taudit(\"credential-channel\", \"Refused: credential channel connection timed out.\");\n\t\t\t\tsocket.destroy(new Error(\"Credential channel connection timed out.\"));\n\t\t\t});\n\t\t\tsocket.on(\"close\", () => {\n\t\t\t\tsockets.delete(socket);\n\t\t\t\tcontroller.abort();\n\t\t\t});\n\t\t\tconst reader = new FrameReader(socket, \"Credential channel\", (detail) =>\n\t\t\t\taudit(\"credential-channel\", `Refused: ${detail}`),\n\t\t\t);\n\t\t\tconst operation = handleConnection(socket, reader, controller.signal)\n\t\t\t\t.catch((error: unknown) => {\n\t\t\t\t\tif (error instanceof ChannelRefusal) {\n\t\t\t\t\t\trefuse(socket, error.command, error.message);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\trefuse(\n\t\t\t\t\t\tsocket,\n\t\t\t\t\t\t\"credential-channel\",\n\t\t\t\t\t\t\"The host credential operation failed.\",\n\t\t\t\t\t\t\"Host credential operation failed without exposing credential data.\",\n\t\t\t\t\t);\n\t\t\t\t})\n\t\t\t\t.finally(() => {\n\t\t\t\t\toperations.delete(operation);\n\t\t\t\t\tsocket.end();\n\t\t\t\t});\n\t\t\toperations.add(operation);\n\t\t});\n\n\t\tconst cleanup = (): void => {\n\t\t\trmSync(options.socketPath, { force: true });\n\t\t\tif (options.cleanupDirectory) rmSync(options.cleanupDirectory, { force: true, recursive: true });\n\t\t};\n\t\tconst failStartup = (error: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tserver.close();\n\t\t\tfor (const socket of sockets) socket.destroy();\n\t\t\tcleanup();\n\t\t\treject(error);\n\t\t};\n\t\tserver.once(\"error\", failStartup);\n\t\tserver.listen(options.socketPath, () => {\n\t\t\ttry {\n\t\t\t\tchmodSync(options.socketPath, 0o600);\n\t\t\t} catch (error) {\n\t\t\t\tfailStartup(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tserver.off(\"error\", failStartup);\n\t\t\tresolve({\n\t\t\t\tclose: () => {\n\t\t\t\t\tif (closing) return closing;\n\t\t\t\t\tclosing = (async () => {\n\t\t\t\t\t\tfor (const socket of sockets) socket.destroy();\n\t\t\t\t\t\tawait new Promise<void>((resolveClose) => server.close(() => resolveClose()));\n\t\t\t\t\t\tawait Promise.allSettled([...operations]);\n\t\t\t\t\t\tcleanup();\n\t\t\t\t\t})();\n\t\t\t\t\treturn closing;\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t});\n}\n"]}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supervisor-owned credential mutation service for sandboxed sessions.
|
|
3
|
+
*
|
|
4
|
+
* Reads stay on the read-only auth.json projection. Mutations cross this Unix socket
|
|
5
|
+
* and execute under the host AuthStorage lock. The child can therefore implement the
|
|
6
|
+
* CredentialStore serialized read-modify-write contract without gaining filesystem
|
|
7
|
+
* write access to the credential file.
|
|
8
|
+
*/
|
|
9
|
+
import { chmodSync, lstatSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
10
|
+
import * as net from "node:net";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { AuthStorage } from "../../auth-storage.js";
|
|
13
|
+
import { getConfigValueEnvVarNames, isCommandConfigValue } from "../../resolve-config-value.js";
|
|
14
|
+
import { FrameReader, isRequestObject, writeFrame } from "./framing.js";
|
|
15
|
+
/** AF_UNIX `sun_path` is 108 bytes on Linux, including the terminating NUL. */
|
|
16
|
+
const SUN_PATH_LIMIT = 108;
|
|
17
|
+
/** Credentials are small; reject larger frames before UTF-8 decoding or JSON parsing. */
|
|
18
|
+
const MAX_CONNECTIONS = 32;
|
|
19
|
+
const CONNECTION_IDLE_TIMEOUT_MS = 30_000;
|
|
20
|
+
function processIsAlive(pid) {
|
|
21
|
+
try {
|
|
22
|
+
process.kill(pid, 0);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return error.code === "EPERM";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Reclaim only same-user directories whose encoded supervisor PID is dead. */
|
|
30
|
+
function reclaimStaleCredentialChannelDirectories() {
|
|
31
|
+
let entries;
|
|
32
|
+
try {
|
|
33
|
+
entries = readdirSync("/tmp");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const match = /^apex-cred-(\d+)-[A-Za-z0-9_-]+$/.exec(entry);
|
|
40
|
+
if (!match)
|
|
41
|
+
continue;
|
|
42
|
+
const pid = Number(match[1]);
|
|
43
|
+
if (!Number.isSafeInteger(pid) || pid < 1 || processIsAlive(pid))
|
|
44
|
+
continue;
|
|
45
|
+
const path = join("/tmp", entry);
|
|
46
|
+
try {
|
|
47
|
+
const stat = lstatSync(path);
|
|
48
|
+
if (!stat.isDirectory() || stat.uid !== process.getuid?.())
|
|
49
|
+
continue;
|
|
50
|
+
rmSync(path, { force: true, recursive: true });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Another launch may have reclaimed it first.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Allocate the endpoint beneath a private fixed-root directory.
|
|
59
|
+
*
|
|
60
|
+
* Do not honor TMPDIR here. It can point inside the workspace on macOS, where the
|
|
61
|
+
* sandbox may write it. A fresh 0700 directory under /tmp also removes the
|
|
62
|
+
* listen-before-chmod authorization race: no other account can reach the socket while
|
|
63
|
+
* Node applies its initial umask-derived mode.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveCredentialChannelPaths() {
|
|
66
|
+
reclaimStaleCredentialChannelDirectories();
|
|
67
|
+
const hostSocketDirectory = mkdtempSync(`/tmp/apex-cred-${process.pid}-`, { encoding: "utf8" });
|
|
68
|
+
chmodSync(hostSocketDirectory, 0o700);
|
|
69
|
+
const hostSocketPath = join(hostSocketDirectory, "channel.sock");
|
|
70
|
+
if (Buffer.byteLength(hostSocketPath) + 1 > SUN_PATH_LIMIT) {
|
|
71
|
+
rmSync(hostSocketDirectory, { force: true, recursive: true });
|
|
72
|
+
throw new Error("Credential channel path exceeds the Unix socket path limit.");
|
|
73
|
+
}
|
|
74
|
+
const childSocketPath = process.platform === "linux" ? "/home/apex-credential-channel.sock" : hostSocketPath;
|
|
75
|
+
return { hostSocketDirectory, hostSocketPath, childSocketPath };
|
|
76
|
+
}
|
|
77
|
+
/** Find a config-value reference without recursively walking attacker-controlled input. */
|
|
78
|
+
export function findCredentialConfigReference(credential) {
|
|
79
|
+
const pending = [{ value: credential, field: "" }];
|
|
80
|
+
while (pending.length > 0) {
|
|
81
|
+
const current = pending.pop();
|
|
82
|
+
if (!current)
|
|
83
|
+
break;
|
|
84
|
+
if (typeof current.value === "string") {
|
|
85
|
+
if (isCommandConfigValue(current.value))
|
|
86
|
+
return { field: current.field, kind: "command" };
|
|
87
|
+
if (getConfigValueEnvVarNames(current.value).length > 0) {
|
|
88
|
+
return { field: current.field, kind: "environment" };
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(current.value)) {
|
|
93
|
+
for (let index = current.value.length - 1; index >= 0; index--) {
|
|
94
|
+
pending.push({ value: current.value[index], field: `${current.field}[${index}]` });
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (current.value !== null && typeof current.value === "object") {
|
|
99
|
+
const entries = Object.entries(current.value);
|
|
100
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
101
|
+
const [key, value] = entries[index];
|
|
102
|
+
pending.push({ value, field: current.field ? `${current.field}.${key}` : key });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
function isStringRecord(value) {
|
|
109
|
+
return (typeof value === "object" &&
|
|
110
|
+
value !== null &&
|
|
111
|
+
!Array.isArray(value) &&
|
|
112
|
+
Object.values(value).every((entry) => typeof entry === "string"));
|
|
113
|
+
}
|
|
114
|
+
/** Runtime validation at the untrusted child-to-host boundary. */
|
|
115
|
+
export function isCredential(value) {
|
|
116
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
117
|
+
return false;
|
|
118
|
+
const credential = value;
|
|
119
|
+
if (credential.type === "api_key") {
|
|
120
|
+
return ((credential.key === undefined || typeof credential.key === "string") &&
|
|
121
|
+
(credential.env === undefined || isStringRecord(credential.env)));
|
|
122
|
+
}
|
|
123
|
+
return (credential.type === "oauth" &&
|
|
124
|
+
typeof credential.access === "string" &&
|
|
125
|
+
typeof credential.refresh === "string" &&
|
|
126
|
+
typeof credential.expires === "number" &&
|
|
127
|
+
Number.isFinite(credential.expires));
|
|
128
|
+
}
|
|
129
|
+
function referenceRefusal(reference) {
|
|
130
|
+
return (`Credential values written from a sandboxed session must be literal secrets: field ` +
|
|
131
|
+
`"${reference.field}" contains a ${reference.kind} reference. To store a reference, ` +
|
|
132
|
+
`edit the credential file on the host directly or use the provider environment variable.`);
|
|
133
|
+
}
|
|
134
|
+
class ChannelRefusal extends Error {
|
|
135
|
+
command;
|
|
136
|
+
constructor(command, message) {
|
|
137
|
+
super(message);
|
|
138
|
+
this.name = "ChannelRefusal";
|
|
139
|
+
this.command = command;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Serve serialized credential mutations on one private Unix socket. */
|
|
143
|
+
export function createCredentialProxy(options) {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const authStorage = AuthStorage.create(options.authPath);
|
|
146
|
+
const sockets = new Set();
|
|
147
|
+
const operations = new Set();
|
|
148
|
+
let settled = false;
|
|
149
|
+
let closing;
|
|
150
|
+
const audit = (command, detail) => {
|
|
151
|
+
options.violationStore?.add({ kind: "unknown", command, detail, timestamp: new Date() });
|
|
152
|
+
};
|
|
153
|
+
const refuse = (socket, command, error, detail = error) => {
|
|
154
|
+
audit(command, `Refused: ${detail}`);
|
|
155
|
+
writeFrame(socket, { ok: false, error });
|
|
156
|
+
};
|
|
157
|
+
const handleConnection = async (socket, reader, signal) => {
|
|
158
|
+
const raw = await reader.next();
|
|
159
|
+
if (!isRequestObject(raw)) {
|
|
160
|
+
refuse(socket, "credential-channel", "Invalid request: expected an object.");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const request = raw;
|
|
164
|
+
const providerId = request.providerId;
|
|
165
|
+
if (typeof providerId !== "string" ||
|
|
166
|
+
providerId.length === 0 ||
|
|
167
|
+
Buffer.byteLength(providerId, "utf8") > 256 ||
|
|
168
|
+
/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u.test(providerId)) {
|
|
169
|
+
refuse(socket, "credential-channel", "Invalid request: providerId contains unsafe characters or is too long.");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (request.action === "delete") {
|
|
173
|
+
await authStorage.delete(providerId, { signal });
|
|
174
|
+
audit(`credential-delete ${providerId}`, "Accepted: credential deleted through the sandbox channel.");
|
|
175
|
+
writeFrame(socket, { ok: true });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (request.action !== "modify") {
|
|
179
|
+
refuse(socket, "credential-channel", 'Invalid request: action must be "modify" or "delete".');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
let changed = false;
|
|
183
|
+
const result = await authStorage.modify(providerId, async (current) => {
|
|
184
|
+
writeFrame(socket, { type: "current", ...(current === undefined ? {} : { credential: current }) });
|
|
185
|
+
const rawCommit = await reader.next();
|
|
186
|
+
if (!isRequestObject(rawCommit)) {
|
|
187
|
+
throw new ChannelRefusal(`credential-write ${providerId}`, "Invalid modify response: expected an object.");
|
|
188
|
+
}
|
|
189
|
+
const commit = rawCommit;
|
|
190
|
+
if (commit.action === "abort") {
|
|
191
|
+
throw new ChannelRefusal(`credential-write ${providerId}`, "Credential modification was aborted by the client.");
|
|
192
|
+
}
|
|
193
|
+
if (commit.action === "no_change")
|
|
194
|
+
return undefined;
|
|
195
|
+
if (commit.action !== "commit") {
|
|
196
|
+
throw new ChannelRefusal(`credential-write ${providerId}`, "Invalid modify response action.");
|
|
197
|
+
}
|
|
198
|
+
if (!isCredential(commit.credential)) {
|
|
199
|
+
throw new ChannelRefusal(`credential-write ${providerId}`, "Invalid credential shape.");
|
|
200
|
+
}
|
|
201
|
+
const reference = findCredentialConfigReference(commit.credential);
|
|
202
|
+
if (reference) {
|
|
203
|
+
throw new ChannelRefusal(`credential-write ${providerId}`, referenceRefusal(reference));
|
|
204
|
+
}
|
|
205
|
+
changed = true;
|
|
206
|
+
return commit.credential;
|
|
207
|
+
}, { signal });
|
|
208
|
+
if (changed) {
|
|
209
|
+
audit(`credential-write ${providerId}`, "Accepted: credential written through the sandbox channel.");
|
|
210
|
+
}
|
|
211
|
+
writeFrame(socket, { ok: true, ...(result === undefined ? {} : { credential: result }) });
|
|
212
|
+
};
|
|
213
|
+
const server = net.createServer((socket) => {
|
|
214
|
+
if (sockets.size >= MAX_CONNECTIONS) {
|
|
215
|
+
refuse(socket, "credential-channel", "Credential channel connection limit reached.");
|
|
216
|
+
socket.end();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
sockets.add(socket);
|
|
220
|
+
const controller = new AbortController();
|
|
221
|
+
socket.setTimeout(CONNECTION_IDLE_TIMEOUT_MS, () => {
|
|
222
|
+
audit("credential-channel", "Refused: credential channel connection timed out.");
|
|
223
|
+
socket.destroy(new Error("Credential channel connection timed out."));
|
|
224
|
+
});
|
|
225
|
+
socket.on("close", () => {
|
|
226
|
+
sockets.delete(socket);
|
|
227
|
+
controller.abort();
|
|
228
|
+
});
|
|
229
|
+
const reader = new FrameReader(socket, "Credential channel", (detail) => audit("credential-channel", `Refused: ${detail}`));
|
|
230
|
+
const operation = handleConnection(socket, reader, controller.signal)
|
|
231
|
+
.catch((error) => {
|
|
232
|
+
if (error instanceof ChannelRefusal) {
|
|
233
|
+
refuse(socket, error.command, error.message);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
refuse(socket, "credential-channel", "The host credential operation failed.", "Host credential operation failed without exposing credential data.");
|
|
237
|
+
})
|
|
238
|
+
.finally(() => {
|
|
239
|
+
operations.delete(operation);
|
|
240
|
+
socket.end();
|
|
241
|
+
});
|
|
242
|
+
operations.add(operation);
|
|
243
|
+
});
|
|
244
|
+
const cleanup = () => {
|
|
245
|
+
rmSync(options.socketPath, { force: true });
|
|
246
|
+
if (options.cleanupDirectory)
|
|
247
|
+
rmSync(options.cleanupDirectory, { force: true, recursive: true });
|
|
248
|
+
};
|
|
249
|
+
const failStartup = (error) => {
|
|
250
|
+
if (settled)
|
|
251
|
+
return;
|
|
252
|
+
settled = true;
|
|
253
|
+
server.close();
|
|
254
|
+
for (const socket of sockets)
|
|
255
|
+
socket.destroy();
|
|
256
|
+
cleanup();
|
|
257
|
+
reject(error);
|
|
258
|
+
};
|
|
259
|
+
server.once("error", failStartup);
|
|
260
|
+
server.listen(options.socketPath, () => {
|
|
261
|
+
try {
|
|
262
|
+
chmodSync(options.socketPath, 0o600);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
failStartup(error instanceof Error ? error : new Error(String(error)));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
settled = true;
|
|
269
|
+
server.off("error", failStartup);
|
|
270
|
+
resolve({
|
|
271
|
+
close: () => {
|
|
272
|
+
if (closing)
|
|
273
|
+
return closing;
|
|
274
|
+
closing = (async () => {
|
|
275
|
+
for (const socket of sockets)
|
|
276
|
+
socket.destroy();
|
|
277
|
+
await new Promise((resolveClose) => server.close(() => resolveClose()));
|
|
278
|
+
await Promise.allSettled([...operations]);
|
|
279
|
+
cleanup();
|
|
280
|
+
})();
|
|
281
|
+
return closing;
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=credential-proxy.js.map
|