apex-code 0.0.6 → 0.1.0
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 +26 -0
- package/README.md +7 -8
- package/dist/cli/args.d.ts +0 -12
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -32
- package/dist/cli/args.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -130
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +0 -8
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/delegation/agents.d.ts.map +1 -1
- package/dist/core/delegation/agents.js +2 -1
- package/dist/core/delegation/agents.js.map +1 -1
- package/dist/core/delegation/runtime.d.ts +6 -14
- package/dist/core/delegation/runtime.d.ts.map +1 -1
- package/dist/core/delegation/runtime.js +1 -2
- package/dist/core/delegation/runtime.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +3 -4
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/source-runtime.d.ts +4 -5
- package/dist/core/extensions/source-runtime.d.ts.map +1 -1
- package/dist/core/extensions/source-runtime.js +4 -5
- package/dist/core/extensions/source-runtime.js.map +1 -1
- package/dist/core/formatter-lifecycle.d.ts +2 -1
- package/dist/core/formatter-lifecycle.d.ts.map +1 -1
- package/dist/core/formatter-lifecycle.js +2 -1
- package/dist/core/formatter-lifecycle.js.map +1 -1
- package/dist/core/hooks/types.d.ts +2 -3
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js +2 -3
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/http-idle-timeout.d.ts +3 -5
- package/dist/core/http-idle-timeout.d.ts.map +1 -1
- package/dist/core/http-idle-timeout.js +3 -5
- package/dist/core/http-idle-timeout.js.map +1 -1
- package/dist/core/mcp/config.d.ts +18 -1
- package/dist/core/mcp/config.d.ts.map +1 -1
- package/dist/core/mcp/config.js +24 -7
- package/dist/core/mcp/config.js.map +1 -1
- package/dist/core/mcp/connector.d.ts +2 -3
- package/dist/core/mcp/connector.d.ts.map +1 -1
- package/dist/core/mcp/connector.js.map +1 -1
- package/dist/core/mcp/oauth/authorize.d.ts +2 -0
- package/dist/core/mcp/oauth/authorize.d.ts.map +1 -1
- package/dist/core/mcp/oauth/authorize.js +5 -2
- package/dist/core/mcp/oauth/authorize.js.map +1 -1
- package/dist/core/mcp/oauth/mcp-token.d.ts +4 -6
- package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -1
- package/dist/core/mcp/oauth/mcp-token.js +5 -7
- package/dist/core/mcp/oauth/mcp-token.js.map +1 -1
- package/dist/core/mcp/runtime.d.ts +1 -0
- package/dist/core/mcp/runtime.d.ts.map +1 -1
- package/dist/core/mcp/runtime.js +2 -1
- package/dist/core/mcp/runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +0 -15
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/permissions/store.d.ts +6 -11
- package/dist/core/permissions/store.d.ts.map +1 -1
- package/dist/core/permissions/store.js +23 -51
- package/dist/core/permissions/store.js.map +1 -1
- package/dist/core/project-resources.d.ts +62 -0
- package/dist/core/project-resources.d.ts.map +1 -0
- package/dist/core/project-resources.js +42 -0
- package/dist/core/project-resources.js.map +1 -0
- package/dist/core/sdk.d.ts +1 -9
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +10 -35
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-share.d.ts +6 -8
- package/dist/core/session-share.d.ts.map +1 -1
- package/dist/core/session-share.js +9 -12
- package/dist/core/session-share.js.map +1 -1
- package/dist/core/settings-manager.d.ts +0 -21
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -12
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +0 -47
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/web-fetch.d.ts.map +1 -1
- package/dist/core/tools/web-fetch.js +4 -8
- package/dist/core/tools/web-fetch.js.map +1 -1
- package/dist/core/tools/web-search-exa.d.ts +3 -6
- package/dist/core/tools/web-search-exa.d.ts.map +1 -1
- package/dist/core/tools/web-search-exa.js +5 -8
- package/dist/core/tools/web-search-exa.js.map +1 -1
- package/dist/core/trust-manager.d.ts +6 -5
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +42 -19
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/core/web-search-provider.d.ts +0 -10
- package/dist/core/web-search-provider.d.ts.map +1 -1
- package/dist/core/web-search-provider.js +2 -16
- package/dist/core/web-search-provider.js.map +1 -1
- package/dist/main.d.ts +0 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +0 -29
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +1 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +0 -25
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +3 -52
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +0 -2
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +0 -2
- package/dist/utils/version-check.js.map +1 -1
- package/docs/settings.md +2 -7
- package/npm-shrinkwrap.json +5 -5
- package/package.json +2 -2
- package/dist/core/sandbox/bwrap-arguments.d.ts +0 -41
- package/dist/core/sandbox/bwrap-arguments.d.ts.map +0 -1
- package/dist/core/sandbox/bwrap-arguments.js +0 -136
- package/dist/core/sandbox/bwrap-arguments.js.map +0 -1
- package/dist/core/sandbox/child-entry.d.ts +0 -2
- package/dist/core/sandbox/child-entry.d.ts.map +0 -1
- package/dist/core/sandbox/child-entry.js +0 -24
- package/dist/core/sandbox/child-entry.js.map +0 -1
- package/dist/core/sandbox/cli-launch.d.ts +0 -162
- package/dist/core/sandbox/cli-launch.d.ts.map +0 -1
- package/dist/core/sandbox/cli-launch.js +0 -366
- package/dist/core/sandbox/cli-launch.js.map +0 -1
- package/dist/core/sandbox/cli-supervisor.d.ts +0 -31
- package/dist/core/sandbox/cli-supervisor.d.ts.map +0 -1
- package/dist/core/sandbox/cli-supervisor.js +0 -147
- package/dist/core/sandbox/cli-supervisor.js.map +0 -1
- package/dist/core/sandbox/default-hosts.d.ts +0 -28
- package/dist/core/sandbox/default-hosts.d.ts.map +0 -1
- package/dist/core/sandbox/default-hosts.js +0 -69
- package/dist/core/sandbox/default-hosts.js.map +0 -1
- package/dist/core/sandbox/full-access.d.ts +0 -28
- package/dist/core/sandbox/full-access.d.ts.map +0 -1
- package/dist/core/sandbox/full-access.js +0 -63
- package/dist/core/sandbox/full-access.js.map +0 -1
- package/dist/core/sandbox/git-identity.d.ts +0 -57
- package/dist/core/sandbox/git-identity.d.ts.map +0 -1
- package/dist/core/sandbox/git-identity.js +0 -78
- package/dist/core/sandbox/git-identity.js.map +0 -1
- package/dist/core/sandbox/host-approval.d.ts +0 -42
- package/dist/core/sandbox/host-approval.d.ts.map +0 -1
- package/dist/core/sandbox/host-approval.js +0 -99
- package/dist/core/sandbox/host-approval.js.map +0 -1
- package/dist/core/sandbox/linux-backend.d.ts +0 -48
- package/dist/core/sandbox/linux-backend.d.ts.map +0 -1
- package/dist/core/sandbox/linux-backend.js +0 -328
- package/dist/core/sandbox/linux-backend.js.map +0 -1
- package/dist/core/sandbox/macos-backend.d.ts +0 -30
- package/dist/core/sandbox/macos-backend.d.ts.map +0 -1
- package/dist/core/sandbox/macos-backend.js +0 -385
- package/dist/core/sandbox/macos-backend.js.map +0 -1
- package/dist/core/sandbox/network-proxy.d.ts +0 -38
- package/dist/core/sandbox/network-proxy.d.ts.map +0 -1
- package/dist/core/sandbox/network-proxy.js +0 -153
- package/dist/core/sandbox/network-proxy.js.map +0 -1
- package/dist/core/sandbox/network-refusal.d.ts +0 -24
- package/dist/core/sandbox/network-refusal.d.ts.map +0 -1
- package/dist/core/sandbox/network-refusal.js +0 -72
- package/dist/core/sandbox/network-refusal.js.map +0 -1
- package/dist/core/sandbox/policy.d.ts +0 -40
- package/dist/core/sandbox/policy.d.ts.map +0 -1
- package/dist/core/sandbox/policy.js +0 -55
- package/dist/core/sandbox/policy.js.map +0 -1
- package/dist/core/sandbox/profiles.d.ts +0 -25
- package/dist/core/sandbox/profiles.d.ts.map +0 -1
- package/dist/core/sandbox/profiles.js +0 -23
- package/dist/core/sandbox/profiles.js.map +0 -1
- package/dist/core/sandbox/rpc/command-client.d.ts +0 -28
- package/dist/core/sandbox/rpc/command-client.d.ts.map +0 -1
- package/dist/core/sandbox/rpc/command-client.js +0 -86
- package/dist/core/sandbox/rpc/command-client.js.map +0 -1
- package/dist/core/sandbox/rpc/command-proxy.d.ts +0 -48
- package/dist/core/sandbox/rpc/command-proxy.d.ts.map +0 -1
- package/dist/core/sandbox/rpc/command-proxy.js +0 -120
- package/dist/core/sandbox/rpc/command-proxy.js.map +0 -1
- package/dist/core/sandbox/rpc/credential-client.d.ts +0 -20
- package/dist/core/sandbox/rpc/credential-client.d.ts.map +0 -1
- package/dist/core/sandbox/rpc/credential-client.js +0 -170
- package/dist/core/sandbox/rpc/credential-client.js.map +0 -1
- package/dist/core/sandbox/rpc/credential-proxy.d.ts +0 -44
- package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +0 -1
- package/dist/core/sandbox/rpc/credential-proxy.js +0 -287
- package/dist/core/sandbox/rpc/credential-proxy.js.map +0 -1
- package/dist/core/sandbox/rpc/framing.d.ts +0 -35
- package/dist/core/sandbox/rpc/framing.d.ts.map +0 -1
- package/dist/core/sandbox/rpc/framing.js +0 -114
- package/dist/core/sandbox/rpc/framing.js.map +0 -1
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts +0 -47
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +0 -1
- package/dist/core/sandbox/rpc/git-credential-helper.js +0 -222
- package/dist/core/sandbox/rpc/git-credential-helper.js.map +0 -1
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +0 -66
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +0 -1
- package/dist/core/sandbox/rpc/git-credential-proxy.js +0 -209
- package/dist/core/sandbox/rpc/git-credential-proxy.js.map +0 -1
- package/dist/core/sandbox/supervisor-temp.d.ts +0 -18
- package/dist/core/sandbox/supervisor-temp.d.ts.map +0 -1
- package/dist/core/sandbox/supervisor-temp.js +0 -21
- package/dist/core/sandbox/supervisor-temp.js.map +0 -1
- package/dist/core/sandbox/supervisor.d.ts +0 -62
- package/dist/core/sandbox/supervisor.d.ts.map +0 -1
- package/dist/core/sandbox/supervisor.js +0 -32
- package/dist/core/sandbox/supervisor.js.map +0 -1
- package/dist/core/sandbox/terminal-handoff.d.ts +0 -68
- package/dist/core/sandbox/terminal-handoff.d.ts.map +0 -1
- package/dist/core/sandbox/terminal-handoff.js +0 -221
- package/dist/core/sandbox/terminal-handoff.js.map +0 -1
- package/dist/core/sandbox/terminal-size.d.ts +0 -53
- package/dist/core/sandbox/terminal-size.d.ts.map +0 -1
- package/dist/core/sandbox/terminal-size.js +0 -126
- package/dist/core/sandbox/terminal-size.js.map +0 -1
- package/dist/core/sandbox/violations.d.ts +0 -23
- package/dist/core/sandbox/violations.d.ts.map +0 -1
- package/dist/core/sandbox/violations.js +0 -30
- package/dist/core/sandbox/violations.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"child-entry.js","sourceRoot":"","sources":["../../../src/core/sandbox/child-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEpF,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC;AACzB,kBAAkB,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;AACrD,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,WAAW,CAAC;AACnC,OAAO,CAAC,WAAW,GAAG,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAA+B,CAAC;AAC/D,uBAAuB,EAAE,CAAC;AAC1B,oFAAoF;AACpF,2FAA2F;AAC3F,oCAAoC,EAAE,CAAC;AACvC,6EAA6E;AAC7E,8EAA8E;AAC9E,2DAA2D;AAC3D,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;AAClE,IAAI,gBAAgB;IAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;AAE1D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,iBAAiB,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC","sourcesContent":["import { parseCliCommand } from \"../../cli/args.ts\";\nimport { APP_NAME } from \"../../config.ts\";\nimport { main } from \"../../main.ts\";\nimport { setApexEnvironment } from \"../environment.ts\";\nimport { configureHttpDispatcher } from \"../http-dispatcher.ts\";\nimport { installSandboxNetworkRefusalMessages } from \"./network-refusal.ts\";\nimport { applyTerminalSize, TERMINAL_SIZE_PATH_VARIABLE } from \"./terminal-size.ts\";\n\nprocess.title = APP_NAME;\nsetApexEnvironment(\"APEX_CODE_CODING_AGENT\", \"true\");\nprocess.env.AI_AGENT = \"apex-code\";\nprocess.emitWarning = (() => {}) as typeof process.emitWarning;\nconfigureHttpDispatcher();\n// Only the sandboxed child installs this, which is what makes attributing a refused\n// request to the sandbox allowlist accurate rather than a guess about whose proxy replied.\ninstallSandboxNetworkRefusalMessages();\n// bwrap --new-session leaves this process without a controlling terminal, so\n// its stdout reports a window size frozen at sandbox creation. The supervisor\n// publishes the real one; adopt it before any UI is built.\nconst terminalSizePath = process.env[TERMINAL_SIZE_PATH_VARIABLE];\nif (terminalSizePath) applyTerminalSize(terminalSizePath);\n\nconst args = process.argv.slice(2);\nawait main(args, { sessionLeaseOwner: \"supervisor\", parsedCommand: parseCliCommand(args) });\n"]}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import type { ParsedCliCommand } from "../../cli/args.ts";
|
|
2
|
-
import type { HostToolBinary } from "../../utils/tools-manager.ts";
|
|
3
|
-
import type { SandboxLaunch } from "./supervisor.ts";
|
|
4
|
-
/**
|
|
5
|
-
* OS containment is the normal startup path for every parsed command that can
|
|
6
|
-
* construct an agent session. Classification consumes the same typed parse result
|
|
7
|
-
* as execution, so option values and text after `--` cannot impersonate metadata.
|
|
8
|
-
*/
|
|
9
|
-
export declare function requiresSandboxedChild(command: ParsedCliCommand): boolean;
|
|
10
|
-
export interface SandboxedCliLaunch extends SandboxLaunch {
|
|
11
|
-
readonly environment: NodeJS.ProcessEnv;
|
|
12
|
-
/**
|
|
13
|
-
* The supervisor's own 0700 state directory, outside every writable root the child
|
|
14
|
-
* has.
|
|
15
|
-
*/
|
|
16
|
-
readonly supervisorStateDirectory: string;
|
|
17
|
-
}
|
|
18
|
-
export declare function getSandboxSessionDirectory(workspace: string): string;
|
|
19
|
-
/**
|
|
20
|
-
* Env var naming the file that carries the host's `policy` and `user` permission scopes
|
|
21
|
-
* into the child (PS.3).
|
|
22
|
-
*
|
|
23
|
-
* Deliberately absent from `SAFE_CHILD_ENVIRONMENT_KEYS`: only the supervisor's own
|
|
24
|
-
* resolution may name it, never the invoking shell and never a project file (ADR 0016).
|
|
25
|
-
*/
|
|
26
|
-
export declare const POLICY_SNAPSHOT_PATH_VARIABLE = "APEX_CODE_POLICY_SNAPSHOT_PATH";
|
|
27
|
-
/**
|
|
28
|
-
* The marker asserting that an OS boundary is actually enforcing this process (PS.5).
|
|
29
|
-
*
|
|
30
|
-
* Set only on the supervisor's own launch environment for a child it is about to contain.
|
|
31
|
-
* It is not in the child-environment allowlist, so a value of this name in the invoking
|
|
32
|
-
* shell never survives into a child; the supervisor's own value is the only one that can.
|
|
33
|
-
* `core/sdk.ts` reads it for `sandbox: "required"` and keeps the same two literals -- a
|
|
34
|
-
* marker spelled two ways would be a drift the SDK could not detect at runtime, so
|
|
35
|
-
* `test/sdk-sandbox-contract.test.ts` pins them to each other.
|
|
36
|
-
*/
|
|
37
|
-
export declare const SANDBOX_ENFORCEMENT_MARKER_VARIABLE = "APEX_CODE_SANDBOX_ENFORCED";
|
|
38
|
-
export declare const SANDBOX_ENFORCEMENT_MARKER_VALUE = "1";
|
|
39
|
-
export interface SupervisorStateDirectory {
|
|
40
|
-
/** A 0700 directory under the supervisor temp root, outside every child-writable root. */
|
|
41
|
-
readonly path: string;
|
|
42
|
-
readonly dispose: () => void;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Allocate the directory the supervisor keeps its own launch state in.
|
|
46
|
-
*
|
|
47
|
-
* Everything the supervisor writes for a launch -- the terminal handoff latch, the
|
|
48
|
-
* published terminal size, the network relay, the git credential helper, the permission
|
|
49
|
-
* snapshot -- used to live in `workspace/.apex-code/sandbox-state`, which is inside the
|
|
50
|
-
* one writable bind the child has. A child that planted a symlink there redirected the
|
|
51
|
-
* *next* launch's supervisor writes onto arbitrary host paths, and could rewrite the
|
|
52
|
-
* scripts the supervisor was about to run. Under the supervisor temp root the child sees
|
|
53
|
-
* these files through the read-only root bind (Linux) or Seatbelt's write denial outside
|
|
54
|
-
* the workspace (macOS), so it can neither replace them nor plant a link where one goes.
|
|
55
|
-
*/
|
|
56
|
-
export declare function createSupervisorStateDirectory(): SupervisorStateDirectory;
|
|
57
|
-
/**
|
|
58
|
-
* The managed policy file this host resolves, read before the child's environment exists.
|
|
59
|
-
*
|
|
60
|
-
* `cli-launch`'s child-environment allowlist filters `APEX_CODE_POLICY_PATH` out, so a
|
|
61
|
-
* host that configured a custom managed policy had it silently disappear at the boundary
|
|
62
|
-
* and the child fell back to the system path -- the config-projection defect PS.3 closes.
|
|
63
|
-
* Resolution here mirrors `core/permissions/store.ts`'s own default exactly.
|
|
64
|
-
*/
|
|
65
|
-
export declare function resolveSupervisorPolicyPath(environment?: NodeJS.ProcessEnv): string;
|
|
66
|
-
/**
|
|
67
|
-
* Capture the host `policy` and `user` permission scopes into the supervisor's private
|
|
68
|
-
* directory and return the file's path.
|
|
69
|
-
*
|
|
70
|
-
* Called before `buildSandboxedCliLaunch` repoints `APEX_CODE_CODING_AGENT_DIR` at a
|
|
71
|
-
* workspace directory the child can write. Without it the child's `user` scope came from a
|
|
72
|
-
* file the child itself owns, and its `policy` scope from a path the environment allowlist
|
|
73
|
-
* had already removed.
|
|
74
|
-
*/
|
|
75
|
-
export declare function writeSupervisorPolicySnapshot(options: {
|
|
76
|
-
directory: string;
|
|
77
|
-
/** The *host* agent directory, before the launch repoints it. */
|
|
78
|
-
agentDir: string;
|
|
79
|
-
policyPath?: string;
|
|
80
|
-
environment?: NodeJS.ProcessEnv;
|
|
81
|
-
}): string;
|
|
82
|
-
/**
|
|
83
|
-
* Read supervisor policy only from global settings; project settings are untrusted here.
|
|
84
|
-
*
|
|
85
|
-
* The built-in provider hosts are added unless explicitly refused, because a deny-all
|
|
86
|
-
* default made a fresh install unable to reach any model while giving the user no way to
|
|
87
|
-
* learn which host to permit. Configured hosts are additive on top, and
|
|
88
|
-
* `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.
|
|
89
|
-
*/
|
|
90
|
-
export declare function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined;
|
|
91
|
-
/**
|
|
92
|
-
* The host's two user-scope skill roots, each present only when it exists on the
|
|
93
|
-
* host. Kept as two named slots rather than one list because
|
|
94
|
-
* `core/package-manager.ts` discovers them in different modes -- root `.md` files
|
|
95
|
-
* count as skills under `agentSkills` ("pi" mode) and are ignored under
|
|
96
|
-
* `agentsHomeSkills` ("agents" mode), per `docs/skills.md` -- and a flat list of 0-2
|
|
97
|
-
* paths cannot tell the child which root a lone survivor was.
|
|
98
|
-
*/
|
|
99
|
-
export interface HostSkillPaths {
|
|
100
|
-
/** Host `<agentDir>/skills`. */
|
|
101
|
-
readonly agentSkills?: string;
|
|
102
|
-
/** Host `<home>/.agents/skills`. */
|
|
103
|
-
readonly agentsHomeSkills?: string;
|
|
104
|
-
}
|
|
105
|
-
/** A candidate skill root that exists but was excluded, and why. */
|
|
106
|
-
export interface HostSkillPathRefusal {
|
|
107
|
-
readonly root: keyof HostSkillPaths;
|
|
108
|
-
readonly path: string;
|
|
109
|
-
readonly reason: string;
|
|
110
|
-
}
|
|
111
|
-
export interface ResolvedHostSkillPaths {
|
|
112
|
-
readonly paths: HostSkillPaths;
|
|
113
|
-
readonly refusals: readonly HostSkillPathRefusal[];
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Resolve the host's user-scope skill directories, before the sandbox exists to hide
|
|
117
|
-
* them. Read only from the runtime environment and the host agent directory --
|
|
118
|
-
* never from project files -- matching ADR 0016's rule that supervisor policy is
|
|
119
|
-
* trust-first. Mirrors `core/package-manager.ts`'s own user-scope roots so the two
|
|
120
|
-
* sides agree on where a skill lives. A candidate that resolves (directly or via a
|
|
121
|
-
* symlink) onto the host home or an ancestor of it is refused rather than mounted --
|
|
122
|
-
* see `isHomeOrAncestorOfHome` -- and reported so the caller can surface a startup
|
|
123
|
-
* diagnostic instead of silently mounting or silently skipping it.
|
|
124
|
-
*/
|
|
125
|
-
export declare function resolveHostSkillPaths(agentDir: string, homeDir: string): ResolvedHostSkillPaths;
|
|
126
|
-
/**
|
|
127
|
-
* Allocate agent-owned state under the sole writable workspace mount. The child does
|
|
128
|
-
* not inherit a host home or a host session/config directory, preventing the sandbox
|
|
129
|
-
* from presenting a write boundary while its own state quietly escapes it.
|
|
130
|
-
*/
|
|
131
|
-
export declare function buildSandboxedCliLaunch(options: {
|
|
132
|
-
workspace: string;
|
|
133
|
-
command: string;
|
|
134
|
-
args: readonly string[];
|
|
135
|
-
environment: NodeJS.ProcessEnv;
|
|
136
|
-
allowedHosts?: readonly string[];
|
|
137
|
-
additionalWritableRoots?: readonly string[];
|
|
138
|
-
readOnlyPaths?: readonly string[];
|
|
139
|
-
sessionPath?: string;
|
|
140
|
-
authPath?: string;
|
|
141
|
-
/**
|
|
142
|
-
* A synthesized two-key git config, projected read-only so the child can author a
|
|
143
|
-
* commit as the user. Absent when the host had no global identity to resolve.
|
|
144
|
-
*/
|
|
145
|
-
gitConfigPath?: string;
|
|
146
|
-
toolBinaries?: readonly HostToolBinary[];
|
|
147
|
-
/**
|
|
148
|
-
* Host user-scope skill directories, pre-filtered by the caller to those that
|
|
149
|
-
* exist and pass the host-home escape check (SKILL.4). Mounted read-only at their
|
|
150
|
-
* original host location -- Seatbelt cannot remap a path, so this must hold for
|
|
151
|
-
* both backends -- and named to the child via `APEX_CODE_SKILL_PATH_*` so its
|
|
152
|
-
* discovery can find them under the sandbox's own repointed `HOME`/agent dir.
|
|
153
|
-
*/
|
|
154
|
-
skillPaths?: HostSkillPaths;
|
|
155
|
-
/** The supervisor-owned credential write channel, when one was opened. */
|
|
156
|
-
credentialChannel?: SandboxLaunch["credentialChannel"];
|
|
157
|
-
/** The supervisor's private 0700 state directory for this launch. */
|
|
158
|
-
supervisorStateDirectory?: string;
|
|
159
|
-
/** The captured host permission scopes, named to the child read-only. */
|
|
160
|
-
policySnapshotPath?: string;
|
|
161
|
-
}): SandboxedCliLaunch;
|
|
162
|
-
//# sourceMappingURL=cli-launch.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli-launch.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/cli-launch.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAEzE;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CAC1C;AAED,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,mCAAmC,CAAC;AAE9E;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC,+BAA+B,CAAC;AAChF,eAAO,MAAM,gCAAgC,MAAM,CAAC;AAEpD,MAAM,WAAW,wBAAwB;IACxC,0FAA0F;IAC1F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,IAAI,wBAAwB,CAczE;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAKhG;AA0BD;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAChC,GAAG,MAAM,CAWT;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAW1G;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC9B,gCAAgC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,oCAAoC;IACpC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,oEAAoE;AACpE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,cAAc,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACnD;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAY/F;AA0GD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,uBAAuB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACzC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IACvD,qEAAqE;IACrE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B,GAAG,kBAAkB,CA0ErB","sourcesContent":["import {\n\tchmodSync,\n\texistsSync,\n\tmkdirSync,\n\tmkdtempSync,\n\treaddirSync,\n\treadFileSync,\n\trealpathSync,\n\trmSync,\n\tstatSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { join, sep } from \"node:path\";\nimport type { ParsedCliCommand } from \"../../cli/args.ts\";\nimport type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { SettingsManager } from \"../settings-manager.ts\";\nimport { resolveWebSearchHost } from \"../web-search-provider.ts\";\nimport { resolveDefaultAllowedHosts } from \"./default-hosts.ts\";\nimport type { SandboxLaunch } from \"./supervisor.ts\";\nimport { supervisorTempDirectory } from \"./supervisor-temp.ts\";\n\n/**\n * OS containment is the normal startup path for every parsed command that can\n * construct an agent session. Classification consumes the same typed parse result\n * as execution, so option values and text after `--` cannot impersonate metadata.\n */\nexport function requiresSandboxedChild(command: ParsedCliCommand): boolean {\n\treturn command.kind === \"session\";\n}\n\nexport interface SandboxedCliLaunch extends SandboxLaunch {\n\treadonly environment: NodeJS.ProcessEnv;\n\t/**\n\t * The supervisor's own 0700 state directory, outside every writable root the child\n\t * has.\n\t */\n\treadonly supervisorStateDirectory: string;\n}\n\nexport function getSandboxSessionDirectory(workspace: string): string {\n\treturn join(workspace, \".apex-code\", \"sandbox-sessions\");\n}\n\n/**\n * Env var naming the file that carries the host's `policy` and `user` permission scopes\n * into the child (PS.3).\n *\n * Deliberately absent from `SAFE_CHILD_ENVIRONMENT_KEYS`: only the supervisor's own\n * resolution may name it, never the invoking shell and never a project file (ADR 0016).\n */\nexport const POLICY_SNAPSHOT_PATH_VARIABLE = \"APEX_CODE_POLICY_SNAPSHOT_PATH\";\n\n/**\n * The marker asserting that an OS boundary is actually enforcing this process (PS.5).\n *\n * Set only on the supervisor's own launch environment for a child it is about to contain.\n * It is not in the child-environment allowlist, so a value of this name in the invoking\n * shell never survives into a child; the supervisor's own value is the only one that can.\n * `core/sdk.ts` reads it for `sandbox: \"required\"` and keeps the same two literals -- a\n * marker spelled two ways would be a drift the SDK could not detect at runtime, so\n * `test/sdk-sandbox-contract.test.ts` pins them to each other.\n */\nexport const SANDBOX_ENFORCEMENT_MARKER_VARIABLE = \"APEX_CODE_SANDBOX_ENFORCED\";\nexport const SANDBOX_ENFORCEMENT_MARKER_VALUE = \"1\";\n\nexport interface SupervisorStateDirectory {\n\t/** A 0700 directory under the supervisor temp root, outside every child-writable root. */\n\treadonly path: string;\n\treadonly dispose: () => void;\n}\n\n/**\n * Allocate the directory the supervisor keeps its own launch state in.\n *\n * Everything the supervisor writes for a launch -- the terminal handoff latch, the\n * published terminal size, the network relay, the git credential helper, the permission\n * snapshot -- used to live in `workspace/.apex-code/sandbox-state`, which is inside the\n * one writable bind the child has. A child that planted a symlink there redirected the\n * *next* launch's supervisor writes onto arbitrary host paths, and could rewrite the\n * scripts the supervisor was about to run. Under the supervisor temp root the child sees\n * these files through the read-only root bind (Linux) or Seatbelt's write denial outside\n * the workspace (macOS), so it can neither replace them nor plant a link where one goes.\n */\nexport function createSupervisorStateDirectory(): SupervisorStateDirectory {\n\tconst path = mkdtempSync(join(supervisorTempDirectory(), `apex-supervisor-${process.pid}-`), { encoding: \"utf8\" });\n\tchmodSync(path, 0o700);\n\treturn {\n\t\tpath,\n\t\tdispose: () => {\n\t\t\ttry {\n\t\t\t\trmSync(path, { force: true, recursive: true });\n\t\t\t} catch {\n\t\t\t\t// A leftover directory under the supervisor's own temp root is not worth\n\t\t\t\t// failing a session teardown over.\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * The managed policy file this host resolves, read before the child's environment exists.\n *\n * `cli-launch`'s child-environment allowlist filters `APEX_CODE_POLICY_PATH` out, so a\n * host that configured a custom managed policy had it silently disappear at the boundary\n * and the child fell back to the system path -- the config-projection defect PS.3 closes.\n * Resolution here mirrors `core/permissions/store.ts`'s own default exactly.\n */\nexport function resolveSupervisorPolicyPath(environment: NodeJS.ProcessEnv = process.env): string {\n\tif (environment.APEX_CODE_POLICY_PATH) return environment.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(environment.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\n/**\n * The two host-owned permission scopes, captured verbatim.\n *\n * Raw file text rather than parsed rules, so the child's store applies exactly the same\n * validation -- and reports exactly the same malformed-file error -- as it would have on\n * the host. A snapshot that pre-parsed would have to decide what a malformed host policy\n * means, and the only safe answer to that lives in the store.\n */\ninterface SupervisorPolicySnapshotFile {\n\treadonly version: 1;\n\treadonly policy: string | null;\n\treadonly user: string | null;\n}\n\nfunction readHostScopeText(path: string): string | null {\n\ttry {\n\t\treturn readFileSync(path, \"utf8\");\n\t} catch {\n\t\t// Absent is the ordinary case for both files; an unreadable one projects as absent\n\t\t// rather than as authority the child cannot see the shape of.\n\t\treturn null;\n\t}\n}\n\n/**\n * Capture the host `policy` and `user` permission scopes into the supervisor's private\n * directory and return the file's path.\n *\n * Called before `buildSandboxedCliLaunch` repoints `APEX_CODE_CODING_AGENT_DIR` at a\n * workspace directory the child can write. Without it the child's `user` scope came from a\n * file the child itself owns, and its `policy` scope from a path the environment allowlist\n * had already removed.\n */\nexport function writeSupervisorPolicySnapshot(options: {\n\tdirectory: string;\n\t/** The *host* agent directory, before the launch repoints it. */\n\tagentDir: string;\n\tpolicyPath?: string;\n\tenvironment?: NodeJS.ProcessEnv;\n}): string {\n\tconst directory = join(options.directory, \"policy\");\n\tmkdirSync(directory, { recursive: true, mode: 0o700 });\n\tconst snapshot: SupervisorPolicySnapshotFile = {\n\t\tversion: 1,\n\t\tpolicy: readHostScopeText(options.policyPath ?? resolveSupervisorPolicyPath(options.environment)),\n\t\tuser: readHostScopeText(join(options.agentDir, \"permissions.json\")),\n\t};\n\tconst path = join(directory, \"permission-snapshot.json\");\n\twriteFileSync(path, JSON.stringify(snapshot), { mode: 0o600 });\n\treturn path;\n}\n\n/**\n * Read supervisor policy only from global settings; project settings are untrusted here.\n *\n * The built-in provider hosts are added unless explicitly refused, because a deny-all\n * default made a fresh install unable to reach any model while giving the user no way to\n * learn which host to permit. Configured hosts are additive on top, and\n * `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.\n */\nexport function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined {\n\tconst settings = SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tconst network = settings.getNetworkSettings();\n\tconst configured = network?.allowedHosts ?? [];\n\tif (network?.allowDefaultHosts === false) return configured;\n\t// The configured `web_search` backend's host, added only when a credential for it\n\t// is present. Without this the tool would be configured and still unreachable,\n\t// which reads as a broken search rather than a missing allowlist entry. Nothing is\n\t// opened for a user who never set the key.\n\tconst webSearchHost = resolveWebSearchHost(settings.getWebSearchSettings());\n\treturn [...new Set([...resolveDefaultAllowedHosts(), ...configured, ...(webSearchHost ? [webSearchHost] : [])])];\n}\n\n/**\n * The host's two user-scope skill roots, each present only when it exists on the\n * host. Kept as two named slots rather than one list because\n * `core/package-manager.ts` discovers them in different modes -- root `.md` files\n * count as skills under `agentSkills` (\"pi\" mode) and are ignored under\n * `agentsHomeSkills` (\"agents\" mode), per `docs/skills.md` -- and a flat list of 0-2\n * paths cannot tell the child which root a lone survivor was.\n */\nexport interface HostSkillPaths {\n\t/** Host `<agentDir>/skills`. */\n\treadonly agentSkills?: string;\n\t/** Host `<home>/.agents/skills`. */\n\treadonly agentsHomeSkills?: string;\n}\n\n/** A candidate skill root that exists but was excluded, and why. */\nexport interface HostSkillPathRefusal {\n\treadonly root: keyof HostSkillPaths;\n\treadonly path: string;\n\treadonly reason: string;\n}\n\nexport interface ResolvedHostSkillPaths {\n\treadonly paths: HostSkillPaths;\n\treadonly refusals: readonly HostSkillPathRefusal[];\n}\n\n/**\n * True when `candidate` is the host home directory itself, or an ancestor of it.\n * Both paths must already be resolved (`realpathSync`), so a symlink can't disguise\n * either side. Mounting such a candidate read-only would re-expose the entire home\n * tree the sandbox's `--tmpfs /home` (Linux) / `(deny file-read* USER_HOME)` (macOS)\n * deliberately hides -- SSH keys, cloud credentials, other projects -- not just a\n * skills subtree.\n */\nfunction isHomeOrAncestorOfHome(candidate: string, home: string): boolean {\n\tif (candidate === home) return true;\n\tconst prefix = candidate.endsWith(sep) ? candidate : candidate + sep;\n\treturn home.startsWith(prefix);\n}\n\n/** Resolve one candidate root: absent, refused (symlinked onto the host home), or usable. */\nfunction resolveHostSkillRoot(\n\troot: keyof HostSkillPaths,\n\tcandidate: string,\n\thomeDir: string,\n): { path?: string; refusal?: HostSkillPathRefusal } {\n\tif (!existsSync(candidate)) return {};\n\tlet realCandidate: string;\n\tlet realHome: string;\n\ttry {\n\t\trealCandidate = realpathSync(candidate);\n\t\trealHome = realpathSync(homeDir);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn { refusal: { root, path: candidate, reason: `could not resolve real path: ${message}` } };\n\t}\n\tif (isHomeOrAncestorOfHome(realCandidate, realHome)) {\n\t\treturn {\n\t\t\trefusal: {\n\t\t\t\troot,\n\t\t\t\tpath: candidate,\n\t\t\t\treason: \"resolves to the host home directory or an ancestor of it\",\n\t\t\t},\n\t\t};\n\t}\n\treturn { path: candidate };\n}\n\n/**\n * Resolve the host's user-scope skill directories, before the sandbox exists to hide\n * them. Read only from the runtime environment and the host agent directory --\n * never from project files -- matching ADR 0016's rule that supervisor policy is\n * trust-first. Mirrors `core/package-manager.ts`'s own user-scope roots so the two\n * sides agree on where a skill lives. A candidate that resolves (directly or via a\n * symlink) onto the host home or an ancestor of it is refused rather than mounted --\n * see `isHomeOrAncestorOfHome` -- and reported so the caller can surface a startup\n * diagnostic instead of silently mounting or silently skipping it.\n */\nexport function resolveHostSkillPaths(agentDir: string, homeDir: string): ResolvedHostSkillPaths {\n\tconst agentSkills = resolveHostSkillRoot(\"agentSkills\", join(agentDir, \"skills\"), homeDir);\n\tconst agentsHomeSkills = resolveHostSkillRoot(\"agentsHomeSkills\", join(homeDir, \".agents\", \"skills\"), homeDir);\n\treturn {\n\t\tpaths: {\n\t\t\t...(agentSkills.path ? { agentSkills: agentSkills.path } : {}),\n\t\t\t...(agentsHomeSkills.path ? { agentsHomeSkills: agentsHomeSkills.path } : {}),\n\t\t},\n\t\trefusals: [agentSkills.refusal, agentsHomeSkills.refusal].filter(\n\t\t\t(refusal): refusal is HostSkillPathRefusal => refusal !== undefined,\n\t\t),\n\t};\n}\n\nconst SAFE_CHILD_ENVIRONMENT_KEYS = new Set([\n\t\"PATH\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"LC_CTYPE\",\n\t\"TERM\",\n\t\"COLORTERM\",\n\t\"NO_COLOR\",\n\t\"FORCE_COLOR\",\n\t\"TSX_TSCONFIG_PATH\",\n\t\"APEX_CODE_OFFLINE\",\n\t\"APEX_CODE_SKIP_VERSION_CHECK\",\n\t\"APEX_CODE_EXPERIMENTAL\",\n\t\"APEX_CODE_STARTUP_BENCHMARK\",\n\t\"APEX_CODE_TIMING\",\n\t\"APEX_CODE_CLEAR_ON_SHRINK\",\n\t\"APEX_CODE_HARDWARE_CURSOR\",\n\t\"APEX_CODE_MODEL_CATALOG_URL\",\n\t\"APEX_CODE_SHARE_VIEWER_URL\",\n\t\"VISUAL\",\n\t\"EDITOR\",\n]);\n\n// Provider API keys are explicit credential inputs, unlike arbitrary ambient variables.\n// Keep this list in sync with the documented provider environment-variable reference.\nconst SAFE_PROVIDER_CREDENTIAL_KEYS = new Set([\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANT_LING_API_KEY\",\n\t\"OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_BASE_URL\",\n\t\"AZURE_OPENAI_RESOURCE_NAME\",\n\t\"AZURE_OPENAI_API_VERSION\",\n\t\"AZURE_OPENAI_DEPLOYMENT_NAME_MAP\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"NVIDIA_API_KEY\",\n\t\"GEMINI_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"CEREBRAS_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"FIREWORKS_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"BASETEN_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"AI_GATEWAY_API_KEY\",\n\t\"ZAI_API_KEY\",\n\t\"ZAI_CODING_CN_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"MINIMAX_API_KEY\",\n\t\"MOONSHOT_API_KEY\",\n\t\"OPENCODE_API_KEY\",\n\t\"KIMI_API_KEY\",\n\t\"CLOUDFLARE_API_KEY\",\n\t\"CLOUDFLARE_ACCOUNT_ID\",\n\t\"CLOUDFLARE_GATEWAY_ID\",\n\t\"QWEN_TOKEN_PLAN_API_KEY\",\n\t\"QWEN_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_AMS_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_SGP_API_KEY\",\n\t\"AWS_PROFILE\",\n\t\"AWS_ACCESS_KEY_ID\",\n\t\"AWS_SECRET_ACCESS_KEY\",\n\t\"AWS_BEARER_TOKEN_BEDROCK\",\n\t\"AWS_REGION\",\n]);\n\n// Credentials for tools rather than model providers. Separate from the provider set\n// above so the provider list stays a mirror of the documented provider reference.\nconst SAFE_TOOL_CREDENTIAL_KEYS = new Set([\"EXA_API_KEY\"]);\n\nfunction buildChildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n\treturn Object.fromEntries(\n\t\tObject.entries(environment).filter(\n\t\t\t([key]) =>\n\t\t\t\tSAFE_CHILD_ENVIRONMENT_KEYS.has(key) ||\n\t\t\t\tSAFE_PROVIDER_CREDENTIAL_KEYS.has(key) ||\n\t\t\t\tSAFE_TOOL_CREDENTIAL_KEYS.has(key),\n\t\t),\n\t);\n}\n\n/**\n * Drop empty files left in the child's tools directory by a previous launch.\n *\n * A projected tool is bind-mounted over a file there, and bwrap materialises that\n * mountpoint as an empty file on the host which outlives the namespace. If the host\n * tool later disappears, nothing is projected over the stub and the child would\n * otherwise find a 0-byte file where its binary should be. A real downloaded binary\n * is never empty, so size is a safe discriminator.\n */\nfunction clearStaleToolMountpoints(toolsDirectory: string): void {\n\tfor (const entry of readdirSync(toolsDirectory, { withFileTypes: true })) {\n\t\tif (!entry.isFile()) continue;\n\t\tconst entryPath = join(toolsDirectory, entry.name);\n\t\tif (statSync(entryPath).size === 0) {\n\t\t\trmSync(entryPath, { force: true });\n\t\t}\n\t}\n}\n\n/**\n * Allocate agent-owned state under the sole writable workspace mount. The child does\n * not inherit a host home or a host session/config directory, preventing the sandbox\n * from presenting a write boundary while its own state quietly escapes it.\n */\nexport function buildSandboxedCliLaunch(options: {\n\tworkspace: string;\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tallowedHosts?: readonly string[];\n\tadditionalWritableRoots?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tsessionPath?: string;\n\tauthPath?: string;\n\t/**\n\t * A synthesized two-key git config, projected read-only so the child can author a\n\t * commit as the user. Absent when the host had no global identity to resolve.\n\t */\n\tgitConfigPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n\t/**\n\t * Host user-scope skill directories, pre-filtered by the caller to those that\n\t * exist and pass the host-home escape check (SKILL.4). Mounted read-only at their\n\t * original host location -- Seatbelt cannot remap a path, so this must hold for\n\t * both backends -- and named to the child via `APEX_CODE_SKILL_PATH_*` so its\n\t * discovery can find them under the sandbox's own repointed `HOME`/agent dir.\n\t */\n\tskillPaths?: HostSkillPaths;\n\t/** The supervisor-owned credential write channel, when one was opened. */\n\tcredentialChannel?: SandboxLaunch[\"credentialChannel\"];\n\t/** The supervisor's private 0700 state directory for this launch. */\n\tsupervisorStateDirectory?: string;\n\t/** The captured host permission scopes, named to the child read-only. */\n\tpolicySnapshotPath?: string;\n}): SandboxedCliLaunch {\n\tconst supervisorStateDirectory = options.supervisorStateDirectory ?? createSupervisorStateDirectory().path;\n\tconst stateDirectory = join(options.workspace, \".apex-code\", \"sandbox-state\");\n\tconst agentDirectory = join(options.workspace, \".apex-code\", \"sandbox-agent\");\n\tconst sessionDirectory = getSandboxSessionDirectory(options.workspace);\n\t// Mirrors getBinDir() as the child will compute it from APEX_CODE_CODING_AGENT_DIR,\n\t// so a projected tool lands exactly where the child's own lookup already checks.\n\tconst toolsDirectory = join(agentDirectory, \"bin\");\n\tconst xdgDirectories = {\n\t\tXDG_CONFIG_HOME: join(stateDirectory, \"config\"),\n\t\tXDG_CACHE_HOME: join(stateDirectory, \"cache\"),\n\t\tXDG_DATA_HOME: join(stateDirectory, \"data\"),\n\t\tXDG_STATE_HOME: join(stateDirectory, \"state\"),\n\t};\n\tfor (const directory of [\n\t\tstateDirectory,\n\t\tagentDirectory,\n\t\tsessionDirectory,\n\t\ttoolsDirectory,\n\t\t...Object.values(xdgDirectories),\n\t]) {\n\t\tmkdirSync(directory, { recursive: true });\n\t}\n\tclearStaleToolMountpoints(toolsDirectory);\n\tconst childEnvironment = buildChildEnvironment(options.environment);\n\tconst { agentSkills, agentsHomeSkills } = options.skillPaths ?? {};\n\tconst skillMountPaths = [agentSkills, agentsHomeSkills].filter((path): path is string => path !== undefined);\n\tconst readOnlyPaths = [...(options.readOnlyPaths ?? []), ...skillMountPaths];\n\tconst readOnlyFiles = [options.sessionPath, options.authPath, options.gitConfigPath].filter(\n\t\t(path): path is string => path !== undefined,\n\t);\n\tconst readOnlyBinaries = (options.toolBinaries ?? []).map((binary) => ({\n\t\tsource: binary.path,\n\t\tdestination: join(toolsDirectory, binary.name),\n\t}));\n\treturn {\n\t\tcommand: options.command,\n\t\targs: [...options.args],\n\t\tpolicy: {\n\t\t\tworkspace: options.workspace,\n\t\t\tallowedHosts: options.allowedHosts ?? [],\n\t\t\tadditionalWritableRoots: options.additionalWritableRoots ?? [],\n\t\t},\n\t\treadOnlyPaths,\n\t\treadOnlyFiles,\n\t\treadOnlyBinaries,\n\t\t// Travels on the launch contract so the platform backend projects the socket;\n\t\t// the environment entry above is what tells the child where to find it.\n\t\t...(options.credentialChannel ? { credentialChannel: options.credentialChannel } : {}),\n\t\tsupervisorStateDirectory,\n\t\tenvironment: {\n\t\t\t...childEnvironment,\n\t\t\t...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),\n\t\t\t...(options.credentialChannel\n\t\t\t\t? { APEX_CREDENTIAL_PROXY_PATH: options.credentialChannel.childSocketPath }\n\t\t\t\t: {}),\n\t\t\t// After childEnvironment: these come only from the supervisor's own\n\t\t\t// resolution, never from the invoking shell, so their value always wins over\n\t\t\t// anything of the same name that childEnvironment's allowlist let through.\n\t\t\t...(options.gitConfigPath ? { GIT_CONFIG_GLOBAL: options.gitConfigPath } : {}),\n\t\t\t...(agentSkills ? { APEX_CODE_SKILL_PATH_AGENT: agentSkills } : {}),\n\t\t\t...(agentsHomeSkills ? { APEX_CODE_SKILL_PATH_AGENTS_HOME: agentsHomeSkills } : {}),\n\t\t\t// The host's own permission scopes, and the assertion that a boundary is\n\t\t\t// enforcing this process. Both are supervisor-resolved for exactly the reason\n\t\t\t// `agentDirectory` below is: the child's copy is writable by the child.\n\t\t\t...(options.policySnapshotPath ? { [POLICY_SNAPSHOT_PATH_VARIABLE]: options.policySnapshotPath } : {}),\n\t\t\t[SANDBOX_ENFORCEMENT_MARKER_VARIABLE]: SANDBOX_ENFORCEMENT_MARKER_VALUE,\n\t\t\tAPEX_CODE_CODING_AGENT_DIR: agentDirectory,\n\t\t\tAPEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,\n\t\t\tHOME: stateDirectory,\n\t\t\tTMPDIR: stateDirectory,\n\t\t\t...xdgDirectories,\n\t\t},\n\t};\n}\n"]}
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
2
|
-
import { join, sep } from "node:path";
|
|
3
|
-
import { SettingsManager } from "../settings-manager.js";
|
|
4
|
-
import { resolveWebSearchHost } from "../web-search-provider.js";
|
|
5
|
-
import { resolveDefaultAllowedHosts } from "./default-hosts.js";
|
|
6
|
-
import { supervisorTempDirectory } from "./supervisor-temp.js";
|
|
7
|
-
/**
|
|
8
|
-
* OS containment is the normal startup path for every parsed command that can
|
|
9
|
-
* construct an agent session. Classification consumes the same typed parse result
|
|
10
|
-
* as execution, so option values and text after `--` cannot impersonate metadata.
|
|
11
|
-
*/
|
|
12
|
-
export function requiresSandboxedChild(command) {
|
|
13
|
-
return command.kind === "session";
|
|
14
|
-
}
|
|
15
|
-
export function getSandboxSessionDirectory(workspace) {
|
|
16
|
-
return join(workspace, ".apex-code", "sandbox-sessions");
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Env var naming the file that carries the host's `policy` and `user` permission scopes
|
|
20
|
-
* into the child (PS.3).
|
|
21
|
-
*
|
|
22
|
-
* Deliberately absent from `SAFE_CHILD_ENVIRONMENT_KEYS`: only the supervisor's own
|
|
23
|
-
* resolution may name it, never the invoking shell and never a project file (ADR 0016).
|
|
24
|
-
*/
|
|
25
|
-
export const POLICY_SNAPSHOT_PATH_VARIABLE = "APEX_CODE_POLICY_SNAPSHOT_PATH";
|
|
26
|
-
/**
|
|
27
|
-
* The marker asserting that an OS boundary is actually enforcing this process (PS.5).
|
|
28
|
-
*
|
|
29
|
-
* Set only on the supervisor's own launch environment for a child it is about to contain.
|
|
30
|
-
* It is not in the child-environment allowlist, so a value of this name in the invoking
|
|
31
|
-
* shell never survives into a child; the supervisor's own value is the only one that can.
|
|
32
|
-
* `core/sdk.ts` reads it for `sandbox: "required"` and keeps the same two literals -- a
|
|
33
|
-
* marker spelled two ways would be a drift the SDK could not detect at runtime, so
|
|
34
|
-
* `test/sdk-sandbox-contract.test.ts` pins them to each other.
|
|
35
|
-
*/
|
|
36
|
-
export const SANDBOX_ENFORCEMENT_MARKER_VARIABLE = "APEX_CODE_SANDBOX_ENFORCED";
|
|
37
|
-
export const SANDBOX_ENFORCEMENT_MARKER_VALUE = "1";
|
|
38
|
-
/**
|
|
39
|
-
* Allocate the directory the supervisor keeps its own launch state in.
|
|
40
|
-
*
|
|
41
|
-
* Everything the supervisor writes for a launch -- the terminal handoff latch, the
|
|
42
|
-
* published terminal size, the network relay, the git credential helper, the permission
|
|
43
|
-
* snapshot -- used to live in `workspace/.apex-code/sandbox-state`, which is inside the
|
|
44
|
-
* one writable bind the child has. A child that planted a symlink there redirected the
|
|
45
|
-
* *next* launch's supervisor writes onto arbitrary host paths, and could rewrite the
|
|
46
|
-
* scripts the supervisor was about to run. Under the supervisor temp root the child sees
|
|
47
|
-
* these files through the read-only root bind (Linux) or Seatbelt's write denial outside
|
|
48
|
-
* the workspace (macOS), so it can neither replace them nor plant a link where one goes.
|
|
49
|
-
*/
|
|
50
|
-
export function createSupervisorStateDirectory() {
|
|
51
|
-
const path = mkdtempSync(join(supervisorTempDirectory(), `apex-supervisor-${process.pid}-`), { encoding: "utf8" });
|
|
52
|
-
chmodSync(path, 0o700);
|
|
53
|
-
return {
|
|
54
|
-
path,
|
|
55
|
-
dispose: () => {
|
|
56
|
-
try {
|
|
57
|
-
rmSync(path, { force: true, recursive: true });
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// A leftover directory under the supervisor's own temp root is not worth
|
|
61
|
-
// failing a session teardown over.
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* The managed policy file this host resolves, read before the child's environment exists.
|
|
68
|
-
*
|
|
69
|
-
* `cli-launch`'s child-environment allowlist filters `APEX_CODE_POLICY_PATH` out, so a
|
|
70
|
-
* host that configured a custom managed policy had it silently disappear at the boundary
|
|
71
|
-
* and the child fell back to the system path -- the config-projection defect PS.3 closes.
|
|
72
|
-
* Resolution here mirrors `core/permissions/store.ts`'s own default exactly.
|
|
73
|
-
*/
|
|
74
|
-
export function resolveSupervisorPolicyPath(environment = process.env) {
|
|
75
|
-
if (environment.APEX_CODE_POLICY_PATH)
|
|
76
|
-
return environment.APEX_CODE_POLICY_PATH;
|
|
77
|
-
return process.platform === "win32"
|
|
78
|
-
? join(environment.ProgramData ?? "C:\\ProgramData", "apex-code", "policy.json")
|
|
79
|
-
: "/etc/apex-code/policy.json";
|
|
80
|
-
}
|
|
81
|
-
function readHostScopeText(path) {
|
|
82
|
-
try {
|
|
83
|
-
return readFileSync(path, "utf8");
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
// Absent is the ordinary case for both files; an unreadable one projects as absent
|
|
87
|
-
// rather than as authority the child cannot see the shape of.
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Capture the host `policy` and `user` permission scopes into the supervisor's private
|
|
93
|
-
* directory and return the file's path.
|
|
94
|
-
*
|
|
95
|
-
* Called before `buildSandboxedCliLaunch` repoints `APEX_CODE_CODING_AGENT_DIR` at a
|
|
96
|
-
* workspace directory the child can write. Without it the child's `user` scope came from a
|
|
97
|
-
* file the child itself owns, and its `policy` scope from a path the environment allowlist
|
|
98
|
-
* had already removed.
|
|
99
|
-
*/
|
|
100
|
-
export function writeSupervisorPolicySnapshot(options) {
|
|
101
|
-
const directory = join(options.directory, "policy");
|
|
102
|
-
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
103
|
-
const snapshot = {
|
|
104
|
-
version: 1,
|
|
105
|
-
policy: readHostScopeText(options.policyPath ?? resolveSupervisorPolicyPath(options.environment)),
|
|
106
|
-
user: readHostScopeText(join(options.agentDir, "permissions.json")),
|
|
107
|
-
};
|
|
108
|
-
const path = join(directory, "permission-snapshot.json");
|
|
109
|
-
writeFileSync(path, JSON.stringify(snapshot), { mode: 0o600 });
|
|
110
|
-
return path;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Read supervisor policy only from global settings; project settings are untrusted here.
|
|
114
|
-
*
|
|
115
|
-
* The built-in provider hosts are added unless explicitly refused, because a deny-all
|
|
116
|
-
* default made a fresh install unable to reach any model while giving the user no way to
|
|
117
|
-
* learn which host to permit. Configured hosts are additive on top, and
|
|
118
|
-
* `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.
|
|
119
|
-
*/
|
|
120
|
-
export function resolveSupervisorAllowedHosts(cwd, agentDir) {
|
|
121
|
-
const settings = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
|
|
122
|
-
const network = settings.getNetworkSettings();
|
|
123
|
-
const configured = network?.allowedHosts ?? [];
|
|
124
|
-
if (network?.allowDefaultHosts === false)
|
|
125
|
-
return configured;
|
|
126
|
-
// The configured `web_search` backend's host, added only when a credential for it
|
|
127
|
-
// is present. Without this the tool would be configured and still unreachable,
|
|
128
|
-
// which reads as a broken search rather than a missing allowlist entry. Nothing is
|
|
129
|
-
// opened for a user who never set the key.
|
|
130
|
-
const webSearchHost = resolveWebSearchHost(settings.getWebSearchSettings());
|
|
131
|
-
return [...new Set([...resolveDefaultAllowedHosts(), ...configured, ...(webSearchHost ? [webSearchHost] : [])])];
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* True when `candidate` is the host home directory itself, or an ancestor of it.
|
|
135
|
-
* Both paths must already be resolved (`realpathSync`), so a symlink can't disguise
|
|
136
|
-
* either side. Mounting such a candidate read-only would re-expose the entire home
|
|
137
|
-
* tree the sandbox's `--tmpfs /home` (Linux) / `(deny file-read* USER_HOME)` (macOS)
|
|
138
|
-
* deliberately hides -- SSH keys, cloud credentials, other projects -- not just a
|
|
139
|
-
* skills subtree.
|
|
140
|
-
*/
|
|
141
|
-
function isHomeOrAncestorOfHome(candidate, home) {
|
|
142
|
-
if (candidate === home)
|
|
143
|
-
return true;
|
|
144
|
-
const prefix = candidate.endsWith(sep) ? candidate : candidate + sep;
|
|
145
|
-
return home.startsWith(prefix);
|
|
146
|
-
}
|
|
147
|
-
/** Resolve one candidate root: absent, refused (symlinked onto the host home), or usable. */
|
|
148
|
-
function resolveHostSkillRoot(root, candidate, homeDir) {
|
|
149
|
-
if (!existsSync(candidate))
|
|
150
|
-
return {};
|
|
151
|
-
let realCandidate;
|
|
152
|
-
let realHome;
|
|
153
|
-
try {
|
|
154
|
-
realCandidate = realpathSync(candidate);
|
|
155
|
-
realHome = realpathSync(homeDir);
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
159
|
-
return { refusal: { root, path: candidate, reason: `could not resolve real path: ${message}` } };
|
|
160
|
-
}
|
|
161
|
-
if (isHomeOrAncestorOfHome(realCandidate, realHome)) {
|
|
162
|
-
return {
|
|
163
|
-
refusal: {
|
|
164
|
-
root,
|
|
165
|
-
path: candidate,
|
|
166
|
-
reason: "resolves to the host home directory or an ancestor of it",
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
return { path: candidate };
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Resolve the host's user-scope skill directories, before the sandbox exists to hide
|
|
174
|
-
* them. Read only from the runtime environment and the host agent directory --
|
|
175
|
-
* never from project files -- matching ADR 0016's rule that supervisor policy is
|
|
176
|
-
* trust-first. Mirrors `core/package-manager.ts`'s own user-scope roots so the two
|
|
177
|
-
* sides agree on where a skill lives. A candidate that resolves (directly or via a
|
|
178
|
-
* symlink) onto the host home or an ancestor of it is refused rather than mounted --
|
|
179
|
-
* see `isHomeOrAncestorOfHome` -- and reported so the caller can surface a startup
|
|
180
|
-
* diagnostic instead of silently mounting or silently skipping it.
|
|
181
|
-
*/
|
|
182
|
-
export function resolveHostSkillPaths(agentDir, homeDir) {
|
|
183
|
-
const agentSkills = resolveHostSkillRoot("agentSkills", join(agentDir, "skills"), homeDir);
|
|
184
|
-
const agentsHomeSkills = resolveHostSkillRoot("agentsHomeSkills", join(homeDir, ".agents", "skills"), homeDir);
|
|
185
|
-
return {
|
|
186
|
-
paths: {
|
|
187
|
-
...(agentSkills.path ? { agentSkills: agentSkills.path } : {}),
|
|
188
|
-
...(agentsHomeSkills.path ? { agentsHomeSkills: agentsHomeSkills.path } : {}),
|
|
189
|
-
},
|
|
190
|
-
refusals: [agentSkills.refusal, agentsHomeSkills.refusal].filter((refusal) => refusal !== undefined),
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
const SAFE_CHILD_ENVIRONMENT_KEYS = new Set([
|
|
194
|
-
"PATH",
|
|
195
|
-
"LANG",
|
|
196
|
-
"LC_ALL",
|
|
197
|
-
"LC_CTYPE",
|
|
198
|
-
"TERM",
|
|
199
|
-
"COLORTERM",
|
|
200
|
-
"NO_COLOR",
|
|
201
|
-
"FORCE_COLOR",
|
|
202
|
-
"TSX_TSCONFIG_PATH",
|
|
203
|
-
"APEX_CODE_OFFLINE",
|
|
204
|
-
"APEX_CODE_SKIP_VERSION_CHECK",
|
|
205
|
-
"APEX_CODE_EXPERIMENTAL",
|
|
206
|
-
"APEX_CODE_STARTUP_BENCHMARK",
|
|
207
|
-
"APEX_CODE_TIMING",
|
|
208
|
-
"APEX_CODE_CLEAR_ON_SHRINK",
|
|
209
|
-
"APEX_CODE_HARDWARE_CURSOR",
|
|
210
|
-
"APEX_CODE_MODEL_CATALOG_URL",
|
|
211
|
-
"APEX_CODE_SHARE_VIEWER_URL",
|
|
212
|
-
"VISUAL",
|
|
213
|
-
"EDITOR",
|
|
214
|
-
]);
|
|
215
|
-
// Provider API keys are explicit credential inputs, unlike arbitrary ambient variables.
|
|
216
|
-
// Keep this list in sync with the documented provider environment-variable reference.
|
|
217
|
-
const SAFE_PROVIDER_CREDENTIAL_KEYS = new Set([
|
|
218
|
-
"ANTHROPIC_API_KEY",
|
|
219
|
-
"ANTHROPIC_AUTH_TOKEN",
|
|
220
|
-
"ANTHROPIC_OAUTH_TOKEN",
|
|
221
|
-
"ANT_LING_API_KEY",
|
|
222
|
-
"OPENAI_API_KEY",
|
|
223
|
-
"AZURE_OPENAI_API_KEY",
|
|
224
|
-
"AZURE_OPENAI_BASE_URL",
|
|
225
|
-
"AZURE_OPENAI_RESOURCE_NAME",
|
|
226
|
-
"AZURE_OPENAI_API_VERSION",
|
|
227
|
-
"AZURE_OPENAI_DEPLOYMENT_NAME_MAP",
|
|
228
|
-
"DEEPSEEK_API_KEY",
|
|
229
|
-
"NVIDIA_API_KEY",
|
|
230
|
-
"GEMINI_API_KEY",
|
|
231
|
-
"GROQ_API_KEY",
|
|
232
|
-
"CEREBRAS_API_KEY",
|
|
233
|
-
"XAI_API_KEY",
|
|
234
|
-
"FIREWORKS_API_KEY",
|
|
235
|
-
"TOGETHER_API_KEY",
|
|
236
|
-
"BASETEN_API_KEY",
|
|
237
|
-
"OPENROUTER_API_KEY",
|
|
238
|
-
"AI_GATEWAY_API_KEY",
|
|
239
|
-
"ZAI_API_KEY",
|
|
240
|
-
"ZAI_CODING_CN_API_KEY",
|
|
241
|
-
"MISTRAL_API_KEY",
|
|
242
|
-
"MINIMAX_API_KEY",
|
|
243
|
-
"MOONSHOT_API_KEY",
|
|
244
|
-
"OPENCODE_API_KEY",
|
|
245
|
-
"KIMI_API_KEY",
|
|
246
|
-
"CLOUDFLARE_API_KEY",
|
|
247
|
-
"CLOUDFLARE_ACCOUNT_ID",
|
|
248
|
-
"CLOUDFLARE_GATEWAY_ID",
|
|
249
|
-
"QWEN_TOKEN_PLAN_API_KEY",
|
|
250
|
-
"QWEN_TOKEN_PLAN_CN_API_KEY",
|
|
251
|
-
"XIAOMI_API_KEY",
|
|
252
|
-
"XIAOMI_TOKEN_PLAN_CN_API_KEY",
|
|
253
|
-
"XIAOMI_TOKEN_PLAN_AMS_API_KEY",
|
|
254
|
-
"XIAOMI_TOKEN_PLAN_SGP_API_KEY",
|
|
255
|
-
"AWS_PROFILE",
|
|
256
|
-
"AWS_ACCESS_KEY_ID",
|
|
257
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
258
|
-
"AWS_BEARER_TOKEN_BEDROCK",
|
|
259
|
-
"AWS_REGION",
|
|
260
|
-
]);
|
|
261
|
-
// Credentials for tools rather than model providers. Separate from the provider set
|
|
262
|
-
// above so the provider list stays a mirror of the documented provider reference.
|
|
263
|
-
const SAFE_TOOL_CREDENTIAL_KEYS = new Set(["EXA_API_KEY"]);
|
|
264
|
-
function buildChildEnvironment(environment) {
|
|
265
|
-
return Object.fromEntries(Object.entries(environment).filter(([key]) => SAFE_CHILD_ENVIRONMENT_KEYS.has(key) ||
|
|
266
|
-
SAFE_PROVIDER_CREDENTIAL_KEYS.has(key) ||
|
|
267
|
-
SAFE_TOOL_CREDENTIAL_KEYS.has(key)));
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Drop empty files left in the child's tools directory by a previous launch.
|
|
271
|
-
*
|
|
272
|
-
* A projected tool is bind-mounted over a file there, and bwrap materialises that
|
|
273
|
-
* mountpoint as an empty file on the host which outlives the namespace. If the host
|
|
274
|
-
* tool later disappears, nothing is projected over the stub and the child would
|
|
275
|
-
* otherwise find a 0-byte file where its binary should be. A real downloaded binary
|
|
276
|
-
* is never empty, so size is a safe discriminator.
|
|
277
|
-
*/
|
|
278
|
-
function clearStaleToolMountpoints(toolsDirectory) {
|
|
279
|
-
for (const entry of readdirSync(toolsDirectory, { withFileTypes: true })) {
|
|
280
|
-
if (!entry.isFile())
|
|
281
|
-
continue;
|
|
282
|
-
const entryPath = join(toolsDirectory, entry.name);
|
|
283
|
-
if (statSync(entryPath).size === 0) {
|
|
284
|
-
rmSync(entryPath, { force: true });
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Allocate agent-owned state under the sole writable workspace mount. The child does
|
|
290
|
-
* not inherit a host home or a host session/config directory, preventing the sandbox
|
|
291
|
-
* from presenting a write boundary while its own state quietly escapes it.
|
|
292
|
-
*/
|
|
293
|
-
export function buildSandboxedCliLaunch(options) {
|
|
294
|
-
const supervisorStateDirectory = options.supervisorStateDirectory ?? createSupervisorStateDirectory().path;
|
|
295
|
-
const stateDirectory = join(options.workspace, ".apex-code", "sandbox-state");
|
|
296
|
-
const agentDirectory = join(options.workspace, ".apex-code", "sandbox-agent");
|
|
297
|
-
const sessionDirectory = getSandboxSessionDirectory(options.workspace);
|
|
298
|
-
// Mirrors getBinDir() as the child will compute it from APEX_CODE_CODING_AGENT_DIR,
|
|
299
|
-
// so a projected tool lands exactly where the child's own lookup already checks.
|
|
300
|
-
const toolsDirectory = join(agentDirectory, "bin");
|
|
301
|
-
const xdgDirectories = {
|
|
302
|
-
XDG_CONFIG_HOME: join(stateDirectory, "config"),
|
|
303
|
-
XDG_CACHE_HOME: join(stateDirectory, "cache"),
|
|
304
|
-
XDG_DATA_HOME: join(stateDirectory, "data"),
|
|
305
|
-
XDG_STATE_HOME: join(stateDirectory, "state"),
|
|
306
|
-
};
|
|
307
|
-
for (const directory of [
|
|
308
|
-
stateDirectory,
|
|
309
|
-
agentDirectory,
|
|
310
|
-
sessionDirectory,
|
|
311
|
-
toolsDirectory,
|
|
312
|
-
...Object.values(xdgDirectories),
|
|
313
|
-
]) {
|
|
314
|
-
mkdirSync(directory, { recursive: true });
|
|
315
|
-
}
|
|
316
|
-
clearStaleToolMountpoints(toolsDirectory);
|
|
317
|
-
const childEnvironment = buildChildEnvironment(options.environment);
|
|
318
|
-
const { agentSkills, agentsHomeSkills } = options.skillPaths ?? {};
|
|
319
|
-
const skillMountPaths = [agentSkills, agentsHomeSkills].filter((path) => path !== undefined);
|
|
320
|
-
const readOnlyPaths = [...(options.readOnlyPaths ?? []), ...skillMountPaths];
|
|
321
|
-
const readOnlyFiles = [options.sessionPath, options.authPath, options.gitConfigPath].filter((path) => path !== undefined);
|
|
322
|
-
const readOnlyBinaries = (options.toolBinaries ?? []).map((binary) => ({
|
|
323
|
-
source: binary.path,
|
|
324
|
-
destination: join(toolsDirectory, binary.name),
|
|
325
|
-
}));
|
|
326
|
-
return {
|
|
327
|
-
command: options.command,
|
|
328
|
-
args: [...options.args],
|
|
329
|
-
policy: {
|
|
330
|
-
workspace: options.workspace,
|
|
331
|
-
allowedHosts: options.allowedHosts ?? [],
|
|
332
|
-
additionalWritableRoots: options.additionalWritableRoots ?? [],
|
|
333
|
-
},
|
|
334
|
-
readOnlyPaths,
|
|
335
|
-
readOnlyFiles,
|
|
336
|
-
readOnlyBinaries,
|
|
337
|
-
// Travels on the launch contract so the platform backend projects the socket;
|
|
338
|
-
// the environment entry above is what tells the child where to find it.
|
|
339
|
-
...(options.credentialChannel ? { credentialChannel: options.credentialChannel } : {}),
|
|
340
|
-
supervisorStateDirectory,
|
|
341
|
-
environment: {
|
|
342
|
-
...childEnvironment,
|
|
343
|
-
...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),
|
|
344
|
-
...(options.credentialChannel
|
|
345
|
-
? { APEX_CREDENTIAL_PROXY_PATH: options.credentialChannel.childSocketPath }
|
|
346
|
-
: {}),
|
|
347
|
-
// After childEnvironment: these come only from the supervisor's own
|
|
348
|
-
// resolution, never from the invoking shell, so their value always wins over
|
|
349
|
-
// anything of the same name that childEnvironment's allowlist let through.
|
|
350
|
-
...(options.gitConfigPath ? { GIT_CONFIG_GLOBAL: options.gitConfigPath } : {}),
|
|
351
|
-
...(agentSkills ? { APEX_CODE_SKILL_PATH_AGENT: agentSkills } : {}),
|
|
352
|
-
...(agentsHomeSkills ? { APEX_CODE_SKILL_PATH_AGENTS_HOME: agentsHomeSkills } : {}),
|
|
353
|
-
// The host's own permission scopes, and the assertion that a boundary is
|
|
354
|
-
// enforcing this process. Both are supervisor-resolved for exactly the reason
|
|
355
|
-
// `agentDirectory` below is: the child's copy is writable by the child.
|
|
356
|
-
...(options.policySnapshotPath ? { [POLICY_SNAPSHOT_PATH_VARIABLE]: options.policySnapshotPath } : {}),
|
|
357
|
-
[SANDBOX_ENFORCEMENT_MARKER_VARIABLE]: SANDBOX_ENFORCEMENT_MARKER_VALUE,
|
|
358
|
-
APEX_CODE_CODING_AGENT_DIR: agentDirectory,
|
|
359
|
-
APEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,
|
|
360
|
-
HOME: stateDirectory,
|
|
361
|
-
TMPDIR: stateDirectory,
|
|
362
|
-
...xdgDirectories,
|
|
363
|
-
},
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
//# sourceMappingURL=cli-launch.js.map
|