apex-code 0.0.1-alpha.0 → 0.0.1-alpha.10
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 +471 -0
- package/README.md +74 -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 +35 -20
- 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 +121 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +364 -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 +11 -10
- package/dist/core/bash-executor.js.map +1 -1
- 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/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 +115 -48
- 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/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 +161 -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 +46 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +62 -4
- 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 +92 -9
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +138 -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 +5 -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.d.ts +183 -0
- package/dist/core/tools/contract.d.ts.map +1 -0
- package/dist/core/tools/contract.js +48 -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 +51 -6
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +145 -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 +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -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/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 +23 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +182 -21
- 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 +16 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +113 -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 +61 -5
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +642 -203
- 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 +211 -14
- 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/server/create-harness.d.ts +1 -1
- package/dist/server/create-harness.d.ts.map +1 -1
- package/dist/server/create-harness.js +8 -5
- package/dist/server/create-harness.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 +25 -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 +39 -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 +2 -2
- 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 +280 -445
- package/package.json +9 -12
- 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/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":"credential-proxy.js","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/credential-proxy.ts"],"names":[],"mappings":"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","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,35 @@
|
|
|
1
|
+
import type * as net from "node:net";
|
|
2
|
+
/**
|
|
3
|
+
* The framing both supervisor-owned RPC channels share.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from `credential-proxy.ts` when the git credential channel needed the same
|
|
6
|
+
* reader. A second hand-written copy of a byte-bounded parser sitting on a socket that
|
|
7
|
+
* every process inside the sandbox can reach is exactly the divergence ADR 0010 exists to
|
|
8
|
+
* prevent for tool contracts: a hardening applied to one copy and not the other is
|
|
9
|
+
* invisible until it matters. One implementation, named per channel only in its messages.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_FRAME_BYTES: number;
|
|
12
|
+
export declare function writeFrame(socket: net.Socket, response: object): void;
|
|
13
|
+
export declare function isRequestObject(value: unknown): value is Record<string, unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* One request frame at a time, newline-delimited JSON, bounded in bytes.
|
|
16
|
+
*
|
|
17
|
+
* Pipelining is refused rather than queued: a channel that answers a second request before
|
|
18
|
+
* the first is served makes the audit tail ambiguous about which frame a refusal belonged
|
|
19
|
+
* to, and neither channel has a caller that needs it.
|
|
20
|
+
*/
|
|
21
|
+
export declare class FrameReader {
|
|
22
|
+
private readonly socket;
|
|
23
|
+
private readonly channelName;
|
|
24
|
+
private readonly onProtocolRefusal;
|
|
25
|
+
private buffer;
|
|
26
|
+
private readonly queued;
|
|
27
|
+
private readonly waiting;
|
|
28
|
+
private terminalError;
|
|
29
|
+
constructor(socket: net.Socket, channelName: string, onProtocolRefusal: (detail: string) => void);
|
|
30
|
+
next(): Promise<unknown>;
|
|
31
|
+
private push;
|
|
32
|
+
private rejectOversizedFrame;
|
|
33
|
+
private fail;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=framing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framing.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/framing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,UAAU,CAAC;AAErC;;;;;;;;GAQG;AAEH,eAAO,MAAM,eAAe,QAAY,CAAC;AAEzC,wBAAgB,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAOrE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEhF;AAED;;;;;;GAMG;AACH,qBAAa,WAAW;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA2B;IAC7D,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4E;IACpG,OAAO,CAAC,aAAa,CAAoB;IAEzC,YAAY,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,EAO/F;IAED,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAKvB;IAED,OAAO,CAAC,IAAI;IAyCZ,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,IAAI;CAKZ","sourcesContent":["import type * as net from \"node:net\";\n\n/**\n * The framing both supervisor-owned RPC channels share.\n *\n * Extracted from `credential-proxy.ts` when the git credential channel needed the same\n * reader. A second hand-written copy of a byte-bounded parser sitting on a socket that\n * every process inside the sandbox can reach is exactly the divergence ADR 0010 exists to\n * prevent for tool contracts: a hardening applied to one copy and not the other is\n * invisible until it matters. One implementation, named per channel only in its messages.\n */\n\nexport const MAX_FRAME_BYTES = 64 * 1024;\n\nexport function writeFrame(socket: net.Socket, response: object): void {\n\tif (socket.destroyed) return;\n\tconst frame = Buffer.from(`${JSON.stringify(response)}\\n`, \"utf8\");\n\tif (frame.length > MAX_FRAME_BYTES) {\n\t\tthrow new Error(\"Channel response exceeded its byte limit.\");\n\t}\n\tsocket.write(frame);\n}\n\nexport function isRequestObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * One request frame at a time, newline-delimited JSON, bounded in bytes.\n *\n * Pipelining is refused rather than queued: a channel that answers a second request before\n * the first is served makes the audit tail ambiguous about which frame a refusal belonged\n * to, and neither channel has a caller that needs it.\n */\nexport class FrameReader {\n\tprivate readonly socket: net.Socket;\n\tprivate readonly channelName: string;\n\tprivate readonly onProtocolRefusal: (detail: string) => void;\n\tprivate buffer = Buffer.alloc(0);\n\tprivate readonly queued: unknown[] = [];\n\tprivate readonly waiting: Array<{ resolve(value: unknown): void; reject(error: Error): void }> = [];\n\tprivate terminalError: Error | undefined;\n\n\tconstructor(socket: net.Socket, channelName: string, onProtocolRefusal: (detail: string) => void) {\n\t\tthis.socket = socket;\n\t\tthis.channelName = channelName;\n\t\tthis.onProtocolRefusal = onProtocolRefusal;\n\t\tsocket.on(\"data\", (chunk: Buffer) => this.push(chunk));\n\t\tsocket.on(\"close\", () => this.fail(new Error(`${channelName} client disconnected.`)));\n\t\tsocket.on(\"error\", (error) => this.fail(error));\n\t}\n\n\tnext(): Promise<unknown> {\n\t\tconst queued = this.queued.shift();\n\t\tif (queued !== undefined) return Promise.resolve(queued);\n\t\tif (this.terminalError) return Promise.reject(this.terminalError);\n\t\treturn new Promise((resolve, reject) => this.waiting.push({ resolve, reject }));\n\t}\n\n\tprivate push(chunk: Buffer): void {\n\t\tif (this.terminalError) return;\n\t\tthis.buffer = Buffer.concat([this.buffer, chunk]);\n\t\twhile (true) {\n\t\t\tconst newline = this.buffer.indexOf(0x0a);\n\t\t\tif (newline < 0) {\n\t\t\t\tif (this.buffer.length > MAX_FRAME_BYTES) this.rejectOversizedFrame();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (newline > MAX_FRAME_BYTES) {\n\t\t\t\tthis.rejectOversizedFrame();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst frame = this.buffer.subarray(0, newline);\n\t\t\tthis.buffer = this.buffer.subarray(newline + 1);\n\t\t\tif (frame.length === 0) continue;\n\t\t\tlet value: unknown;\n\t\t\ttry {\n\t\t\t\tvalue = JSON.parse(frame.toString(\"utf8\")) as unknown;\n\t\t\t} catch {\n\t\t\t\tthis.onProtocolRefusal(\"Invalid request frame: not JSON.\");\n\t\t\t\twriteFrame(this.socket, { ok: false, error: \"Invalid request frame: not JSON.\" });\n\t\t\t\tthis.fail(new Error(`${this.channelName} received an invalid JSON frame.`));\n\t\t\t\tthis.socket.end();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst waiter = this.waiting.shift();\n\t\t\tif (waiter) {\n\t\t\t\twaiter.resolve(value);\n\t\t\t} else if (this.queued.length === 0) {\n\t\t\t\tthis.queued.push(value);\n\t\t\t} else {\n\t\t\t\tthis.onProtocolRefusal(\"Unexpected pipelined request frame.\");\n\t\t\t\twriteFrame(this.socket, { ok: false, error: \"Unexpected pipelined request frame.\" });\n\t\t\t\tthis.fail(new Error(`${this.channelName} received an unexpected pipelined frame.`));\n\t\t\t\tthis.socket.end();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate rejectOversizedFrame(): void {\n\t\tthis.onProtocolRefusal(\"Request frame exceeded the 64 KiB byte limit.\");\n\t\twriteFrame(this.socket, { ok: false, error: \"Request frame is too large.\" });\n\t\tthis.fail(new Error(`${this.channelName} frame exceeded its byte limit.`));\n\t\tthis.socket.end();\n\t}\n\n\tprivate fail(error: Error): void {\n\t\tif (this.terminalError) return;\n\t\tthis.terminalError = error;\n\t\tfor (const waiter of this.waiting.splice(0)) waiter.reject(error);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framing both supervisor-owned RPC channels share.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `credential-proxy.ts` when the git credential channel needed the same
|
|
5
|
+
* reader. A second hand-written copy of a byte-bounded parser sitting on a socket that
|
|
6
|
+
* every process inside the sandbox can reach is exactly the divergence ADR 0010 exists to
|
|
7
|
+
* prevent for tool contracts: a hardening applied to one copy and not the other is
|
|
8
|
+
* invisible until it matters. One implementation, named per channel only in its messages.
|
|
9
|
+
*/
|
|
10
|
+
export const MAX_FRAME_BYTES = 64 * 1024;
|
|
11
|
+
export function writeFrame(socket, response) {
|
|
12
|
+
if (socket.destroyed)
|
|
13
|
+
return;
|
|
14
|
+
const frame = Buffer.from(`${JSON.stringify(response)}\n`, "utf8");
|
|
15
|
+
if (frame.length > MAX_FRAME_BYTES) {
|
|
16
|
+
throw new Error("Channel response exceeded its byte limit.");
|
|
17
|
+
}
|
|
18
|
+
socket.write(frame);
|
|
19
|
+
}
|
|
20
|
+
export function isRequestObject(value) {
|
|
21
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One request frame at a time, newline-delimited JSON, bounded in bytes.
|
|
25
|
+
*
|
|
26
|
+
* Pipelining is refused rather than queued: a channel that answers a second request before
|
|
27
|
+
* the first is served makes the audit tail ambiguous about which frame a refusal belonged
|
|
28
|
+
* to, and neither channel has a caller that needs it.
|
|
29
|
+
*/
|
|
30
|
+
export class FrameReader {
|
|
31
|
+
socket;
|
|
32
|
+
channelName;
|
|
33
|
+
onProtocolRefusal;
|
|
34
|
+
buffer = Buffer.alloc(0);
|
|
35
|
+
queued = [];
|
|
36
|
+
waiting = [];
|
|
37
|
+
terminalError;
|
|
38
|
+
constructor(socket, channelName, onProtocolRefusal) {
|
|
39
|
+
this.socket = socket;
|
|
40
|
+
this.channelName = channelName;
|
|
41
|
+
this.onProtocolRefusal = onProtocolRefusal;
|
|
42
|
+
socket.on("data", (chunk) => this.push(chunk));
|
|
43
|
+
socket.on("close", () => this.fail(new Error(`${channelName} client disconnected.`)));
|
|
44
|
+
socket.on("error", (error) => this.fail(error));
|
|
45
|
+
}
|
|
46
|
+
next() {
|
|
47
|
+
const queued = this.queued.shift();
|
|
48
|
+
if (queued !== undefined)
|
|
49
|
+
return Promise.resolve(queued);
|
|
50
|
+
if (this.terminalError)
|
|
51
|
+
return Promise.reject(this.terminalError);
|
|
52
|
+
return new Promise((resolve, reject) => this.waiting.push({ resolve, reject }));
|
|
53
|
+
}
|
|
54
|
+
push(chunk) {
|
|
55
|
+
if (this.terminalError)
|
|
56
|
+
return;
|
|
57
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
58
|
+
while (true) {
|
|
59
|
+
const newline = this.buffer.indexOf(0x0a);
|
|
60
|
+
if (newline < 0) {
|
|
61
|
+
if (this.buffer.length > MAX_FRAME_BYTES)
|
|
62
|
+
this.rejectOversizedFrame();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (newline > MAX_FRAME_BYTES) {
|
|
66
|
+
this.rejectOversizedFrame();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const frame = this.buffer.subarray(0, newline);
|
|
70
|
+
this.buffer = this.buffer.subarray(newline + 1);
|
|
71
|
+
if (frame.length === 0)
|
|
72
|
+
continue;
|
|
73
|
+
let value;
|
|
74
|
+
try {
|
|
75
|
+
value = JSON.parse(frame.toString("utf8"));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
this.onProtocolRefusal("Invalid request frame: not JSON.");
|
|
79
|
+
writeFrame(this.socket, { ok: false, error: "Invalid request frame: not JSON." });
|
|
80
|
+
this.fail(new Error(`${this.channelName} received an invalid JSON frame.`));
|
|
81
|
+
this.socket.end();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const waiter = this.waiting.shift();
|
|
85
|
+
if (waiter) {
|
|
86
|
+
waiter.resolve(value);
|
|
87
|
+
}
|
|
88
|
+
else if (this.queued.length === 0) {
|
|
89
|
+
this.queued.push(value);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.onProtocolRefusal("Unexpected pipelined request frame.");
|
|
93
|
+
writeFrame(this.socket, { ok: false, error: "Unexpected pipelined request frame." });
|
|
94
|
+
this.fail(new Error(`${this.channelName} received an unexpected pipelined frame.`));
|
|
95
|
+
this.socket.end();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
rejectOversizedFrame() {
|
|
101
|
+
this.onProtocolRefusal("Request frame exceeded the 64 KiB byte limit.");
|
|
102
|
+
writeFrame(this.socket, { ok: false, error: "Request frame is too large." });
|
|
103
|
+
this.fail(new Error(`${this.channelName} frame exceeded its byte limit.`));
|
|
104
|
+
this.socket.end();
|
|
105
|
+
}
|
|
106
|
+
fail(error) {
|
|
107
|
+
if (this.terminalError)
|
|
108
|
+
return;
|
|
109
|
+
this.terminalError = error;
|
|
110
|
+
for (const waiter of this.waiting.splice(0))
|
|
111
|
+
waiter.reject(error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=framing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framing.js","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/framing.ts"],"names":[],"mappings":"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","sourcesContent":["import type * as net from \"node:net\";\n\n/**\n * The framing both supervisor-owned RPC channels share.\n *\n * Extracted from `credential-proxy.ts` when the git credential channel needed the same\n * reader. A second hand-written copy of a byte-bounded parser sitting on a socket that\n * every process inside the sandbox can reach is exactly the divergence ADR 0010 exists to\n * prevent for tool contracts: a hardening applied to one copy and not the other is\n * invisible until it matters. One implementation, named per channel only in its messages.\n */\n\nexport const MAX_FRAME_BYTES = 64 * 1024;\n\nexport function writeFrame(socket: net.Socket, response: object): void {\n\tif (socket.destroyed) return;\n\tconst frame = Buffer.from(`${JSON.stringify(response)}\\n`, \"utf8\");\n\tif (frame.length > MAX_FRAME_BYTES) {\n\t\tthrow new Error(\"Channel response exceeded its byte limit.\");\n\t}\n\tsocket.write(frame);\n}\n\nexport function isRequestObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * One request frame at a time, newline-delimited JSON, bounded in bytes.\n *\n * Pipelining is refused rather than queued: a channel that answers a second request before\n * the first is served makes the audit tail ambiguous about which frame a refusal belonged\n * to, and neither channel has a caller that needs it.\n */\nexport class FrameReader {\n\tprivate readonly socket: net.Socket;\n\tprivate readonly channelName: string;\n\tprivate readonly onProtocolRefusal: (detail: string) => void;\n\tprivate buffer = Buffer.alloc(0);\n\tprivate readonly queued: unknown[] = [];\n\tprivate readonly waiting: Array<{ resolve(value: unknown): void; reject(error: Error): void }> = [];\n\tprivate terminalError: Error | undefined;\n\n\tconstructor(socket: net.Socket, channelName: string, onProtocolRefusal: (detail: string) => void) {\n\t\tthis.socket = socket;\n\t\tthis.channelName = channelName;\n\t\tthis.onProtocolRefusal = onProtocolRefusal;\n\t\tsocket.on(\"data\", (chunk: Buffer) => this.push(chunk));\n\t\tsocket.on(\"close\", () => this.fail(new Error(`${channelName} client disconnected.`)));\n\t\tsocket.on(\"error\", (error) => this.fail(error));\n\t}\n\n\tnext(): Promise<unknown> {\n\t\tconst queued = this.queued.shift();\n\t\tif (queued !== undefined) return Promise.resolve(queued);\n\t\tif (this.terminalError) return Promise.reject(this.terminalError);\n\t\treturn new Promise((resolve, reject) => this.waiting.push({ resolve, reject }));\n\t}\n\n\tprivate push(chunk: Buffer): void {\n\t\tif (this.terminalError) return;\n\t\tthis.buffer = Buffer.concat([this.buffer, chunk]);\n\t\twhile (true) {\n\t\t\tconst newline = this.buffer.indexOf(0x0a);\n\t\t\tif (newline < 0) {\n\t\t\t\tif (this.buffer.length > MAX_FRAME_BYTES) this.rejectOversizedFrame();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (newline > MAX_FRAME_BYTES) {\n\t\t\t\tthis.rejectOversizedFrame();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst frame = this.buffer.subarray(0, newline);\n\t\t\tthis.buffer = this.buffer.subarray(newline + 1);\n\t\t\tif (frame.length === 0) continue;\n\t\t\tlet value: unknown;\n\t\t\ttry {\n\t\t\t\tvalue = JSON.parse(frame.toString(\"utf8\")) as unknown;\n\t\t\t} catch {\n\t\t\t\tthis.onProtocolRefusal(\"Invalid request frame: not JSON.\");\n\t\t\t\twriteFrame(this.socket, { ok: false, error: \"Invalid request frame: not JSON.\" });\n\t\t\t\tthis.fail(new Error(`${this.channelName} received an invalid JSON frame.`));\n\t\t\t\tthis.socket.end();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst waiter = this.waiting.shift();\n\t\t\tif (waiter) {\n\t\t\t\twaiter.resolve(value);\n\t\t\t} else if (this.queued.length === 0) {\n\t\t\t\tthis.queued.push(value);\n\t\t\t} else {\n\t\t\t\tthis.onProtocolRefusal(\"Unexpected pipelined request frame.\");\n\t\t\t\twriteFrame(this.socket, { ok: false, error: \"Unexpected pipelined request frame.\" });\n\t\t\t\tthis.fail(new Error(`${this.channelName} received an unexpected pipelined frame.`));\n\t\t\t\tthis.socket.end();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate rejectOversizedFrame(): void {\n\t\tthis.onProtocolRefusal(\"Request frame exceeded the 64 KiB byte limit.\");\n\t\twriteFrame(this.socket, { ok: false, error: \"Request frame is too large.\" });\n\t\tthis.fail(new Error(`${this.channelName} frame exceeded its byte limit.`));\n\t\tthis.socket.end();\n\t}\n\n\tprivate fail(error: Error): void {\n\t\tif (this.terminalError) return;\n\t\tthis.terminalError = error;\n\t\tfor (const waiter of this.waiting.splice(0)) waiter.reject(error);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { GitCredential, GitCredentialRequest } from "./git-credential-proxy.ts";
|
|
2
|
+
/** Env var naming the child-side socket the helper talks to. */
|
|
3
|
+
export declare const GIT_CREDENTIAL_SOCKET_VARIABLE = "APEX_GIT_CREDENTIAL_PATH";
|
|
4
|
+
export interface GitCredentialChannelPaths {
|
|
5
|
+
readonly hostSocketDirectory: string;
|
|
6
|
+
readonly hostSocketPath: string;
|
|
7
|
+
readonly childSocketPath: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Where the git credential socket lives on each side, mirroring the credential channel's
|
|
11
|
+
* own resolution. TMPDIR is deliberately not honoured: it can point inside the workspace
|
|
12
|
+
* on macOS, which the sandbox may write. The child-side path sits under `/home` on Linux
|
|
13
|
+
* because the `--tmpfs /home` is the only writable mount when bwrap creates the mountpoint.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveGitCredentialChannelPaths(): GitCredentialChannelPaths;
|
|
16
|
+
/**
|
|
17
|
+
* Where the helper lives, derived rather than passed.
|
|
18
|
+
*
|
|
19
|
+
* The supervisor writes `credential.helper` into the projected git config before the
|
|
20
|
+
* platform backend exists, and the backend writes the file itself. Deriving both from the
|
|
21
|
+
* state directory is what keeps the config from naming a path nothing ever created.
|
|
22
|
+
*/
|
|
23
|
+
export declare function gitCredentialHelperPath(stateDirectory: string): string;
|
|
24
|
+
/** The `credential.helper` value naming that helper, run under the supervisor's own node. */
|
|
25
|
+
export declare function gitCredentialHelperCommand(stateDirectory: string, nodePath?: string): string;
|
|
26
|
+
/** Write the helper into a directory the child can read, returning its path. */
|
|
27
|
+
export declare function writeGitCredentialHelper(directory: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a credential on the host, through the host's own git configuration.
|
|
30
|
+
*
|
|
31
|
+
* `git credential fill` rather than reading any particular store, because the answer may
|
|
32
|
+
* come from `gh`'s helper, libsecret, the macOS keychain, or a plain file, and only git
|
|
33
|
+
* knows which of those this host is configured to ask. Reimplementing that resolution
|
|
34
|
+
* would be a second, quietly diverging copy of it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fillHostGitCredential(request: GitCredentialRequest, options?: {
|
|
37
|
+
environment?: NodeJS.ProcessEnv;
|
|
38
|
+
cwd?: string;
|
|
39
|
+
}): Promise<GitCredential | undefined>;
|
|
40
|
+
//# sourceMappingURL=git-credential-helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-credential-helper.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/git-credential-helper.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAErF,gEAAgE;AAChE,eAAO,MAAM,8BAA8B,6BAA6B,CAAC;AAwEzE,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,IAAI,yBAAyB,CAW5E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,6FAA6F;AAC7F,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,SAAmB,GAAG,MAAM,CAEtG;AAED,gFAAgF;AAChF,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIlE;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,oBAAoB,EAC7B,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACzD,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CA8BpC","sourcesContent":["import { chmodSync, mkdtempSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { supervisorTempDirectory } from \"../supervisor-temp.ts\";\nimport type { GitCredential, GitCredentialRequest } from \"./git-credential-proxy.ts\";\n\n/** Env var naming the child-side socket the helper talks to. */\nexport const GIT_CREDENTIAL_SOCKET_VARIABLE = \"APEX_GIT_CREDENTIAL_PATH\";\n\n/**\n * The credential helper git runs inside the sandbox.\n *\n * `.cjs` for the same reason the network relay is: a plain `.js` under a workspace whose\n * package.json declares `\"type\": \"module\"` is parsed as ESM and dies on `require`.\n *\n * It answers `get` and nothing else. `store` and `erase` exit silently and successfully,\n * which is what tells git the credential was handled and stops it falling through to\n * another helper. Writing is deliberately not served: ADR 0015 keeps credential mutation\n * an explicit host operation, and a channel that let the child rewrite the host's store\n * would be a way out of the boundary rather than a way to work inside it.\n *\n * Any failure exits 0 having printed nothing, which git reads as \"this helper has no\n * credential\" and handles normally. Exiting non-zero would surface as a git error that\n * looks like a repository problem rather than a refused release.\n */\nconst HELPER_SOURCE = `\nconst net = require(\"node:net\");\n\nconst operation = process.argv[2];\nif (operation !== \"get\") process.exit(0);\n\nlet input = \"\";\nprocess.stdin.setEncoding(\"utf8\");\nprocess.stdin.on(\"data\", (chunk) => {\n\tinput += chunk;\n});\nprocess.stdin.on(\"end\", () => {\n\tconst request = {};\n\tfor (const line of input.split(\"\\\\n\")) {\n\t\tconst separator = line.indexOf(\"=\");\n\t\tif (separator > 0) request[line.slice(0, separator)] = line.slice(separator + 1).trim();\n\t}\n\tconst socketPath = process.env.${GIT_CREDENTIAL_SOCKET_VARIABLE};\n\tif (!socketPath || !request.host) process.exit(0);\n\n\tconst socket = net.connect(socketPath);\n\tlet buffer = \"\";\n\tconst giveUp = () => {\n\t\tsocket.destroy();\n\t\tprocess.exit(0);\n\t};\n\tsocket.on(\"connect\", () => {\n\t\tsocket.write(JSON.stringify({ op: \"get\", protocol: request.protocol || \"https\", host: request.host }) + \"\\\\n\");\n\t});\n\tsocket.on(\"data\", (chunk) => {\n\t\tbuffer += chunk.toString();\n\t\tconst newline = buffer.indexOf(\"\\\\n\");\n\t\tif (newline < 0) return;\n\t\tlet response;\n\t\ttry {\n\t\t\tresponse = JSON.parse(buffer.slice(0, newline));\n\t\t} catch {\n\t\t\tgiveUp();\n\t\t\treturn;\n\t\t}\n\t\tif (response && response.ok) {\n\t\t\tprocess.stdout.write(\"username=\" + response.username + \"\\\\n\");\n\t\t\tprocess.stdout.write(\"password=\" + response.password + \"\\\\n\");\n\t\t}\n\t\tsocket.destroy();\n\t\tprocess.exit(0);\n\t});\n\tsocket.on(\"error\", giveUp);\n});\n`.trim();\n\n/** AF_UNIX `sun_path` is 108 bytes on Linux, including the terminating NUL. */\nconst SUN_PATH_LIMIT = 108;\n\nexport interface GitCredentialChannelPaths {\n\treadonly hostSocketDirectory: string;\n\treadonly hostSocketPath: string;\n\treadonly childSocketPath: string;\n}\n\n/**\n * Where the git credential socket lives on each side, mirroring the credential channel's\n * own resolution. TMPDIR is deliberately not honoured: it can point inside the workspace\n * on macOS, which the sandbox may write. The child-side path sits under `/home` on Linux\n * because the `--tmpfs /home` is the only writable mount when bwrap creates the mountpoint.\n */\nexport function resolveGitCredentialChannelPaths(): GitCredentialChannelPaths {\n\tconst hostSocketDirectory = mkdtempSync(join(supervisorTempDirectory(), `apex-gitcred-${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(\"Git credential channel path exceeds the Unix socket path limit.\");\n\t}\n\tconst childSocketPath = process.platform === \"linux\" ? \"/home/apex-git-credential-channel.sock\" : hostSocketPath;\n\treturn { hostSocketDirectory, hostSocketPath, childSocketPath };\n}\n\n/**\n * Where the helper lives, derived rather than passed.\n *\n * The supervisor writes `credential.helper` into the projected git config before the\n * platform backend exists, and the backend writes the file itself. Deriving both from the\n * state directory is what keeps the config from naming a path nothing ever created.\n */\nexport function gitCredentialHelperPath(stateDirectory: string): string {\n\treturn join(stateDirectory, \"git-credential-apex.cjs\");\n}\n\n/** The `credential.helper` value naming that helper, run under the supervisor's own node. */\nexport function gitCredentialHelperCommand(stateDirectory: string, nodePath = process.execPath): string {\n\treturn `!\"${nodePath}\" \"${gitCredentialHelperPath(stateDirectory)}\"`;\n}\n\n/** Write the helper into a directory the child can read, returning its path. */\nexport function writeGitCredentialHelper(directory: string): string {\n\tconst path = gitCredentialHelperPath(directory);\n\twriteFileSync(path, HELPER_SOURCE);\n\treturn path;\n}\n\n/**\n * Resolve a credential on the host, through the host's own git configuration.\n *\n * `git credential fill` rather than reading any particular store, because the answer may\n * come from `gh`'s helper, libsecret, the macOS keychain, or a plain file, and only git\n * knows which of those this host is configured to ask. Reimplementing that resolution\n * would be a second, quietly diverging copy of it.\n */\nexport async function fillHostGitCredential(\n\trequest: GitCredentialRequest,\n\toptions?: { environment?: NodeJS.ProcessEnv; cwd?: string },\n): Promise<GitCredential | undefined> {\n\tconst { spawn } = await import(\"node:child_process\");\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(\"git\", [\"credential\", \"fill\"], {\n\t\t\tcwd: options?.cwd,\n\t\t\tenv: options?.environment ?? process.env,\n\t\t\tstdio: [\"pipe\", \"pipe\", \"ignore\"],\n\t\t});\n\t\tlet stdout = \"\";\n\t\tchild.stdout.setEncoding(\"utf8\");\n\t\tchild.stdout.on(\"data\", (chunk: string) => {\n\t\t\tstdout += chunk;\n\t\t});\n\t\tchild.on(\"error\", () => resolve(undefined));\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code !== 0) {\n\t\t\t\tresolve(undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst fields = new Map<string, string>();\n\t\t\tfor (const line of stdout.split(\"\\n\")) {\n\t\t\t\tconst separator = line.indexOf(\"=\");\n\t\t\t\tif (separator > 0) fields.set(line.slice(0, separator), line.slice(separator + 1).trim());\n\t\t\t}\n\t\t\tconst username = fields.get(\"username\");\n\t\t\tconst password = fields.get(\"password\");\n\t\t\tresolve(username && password ? { username, password } : undefined);\n\t\t});\n\t\tchild.stdin.end(`protocol=${request.protocol}\\nhost=${request.host}\\n\\n`);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { chmodSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { supervisorTempDirectory } from "../supervisor-temp.js";
|
|
4
|
+
/** Env var naming the child-side socket the helper talks to. */
|
|
5
|
+
export const GIT_CREDENTIAL_SOCKET_VARIABLE = "APEX_GIT_CREDENTIAL_PATH";
|
|
6
|
+
/**
|
|
7
|
+
* The credential helper git runs inside the sandbox.
|
|
8
|
+
*
|
|
9
|
+
* `.cjs` for the same reason the network relay is: a plain `.js` under a workspace whose
|
|
10
|
+
* package.json declares `"type": "module"` is parsed as ESM and dies on `require`.
|
|
11
|
+
*
|
|
12
|
+
* It answers `get` and nothing else. `store` and `erase` exit silently and successfully,
|
|
13
|
+
* which is what tells git the credential was handled and stops it falling through to
|
|
14
|
+
* another helper. Writing is deliberately not served: ADR 0015 keeps credential mutation
|
|
15
|
+
* an explicit host operation, and a channel that let the child rewrite the host's store
|
|
16
|
+
* would be a way out of the boundary rather than a way to work inside it.
|
|
17
|
+
*
|
|
18
|
+
* Any failure exits 0 having printed nothing, which git reads as "this helper has no
|
|
19
|
+
* credential" and handles normally. Exiting non-zero would surface as a git error that
|
|
20
|
+
* looks like a repository problem rather than a refused release.
|
|
21
|
+
*/
|
|
22
|
+
const HELPER_SOURCE = `
|
|
23
|
+
const net = require("node:net");
|
|
24
|
+
|
|
25
|
+
const operation = process.argv[2];
|
|
26
|
+
if (operation !== "get") process.exit(0);
|
|
27
|
+
|
|
28
|
+
let input = "";
|
|
29
|
+
process.stdin.setEncoding("utf8");
|
|
30
|
+
process.stdin.on("data", (chunk) => {
|
|
31
|
+
input += chunk;
|
|
32
|
+
});
|
|
33
|
+
process.stdin.on("end", () => {
|
|
34
|
+
const request = {};
|
|
35
|
+
for (const line of input.split("\\n")) {
|
|
36
|
+
const separator = line.indexOf("=");
|
|
37
|
+
if (separator > 0) request[line.slice(0, separator)] = line.slice(separator + 1).trim();
|
|
38
|
+
}
|
|
39
|
+
const socketPath = process.env.${GIT_CREDENTIAL_SOCKET_VARIABLE};
|
|
40
|
+
if (!socketPath || !request.host) process.exit(0);
|
|
41
|
+
|
|
42
|
+
const socket = net.connect(socketPath);
|
|
43
|
+
let buffer = "";
|
|
44
|
+
const giveUp = () => {
|
|
45
|
+
socket.destroy();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
};
|
|
48
|
+
socket.on("connect", () => {
|
|
49
|
+
socket.write(JSON.stringify({ op: "get", protocol: request.protocol || "https", host: request.host }) + "\\n");
|
|
50
|
+
});
|
|
51
|
+
socket.on("data", (chunk) => {
|
|
52
|
+
buffer += chunk.toString();
|
|
53
|
+
const newline = buffer.indexOf("\\n");
|
|
54
|
+
if (newline < 0) return;
|
|
55
|
+
let response;
|
|
56
|
+
try {
|
|
57
|
+
response = JSON.parse(buffer.slice(0, newline));
|
|
58
|
+
} catch {
|
|
59
|
+
giveUp();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (response && response.ok) {
|
|
63
|
+
process.stdout.write("username=" + response.username + "\\n");
|
|
64
|
+
process.stdout.write("password=" + response.password + "\\n");
|
|
65
|
+
}
|
|
66
|
+
socket.destroy();
|
|
67
|
+
process.exit(0);
|
|
68
|
+
});
|
|
69
|
+
socket.on("error", giveUp);
|
|
70
|
+
});
|
|
71
|
+
`.trim();
|
|
72
|
+
/** AF_UNIX `sun_path` is 108 bytes on Linux, including the terminating NUL. */
|
|
73
|
+
const SUN_PATH_LIMIT = 108;
|
|
74
|
+
/**
|
|
75
|
+
* Where the git credential socket lives on each side, mirroring the credential channel's
|
|
76
|
+
* own resolution. TMPDIR is deliberately not honoured: it can point inside the workspace
|
|
77
|
+
* on macOS, which the sandbox may write. The child-side path sits under `/home` on Linux
|
|
78
|
+
* because the `--tmpfs /home` is the only writable mount when bwrap creates the mountpoint.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveGitCredentialChannelPaths() {
|
|
81
|
+
const hostSocketDirectory = mkdtempSync(join(supervisorTempDirectory(), `apex-gitcred-${process.pid}-`), {
|
|
82
|
+
encoding: "utf8",
|
|
83
|
+
});
|
|
84
|
+
chmodSync(hostSocketDirectory, 0o700);
|
|
85
|
+
const hostSocketPath = join(hostSocketDirectory, "channel.sock");
|
|
86
|
+
if (Buffer.byteLength(hostSocketPath) + 1 > SUN_PATH_LIMIT) {
|
|
87
|
+
throw new Error("Git credential channel path exceeds the Unix socket path limit.");
|
|
88
|
+
}
|
|
89
|
+
const childSocketPath = process.platform === "linux" ? "/home/apex-git-credential-channel.sock" : hostSocketPath;
|
|
90
|
+
return { hostSocketDirectory, hostSocketPath, childSocketPath };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Where the helper lives, derived rather than passed.
|
|
94
|
+
*
|
|
95
|
+
* The supervisor writes `credential.helper` into the projected git config before the
|
|
96
|
+
* platform backend exists, and the backend writes the file itself. Deriving both from the
|
|
97
|
+
* state directory is what keeps the config from naming a path nothing ever created.
|
|
98
|
+
*/
|
|
99
|
+
export function gitCredentialHelperPath(stateDirectory) {
|
|
100
|
+
return join(stateDirectory, "git-credential-apex.cjs");
|
|
101
|
+
}
|
|
102
|
+
/** The `credential.helper` value naming that helper, run under the supervisor's own node. */
|
|
103
|
+
export function gitCredentialHelperCommand(stateDirectory, nodePath = process.execPath) {
|
|
104
|
+
return `!"${nodePath}" "${gitCredentialHelperPath(stateDirectory)}"`;
|
|
105
|
+
}
|
|
106
|
+
/** Write the helper into a directory the child can read, returning its path. */
|
|
107
|
+
export function writeGitCredentialHelper(directory) {
|
|
108
|
+
const path = gitCredentialHelperPath(directory);
|
|
109
|
+
writeFileSync(path, HELPER_SOURCE);
|
|
110
|
+
return path;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve a credential on the host, through the host's own git configuration.
|
|
114
|
+
*
|
|
115
|
+
* `git credential fill` rather than reading any particular store, because the answer may
|
|
116
|
+
* come from `gh`'s helper, libsecret, the macOS keychain, or a plain file, and only git
|
|
117
|
+
* knows which of those this host is configured to ask. Reimplementing that resolution
|
|
118
|
+
* would be a second, quietly diverging copy of it.
|
|
119
|
+
*/
|
|
120
|
+
export async function fillHostGitCredential(request, options) {
|
|
121
|
+
const { spawn } = await import("node:child_process");
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
const child = spawn("git", ["credential", "fill"], {
|
|
124
|
+
cwd: options?.cwd,
|
|
125
|
+
env: options?.environment ?? process.env,
|
|
126
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
127
|
+
});
|
|
128
|
+
let stdout = "";
|
|
129
|
+
child.stdout.setEncoding("utf8");
|
|
130
|
+
child.stdout.on("data", (chunk) => {
|
|
131
|
+
stdout += chunk;
|
|
132
|
+
});
|
|
133
|
+
child.on("error", () => resolve(undefined));
|
|
134
|
+
child.on("close", (code) => {
|
|
135
|
+
if (code !== 0) {
|
|
136
|
+
resolve(undefined);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const fields = new Map();
|
|
140
|
+
for (const line of stdout.split("\n")) {
|
|
141
|
+
const separator = line.indexOf("=");
|
|
142
|
+
if (separator > 0)
|
|
143
|
+
fields.set(line.slice(0, separator), line.slice(separator + 1).trim());
|
|
144
|
+
}
|
|
145
|
+
const username = fields.get("username");
|
|
146
|
+
const password = fields.get("password");
|
|
147
|
+
resolve(username && password ? { username, password } : undefined);
|
|
148
|
+
});
|
|
149
|
+
child.stdin.end(`protocol=${request.protocol}\nhost=${request.host}\n\n`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=git-credential-helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-credential-helper.js","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/git-credential-helper.ts"],"names":[],"mappings":"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","sourcesContent":["import { chmodSync, mkdtempSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { supervisorTempDirectory } from \"../supervisor-temp.ts\";\nimport type { GitCredential, GitCredentialRequest } from \"./git-credential-proxy.ts\";\n\n/** Env var naming the child-side socket the helper talks to. */\nexport const GIT_CREDENTIAL_SOCKET_VARIABLE = \"APEX_GIT_CREDENTIAL_PATH\";\n\n/**\n * The credential helper git runs inside the sandbox.\n *\n * `.cjs` for the same reason the network relay is: a plain `.js` under a workspace whose\n * package.json declares `\"type\": \"module\"` is parsed as ESM and dies on `require`.\n *\n * It answers `get` and nothing else. `store` and `erase` exit silently and successfully,\n * which is what tells git the credential was handled and stops it falling through to\n * another helper. Writing is deliberately not served: ADR 0015 keeps credential mutation\n * an explicit host operation, and a channel that let the child rewrite the host's store\n * would be a way out of the boundary rather than a way to work inside it.\n *\n * Any failure exits 0 having printed nothing, which git reads as \"this helper has no\n * credential\" and handles normally. Exiting non-zero would surface as a git error that\n * looks like a repository problem rather than a refused release.\n */\nconst HELPER_SOURCE = `\nconst net = require(\"node:net\");\n\nconst operation = process.argv[2];\nif (operation !== \"get\") process.exit(0);\n\nlet input = \"\";\nprocess.stdin.setEncoding(\"utf8\");\nprocess.stdin.on(\"data\", (chunk) => {\n\tinput += chunk;\n});\nprocess.stdin.on(\"end\", () => {\n\tconst request = {};\n\tfor (const line of input.split(\"\\\\n\")) {\n\t\tconst separator = line.indexOf(\"=\");\n\t\tif (separator > 0) request[line.slice(0, separator)] = line.slice(separator + 1).trim();\n\t}\n\tconst socketPath = process.env.${GIT_CREDENTIAL_SOCKET_VARIABLE};\n\tif (!socketPath || !request.host) process.exit(0);\n\n\tconst socket = net.connect(socketPath);\n\tlet buffer = \"\";\n\tconst giveUp = () => {\n\t\tsocket.destroy();\n\t\tprocess.exit(0);\n\t};\n\tsocket.on(\"connect\", () => {\n\t\tsocket.write(JSON.stringify({ op: \"get\", protocol: request.protocol || \"https\", host: request.host }) + \"\\\\n\");\n\t});\n\tsocket.on(\"data\", (chunk) => {\n\t\tbuffer += chunk.toString();\n\t\tconst newline = buffer.indexOf(\"\\\\n\");\n\t\tif (newline < 0) return;\n\t\tlet response;\n\t\ttry {\n\t\t\tresponse = JSON.parse(buffer.slice(0, newline));\n\t\t} catch {\n\t\t\tgiveUp();\n\t\t\treturn;\n\t\t}\n\t\tif (response && response.ok) {\n\t\t\tprocess.stdout.write(\"username=\" + response.username + \"\\\\n\");\n\t\t\tprocess.stdout.write(\"password=\" + response.password + \"\\\\n\");\n\t\t}\n\t\tsocket.destroy();\n\t\tprocess.exit(0);\n\t});\n\tsocket.on(\"error\", giveUp);\n});\n`.trim();\n\n/** AF_UNIX `sun_path` is 108 bytes on Linux, including the terminating NUL. */\nconst SUN_PATH_LIMIT = 108;\n\nexport interface GitCredentialChannelPaths {\n\treadonly hostSocketDirectory: string;\n\treadonly hostSocketPath: string;\n\treadonly childSocketPath: string;\n}\n\n/**\n * Where the git credential socket lives on each side, mirroring the credential channel's\n * own resolution. TMPDIR is deliberately not honoured: it can point inside the workspace\n * on macOS, which the sandbox may write. The child-side path sits under `/home` on Linux\n * because the `--tmpfs /home` is the only writable mount when bwrap creates the mountpoint.\n */\nexport function resolveGitCredentialChannelPaths(): GitCredentialChannelPaths {\n\tconst hostSocketDirectory = mkdtempSync(join(supervisorTempDirectory(), `apex-gitcred-${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(\"Git credential channel path exceeds the Unix socket path limit.\");\n\t}\n\tconst childSocketPath = process.platform === \"linux\" ? \"/home/apex-git-credential-channel.sock\" : hostSocketPath;\n\treturn { hostSocketDirectory, hostSocketPath, childSocketPath };\n}\n\n/**\n * Where the helper lives, derived rather than passed.\n *\n * The supervisor writes `credential.helper` into the projected git config before the\n * platform backend exists, and the backend writes the file itself. Deriving both from the\n * state directory is what keeps the config from naming a path nothing ever created.\n */\nexport function gitCredentialHelperPath(stateDirectory: string): string {\n\treturn join(stateDirectory, \"git-credential-apex.cjs\");\n}\n\n/** The `credential.helper` value naming that helper, run under the supervisor's own node. */\nexport function gitCredentialHelperCommand(stateDirectory: string, nodePath = process.execPath): string {\n\treturn `!\"${nodePath}\" \"${gitCredentialHelperPath(stateDirectory)}\"`;\n}\n\n/** Write the helper into a directory the child can read, returning its path. */\nexport function writeGitCredentialHelper(directory: string): string {\n\tconst path = gitCredentialHelperPath(directory);\n\twriteFileSync(path, HELPER_SOURCE);\n\treturn path;\n}\n\n/**\n * Resolve a credential on the host, through the host's own git configuration.\n *\n * `git credential fill` rather than reading any particular store, because the answer may\n * come from `gh`'s helper, libsecret, the macOS keychain, or a plain file, and only git\n * knows which of those this host is configured to ask. Reimplementing that resolution\n * would be a second, quietly diverging copy of it.\n */\nexport async function fillHostGitCredential(\n\trequest: GitCredentialRequest,\n\toptions?: { environment?: NodeJS.ProcessEnv; cwd?: string },\n): Promise<GitCredential | undefined> {\n\tconst { spawn } = await import(\"node:child_process\");\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(\"git\", [\"credential\", \"fill\"], {\n\t\t\tcwd: options?.cwd,\n\t\t\tenv: options?.environment ?? process.env,\n\t\t\tstdio: [\"pipe\", \"pipe\", \"ignore\"],\n\t\t});\n\t\tlet stdout = \"\";\n\t\tchild.stdout.setEncoding(\"utf8\");\n\t\tchild.stdout.on(\"data\", (chunk: string) => {\n\t\t\tstdout += chunk;\n\t\t});\n\t\tchild.on(\"error\", () => resolve(undefined));\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code !== 0) {\n\t\t\t\tresolve(undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst fields = new Map<string, string>();\n\t\t\tfor (const line of stdout.split(\"\\n\")) {\n\t\t\t\tconst separator = line.indexOf(\"=\");\n\t\t\t\tif (separator > 0) fields.set(line.slice(0, separator), line.slice(separator + 1).trim());\n\t\t\t}\n\t\t\tconst username = fields.get(\"username\");\n\t\t\tconst password = fields.get(\"password\");\n\t\t\tresolve(username && password ? { username, password } : undefined);\n\t\t});\n\t\tchild.stdin.end(`protocol=${request.protocol}\\nhost=${request.host}\\n\\n`);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SandboxViolationStore } from "../violations.ts";
|
|
2
|
+
export interface GitCredentialRequest {
|
|
3
|
+
readonly protocol: string;
|
|
4
|
+
readonly host: string;
|
|
5
|
+
}
|
|
6
|
+
export interface GitCredential {
|
|
7
|
+
readonly username: string;
|
|
8
|
+
readonly password: string;
|
|
9
|
+
}
|
|
10
|
+
export interface GitCredentialProxy {
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Serve git credentials to the sandboxed child without ever putting one inside it.
|
|
15
|
+
*
|
|
16
|
+
* ADR 0015 keeps credentials host-owned and hands the child a read-only projection. A git
|
|
17
|
+
* credential cannot be projected the same way: it is not one file with a stable shape, it
|
|
18
|
+
* is whatever the host's own helper produces per host. So the credential stays on the host
|
|
19
|
+
* and only the answer to one question crosses, for one host, once it has been released.
|
|
20
|
+
*
|
|
21
|
+
* Two gates, and the first is the one that matters most.
|
|
22
|
+
*
|
|
23
|
+
* The host must be reachable by this session. git only asks for a credential after a
|
|
24
|
+
* server challenged it, so in the ordinary flow the host was already allowed or approved
|
|
25
|
+
* at the network layer. A request for a host the session cannot reach is therefore not git
|
|
26
|
+
* doing its job; it is something that went looking for a token. Refusing it costs nothing
|
|
27
|
+
* real and removes the case where this channel is more useful to an attacker than to git.
|
|
28
|
+
*
|
|
29
|
+
* The human must release it. Per ADR 0023 that decision is the supervisor's, because this
|
|
30
|
+
* socket has no peer authentication -- every descendant in the child's namespace can reach
|
|
31
|
+
* it, which is precisely the code the boundary exists to contain. Without a releaser the
|
|
32
|
+
* channel refuses outright, so a headless session cannot hand out a credential at all.
|
|
33
|
+
*
|
|
34
|
+
* A release covers one host for the session. It is never persisted and never widened to a
|
|
35
|
+
* second host, which is what keeps an approval for a push from also releasing a token to
|
|
36
|
+
* whatever else the session later contacts.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createGitCredentialProxy(options: {
|
|
39
|
+
socketPath: string;
|
|
40
|
+
/** Whether this session may reach the host at all. */
|
|
41
|
+
isHostAllowed: (host: string) => boolean;
|
|
42
|
+
/** Ask a human to release the host's credential. Absent means refuse without asking. */
|
|
43
|
+
requestRelease?: (host: string) => Promise<boolean>;
|
|
44
|
+
/** Resolve the credential on the host, using the host's own configuration. */
|
|
45
|
+
fillCredential: (request: GitCredentialRequest) => Promise<GitCredential | undefined>;
|
|
46
|
+
violationStore?: SandboxViolationStore;
|
|
47
|
+
}): Promise<GitCredentialProxy>;
|
|
48
|
+
//# sourceMappingURL=git-credential-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-credential-proxy.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/rpc/git-credential-proxy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAK9D,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,wFAAwF;IACxF,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,8EAA8E;IAC9E,cAAc,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;IACtF,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACvC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA4G9B","sourcesContent":["import { chmodSync, existsSync, unlinkSync } from \"node:fs\";\nimport * as net from \"node:net\";\nimport type { SandboxViolationStore } from \"../violations.ts\";\nimport { FrameReader, isRequestObject, writeFrame } from \"./framing.ts\";\n\nconst MAX_CONNECTIONS = 32;\n\nexport interface GitCredentialRequest {\n\treadonly protocol: string;\n\treadonly host: string;\n}\n\nexport interface GitCredential {\n\treadonly username: string;\n\treadonly password: string;\n}\n\nexport interface GitCredentialProxy {\n\tclose(): Promise<void>;\n}\n\n/**\n * Serve git credentials to the sandboxed child without ever putting one inside it.\n *\n * ADR 0015 keeps credentials host-owned and hands the child a read-only projection. A git\n * credential cannot be projected the same way: it is not one file with a stable shape, it\n * is whatever the host's own helper produces per host. So the credential stays on the host\n * and only the answer to one question crosses, for one host, once it has been released.\n *\n * Two gates, and the first is the one that matters most.\n *\n * The host must be reachable by this session. git only asks for a credential after a\n * server challenged it, so in the ordinary flow the host was already allowed or approved\n * at the network layer. A request for a host the session cannot reach is therefore not git\n * doing its job; it is something that went looking for a token. Refusing it costs nothing\n * real and removes the case where this channel is more useful to an attacker than to git.\n *\n * The human must release it. Per ADR 0023 that decision is the supervisor's, because this\n * socket has no peer authentication -- every descendant in the child's namespace can reach\n * it, which is precisely the code the boundary exists to contain. Without a releaser the\n * channel refuses outright, so a headless session cannot hand out a credential at all.\n *\n * A release covers one host for the session. It is never persisted and never widened to a\n * second host, which is what keeps an approval for a push from also releasing a token to\n * whatever else the session later contacts.\n */\nexport function createGitCredentialProxy(options: {\n\tsocketPath: string;\n\t/** Whether this session may reach the host at all. */\n\tisHostAllowed: (host: string) => boolean;\n\t/** Ask a human to release the host's credential. Absent means refuse without asking. */\n\trequestRelease?: (host: string) => Promise<boolean>;\n\t/** Resolve the credential on the host, using the host's own configuration. */\n\tfillCredential: (request: GitCredentialRequest) => Promise<GitCredential | undefined>;\n\tviolationStore?: SandboxViolationStore;\n}): Promise<GitCredentialProxy> {\n\t/** Hosts released this session. Never persisted, never widened. */\n\tconst released = new Set<string>();\n\tconst pending = new Map<string, Promise<boolean>>();\n\n\tfunction audit(host: string, detail: string): void {\n\t\toptions.violationStore?.add({\n\t\t\tkind: \"network\",\n\t\t\tcommand: `git credential ${host}`,\n\t\t\tdetail,\n\t\t\ttimestamp: new Date(),\n\t\t});\n\t}\n\n\tasync function release(host: string): Promise<boolean> {\n\t\tif (released.has(host)) return true;\n\t\tif (!options.requestRelease) return false;\n\t\tconst inFlight = pending.get(host);\n\t\tif (inFlight) return inFlight;\n\t\tconst question = (async () => {\n\t\t\ttry {\n\t\t\t\tconst approved = await (options.requestRelease as (h: string) => Promise<boolean>)(host);\n\t\t\t\tif (approved) released.add(host);\n\t\t\t\treturn approved;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t} finally {\n\t\t\t\tpending.delete(host);\n\t\t\t}\n\t\t})();\n\t\tpending.set(host, question);\n\t\treturn question;\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 !== \"get\") {\n\t\t\t// Only `get` crosses. `store` and `erase` would let the child rewrite the host's\n\t\t\t// credential store, which ADR 0015 keeps as an explicit host operation.\n\t\t\treturn { ok: false, error: \"Only credential reads are served over this channel.\" };\n\t\t}\n\t\tconst host = typeof request.host === \"string\" ? request.host : \"\";\n\t\tconst protocol = typeof request.protocol === \"string\" ? request.protocol : \"https\";\n\t\tif (!host) return { ok: false, error: \"Request named no host.\" };\n\n\t\tif (!options.isHostAllowed(host)) {\n\t\t\taudit(host, `Refused a credential for ${host}, which this session may not reach.`);\n\t\t\treturn { ok: false, error: \"Host is not reachable by this session.\" };\n\t\t}\n\t\tif (!(await release(host))) {\n\t\t\taudit(host, `Credential for ${host} was not released.`);\n\t\t\treturn { ok: false, error: \"Credential was not released.\" };\n\t\t}\n\t\tconst credential = await options.fillCredential({ host, protocol });\n\t\tif (!credential) {\n\t\t\taudit(host, `The host credential store had nothing for ${host}.`);\n\t\t\treturn { ok: false, error: \"No credential is configured for that host.\" };\n\t\t}\n\t\treturn { ok: true, username: credential.username, password: credential.password };\n\t}\n\n\treturn new Promise((resolve, reject) => {\n\t\t// A prior launch that never closed leaves a stale socket file that listen() refuses\n\t\t// to bind over, exactly as the network proxy handles.\n\t\tif (existsSync(options.socketPath)) unlinkSync(options.socketPath);\n\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, \"Git credential channel\", (detail) =>\n\t\t\t\taudit(\"(protocol)\", `Refused: ${detail}`),\n\t\t\t);\n\t\t\tvoid (async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await answer(await reader.next());\n\t\t\t\t\twriteFrame(socket, response);\n\t\t\t\t} catch {\n\t\t\t\t\t// A disconnected or malformed client is the reader's business; there is\n\t\t\t\t\t// nothing to answer and nothing to record beyond what it already did.\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\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; the mode here 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"]}
|