open-multi-agent-kit 0.96.0 → 0.96.2
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 +81 -0
- package/README.md +34 -6
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +15 -20
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/commands/resource-doctor-cli.d.ts +22 -0
- package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
- package/dist/commands/resource-doctor-cli.js +98 -0
- package/dist/commands/resource-doctor-cli.js.map +1 -0
- package/dist/commands/sdk-session-cli.d.ts +24 -0
- package/dist/commands/sdk-session-cli.d.ts.map +1 -0
- package/dist/commands/sdk-session-cli.js +225 -0
- package/dist/commands/sdk-session-cli.js.map +1 -0
- package/dist/core/advisory-judge-model.d.ts +18 -0
- package/dist/core/advisory-judge-model.d.ts.map +1 -0
- package/dist/core/advisory-judge-model.js +168 -0
- package/dist/core/advisory-judge-model.js.map +1 -0
- package/dist/core/advisory-judge-response.d.ts +8 -0
- package/dist/core/advisory-judge-response.d.ts.map +1 -0
- package/dist/core/advisory-judge-response.js +61 -0
- package/dist/core/advisory-judge-response.js.map +1 -0
- package/dist/core/advisory-judge-types.d.ts +53 -0
- package/dist/core/advisory-judge-types.d.ts.map +1 -0
- package/dist/core/advisory-judge-types.js +2 -0
- package/dist/core/advisory-judge-types.js.map +1 -0
- package/dist/core/advisory-judge.d.ts +8 -0
- package/dist/core/advisory-judge.d.ts.map +1 -0
- package/dist/core/advisory-judge.js +178 -0
- package/dist/core/advisory-judge.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +41 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +396 -179
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +28 -4
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction-gate.d.ts +28 -0
- package/dist/core/compaction-gate.d.ts.map +1 -0
- package/dist/core/compaction-gate.js +34 -0
- package/dist/core/compaction-gate.js.map +1 -0
- package/dist/core/completion-sound.d.ts +109 -0
- package/dist/core/completion-sound.d.ts.map +1 -0
- package/dist/core/completion-sound.js +165 -0
- package/dist/core/completion-sound.js.map +1 -0
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +5 -3
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/domain-loadouts.d.ts.map +1 -1
- package/dist/core/domain-loadouts.js +2 -2
- package/dist/core/domain-loadouts.js.map +1 -1
- package/dist/core/durable-goal-checkpoint.d.ts +6 -0
- package/dist/core/durable-goal-checkpoint.d.ts.map +1 -0
- package/dist/core/durable-goal-checkpoint.js +195 -0
- package/dist/core/durable-goal-checkpoint.js.map +1 -0
- package/dist/core/durable-goal-parse.d.ts +3 -0
- package/dist/core/durable-goal-parse.d.ts.map +1 -0
- package/dist/core/durable-goal-parse.js +86 -0
- package/dist/core/durable-goal-parse.js.map +1 -0
- package/dist/core/durable-goal-reducer.d.ts +3 -0
- package/dist/core/durable-goal-reducer.d.ts.map +1 -0
- package/dist/core/durable-goal-reducer.js +175 -0
- package/dist/core/durable-goal-reducer.js.map +1 -0
- package/dist/core/durable-goal-store.d.ts +19 -0
- package/dist/core/durable-goal-store.d.ts.map +1 -0
- package/dist/core/durable-goal-store.js +126 -0
- package/dist/core/durable-goal-store.js.map +1 -0
- package/dist/core/durable-goal.d.ts +92 -0
- package/dist/core/durable-goal.d.ts.map +1 -0
- package/dist/core/durable-goal.js +62 -0
- package/dist/core/durable-goal.js.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts +3 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.js +125 -0
- package/dist/core/extensions/builtin/goal-controller.js.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts +3 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.js +31 -0
- package/dist/core/extensions/builtin/identical-loop.js.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts +3 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.js +13 -0
- package/dist/core/extensions/builtin/prompt-preset.js.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts +3 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js +12 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js.map +1 -0
- package/dist/core/extensions/types.d.ts +6 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/goal-continuation.d.ts +13 -0
- package/dist/core/goal-continuation.d.ts.map +1 -0
- package/dist/core/goal-continuation.js +10 -0
- package/dist/core/goal-continuation.js.map +1 -0
- package/dist/core/grok-harness.d.ts +1 -1
- package/dist/core/grok-harness.d.ts.map +1 -1
- package/dist/core/grok-harness.js +1 -1
- package/dist/core/grok-harness.js.map +1 -1
- package/dist/core/grok-playbook.d.ts +1 -1
- package/dist/core/grok-playbook.d.ts.map +1 -1
- package/dist/core/grok-playbook.js +1 -1
- package/dist/core/grok-playbook.js.map +1 -1
- package/dist/core/host-resource-probe.d.ts +63 -0
- package/dist/core/host-resource-probe.d.ts.map +1 -0
- package/dist/core/host-resource-probe.js +126 -0
- package/dist/core/host-resource-probe.js.map +1 -0
- package/dist/core/host-resource-snapshot.d.ts +54 -0
- package/dist/core/host-resource-snapshot.d.ts.map +1 -0
- package/dist/core/host-resource-snapshot.js +139 -0
- package/dist/core/host-resource-snapshot.js.map +1 -0
- package/dist/core/identical-loop.d.ts +15 -0
- package/dist/core/identical-loop.d.ts.map +1 -0
- package/dist/core/identical-loop.js +34 -0
- package/dist/core/identical-loop.js.map +1 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-prompt-policy.d.ts +11 -0
- package/dist/core/model-prompt-policy.d.ts.map +1 -0
- package/dist/core/model-prompt-policy.js +17 -0
- package/dist/core/model-prompt-policy.js.map +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +16 -4
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +2 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-budget.d.ts +21 -0
- package/dist/core/prompt-budget.d.ts.map +1 -0
- package/dist/core/prompt-budget.js +51 -0
- package/dist/core/prompt-budget.js.map +1 -0
- package/dist/core/prompt-cache.d.ts +14 -0
- package/dist/core/prompt-cache.d.ts.map +1 -0
- package/dist/core/prompt-cache.js +16 -0
- package/dist/core/prompt-cache.js.map +1 -0
- package/dist/core/prompt-preset.d.ts +8 -0
- package/dist/core/prompt-preset.d.ts.map +1 -0
- package/dist/core/prompt-preset.js +56 -0
- package/dist/core/prompt-preset.js.map +1 -0
- package/dist/core/prompt-settlement.d.ts +81 -0
- package/dist/core/prompt-settlement.d.ts.map +1 -0
- package/dist/core/prompt-settlement.js +84 -0
- package/dist/core/prompt-settlement.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +16 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +24 -2
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-retry.d.ts +36 -0
- package/dist/core/provider-retry.d.ts.map +1 -0
- package/dist/core/provider-retry.js +73 -0
- package/dist/core/provider-retry.js.map +1 -0
- package/dist/core/provider-usage.d.ts +16 -2
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +150 -8
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
- package/dist/core/reasoning-router-resolver.js +1 -1
- package/dist/core/reasoning-router-resolver.js.map +1 -1
- package/dist/core/resource-admission-config.d.ts +55 -0
- package/dist/core/resource-admission-config.d.ts.map +1 -0
- package/dist/core/resource-admission-config.js +97 -0
- package/dist/core/resource-admission-config.js.map +1 -0
- package/dist/core/resource-admission.d.ts +81 -0
- package/dist/core/resource-admission.d.ts.map +1 -0
- package/dist/core/resource-admission.js +128 -0
- package/dist/core/resource-admission.js.map +1 -0
- package/dist/core/resource-governor-format.d.ts +50 -0
- package/dist/core/resource-governor-format.d.ts.map +1 -0
- package/dist/core/resource-governor-format.js +113 -0
- package/dist/core/resource-governor-format.js.map +1 -0
- package/dist/core/resource-governor-settings.d.ts +56 -0
- package/dist/core/resource-governor-settings.d.ts.map +1 -0
- package/dist/core/resource-governor-settings.js +89 -0
- package/dist/core/resource-governor-settings.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +32 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/resource-observation-journal.d.ts +74 -0
- package/dist/core/resource-observation-journal.d.ts.map +1 -0
- package/dist/core/resource-observation-journal.js +198 -0
- package/dist/core/resource-observation-journal.js.map +1 -0
- package/dist/core/resource-safety-gate.d.ts +54 -0
- package/dist/core/resource-safety-gate.d.ts.map +1 -0
- package/dist/core/resource-safety-gate.js +51 -0
- package/dist/core/resource-safety-gate.js.map +1 -0
- package/dist/core/run-journal.d.ts.map +1 -1
- package/dist/core/run-journal.js +13 -1
- package/dist/core/run-journal.js.map +1 -1
- package/dist/core/run-resource-lease.d.ts +73 -0
- package/dist/core/run-resource-lease.d.ts.map +1 -0
- package/dist/core/run-resource-lease.js +78 -0
- package/dist/core/run-resource-lease.js.map +1 -0
- package/dist/core/sandbox/backend.d.ts +12 -0
- package/dist/core/sandbox/backend.d.ts.map +1 -1
- package/dist/core/sandbox/backend.js +52 -42
- package/dist/core/sandbox/backend.js.map +1 -1
- package/dist/core/sandbox/policy.d.ts +2 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -1
- package/dist/core/sandbox/policy.js +2 -1
- package/dist/core/sandbox/policy.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-bash-service.d.ts +12 -0
- package/dist/core/session-bash-service.d.ts.map +1 -1
- package/dist/core/session-bash-service.js +14 -0
- package/dist/core/session-bash-service.js.map +1 -1
- package/dist/core/session-system-prompt.d.ts +30 -0
- package/dist/core/session-system-prompt.d.ts.map +1 -0
- package/dist/core/session-system-prompt.js +39 -0
- package/dist/core/session-system-prompt.js.map +1 -0
- package/dist/core/session-termination.d.ts +7 -2
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +29 -1
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/settings-manager.d.ts +12 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +36 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/strict-record.d.ts +2 -0
- package/dist/core/strict-record.d.ts.map +1 -0
- package/dist/core/strict-record.js +9 -0
- package/dist/core/strict-record.js.map +1 -0
- package/dist/core/subagent-lane-launcher.d.ts +64 -0
- package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
- package/dist/core/subagent-lane-launcher.js +107 -0
- package/dist/core/subagent-lane-launcher.js.map +1 -0
- package/dist/core/system-cpu-sampler.d.ts +45 -0
- package/dist/core/system-cpu-sampler.d.ts.map +1 -0
- package/dist/core/system-cpu-sampler.js +95 -0
- package/dist/core/system-cpu-sampler.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -8
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tool-pair-repair.d.ts +11 -0
- package/dist/core/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/tool-pair-repair.js +38 -0
- package/dist/core/tool-pair-repair.js.map +1 -0
- package/dist/core/tools/artifact-spill.d.ts +13 -0
- package/dist/core/tools/artifact-spill.d.ts.map +1 -0
- package/dist/core/tools/artifact-spill.js +26 -0
- package/dist/core/tools/artifact-spill.js.map +1 -0
- package/dist/core/tools/bash.d.ts +7 -4
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +8 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +10 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +2 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/workload-classifier.d.ts +32 -0
- package/dist/core/workload-classifier.d.ts.map +1 -0
- package/dist/core/workload-classifier.js +66 -0
- package/dist/core/workload-classifier.js.map +1 -0
- package/dist/core/workload-family-matcher.d.ts +3 -0
- package/dist/core/workload-family-matcher.d.ts.map +1 -0
- package/dist/core/workload-family-matcher.js +126 -0
- package/dist/core/workload-family-matcher.js.map +1 -0
- package/dist/core/workload-permit-pool.d.ts +78 -0
- package/dist/core/workload-permit-pool.d.ts.map +1 -0
- package/dist/core/workload-permit-pool.js +138 -0
- package/dist/core/workload-permit-pool.js.map +1 -0
- package/dist/core/workload-shard-executor.d.ts +68 -0
- package/dist/core/workload-shard-executor.d.ts.map +1 -0
- package/dist/core/workload-shard-executor.js +156 -0
- package/dist/core/workload-shard-executor.js.map +1 -0
- package/dist/core/workload-shard-plan.d.ts +81 -0
- package/dist/core/workload-shard-plan.d.ts.map +1 -0
- package/dist/core/workload-shard-plan.js +183 -0
- package/dist/core/workload-shard-plan.js.map +1 -0
- package/dist/core/workload-shard-runner.d.ts +11 -0
- package/dist/core/workload-shard-runner.d.ts.map +1 -0
- package/dist/core/workload-shard-runner.js +98 -0
- package/dist/core/workload-shard-runner.js.map +1 -0
- package/dist/core/workload-shard-store.d.ts +44 -0
- package/dist/core/workload-shard-store.d.ts.map +1 -0
- package/dist/core/workload-shard-store.js +189 -0
- package/dist/core/workload-shard-store.js.map +1 -0
- package/dist/core/workload-sharders.d.ts +47 -0
- package/dist/core/workload-sharders.d.ts.map +1 -0
- package/dist/core/workload-sharders.js +162 -0
- package/dist/core/workload-sharders.js.map +1 -0
- package/dist/core/workload-shell-scan.d.ts +12 -0
- package/dist/core/workload-shell-scan.d.ts.map +1 -0
- package/dist/core/workload-shell-scan.js +79 -0
- package/dist/core/workload-shell-scan.js.map +1 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +14 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +81 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +14 -3
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +1 -1
- package/docs/adaptorch-preview.md +2 -2
- package/docs/book-to-skill.md +1 -1
- package/docs/containerization.md +9 -0
- package/docs/docs.json +11 -3
- package/docs/environment-variables.md +11 -4
- package/docs/extensions.md +14 -1
- package/docs/grok-harness.md +35 -12
- package/docs/index.md +7 -6
- package/docs/loadout-domains/grok-harness.md +2 -2
- package/docs/models.md +10 -9
- package/docs/provider-resilience.md +9 -3
- package/docs/providers.md +9 -4
- package/docs/run-protocol.md +32 -0
- package/docs/sdk.md +100 -3
- package/docs/security.md +22 -32
- package/docs/sessions.md +14 -0
- package/docs/settings.md +64 -2
- package/docs/skills.md +16 -0
- package/docs/usage.md +37 -15
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +6 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
- package/examples/extensions/subagent/domain-profiles.ts +0 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +7 -9
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from "./policy.ts";
|
|
2
2
|
export type SandboxBackendType = "unsupported" | "seatbelt" | "bubblewrap";
|
|
3
|
+
export type SandboxBackendProbe = {
|
|
4
|
+
readonly platform: "macos";
|
|
5
|
+
readonly seatbeltAvailable: boolean;
|
|
6
|
+
} | {
|
|
7
|
+
readonly platform: "linux";
|
|
8
|
+
readonly bubblewrapAvailable: boolean;
|
|
9
|
+
readonly userNamespacesEnabled: boolean;
|
|
10
|
+
} | {
|
|
11
|
+
readonly platform: "unsupported";
|
|
12
|
+
readonly hostPlatform: string;
|
|
13
|
+
};
|
|
3
14
|
export interface SandboxInvocation {
|
|
4
15
|
readonly argv: readonly string[];
|
|
5
16
|
readonly cwd: string;
|
|
@@ -17,6 +28,7 @@ export interface SandboxInvocation {
|
|
|
17
28
|
readonly deniedDomains?: readonly string[];
|
|
18
29
|
};
|
|
19
30
|
}
|
|
31
|
+
export declare function classifySandboxBackendProbe(probe: SandboxBackendProbe): SandboxBackendStatus;
|
|
20
32
|
export declare function detectSandboxBackend(): SandboxBackendStatus;
|
|
21
33
|
export declare function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[];
|
|
22
34
|
export declare function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGrG,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,UAAU,GAAG,YAAY,CAAC;AAE3E,MAAM,MAAM,mBAAmB,GAC5B;IAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;CAAE,GACnE;IAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAA;CAAE,GAC9G;IAAE,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE;QACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;QACtC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;QACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;KACtC,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE;QACjB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;QAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KAC3C,CAAC;CACF;AA2BD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,CAmC5F;AAED,wBAAgB,oBAAoB,IAAI,oBAAoB,CAe3D;AAyCD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CAElF;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CA+CpF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CAQhH;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,oBAAoB,GAAG,eAAe,CAGjH","sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from \"./policy.ts\";\nimport { decideSandboxFallback } from \"./policy.ts\";\n\nexport type SandboxBackendType = \"unsupported\" | \"seatbelt\" | \"bubblewrap\";\n\nexport type SandboxBackendProbe =\n\t| { readonly platform: \"macos\"; readonly seatbeltAvailable: boolean }\n\t| { readonly platform: \"linux\"; readonly bubblewrapAvailable: boolean; readonly userNamespacesEnabled: boolean }\n\t| { readonly platform: \"unsupported\"; readonly hostPlatform: string };\n\nexport interface SandboxInvocation {\n\treadonly argv: readonly string[];\n\treadonly cwd: string;\n\treadonly env: Record<string, string>;\n\treadonly filesystem: {\n\t\treadonly root: string;\n\t\treadonly readAllow: readonly string[];\n\t\treadonly writeAllow: readonly string[];\n\t\treadonly tempWrite: readonly string[];\n\t\treadonly denyWrite: readonly string[];\n\t};\n\treadonly network: {\n\t\treadonly mode: NetworkMode;\n\t\treadonly allowedDomains?: readonly string[];\n\t\treadonly deniedDomains?: readonly string[];\n\t};\n}\n\nfunction commandExists(command: string): boolean {\n\ttry {\n\t\texecSync(`command -v ${command}`, { stdio: \"ignore\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction userNamespacesEnabled(): boolean {\n\ttry {\n\t\tconst value = execSync(\"sysctl -n kernel.unprivileged_userns_clone\", {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tencoding: \"utf8\",\n\t\t}).trim();\n\t\treturn value === \"1\";\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nfunction assertNever(value: never): never {\n\tthrow new Error(`Unexpected sandbox backend probe: ${JSON.stringify(value)}`);\n}\n\nexport function classifySandboxBackendProbe(probe: SandboxBackendProbe): SandboxBackendStatus {\n\tswitch (probe.platform) {\n\t\tcase \"macos\":\n\t\t\treturn {\n\t\t\t\tplatform: \"macos\",\n\t\t\t\tbackendAvailable: probe.seatbeltAvailable,\n\t\t\t\tdomainAllowlistAvailable: probe.seatbeltAvailable,\n\t\t\t\t...(probe.seatbeltAvailable ? {} : { unavailableReason: \"sandbox-exec is not installed or unavailable.\" }),\n\t\t\t};\n\t\tcase \"linux\": {\n\t\t\tconst backendAvailable = probe.bubblewrapAvailable && probe.userNamespacesEnabled;\n\t\t\tlet unavailableReason: string | undefined;\n\t\t\tif (!probe.bubblewrapAvailable) {\n\t\t\t\tunavailableReason = probe.userNamespacesEnabled\n\t\t\t\t\t? \"bwrap is not installed or unavailable.\"\n\t\t\t\t\t: \"bwrap is unavailable and unprivileged user namespaces are disabled.\";\n\t\t\t} else if (!probe.userNamespacesEnabled) {\n\t\t\t\tunavailableReason = \"Unprivileged user namespaces are disabled.\";\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tplatform: \"linux\",\n\t\t\t\tbackendAvailable,\n\t\t\t\tdomainAllowlistAvailable: false,\n\t\t\t\t...(unavailableReason ? { unavailableReason } : {}),\n\t\t\t};\n\t\t}\n\t\tcase \"unsupported\":\n\t\t\treturn {\n\t\t\t\tplatform: \"unsupported\",\n\t\t\t\tbackendAvailable: false,\n\t\t\t\tunavailableReason: `No supported sandbox backend exists for ${probe.hostPlatform}.`,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn assertNever(probe);\n\t}\n}\n\nexport function detectSandboxBackend(): SandboxBackendStatus {\n\tif (process.platform === \"darwin\") {\n\t\treturn classifySandboxBackendProbe({\n\t\t\tplatform: \"macos\",\n\t\t\tseatbeltAvailable: commandExists(\"sandbox-exec\"),\n\t\t});\n\t}\n\tif (process.platform === \"linux\") {\n\t\treturn classifySandboxBackendProbe({\n\t\t\tplatform: \"linux\",\n\t\t\tbubblewrapAvailable: commandExists(\"bwrap\"),\n\t\t\tuserNamespacesEnabled: userNamespacesEnabled(),\n\t\t});\n\t}\n\treturn classifySandboxBackendProbe({ platform: \"unsupported\", hostPlatform: process.platform });\n}\n\nfunction seatbeltLiteral(value: string): string {\n\treturn JSON.stringify(value);\n}\n\nfunction seatbeltProfile(invocation: SandboxInvocation): string {\n\tconst lines: string[] = [\"(version 1)\", \"(debug deny)\", \"(allow default)\"];\n\tconst writablePaths = [...new Set([...invocation.filesystem.writeAllow, ...invocation.filesystem.tempWrite])];\n\n\tif (writablePaths.length === 0) {\n\t\tlines.push(\"(deny file-write*)\");\n\t} else {\n\t\tlines.push(\"(deny file-write*\");\n\t\tfor (const path of writablePaths) {\n\t\t\tlines.push(` (require-not (subpath ${seatbeltLiteral(path)}))`);\n\t\t}\n\t\tlines.push(' (require-not (literal \"/dev/null\"))');\n\t\tlines.push(' (require-not (literal \"/dev/tty\"))');\n\t\tlines.push(\")\");\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\tlines.push(`(deny file-write* (subpath ${seatbeltLiteral(path)}))`);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\tlines.push(\"(deny network*)\");\n\t} else if (invocation.network.mode === \"domain-allowlist\" && invocation.network.allowedDomains) {\n\t\tlines.push(\"(deny network*)\");\n\t\tfor (const domain of invocation.network.allowedDomains) {\n\t\t\tlines.push(`(allow network-outbound (remote ${seatbeltLiteral(domain)}))`);\n\t\t}\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\tlines.push(\"(deny network*)\");\n\t\tlines.push('(allow network-outbound (remote \"localhost\"))');\n\t\tlines.push('(allow network-outbound (remote \"127.0.0.1\"))');\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[] {\n\treturn [\"sandbox-exec\", \"-p\", seatbeltProfile(invocation), ...invocation.argv];\n}\n\nexport function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[] {\n\tconst argv: string[] = [\n\t\t\"bwrap\",\n\t\t\"--die-with-parent\",\n\t\t\"--new-session\",\n\t\t\"--unshare-all\",\n\t\t\"--ro-bind\",\n\t\t\"/\",\n\t\t\"/\",\n\t\t\"--proc\",\n\t\t\"/proc\",\n\t\t\"--dev\",\n\t\t\"/dev\",\n\t];\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tif (path !== \"/\" && path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--ro-bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\targv.push(\"--bind\", path, path);\n\t}\n\targv.push(\"--bind\", invocation.filesystem.root, invocation.filesystem.root);\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\targv.push(\"--remount-ro\", path);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\targv.push(\"--unshare-net\");\n\t} else {\n\t\targv.push(\"--share-net\");\n\t}\n\n\targv.push(\"--chdir\", invocation.cwd);\n\targv.push(\"--clearenv\");\n\tfor (const [key, value] of Object.entries(invocation.env)) {\n\t\targv.push(\"--setenv\", key, value);\n\t}\n\n\targv.push(...invocation.argv);\n\treturn argv;\n}\n\nexport function buildBackendArgv(backend: SandboxBackendStatus, invocation: SandboxInvocation): readonly string[] {\n\tif (backend.platform === \"macos\" && backend.backendAvailable) {\n\t\treturn buildSeatbeltArgv(invocation);\n\t}\n\tif (backend.platform === \"linux\" && backend.backendAvailable) {\n\t\treturn buildBubblewrapArgv(invocation);\n\t}\n\tthrow new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);\n}\n\nexport function strictBackendMissingVerdict(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxDecision {\n\tconst { allowed, rule, reason } = decideSandboxFallback(policy, backend);\n\treturn { allowed, rule, reason };\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
+
import { decideSandboxFallback } from "./policy.js";
|
|
2
3
|
function commandExists(command) {
|
|
3
4
|
try {
|
|
4
5
|
execSync(`command -v ${command}`, { stdio: "ignore" });
|
|
@@ -20,28 +21,61 @@ function userNamespacesEnabled() {
|
|
|
20
21
|
return true;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
24
|
+
function assertNever(value) {
|
|
25
|
+
throw new Error(`Unexpected sandbox backend probe: ${JSON.stringify(value)}`);
|
|
26
|
+
}
|
|
27
|
+
export function classifySandboxBackendProbe(probe) {
|
|
28
|
+
switch (probe.platform) {
|
|
29
|
+
case "macos":
|
|
30
|
+
return {
|
|
31
|
+
platform: "macos",
|
|
32
|
+
backendAvailable: probe.seatbeltAvailable,
|
|
33
|
+
domainAllowlistAvailable: probe.seatbeltAvailable,
|
|
34
|
+
...(probe.seatbeltAvailable ? {} : { unavailableReason: "sandbox-exec is not installed or unavailable." }),
|
|
35
|
+
};
|
|
36
|
+
case "linux": {
|
|
37
|
+
const backendAvailable = probe.bubblewrapAvailable && probe.userNamespacesEnabled;
|
|
38
|
+
let unavailableReason;
|
|
39
|
+
if (!probe.bubblewrapAvailable) {
|
|
40
|
+
unavailableReason = probe.userNamespacesEnabled
|
|
41
|
+
? "bwrap is not installed or unavailable."
|
|
42
|
+
: "bwrap is unavailable and unprivileged user namespaces are disabled.";
|
|
43
|
+
}
|
|
44
|
+
else if (!probe.userNamespacesEnabled) {
|
|
45
|
+
unavailableReason = "Unprivileged user namespaces are disabled.";
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
platform: "linux",
|
|
49
|
+
backendAvailable,
|
|
50
|
+
domainAllowlistAvailable: false,
|
|
51
|
+
...(unavailableReason ? { unavailableReason } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
case "unsupported":
|
|
55
|
+
return {
|
|
56
|
+
platform: "unsupported",
|
|
57
|
+
backendAvailable: false,
|
|
58
|
+
unavailableReason: `No supported sandbox backend exists for ${probe.hostPlatform}.`,
|
|
59
|
+
};
|
|
60
|
+
default:
|
|
61
|
+
return assertNever(probe);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
23
64
|
export function detectSandboxBackend() {
|
|
24
65
|
if (process.platform === "darwin") {
|
|
25
|
-
|
|
26
|
-
return {
|
|
66
|
+
return classifySandboxBackendProbe({
|
|
27
67
|
platform: "macos",
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
68
|
+
seatbeltAvailable: commandExists("sandbox-exec"),
|
|
69
|
+
});
|
|
31
70
|
}
|
|
32
71
|
if (process.platform === "linux") {
|
|
33
|
-
|
|
34
|
-
const userns = userNamespacesEnabled();
|
|
35
|
-
return {
|
|
72
|
+
return classifySandboxBackendProbe({
|
|
36
73
|
platform: "linux",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
platform: "unsupported",
|
|
43
|
-
backendAvailable: false,
|
|
44
|
-
};
|
|
74
|
+
bubblewrapAvailable: commandExists("bwrap"),
|
|
75
|
+
userNamespacesEnabled: userNamespacesEnabled(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return classifySandboxBackendProbe({ platform: "unsupported", hostPlatform: process.platform });
|
|
45
79
|
}
|
|
46
80
|
function seatbeltLiteral(value) {
|
|
47
81
|
return JSON.stringify(value);
|
|
@@ -138,31 +172,7 @@ export function buildBackendArgv(backend, invocation) {
|
|
|
138
172
|
throw new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);
|
|
139
173
|
}
|
|
140
174
|
export function strictBackendMissingVerdict(policy, backend) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
allowed: true,
|
|
144
|
-
rule: "sandbox.off",
|
|
145
|
-
reason: "Sandbox policy is disabled.",
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
if (backend.backendAvailable) {
|
|
149
|
-
return {
|
|
150
|
-
allowed: true,
|
|
151
|
-
rule: "sandbox.backend_available",
|
|
152
|
-
reason: "Sandbox backend is available.",
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
if (policy.mode === "audit") {
|
|
156
|
-
return {
|
|
157
|
-
allowed: true,
|
|
158
|
-
rule: "sandbox.audit_fallback",
|
|
159
|
-
reason: "Audit mode permits execution without backend.",
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
allowed: false,
|
|
164
|
-
rule: "sandbox.backend_missing",
|
|
165
|
-
reason: "Enforce mode blocks shell and exec when no backend is available.",
|
|
166
|
-
};
|
|
175
|
+
const { allowed, rule, reason } = decideSandboxFallback(policy, backend);
|
|
176
|
+
return { allowed, rule, reason };
|
|
167
177
|
}
|
|
168
178
|
//# sourceMappingURL=backend.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from \"./policy.ts\";\n\nexport type SandboxBackendType = \"unsupported\" | \"seatbelt\" | \"bubblewrap\";\n\nexport interface SandboxInvocation {\n\treadonly argv: readonly string[];\n\treadonly cwd: string;\n\treadonly env: Record<string, string>;\n\treadonly filesystem: {\n\t\treadonly root: string;\n\t\treadonly readAllow: readonly string[];\n\t\treadonly writeAllow: readonly string[];\n\t\treadonly tempWrite: readonly string[];\n\t\treadonly denyWrite: readonly string[];\n\t};\n\treadonly network: {\n\t\treadonly mode: NetworkMode;\n\t\treadonly allowedDomains?: readonly string[];\n\t\treadonly deniedDomains?: readonly string[];\n\t};\n}\n\nfunction commandExists(command: string): boolean {\n\ttry {\n\t\texecSync(`command -v ${command}`, { stdio: \"ignore\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction userNamespacesEnabled(): boolean {\n\ttry {\n\t\tconst value = execSync(\"sysctl -n kernel.unprivileged_userns_clone\", {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tencoding: \"utf8\",\n\t\t}).trim();\n\t\treturn value === \"1\";\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nexport function detectSandboxBackend(): SandboxBackendStatus {\n\tif (process.platform === \"darwin\") {\n\t\tconst available = commandExists(\"sandbox-exec\");\n\t\treturn {\n\t\t\tplatform: \"macos\",\n\t\t\tbackendAvailable: available,\n\t\t\tdomainAllowlistAvailable: available,\n\t\t};\n\t}\n\tif (process.platform === \"linux\") {\n\t\tconst bwrap = commandExists(\"bwrap\");\n\t\tconst userns = userNamespacesEnabled();\n\t\treturn {\n\t\t\tplatform: \"linux\",\n\t\t\tbackendAvailable: bwrap && userns,\n\t\t\tdomainAllowlistAvailable: false,\n\t\t};\n\t}\n\treturn {\n\t\tplatform: \"unsupported\",\n\t\tbackendAvailable: false,\n\t};\n}\n\nfunction seatbeltLiteral(value: string): string {\n\treturn JSON.stringify(value);\n}\n\nfunction seatbeltProfile(invocation: SandboxInvocation): string {\n\tconst lines: string[] = [\"(version 1)\", \"(debug deny)\", \"(allow default)\"];\n\tconst writablePaths = [...new Set([...invocation.filesystem.writeAllow, ...invocation.filesystem.tempWrite])];\n\n\tif (writablePaths.length === 0) {\n\t\tlines.push(\"(deny file-write*)\");\n\t} else {\n\t\tlines.push(\"(deny file-write*\");\n\t\tfor (const path of writablePaths) {\n\t\t\tlines.push(` (require-not (subpath ${seatbeltLiteral(path)}))`);\n\t\t}\n\t\tlines.push(' (require-not (literal \"/dev/null\"))');\n\t\tlines.push(' (require-not (literal \"/dev/tty\"))');\n\t\tlines.push(\")\");\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\tlines.push(`(deny file-write* (subpath ${seatbeltLiteral(path)}))`);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\tlines.push(\"(deny network*)\");\n\t} else if (invocation.network.mode === \"domain-allowlist\" && invocation.network.allowedDomains) {\n\t\tlines.push(\"(deny network*)\");\n\t\tfor (const domain of invocation.network.allowedDomains) {\n\t\t\tlines.push(`(allow network-outbound (remote ${seatbeltLiteral(domain)}))`);\n\t\t}\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\tlines.push(\"(deny network*)\");\n\t\tlines.push('(allow network-outbound (remote \"localhost\"))');\n\t\tlines.push('(allow network-outbound (remote \"127.0.0.1\"))');\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[] {\n\treturn [\"sandbox-exec\", \"-p\", seatbeltProfile(invocation), ...invocation.argv];\n}\n\nexport function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[] {\n\tconst argv: string[] = [\n\t\t\"bwrap\",\n\t\t\"--die-with-parent\",\n\t\t\"--new-session\",\n\t\t\"--unshare-all\",\n\t\t\"--ro-bind\",\n\t\t\"/\",\n\t\t\"/\",\n\t\t\"--proc\",\n\t\t\"/proc\",\n\t\t\"--dev\",\n\t\t\"/dev\",\n\t];\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tif (path !== \"/\" && path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--ro-bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\targv.push(\"--bind\", path, path);\n\t}\n\targv.push(\"--bind\", invocation.filesystem.root, invocation.filesystem.root);\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\targv.push(\"--remount-ro\", path);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\targv.push(\"--unshare-net\");\n\t} else {\n\t\targv.push(\"--share-net\");\n\t}\n\n\targv.push(\"--chdir\", invocation.cwd);\n\targv.push(\"--clearenv\");\n\tfor (const [key, value] of Object.entries(invocation.env)) {\n\t\targv.push(\"--setenv\", key, value);\n\t}\n\n\targv.push(...invocation.argv);\n\treturn argv;\n}\n\nexport function buildBackendArgv(backend: SandboxBackendStatus, invocation: SandboxInvocation): readonly string[] {\n\tif (backend.platform === \"macos\" && backend.backendAvailable) {\n\t\treturn buildSeatbeltArgv(invocation);\n\t}\n\tif (backend.platform === \"linux\" && backend.backendAvailable) {\n\t\treturn buildBubblewrapArgv(invocation);\n\t}\n\tthrow new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);\n}\n\nexport function strictBackendMissingVerdict(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.off\",\n\t\t\treason: \"Sandbox policy is disabled.\",\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.backend_available\",\n\t\t\treason: \"Sandbox backend is available.\",\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.audit_fallback\",\n\t\t\treason: \"Audit mode permits execution without backend.\",\n\t\t};\n\t}\n\treturn {\n\t\tallowed: false,\n\t\trule: \"sandbox.backend_missing\",\n\t\treason: \"Enforce mode blocks shell and exec when no backend is available.\",\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from \"./policy.ts\";\nimport { decideSandboxFallback } from \"./policy.ts\";\n\nexport type SandboxBackendType = \"unsupported\" | \"seatbelt\" | \"bubblewrap\";\n\nexport type SandboxBackendProbe =\n\t| { readonly platform: \"macos\"; readonly seatbeltAvailable: boolean }\n\t| { readonly platform: \"linux\"; readonly bubblewrapAvailable: boolean; readonly userNamespacesEnabled: boolean }\n\t| { readonly platform: \"unsupported\"; readonly hostPlatform: string };\n\nexport interface SandboxInvocation {\n\treadonly argv: readonly string[];\n\treadonly cwd: string;\n\treadonly env: Record<string, string>;\n\treadonly filesystem: {\n\t\treadonly root: string;\n\t\treadonly readAllow: readonly string[];\n\t\treadonly writeAllow: readonly string[];\n\t\treadonly tempWrite: readonly string[];\n\t\treadonly denyWrite: readonly string[];\n\t};\n\treadonly network: {\n\t\treadonly mode: NetworkMode;\n\t\treadonly allowedDomains?: readonly string[];\n\t\treadonly deniedDomains?: readonly string[];\n\t};\n}\n\nfunction commandExists(command: string): boolean {\n\ttry {\n\t\texecSync(`command -v ${command}`, { stdio: \"ignore\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction userNamespacesEnabled(): boolean {\n\ttry {\n\t\tconst value = execSync(\"sysctl -n kernel.unprivileged_userns_clone\", {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tencoding: \"utf8\",\n\t\t}).trim();\n\t\treturn value === \"1\";\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nfunction assertNever(value: never): never {\n\tthrow new Error(`Unexpected sandbox backend probe: ${JSON.stringify(value)}`);\n}\n\nexport function classifySandboxBackendProbe(probe: SandboxBackendProbe): SandboxBackendStatus {\n\tswitch (probe.platform) {\n\t\tcase \"macos\":\n\t\t\treturn {\n\t\t\t\tplatform: \"macos\",\n\t\t\t\tbackendAvailable: probe.seatbeltAvailable,\n\t\t\t\tdomainAllowlistAvailable: probe.seatbeltAvailable,\n\t\t\t\t...(probe.seatbeltAvailable ? {} : { unavailableReason: \"sandbox-exec is not installed or unavailable.\" }),\n\t\t\t};\n\t\tcase \"linux\": {\n\t\t\tconst backendAvailable = probe.bubblewrapAvailable && probe.userNamespacesEnabled;\n\t\t\tlet unavailableReason: string | undefined;\n\t\t\tif (!probe.bubblewrapAvailable) {\n\t\t\t\tunavailableReason = probe.userNamespacesEnabled\n\t\t\t\t\t? \"bwrap is not installed or unavailable.\"\n\t\t\t\t\t: \"bwrap is unavailable and unprivileged user namespaces are disabled.\";\n\t\t\t} else if (!probe.userNamespacesEnabled) {\n\t\t\t\tunavailableReason = \"Unprivileged user namespaces are disabled.\";\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tplatform: \"linux\",\n\t\t\t\tbackendAvailable,\n\t\t\t\tdomainAllowlistAvailable: false,\n\t\t\t\t...(unavailableReason ? { unavailableReason } : {}),\n\t\t\t};\n\t\t}\n\t\tcase \"unsupported\":\n\t\t\treturn {\n\t\t\t\tplatform: \"unsupported\",\n\t\t\t\tbackendAvailable: false,\n\t\t\t\tunavailableReason: `No supported sandbox backend exists for ${probe.hostPlatform}.`,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn assertNever(probe);\n\t}\n}\n\nexport function detectSandboxBackend(): SandboxBackendStatus {\n\tif (process.platform === \"darwin\") {\n\t\treturn classifySandboxBackendProbe({\n\t\t\tplatform: \"macos\",\n\t\t\tseatbeltAvailable: commandExists(\"sandbox-exec\"),\n\t\t});\n\t}\n\tif (process.platform === \"linux\") {\n\t\treturn classifySandboxBackendProbe({\n\t\t\tplatform: \"linux\",\n\t\t\tbubblewrapAvailable: commandExists(\"bwrap\"),\n\t\t\tuserNamespacesEnabled: userNamespacesEnabled(),\n\t\t});\n\t}\n\treturn classifySandboxBackendProbe({ platform: \"unsupported\", hostPlatform: process.platform });\n}\n\nfunction seatbeltLiteral(value: string): string {\n\treturn JSON.stringify(value);\n}\n\nfunction seatbeltProfile(invocation: SandboxInvocation): string {\n\tconst lines: string[] = [\"(version 1)\", \"(debug deny)\", \"(allow default)\"];\n\tconst writablePaths = [...new Set([...invocation.filesystem.writeAllow, ...invocation.filesystem.tempWrite])];\n\n\tif (writablePaths.length === 0) {\n\t\tlines.push(\"(deny file-write*)\");\n\t} else {\n\t\tlines.push(\"(deny file-write*\");\n\t\tfor (const path of writablePaths) {\n\t\t\tlines.push(` (require-not (subpath ${seatbeltLiteral(path)}))`);\n\t\t}\n\t\tlines.push(' (require-not (literal \"/dev/null\"))');\n\t\tlines.push(' (require-not (literal \"/dev/tty\"))');\n\t\tlines.push(\")\");\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\tlines.push(`(deny file-write* (subpath ${seatbeltLiteral(path)}))`);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\tlines.push(\"(deny network*)\");\n\t} else if (invocation.network.mode === \"domain-allowlist\" && invocation.network.allowedDomains) {\n\t\tlines.push(\"(deny network*)\");\n\t\tfor (const domain of invocation.network.allowedDomains) {\n\t\t\tlines.push(`(allow network-outbound (remote ${seatbeltLiteral(domain)}))`);\n\t\t}\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\tlines.push(\"(deny network*)\");\n\t\tlines.push('(allow network-outbound (remote \"localhost\"))');\n\t\tlines.push('(allow network-outbound (remote \"127.0.0.1\"))');\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[] {\n\treturn [\"sandbox-exec\", \"-p\", seatbeltProfile(invocation), ...invocation.argv];\n}\n\nexport function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[] {\n\tconst argv: string[] = [\n\t\t\"bwrap\",\n\t\t\"--die-with-parent\",\n\t\t\"--new-session\",\n\t\t\"--unshare-all\",\n\t\t\"--ro-bind\",\n\t\t\"/\",\n\t\t\"/\",\n\t\t\"--proc\",\n\t\t\"/proc\",\n\t\t\"--dev\",\n\t\t\"/dev\",\n\t];\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tif (path !== \"/\" && path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--ro-bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\targv.push(\"--bind\", path, path);\n\t}\n\targv.push(\"--bind\", invocation.filesystem.root, invocation.filesystem.root);\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\targv.push(\"--remount-ro\", path);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\targv.push(\"--unshare-net\");\n\t} else {\n\t\targv.push(\"--share-net\");\n\t}\n\n\targv.push(\"--chdir\", invocation.cwd);\n\targv.push(\"--clearenv\");\n\tfor (const [key, value] of Object.entries(invocation.env)) {\n\t\targv.push(\"--setenv\", key, value);\n\t}\n\n\targv.push(...invocation.argv);\n\treturn argv;\n}\n\nexport function buildBackendArgv(backend: SandboxBackendStatus, invocation: SandboxInvocation): readonly string[] {\n\tif (backend.platform === \"macos\" && backend.backendAvailable) {\n\t\treturn buildSeatbeltArgv(invocation);\n\t}\n\tif (backend.platform === \"linux\" && backend.backendAvailable) {\n\t\treturn buildBubblewrapArgv(invocation);\n\t}\n\tthrow new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);\n}\n\nexport function strictBackendMissingVerdict(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxDecision {\n\tconst { allowed, rule, reason } = decideSandboxFallback(policy, backend);\n\treturn { allowed, rule, reason };\n}\n"]}
|
|
@@ -57,6 +57,8 @@ export interface SandboxBackendStatus {
|
|
|
57
57
|
platform: SandboxPlatform;
|
|
58
58
|
backendAvailable: boolean;
|
|
59
59
|
domainAllowlistAvailable?: boolean;
|
|
60
|
+
/** Human-readable probe detail when the backend cannot enforce the policy. */
|
|
61
|
+
unavailableReason?: string;
|
|
60
62
|
}
|
|
61
63
|
export interface SandboxFallbackDecision extends SandboxDecision {
|
|
62
64
|
allowShell: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\n\nexport type SandboxMode = \"off\" | \"audit\" | \"enforce\";\nexport type SandboxProfile = \"readonly\" | \"workspace-write\" | \"dev-server\" | \"networked\";\nexport type NetworkMode = \"none\" | \"loopback\" | \"domain-allowlist\" | \"all-explicit\";\nexport type PathAccessKind = \"read\" | \"write\";\nexport type SandboxPlatform = \"linux\" | \"macos\" | \"unsupported\";\n\nexport interface SandboxPolicy {\n\tmode: SandboxMode;\n\tprofile: SandboxProfile;\n\tfilesystem: {\n\t\troot: string;\n\t\treadAllow: readonly string[];\n\t\treadDeny: readonly string[];\n\t\twriteAllow: readonly string[];\n\t\tdenyWrite: readonly string[];\n\t\ttempWrite: readonly string[];\n\t\tfollowSymlinks: false;\n\t};\n\tnetwork: {\n\t\tmode: NetworkMode;\n\t\tallowedDomains: readonly string[];\n\t\tdeniedDomains: readonly string[];\n\t\tallowUnixSockets: readonly string[];\n\t\tallowBrowser: false;\n\t};\n\tprocess: {\n\t\tallowExec: boolean;\n\t\tallowShell: boolean;\n\t\tallowPrivilege: false;\n\t};\n}\n\nexport interface ResolvedSandboxPath {\n\trequestedPath: string;\n\texists: boolean;\n\trealPath?: string;\n\tnearestExistingParentRealPath?: string;\n\tisSymlink?: boolean;\n\terror?: string;\n}\n\nexport type SandboxPathResolver = (requestPath: string) => ResolvedSandboxPath;\n\nexport interface PathAccessRequest {\n\tkind: PathAccessKind;\n\tpath: string;\n}\n\nexport interface SandboxDecision {\n\tallowed: boolean;\n\trule: string;\n\treason: string;\n}\n\nexport interface NetworkAccessRequest {\n\thost?: string;\n\turl?: string;\n\tunixSocketPath?: string;\n\tbrowser?: boolean;\n\tloopback?: boolean;\n}\n\nexport interface SandboxBackendStatus {\n\tplatform: SandboxPlatform;\n\tbackendAvailable: boolean;\n\tdomainAllowlistAvailable?: boolean;\n}\n\nexport interface SandboxFallbackDecision extends SandboxDecision {\n\tallowShell: boolean;\n\tallowExec: boolean;\n\tallowReadOnlyTools: boolean;\n}\n\nconst SENSITIVE_PATH_PATTERN =\n\t/(?:^|[/\\\\])(?:\\.env(?:\\..*)?|auth\\.json|oauth\\.json|\\.netrc|\\.npmrc|\\.pgpass|credentials|id_rsa|id_dsa|id_ecdsa|id_ed25519|.*(?:secret|token|private[_-]?key|credential).*)$/i;\n\nfunction decision(allowed: boolean, rule: string, reason: string): SandboxDecision {\n\treturn { allowed, rule, reason };\n}\n\nfunction normalize(value: string): string {\n\treturn path\n\t\t.resolve(value)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.replace(/\\/+$/g, (suffix) => (value === suffix ? \"/\" : \"\"));\n}\n\nfunction isInside(parent: string, child: string): boolean {\n\tconst normalizedParent = normalize(parent);\n\tconst normalizedChild = normalize(child);\n\treturn normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}/`);\n}\n\nfunction isSensitivePath(value: string): boolean {\n\treturn SENSITIVE_PATH_PATTERN.test(value.replace(/\\\\/g, \"/\"));\n}\n\nfunction stripGlobSuffix(pattern: string): string {\n\tif (pattern.endsWith(\"/**\")) return pattern.slice(0, -3);\n\tif (pattern.endsWith(\"/*\")) return pattern.slice(0, -2);\n\treturn pattern;\n}\n\nfunction matchesPattern(pattern: string, candidate: string): boolean {\n\tconst normalizedPattern = normalize(stripGlobSuffix(pattern));\n\tconst normalizedCandidate = normalize(candidate);\n\treturn normalizedCandidate === normalizedPattern || normalizedCandidate.startsWith(`${normalizedPattern}/`);\n}\n\nfunction matchesAny(patterns: readonly string[], candidate: string): boolean {\n\treturn patterns.some((pattern) => matchesPattern(pattern, candidate));\n}\n\nfunction resolvedDecisionPath(resolved: ResolvedSandboxPath): string | undefined {\n\treturn resolved.realPath ?? resolved.nearestExistingParentRealPath;\n}\n\nfunction rootEscaped(policy: SandboxPolicy, resolved: ResolvedSandboxPath): boolean {\n\tconst candidate = resolvedDecisionPath(resolved);\n\treturn candidate === undefined || !isInside(policy.filesystem.root, candidate);\n}\n\nexport function decidePathAccess(\n\tpolicy: SandboxPolicy,\n\trequest: PathAccessRequest,\n\tresolver: SandboxPathResolver,\n): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\t}\n\n\tif (request.path.includes(\"\\0\")) {\n\t\treturn decision(false, \"path.invalid\", \"Path contains a NUL byte.\");\n\t}\n\n\tif (isSensitivePath(request.path)) {\n\t\treturn decision(false, \"path.secret\", \"Sensitive credential-like paths are denied.\");\n\t}\n\n\tconst resolved = resolver(request.path);\n\tif (resolved.error) {\n\t\treturn decision(false, \"path.resolve_error\", resolved.error);\n\t}\n\tif (rootEscaped(policy, resolved)) {\n\t\treturn decision(false, \"path.root_escape\", \"Resolved path is outside the sandbox root.\");\n\t}\n\tif (resolved.isSymlink && !policy.filesystem.followSymlinks) {\n\t\treturn decision(false, \"path.symlink\", \"Symlink traversal is denied by policy.\");\n\t}\n\n\tconst candidate = resolvedDecisionPath(resolved) ?? request.path;\n\tif (request.kind === \"read\") {\n\t\tif (matchesAny(policy.filesystem.readDeny, candidate) || matchesAny(policy.filesystem.readDeny, request.path)) {\n\t\t\treturn decision(false, \"path.read_deny\", \"Read path matches a deny rule.\");\n\t\t}\n\t\tif (!matchesAny(policy.filesystem.readAllow, candidate)) {\n\t\t\treturn decision(false, \"path.read_not_allowed\", \"Read path is not in an allow rule.\");\n\t\t}\n\t\treturn decision(true, \"path.read_allow\", \"Read path is allowed.\");\n\t}\n\n\tif (matchesAny(policy.filesystem.denyWrite, candidate) || matchesAny(policy.filesystem.denyWrite, request.path)) {\n\t\treturn decision(false, \"path.deny_write\", \"Write path matches an unoverrideable denyWrite rule.\");\n\t}\n\tif (matchesAny(policy.filesystem.writeAllow, candidate) || matchesAny(policy.filesystem.tempWrite, candidate)) {\n\t\treturn decision(true, \"path.write_allow\", \"Write path is allowed.\");\n\t}\n\treturn decision(false, \"path.write_not_allowed\", \"Write path is not in an allow rule.\");\n}\n\nfunction hostFromRequest(request: NetworkAccessRequest): string | undefined {\n\tif (request.host) return request.host.toLowerCase();\n\tif (!request.url) return undefined;\n\ttry {\n\t\treturn new URL(request.url).hostname.toLowerCase();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction hostMatches(patterns: readonly string[], host: string): boolean {\n\treturn patterns.some((pattern) => {\n\t\tconst normalized = pattern.toLowerCase();\n\t\treturn host === normalized || host.endsWith(`.${normalized}`);\n\t});\n}\n\nexport function decideNetworkAccess(policy: SandboxPolicy, request: NetworkAccessRequest): SandboxDecision {\n\tif (policy.mode === \"off\") return decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\tif (request.browser || request.unixSocketPath) {\n\t\treturn decision(false, \"network.socket_or_browser\", \"Browser and Unix socket access is denied by default.\");\n\t}\n\tif (policy.network.mode === \"none\") {\n\t\treturn decision(false, \"network.none\", \"Network access is disabled by policy.\");\n\t}\n\tif (request.loopback) {\n\t\treturn policy.network.mode === \"loopback\" ||\n\t\t\tpolicy.network.mode === \"domain-allowlist\" ||\n\t\t\tpolicy.network.mode === \"all-explicit\"\n\t\t\t? decision(true, \"network.loopback\", \"Loopback access is allowed.\")\n\t\t\t: decision(false, \"network.loopback_denied\", \"Loopback access is denied.\");\n\t}\n\tconst host = hostFromRequest(request);\n\tif (!host) return decision(false, \"network.unknown_host\", \"Network host could not be resolved from request.\");\n\tif (hostMatches(policy.network.deniedDomains, host)) {\n\t\treturn decision(false, \"network.domain_deny\", \"Host matches a denied domain.\");\n\t}\n\tif (policy.network.mode === \"all-explicit\") {\n\t\treturn decision(true, \"network.explicit_all\", \"Explicit network profile allows outbound host access.\");\n\t}\n\tif (policy.network.mode === \"domain-allowlist\" && hostMatches(policy.network.allowedDomains, host)) {\n\t\treturn decision(true, \"network.domain_allow\", \"Host matches an allowed domain.\");\n\t}\n\treturn decision(false, \"network.domain_not_allowed\", \"Host is not allowed by network policy.\");\n}\n\nexport function decideSandboxFallback(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxFallbackDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.backend_available\", \"Sandbox backend is available.\"),\n\t\t\tallowShell: policy.process.allowShell,\n\t\t\tallowExec: policy.process.allowExec,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.audit_fallback\", \"Audit mode permits execution without backend.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(false, \"sandbox.backend_missing\", \"Enforce mode blocks shell and exec when no backend is available.\"),\n\t\tallowShell: false,\n\t\tallowExec: false,\n\t\tallowReadOnlyTools: true,\n\t};\n}\n\nexport function mergeSandboxPolicy(\n\tbase: SandboxPolicy,\n\toverride: Partial<SandboxPolicy>,\n\toptions: { allowBroaden?: boolean } = {},\n): SandboxPolicy {\n\tconst allowBroaden = options.allowBroaden === true;\n\treturn {\n\t\tmode: override.mode ?? base.mode,\n\t\tprofile: override.profile ?? base.profile,\n\t\tfilesystem: {\n\t\t\troot: override.filesystem?.root ?? base.filesystem.root,\n\t\t\treadAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.readAllow ?? base.filesystem.readAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.readAllow, override.filesystem?.readAllow),\n\t\t\treadDeny: union(base.filesystem.readDeny, override.filesystem?.readDeny),\n\t\t\twriteAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.writeAllow ?? base.filesystem.writeAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.writeAllow, override.filesystem?.writeAllow),\n\t\t\tdenyWrite: union(base.filesystem.denyWrite, override.filesystem?.denyWrite),\n\t\t\ttempWrite: allowBroaden\n\t\t\t\t? (override.filesystem?.tempWrite ?? base.filesystem.tempWrite)\n\t\t\t\t: intersectOrBase(base.filesystem.tempWrite, override.filesystem?.tempWrite),\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: allowBroaden\n\t\t\t\t? (override.network?.mode ?? base.network.mode)\n\t\t\t\t: narrowNetworkMode(base.network.mode, override.network?.mode),\n\t\t\tallowedDomains: allowBroaden\n\t\t\t\t? (override.network?.allowedDomains ?? base.network.allowedDomains)\n\t\t\t\t: intersectOrBase(base.network.allowedDomains, override.network?.allowedDomains),\n\t\t\tdeniedDomains: union(base.network.deniedDomains, override.network?.deniedDomains),\n\t\t\tallowUnixSockets: allowBroaden\n\t\t\t\t? (override.network?.allowUnixSockets ?? base.network.allowUnixSockets)\n\t\t\t\t: intersectOrBase(base.network.allowUnixSockets, override.network?.allowUnixSockets),\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: allowBroaden\n\t\t\t\t? (override.process?.allowExec ?? base.process.allowExec)\n\t\t\t\t: base.process.allowExec && (override.process?.allowExec ?? true),\n\t\t\tallowShell: allowBroaden\n\t\t\t\t? (override.process?.allowShell ?? base.process.allowShell)\n\t\t\t\t: base.process.allowShell && (override.process?.allowShell ?? true),\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n\nfunction union(base: readonly string[], override: readonly string[] | undefined): string[] {\n\treturn [...new Set([...base, ...(override ?? [])])];\n}\n\nfunction intersectOrBase(base: readonly string[], override: readonly string[] | undefined): string[] {\n\tif (override === undefined) return [...base];\n\tconst result: string[] = [];\n\tfor (const candidate of override) {\n\t\tfor (const entry of base) {\n\t\t\tif (normalize(candidate) === normalize(entry)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(entry, candidate)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(candidate, entry)) {\n\t\t\t\tresult.push(entry);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...new Set(result)];\n}\n\nfunction narrowNetworkMode(base: NetworkMode, override: NetworkMode | undefined): NetworkMode {\n\tif (override === undefined) return base;\n\tconst rank: Record<NetworkMode, number> = { none: 0, loopback: 1, \"domain-allowlist\": 2, \"all-explicit\": 3 };\n\treturn rank[override] <= rank[base] ? override : base;\n}\n\nexport interface BashSpawnPreflightContext {\n\tcommand: string;\n\tcwd: string;\n}\n\nexport interface BashSpawnPreflightDecision extends SandboxDecision {\n\tallowShell: boolean;\n}\n\n/**\n * Final pure preflight for a local bash spawn. Combines the backend-availability\n * fallback (deny shell when an OS sandbox backend is missing under enforce mode)\n * with a working-directory containment check (deny when cwd resolves outside the\n * sandbox root). This helper does not itself wrap the process in an OS sandbox; it\n * only decides whether the spawn may proceed.\n */\nexport function preflightBashSpawn(\n\tpolicy: SandboxPolicy,\n\tbackend: SandboxBackendStatus,\n\tcontext: BashSpawnPreflightContext,\n\tresolver?: SandboxPathResolver,\n): BashSpawnPreflightDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn { ...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"), allowShell: true };\n\t}\n\tif (!policy.process.allowShell) {\n\t\treturn {\n\t\t\t...decision(false, \"process.shell_denied\", \"Shell execution is disabled by policy.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst fallback = decideSandboxFallback(policy, backend);\n\tif (!fallback.allowShell) {\n\t\treturn { ...decision(false, fallback.rule, fallback.reason), allowShell: false };\n\t}\n\tif (context.cwd.includes(\"\\0\")) {\n\t\treturn { ...decision(false, \"cwd.invalid\", \"Working directory contains a NUL byte.\"), allowShell: false };\n\t}\n\tconst resolved = resolver?.(context.cwd);\n\tif (resolved?.error) {\n\t\treturn { ...decision(false, \"cwd.resolve_error\", resolved.error), allowShell: false };\n\t}\n\tif (resolved?.isSymlink) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.symlink\", \"Working directory is a symlink and traversal is denied.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst candidate = resolved ? (resolvedDecisionPath(resolved) ?? context.cwd) : context.cwd;\n\tif (!isInside(policy.filesystem.root, candidate)) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.root_escape\", \"Working directory is outside the sandbox root.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(true, \"sandbox.shell_preflight_ok\", \"Bash spawn is permitted by sandbox policy.\"),\n\t\tallowShell: true,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\n\nexport type SandboxMode = \"off\" | \"audit\" | \"enforce\";\nexport type SandboxProfile = \"readonly\" | \"workspace-write\" | \"dev-server\" | \"networked\";\nexport type NetworkMode = \"none\" | \"loopback\" | \"domain-allowlist\" | \"all-explicit\";\nexport type PathAccessKind = \"read\" | \"write\";\nexport type SandboxPlatform = \"linux\" | \"macos\" | \"unsupported\";\n\nexport interface SandboxPolicy {\n\tmode: SandboxMode;\n\tprofile: SandboxProfile;\n\tfilesystem: {\n\t\troot: string;\n\t\treadAllow: readonly string[];\n\t\treadDeny: readonly string[];\n\t\twriteAllow: readonly string[];\n\t\tdenyWrite: readonly string[];\n\t\ttempWrite: readonly string[];\n\t\tfollowSymlinks: false;\n\t};\n\tnetwork: {\n\t\tmode: NetworkMode;\n\t\tallowedDomains: readonly string[];\n\t\tdeniedDomains: readonly string[];\n\t\tallowUnixSockets: readonly string[];\n\t\tallowBrowser: false;\n\t};\n\tprocess: {\n\t\tallowExec: boolean;\n\t\tallowShell: boolean;\n\t\tallowPrivilege: false;\n\t};\n}\n\nexport interface ResolvedSandboxPath {\n\trequestedPath: string;\n\texists: boolean;\n\trealPath?: string;\n\tnearestExistingParentRealPath?: string;\n\tisSymlink?: boolean;\n\terror?: string;\n}\n\nexport type SandboxPathResolver = (requestPath: string) => ResolvedSandboxPath;\n\nexport interface PathAccessRequest {\n\tkind: PathAccessKind;\n\tpath: string;\n}\n\nexport interface SandboxDecision {\n\tallowed: boolean;\n\trule: string;\n\treason: string;\n}\n\nexport interface NetworkAccessRequest {\n\thost?: string;\n\turl?: string;\n\tunixSocketPath?: string;\n\tbrowser?: boolean;\n\tloopback?: boolean;\n}\n\nexport interface SandboxBackendStatus {\n\tplatform: SandboxPlatform;\n\tbackendAvailable: boolean;\n\tdomainAllowlistAvailable?: boolean;\n\t/** Human-readable probe detail when the backend cannot enforce the policy. */\n\tunavailableReason?: string;\n}\n\nexport interface SandboxFallbackDecision extends SandboxDecision {\n\tallowShell: boolean;\n\tallowExec: boolean;\n\tallowReadOnlyTools: boolean;\n}\n\nconst SENSITIVE_PATH_PATTERN =\n\t/(?:^|[/\\\\])(?:\\.env(?:\\..*)?|auth\\.json|oauth\\.json|\\.netrc|\\.npmrc|\\.pgpass|credentials|id_rsa|id_dsa|id_ecdsa|id_ed25519|.*(?:secret|token|private[_-]?key|credential).*)$/i;\n\nfunction decision(allowed: boolean, rule: string, reason: string): SandboxDecision {\n\treturn { allowed, rule, reason };\n}\n\nfunction normalize(value: string): string {\n\treturn path\n\t\t.resolve(value)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.replace(/\\/+$/g, (suffix) => (value === suffix ? \"/\" : \"\"));\n}\n\nfunction isInside(parent: string, child: string): boolean {\n\tconst normalizedParent = normalize(parent);\n\tconst normalizedChild = normalize(child);\n\treturn normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}/`);\n}\n\nfunction isSensitivePath(value: string): boolean {\n\treturn SENSITIVE_PATH_PATTERN.test(value.replace(/\\\\/g, \"/\"));\n}\n\nfunction stripGlobSuffix(pattern: string): string {\n\tif (pattern.endsWith(\"/**\")) return pattern.slice(0, -3);\n\tif (pattern.endsWith(\"/*\")) return pattern.slice(0, -2);\n\treturn pattern;\n}\n\nfunction matchesPattern(pattern: string, candidate: string): boolean {\n\tconst normalizedPattern = normalize(stripGlobSuffix(pattern));\n\tconst normalizedCandidate = normalize(candidate);\n\treturn normalizedCandidate === normalizedPattern || normalizedCandidate.startsWith(`${normalizedPattern}/`);\n}\n\nfunction matchesAny(patterns: readonly string[], candidate: string): boolean {\n\treturn patterns.some((pattern) => matchesPattern(pattern, candidate));\n}\n\nfunction resolvedDecisionPath(resolved: ResolvedSandboxPath): string | undefined {\n\treturn resolved.realPath ?? resolved.nearestExistingParentRealPath;\n}\n\nfunction rootEscaped(policy: SandboxPolicy, resolved: ResolvedSandboxPath): boolean {\n\tconst candidate = resolvedDecisionPath(resolved);\n\treturn candidate === undefined || !isInside(policy.filesystem.root, candidate);\n}\n\nexport function decidePathAccess(\n\tpolicy: SandboxPolicy,\n\trequest: PathAccessRequest,\n\tresolver: SandboxPathResolver,\n): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\t}\n\n\tif (request.path.includes(\"\\0\")) {\n\t\treturn decision(false, \"path.invalid\", \"Path contains a NUL byte.\");\n\t}\n\n\tif (isSensitivePath(request.path)) {\n\t\treturn decision(false, \"path.secret\", \"Sensitive credential-like paths are denied.\");\n\t}\n\n\tconst resolved = resolver(request.path);\n\tif (resolved.error) {\n\t\treturn decision(false, \"path.resolve_error\", resolved.error);\n\t}\n\tif (rootEscaped(policy, resolved)) {\n\t\treturn decision(false, \"path.root_escape\", \"Resolved path is outside the sandbox root.\");\n\t}\n\tif (resolved.isSymlink && !policy.filesystem.followSymlinks) {\n\t\treturn decision(false, \"path.symlink\", \"Symlink traversal is denied by policy.\");\n\t}\n\n\tconst candidate = resolvedDecisionPath(resolved) ?? request.path;\n\tif (request.kind === \"read\") {\n\t\tif (matchesAny(policy.filesystem.readDeny, candidate) || matchesAny(policy.filesystem.readDeny, request.path)) {\n\t\t\treturn decision(false, \"path.read_deny\", \"Read path matches a deny rule.\");\n\t\t}\n\t\tif (!matchesAny(policy.filesystem.readAllow, candidate)) {\n\t\t\treturn decision(false, \"path.read_not_allowed\", \"Read path is not in an allow rule.\");\n\t\t}\n\t\treturn decision(true, \"path.read_allow\", \"Read path is allowed.\");\n\t}\n\n\tif (matchesAny(policy.filesystem.denyWrite, candidate) || matchesAny(policy.filesystem.denyWrite, request.path)) {\n\t\treturn decision(false, \"path.deny_write\", \"Write path matches an unoverrideable denyWrite rule.\");\n\t}\n\tif (matchesAny(policy.filesystem.writeAllow, candidate) || matchesAny(policy.filesystem.tempWrite, candidate)) {\n\t\treturn decision(true, \"path.write_allow\", \"Write path is allowed.\");\n\t}\n\treturn decision(false, \"path.write_not_allowed\", \"Write path is not in an allow rule.\");\n}\n\nfunction hostFromRequest(request: NetworkAccessRequest): string | undefined {\n\tif (request.host) return request.host.toLowerCase();\n\tif (!request.url) return undefined;\n\ttry {\n\t\treturn new URL(request.url).hostname.toLowerCase();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction hostMatches(patterns: readonly string[], host: string): boolean {\n\treturn patterns.some((pattern) => {\n\t\tconst normalized = pattern.toLowerCase();\n\t\treturn host === normalized || host.endsWith(`.${normalized}`);\n\t});\n}\n\nexport function decideNetworkAccess(policy: SandboxPolicy, request: NetworkAccessRequest): SandboxDecision {\n\tif (policy.mode === \"off\") return decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\tif (request.browser || request.unixSocketPath) {\n\t\treturn decision(false, \"network.socket_or_browser\", \"Browser and Unix socket access is denied by default.\");\n\t}\n\tif (policy.network.mode === \"none\") {\n\t\treturn decision(false, \"network.none\", \"Network access is disabled by policy.\");\n\t}\n\tif (request.loopback) {\n\t\treturn policy.network.mode === \"loopback\" ||\n\t\t\tpolicy.network.mode === \"domain-allowlist\" ||\n\t\t\tpolicy.network.mode === \"all-explicit\"\n\t\t\t? decision(true, \"network.loopback\", \"Loopback access is allowed.\")\n\t\t\t: decision(false, \"network.loopback_denied\", \"Loopback access is denied.\");\n\t}\n\tconst host = hostFromRequest(request);\n\tif (!host) return decision(false, \"network.unknown_host\", \"Network host could not be resolved from request.\");\n\tif (hostMatches(policy.network.deniedDomains, host)) {\n\t\treturn decision(false, \"network.domain_deny\", \"Host matches a denied domain.\");\n\t}\n\tif (policy.network.mode === \"all-explicit\") {\n\t\treturn decision(true, \"network.explicit_all\", \"Explicit network profile allows outbound host access.\");\n\t}\n\tif (policy.network.mode === \"domain-allowlist\" && hostMatches(policy.network.allowedDomains, host)) {\n\t\treturn decision(true, \"network.domain_allow\", \"Host matches an allowed domain.\");\n\t}\n\treturn decision(false, \"network.domain_not_allowed\", \"Host is not allowed by network policy.\");\n}\n\nexport function decideSandboxFallback(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxFallbackDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.backend_available\", \"Sandbox backend is available.\"),\n\t\t\tallowShell: policy.process.allowShell,\n\t\t\tallowExec: policy.process.allowExec,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.audit_fallback\", \"Audit mode permits execution without backend.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tconst backendDetail = backend.unavailableReason ? ` ${backend.unavailableReason}` : \"\";\n\treturn {\n\t\t...decision(\n\t\t\tfalse,\n\t\t\t\"sandbox.backend_missing\",\n\t\t\t`Enforce mode blocks shell and exec when no backend is available.${backendDetail}`,\n\t\t),\n\t\tallowShell: false,\n\t\tallowExec: false,\n\t\tallowReadOnlyTools: true,\n\t};\n}\n\nexport function mergeSandboxPolicy(\n\tbase: SandboxPolicy,\n\toverride: Partial<SandboxPolicy>,\n\toptions: { allowBroaden?: boolean } = {},\n): SandboxPolicy {\n\tconst allowBroaden = options.allowBroaden === true;\n\treturn {\n\t\tmode: override.mode ?? base.mode,\n\t\tprofile: override.profile ?? base.profile,\n\t\tfilesystem: {\n\t\t\troot: override.filesystem?.root ?? base.filesystem.root,\n\t\t\treadAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.readAllow ?? base.filesystem.readAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.readAllow, override.filesystem?.readAllow),\n\t\t\treadDeny: union(base.filesystem.readDeny, override.filesystem?.readDeny),\n\t\t\twriteAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.writeAllow ?? base.filesystem.writeAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.writeAllow, override.filesystem?.writeAllow),\n\t\t\tdenyWrite: union(base.filesystem.denyWrite, override.filesystem?.denyWrite),\n\t\t\ttempWrite: allowBroaden\n\t\t\t\t? (override.filesystem?.tempWrite ?? base.filesystem.tempWrite)\n\t\t\t\t: intersectOrBase(base.filesystem.tempWrite, override.filesystem?.tempWrite),\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: allowBroaden\n\t\t\t\t? (override.network?.mode ?? base.network.mode)\n\t\t\t\t: narrowNetworkMode(base.network.mode, override.network?.mode),\n\t\t\tallowedDomains: allowBroaden\n\t\t\t\t? (override.network?.allowedDomains ?? base.network.allowedDomains)\n\t\t\t\t: intersectOrBase(base.network.allowedDomains, override.network?.allowedDomains),\n\t\t\tdeniedDomains: union(base.network.deniedDomains, override.network?.deniedDomains),\n\t\t\tallowUnixSockets: allowBroaden\n\t\t\t\t? (override.network?.allowUnixSockets ?? base.network.allowUnixSockets)\n\t\t\t\t: intersectOrBase(base.network.allowUnixSockets, override.network?.allowUnixSockets),\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: allowBroaden\n\t\t\t\t? (override.process?.allowExec ?? base.process.allowExec)\n\t\t\t\t: base.process.allowExec && (override.process?.allowExec ?? true),\n\t\t\tallowShell: allowBroaden\n\t\t\t\t? (override.process?.allowShell ?? base.process.allowShell)\n\t\t\t\t: base.process.allowShell && (override.process?.allowShell ?? true),\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n\nfunction union(base: readonly string[], override: readonly string[] | undefined): string[] {\n\treturn [...new Set([...base, ...(override ?? [])])];\n}\n\nfunction intersectOrBase(base: readonly string[], override: readonly string[] | undefined): string[] {\n\tif (override === undefined) return [...base];\n\tconst result: string[] = [];\n\tfor (const candidate of override) {\n\t\tfor (const entry of base) {\n\t\t\tif (normalize(candidate) === normalize(entry)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(entry, candidate)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(candidate, entry)) {\n\t\t\t\tresult.push(entry);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...new Set(result)];\n}\n\nfunction narrowNetworkMode(base: NetworkMode, override: NetworkMode | undefined): NetworkMode {\n\tif (override === undefined) return base;\n\tconst rank: Record<NetworkMode, number> = { none: 0, loopback: 1, \"domain-allowlist\": 2, \"all-explicit\": 3 };\n\treturn rank[override] <= rank[base] ? override : base;\n}\n\nexport interface BashSpawnPreflightContext {\n\tcommand: string;\n\tcwd: string;\n}\n\nexport interface BashSpawnPreflightDecision extends SandboxDecision {\n\tallowShell: boolean;\n}\n\n/**\n * Final pure preflight for a local bash spawn. Combines the backend-availability\n * fallback (deny shell when an OS sandbox backend is missing under enforce mode)\n * with a working-directory containment check (deny when cwd resolves outside the\n * sandbox root). This helper does not itself wrap the process in an OS sandbox; it\n * only decides whether the spawn may proceed.\n */\nexport function preflightBashSpawn(\n\tpolicy: SandboxPolicy,\n\tbackend: SandboxBackendStatus,\n\tcontext: BashSpawnPreflightContext,\n\tresolver?: SandboxPathResolver,\n): BashSpawnPreflightDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn { ...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"), allowShell: true };\n\t}\n\tif (!policy.process.allowShell) {\n\t\treturn {\n\t\t\t...decision(false, \"process.shell_denied\", \"Shell execution is disabled by policy.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst fallback = decideSandboxFallback(policy, backend);\n\tif (!fallback.allowShell) {\n\t\treturn { ...decision(false, fallback.rule, fallback.reason), allowShell: false };\n\t}\n\tif (context.cwd.includes(\"\\0\")) {\n\t\treturn { ...decision(false, \"cwd.invalid\", \"Working directory contains a NUL byte.\"), allowShell: false };\n\t}\n\tconst resolved = resolver?.(context.cwd);\n\tif (resolved?.error) {\n\t\treturn { ...decision(false, \"cwd.resolve_error\", resolved.error), allowShell: false };\n\t}\n\tif (resolved?.isSymlink) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.symlink\", \"Working directory is a symlink and traversal is denied.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst candidate = resolved ? (resolvedDecisionPath(resolved) ?? context.cwd) : context.cwd;\n\tif (!isInside(policy.filesystem.root, candidate)) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.root_escape\", \"Working directory is outside the sandbox root.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(true, \"sandbox.shell_preflight_ok\", \"Bash spawn is permitted by sandbox policy.\"),\n\t\tallowShell: true,\n\t};\n}\n"]}
|
|
@@ -150,8 +150,9 @@ export function decideSandboxFallback(policy, backend) {
|
|
|
150
150
|
allowReadOnlyTools: true,
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
|
+
const backendDetail = backend.unavailableReason ? ` ${backend.unavailableReason}` : "";
|
|
153
154
|
return {
|
|
154
|
-
...decision(false, "sandbox.backend_missing",
|
|
155
|
+
...decision(false, "sandbox.backend_missing", `Enforce mode blocks shell and exec when no backend is available.${backendDetail}`),
|
|
155
156
|
allowShell: false,
|
|
156
157
|
allowExec: false,
|
|
157
158
|
allowReadOnlyTools: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\n\nexport type SandboxMode = \"off\" | \"audit\" | \"enforce\";\nexport type SandboxProfile = \"readonly\" | \"workspace-write\" | \"dev-server\" | \"networked\";\nexport type NetworkMode = \"none\" | \"loopback\" | \"domain-allowlist\" | \"all-explicit\";\nexport type PathAccessKind = \"read\" | \"write\";\nexport type SandboxPlatform = \"linux\" | \"macos\" | \"unsupported\";\n\nexport interface SandboxPolicy {\n\tmode: SandboxMode;\n\tprofile: SandboxProfile;\n\tfilesystem: {\n\t\troot: string;\n\t\treadAllow: readonly string[];\n\t\treadDeny: readonly string[];\n\t\twriteAllow: readonly string[];\n\t\tdenyWrite: readonly string[];\n\t\ttempWrite: readonly string[];\n\t\tfollowSymlinks: false;\n\t};\n\tnetwork: {\n\t\tmode: NetworkMode;\n\t\tallowedDomains: readonly string[];\n\t\tdeniedDomains: readonly string[];\n\t\tallowUnixSockets: readonly string[];\n\t\tallowBrowser: false;\n\t};\n\tprocess: {\n\t\tallowExec: boolean;\n\t\tallowShell: boolean;\n\t\tallowPrivilege: false;\n\t};\n}\n\nexport interface ResolvedSandboxPath {\n\trequestedPath: string;\n\texists: boolean;\n\trealPath?: string;\n\tnearestExistingParentRealPath?: string;\n\tisSymlink?: boolean;\n\terror?: string;\n}\n\nexport type SandboxPathResolver = (requestPath: string) => ResolvedSandboxPath;\n\nexport interface PathAccessRequest {\n\tkind: PathAccessKind;\n\tpath: string;\n}\n\nexport interface SandboxDecision {\n\tallowed: boolean;\n\trule: string;\n\treason: string;\n}\n\nexport interface NetworkAccessRequest {\n\thost?: string;\n\turl?: string;\n\tunixSocketPath?: string;\n\tbrowser?: boolean;\n\tloopback?: boolean;\n}\n\nexport interface SandboxBackendStatus {\n\tplatform: SandboxPlatform;\n\tbackendAvailable: boolean;\n\tdomainAllowlistAvailable?: boolean;\n}\n\nexport interface SandboxFallbackDecision extends SandboxDecision {\n\tallowShell: boolean;\n\tallowExec: boolean;\n\tallowReadOnlyTools: boolean;\n}\n\nconst SENSITIVE_PATH_PATTERN =\n\t/(?:^|[/\\\\])(?:\\.env(?:\\..*)?|auth\\.json|oauth\\.json|\\.netrc|\\.npmrc|\\.pgpass|credentials|id_rsa|id_dsa|id_ecdsa|id_ed25519|.*(?:secret|token|private[_-]?key|credential).*)$/i;\n\nfunction decision(allowed: boolean, rule: string, reason: string): SandboxDecision {\n\treturn { allowed, rule, reason };\n}\n\nfunction normalize(value: string): string {\n\treturn path\n\t\t.resolve(value)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.replace(/\\/+$/g, (suffix) => (value === suffix ? \"/\" : \"\"));\n}\n\nfunction isInside(parent: string, child: string): boolean {\n\tconst normalizedParent = normalize(parent);\n\tconst normalizedChild = normalize(child);\n\treturn normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}/`);\n}\n\nfunction isSensitivePath(value: string): boolean {\n\treturn SENSITIVE_PATH_PATTERN.test(value.replace(/\\\\/g, \"/\"));\n}\n\nfunction stripGlobSuffix(pattern: string): string {\n\tif (pattern.endsWith(\"/**\")) return pattern.slice(0, -3);\n\tif (pattern.endsWith(\"/*\")) return pattern.slice(0, -2);\n\treturn pattern;\n}\n\nfunction matchesPattern(pattern: string, candidate: string): boolean {\n\tconst normalizedPattern = normalize(stripGlobSuffix(pattern));\n\tconst normalizedCandidate = normalize(candidate);\n\treturn normalizedCandidate === normalizedPattern || normalizedCandidate.startsWith(`${normalizedPattern}/`);\n}\n\nfunction matchesAny(patterns: readonly string[], candidate: string): boolean {\n\treturn patterns.some((pattern) => matchesPattern(pattern, candidate));\n}\n\nfunction resolvedDecisionPath(resolved: ResolvedSandboxPath): string | undefined {\n\treturn resolved.realPath ?? resolved.nearestExistingParentRealPath;\n}\n\nfunction rootEscaped(policy: SandboxPolicy, resolved: ResolvedSandboxPath): boolean {\n\tconst candidate = resolvedDecisionPath(resolved);\n\treturn candidate === undefined || !isInside(policy.filesystem.root, candidate);\n}\n\nexport function decidePathAccess(\n\tpolicy: SandboxPolicy,\n\trequest: PathAccessRequest,\n\tresolver: SandboxPathResolver,\n): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\t}\n\n\tif (request.path.includes(\"\\0\")) {\n\t\treturn decision(false, \"path.invalid\", \"Path contains a NUL byte.\");\n\t}\n\n\tif (isSensitivePath(request.path)) {\n\t\treturn decision(false, \"path.secret\", \"Sensitive credential-like paths are denied.\");\n\t}\n\n\tconst resolved = resolver(request.path);\n\tif (resolved.error) {\n\t\treturn decision(false, \"path.resolve_error\", resolved.error);\n\t}\n\tif (rootEscaped(policy, resolved)) {\n\t\treturn decision(false, \"path.root_escape\", \"Resolved path is outside the sandbox root.\");\n\t}\n\tif (resolved.isSymlink && !policy.filesystem.followSymlinks) {\n\t\treturn decision(false, \"path.symlink\", \"Symlink traversal is denied by policy.\");\n\t}\n\n\tconst candidate = resolvedDecisionPath(resolved) ?? request.path;\n\tif (request.kind === \"read\") {\n\t\tif (matchesAny(policy.filesystem.readDeny, candidate) || matchesAny(policy.filesystem.readDeny, request.path)) {\n\t\t\treturn decision(false, \"path.read_deny\", \"Read path matches a deny rule.\");\n\t\t}\n\t\tif (!matchesAny(policy.filesystem.readAllow, candidate)) {\n\t\t\treturn decision(false, \"path.read_not_allowed\", \"Read path is not in an allow rule.\");\n\t\t}\n\t\treturn decision(true, \"path.read_allow\", \"Read path is allowed.\");\n\t}\n\n\tif (matchesAny(policy.filesystem.denyWrite, candidate) || matchesAny(policy.filesystem.denyWrite, request.path)) {\n\t\treturn decision(false, \"path.deny_write\", \"Write path matches an unoverrideable denyWrite rule.\");\n\t}\n\tif (matchesAny(policy.filesystem.writeAllow, candidate) || matchesAny(policy.filesystem.tempWrite, candidate)) {\n\t\treturn decision(true, \"path.write_allow\", \"Write path is allowed.\");\n\t}\n\treturn decision(false, \"path.write_not_allowed\", \"Write path is not in an allow rule.\");\n}\n\nfunction hostFromRequest(request: NetworkAccessRequest): string | undefined {\n\tif (request.host) return request.host.toLowerCase();\n\tif (!request.url) return undefined;\n\ttry {\n\t\treturn new URL(request.url).hostname.toLowerCase();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction hostMatches(patterns: readonly string[], host: string): boolean {\n\treturn patterns.some((pattern) => {\n\t\tconst normalized = pattern.toLowerCase();\n\t\treturn host === normalized || host.endsWith(`.${normalized}`);\n\t});\n}\n\nexport function decideNetworkAccess(policy: SandboxPolicy, request: NetworkAccessRequest): SandboxDecision {\n\tif (policy.mode === \"off\") return decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\tif (request.browser || request.unixSocketPath) {\n\t\treturn decision(false, \"network.socket_or_browser\", \"Browser and Unix socket access is denied by default.\");\n\t}\n\tif (policy.network.mode === \"none\") {\n\t\treturn decision(false, \"network.none\", \"Network access is disabled by policy.\");\n\t}\n\tif (request.loopback) {\n\t\treturn policy.network.mode === \"loopback\" ||\n\t\t\tpolicy.network.mode === \"domain-allowlist\" ||\n\t\t\tpolicy.network.mode === \"all-explicit\"\n\t\t\t? decision(true, \"network.loopback\", \"Loopback access is allowed.\")\n\t\t\t: decision(false, \"network.loopback_denied\", \"Loopback access is denied.\");\n\t}\n\tconst host = hostFromRequest(request);\n\tif (!host) return decision(false, \"network.unknown_host\", \"Network host could not be resolved from request.\");\n\tif (hostMatches(policy.network.deniedDomains, host)) {\n\t\treturn decision(false, \"network.domain_deny\", \"Host matches a denied domain.\");\n\t}\n\tif (policy.network.mode === \"all-explicit\") {\n\t\treturn decision(true, \"network.explicit_all\", \"Explicit network profile allows outbound host access.\");\n\t}\n\tif (policy.network.mode === \"domain-allowlist\" && hostMatches(policy.network.allowedDomains, host)) {\n\t\treturn decision(true, \"network.domain_allow\", \"Host matches an allowed domain.\");\n\t}\n\treturn decision(false, \"network.domain_not_allowed\", \"Host is not allowed by network policy.\");\n}\n\nexport function decideSandboxFallback(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxFallbackDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.backend_available\", \"Sandbox backend is available.\"),\n\t\t\tallowShell: policy.process.allowShell,\n\t\t\tallowExec: policy.process.allowExec,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.audit_fallback\", \"Audit mode permits execution without backend.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(false, \"sandbox.backend_missing\", \"Enforce mode blocks shell and exec when no backend is available.\"),\n\t\tallowShell: false,\n\t\tallowExec: false,\n\t\tallowReadOnlyTools: true,\n\t};\n}\n\nexport function mergeSandboxPolicy(\n\tbase: SandboxPolicy,\n\toverride: Partial<SandboxPolicy>,\n\toptions: { allowBroaden?: boolean } = {},\n): SandboxPolicy {\n\tconst allowBroaden = options.allowBroaden === true;\n\treturn {\n\t\tmode: override.mode ?? base.mode,\n\t\tprofile: override.profile ?? base.profile,\n\t\tfilesystem: {\n\t\t\troot: override.filesystem?.root ?? base.filesystem.root,\n\t\t\treadAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.readAllow ?? base.filesystem.readAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.readAllow, override.filesystem?.readAllow),\n\t\t\treadDeny: union(base.filesystem.readDeny, override.filesystem?.readDeny),\n\t\t\twriteAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.writeAllow ?? base.filesystem.writeAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.writeAllow, override.filesystem?.writeAllow),\n\t\t\tdenyWrite: union(base.filesystem.denyWrite, override.filesystem?.denyWrite),\n\t\t\ttempWrite: allowBroaden\n\t\t\t\t? (override.filesystem?.tempWrite ?? base.filesystem.tempWrite)\n\t\t\t\t: intersectOrBase(base.filesystem.tempWrite, override.filesystem?.tempWrite),\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: allowBroaden\n\t\t\t\t? (override.network?.mode ?? base.network.mode)\n\t\t\t\t: narrowNetworkMode(base.network.mode, override.network?.mode),\n\t\t\tallowedDomains: allowBroaden\n\t\t\t\t? (override.network?.allowedDomains ?? base.network.allowedDomains)\n\t\t\t\t: intersectOrBase(base.network.allowedDomains, override.network?.allowedDomains),\n\t\t\tdeniedDomains: union(base.network.deniedDomains, override.network?.deniedDomains),\n\t\t\tallowUnixSockets: allowBroaden\n\t\t\t\t? (override.network?.allowUnixSockets ?? base.network.allowUnixSockets)\n\t\t\t\t: intersectOrBase(base.network.allowUnixSockets, override.network?.allowUnixSockets),\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: allowBroaden\n\t\t\t\t? (override.process?.allowExec ?? base.process.allowExec)\n\t\t\t\t: base.process.allowExec && (override.process?.allowExec ?? true),\n\t\t\tallowShell: allowBroaden\n\t\t\t\t? (override.process?.allowShell ?? base.process.allowShell)\n\t\t\t\t: base.process.allowShell && (override.process?.allowShell ?? true),\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n\nfunction union(base: readonly string[], override: readonly string[] | undefined): string[] {\n\treturn [...new Set([...base, ...(override ?? [])])];\n}\n\nfunction intersectOrBase(base: readonly string[], override: readonly string[] | undefined): string[] {\n\tif (override === undefined) return [...base];\n\tconst result: string[] = [];\n\tfor (const candidate of override) {\n\t\tfor (const entry of base) {\n\t\t\tif (normalize(candidate) === normalize(entry)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(entry, candidate)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(candidate, entry)) {\n\t\t\t\tresult.push(entry);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...new Set(result)];\n}\n\nfunction narrowNetworkMode(base: NetworkMode, override: NetworkMode | undefined): NetworkMode {\n\tif (override === undefined) return base;\n\tconst rank: Record<NetworkMode, number> = { none: 0, loopback: 1, \"domain-allowlist\": 2, \"all-explicit\": 3 };\n\treturn rank[override] <= rank[base] ? override : base;\n}\n\nexport interface BashSpawnPreflightContext {\n\tcommand: string;\n\tcwd: string;\n}\n\nexport interface BashSpawnPreflightDecision extends SandboxDecision {\n\tallowShell: boolean;\n}\n\n/**\n * Final pure preflight for a local bash spawn. Combines the backend-availability\n * fallback (deny shell when an OS sandbox backend is missing under enforce mode)\n * with a working-directory containment check (deny when cwd resolves outside the\n * sandbox root). This helper does not itself wrap the process in an OS sandbox; it\n * only decides whether the spawn may proceed.\n */\nexport function preflightBashSpawn(\n\tpolicy: SandboxPolicy,\n\tbackend: SandboxBackendStatus,\n\tcontext: BashSpawnPreflightContext,\n\tresolver?: SandboxPathResolver,\n): BashSpawnPreflightDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn { ...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"), allowShell: true };\n\t}\n\tif (!policy.process.allowShell) {\n\t\treturn {\n\t\t\t...decision(false, \"process.shell_denied\", \"Shell execution is disabled by policy.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst fallback = decideSandboxFallback(policy, backend);\n\tif (!fallback.allowShell) {\n\t\treturn { ...decision(false, fallback.rule, fallback.reason), allowShell: false };\n\t}\n\tif (context.cwd.includes(\"\\0\")) {\n\t\treturn { ...decision(false, \"cwd.invalid\", \"Working directory contains a NUL byte.\"), allowShell: false };\n\t}\n\tconst resolved = resolver?.(context.cwd);\n\tif (resolved?.error) {\n\t\treturn { ...decision(false, \"cwd.resolve_error\", resolved.error), allowShell: false };\n\t}\n\tif (resolved?.isSymlink) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.symlink\", \"Working directory is a symlink and traversal is denied.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst candidate = resolved ? (resolvedDecisionPath(resolved) ?? context.cwd) : context.cwd;\n\tif (!isInside(policy.filesystem.root, candidate)) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.root_escape\", \"Working directory is outside the sandbox root.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(true, \"sandbox.shell_preflight_ok\", \"Bash spawn is permitted by sandbox policy.\"),\n\t\tallowShell: true,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\n\nexport type SandboxMode = \"off\" | \"audit\" | \"enforce\";\nexport type SandboxProfile = \"readonly\" | \"workspace-write\" | \"dev-server\" | \"networked\";\nexport type NetworkMode = \"none\" | \"loopback\" | \"domain-allowlist\" | \"all-explicit\";\nexport type PathAccessKind = \"read\" | \"write\";\nexport type SandboxPlatform = \"linux\" | \"macos\" | \"unsupported\";\n\nexport interface SandboxPolicy {\n\tmode: SandboxMode;\n\tprofile: SandboxProfile;\n\tfilesystem: {\n\t\troot: string;\n\t\treadAllow: readonly string[];\n\t\treadDeny: readonly string[];\n\t\twriteAllow: readonly string[];\n\t\tdenyWrite: readonly string[];\n\t\ttempWrite: readonly string[];\n\t\tfollowSymlinks: false;\n\t};\n\tnetwork: {\n\t\tmode: NetworkMode;\n\t\tallowedDomains: readonly string[];\n\t\tdeniedDomains: readonly string[];\n\t\tallowUnixSockets: readonly string[];\n\t\tallowBrowser: false;\n\t};\n\tprocess: {\n\t\tallowExec: boolean;\n\t\tallowShell: boolean;\n\t\tallowPrivilege: false;\n\t};\n}\n\nexport interface ResolvedSandboxPath {\n\trequestedPath: string;\n\texists: boolean;\n\trealPath?: string;\n\tnearestExistingParentRealPath?: string;\n\tisSymlink?: boolean;\n\terror?: string;\n}\n\nexport type SandboxPathResolver = (requestPath: string) => ResolvedSandboxPath;\n\nexport interface PathAccessRequest {\n\tkind: PathAccessKind;\n\tpath: string;\n}\n\nexport interface SandboxDecision {\n\tallowed: boolean;\n\trule: string;\n\treason: string;\n}\n\nexport interface NetworkAccessRequest {\n\thost?: string;\n\turl?: string;\n\tunixSocketPath?: string;\n\tbrowser?: boolean;\n\tloopback?: boolean;\n}\n\nexport interface SandboxBackendStatus {\n\tplatform: SandboxPlatform;\n\tbackendAvailable: boolean;\n\tdomainAllowlistAvailable?: boolean;\n\t/** Human-readable probe detail when the backend cannot enforce the policy. */\n\tunavailableReason?: string;\n}\n\nexport interface SandboxFallbackDecision extends SandboxDecision {\n\tallowShell: boolean;\n\tallowExec: boolean;\n\tallowReadOnlyTools: boolean;\n}\n\nconst SENSITIVE_PATH_PATTERN =\n\t/(?:^|[/\\\\])(?:\\.env(?:\\..*)?|auth\\.json|oauth\\.json|\\.netrc|\\.npmrc|\\.pgpass|credentials|id_rsa|id_dsa|id_ecdsa|id_ed25519|.*(?:secret|token|private[_-]?key|credential).*)$/i;\n\nfunction decision(allowed: boolean, rule: string, reason: string): SandboxDecision {\n\treturn { allowed, rule, reason };\n}\n\nfunction normalize(value: string): string {\n\treturn path\n\t\t.resolve(value)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.replace(/\\/+$/g, (suffix) => (value === suffix ? \"/\" : \"\"));\n}\n\nfunction isInside(parent: string, child: string): boolean {\n\tconst normalizedParent = normalize(parent);\n\tconst normalizedChild = normalize(child);\n\treturn normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}/`);\n}\n\nfunction isSensitivePath(value: string): boolean {\n\treturn SENSITIVE_PATH_PATTERN.test(value.replace(/\\\\/g, \"/\"));\n}\n\nfunction stripGlobSuffix(pattern: string): string {\n\tif (pattern.endsWith(\"/**\")) return pattern.slice(0, -3);\n\tif (pattern.endsWith(\"/*\")) return pattern.slice(0, -2);\n\treturn pattern;\n}\n\nfunction matchesPattern(pattern: string, candidate: string): boolean {\n\tconst normalizedPattern = normalize(stripGlobSuffix(pattern));\n\tconst normalizedCandidate = normalize(candidate);\n\treturn normalizedCandidate === normalizedPattern || normalizedCandidate.startsWith(`${normalizedPattern}/`);\n}\n\nfunction matchesAny(patterns: readonly string[], candidate: string): boolean {\n\treturn patterns.some((pattern) => matchesPattern(pattern, candidate));\n}\n\nfunction resolvedDecisionPath(resolved: ResolvedSandboxPath): string | undefined {\n\treturn resolved.realPath ?? resolved.nearestExistingParentRealPath;\n}\n\nfunction rootEscaped(policy: SandboxPolicy, resolved: ResolvedSandboxPath): boolean {\n\tconst candidate = resolvedDecisionPath(resolved);\n\treturn candidate === undefined || !isInside(policy.filesystem.root, candidate);\n}\n\nexport function decidePathAccess(\n\tpolicy: SandboxPolicy,\n\trequest: PathAccessRequest,\n\tresolver: SandboxPathResolver,\n): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\t}\n\n\tif (request.path.includes(\"\\0\")) {\n\t\treturn decision(false, \"path.invalid\", \"Path contains a NUL byte.\");\n\t}\n\n\tif (isSensitivePath(request.path)) {\n\t\treturn decision(false, \"path.secret\", \"Sensitive credential-like paths are denied.\");\n\t}\n\n\tconst resolved = resolver(request.path);\n\tif (resolved.error) {\n\t\treturn decision(false, \"path.resolve_error\", resolved.error);\n\t}\n\tif (rootEscaped(policy, resolved)) {\n\t\treturn decision(false, \"path.root_escape\", \"Resolved path is outside the sandbox root.\");\n\t}\n\tif (resolved.isSymlink && !policy.filesystem.followSymlinks) {\n\t\treturn decision(false, \"path.symlink\", \"Symlink traversal is denied by policy.\");\n\t}\n\n\tconst candidate = resolvedDecisionPath(resolved) ?? request.path;\n\tif (request.kind === \"read\") {\n\t\tif (matchesAny(policy.filesystem.readDeny, candidate) || matchesAny(policy.filesystem.readDeny, request.path)) {\n\t\t\treturn decision(false, \"path.read_deny\", \"Read path matches a deny rule.\");\n\t\t}\n\t\tif (!matchesAny(policy.filesystem.readAllow, candidate)) {\n\t\t\treturn decision(false, \"path.read_not_allowed\", \"Read path is not in an allow rule.\");\n\t\t}\n\t\treturn decision(true, \"path.read_allow\", \"Read path is allowed.\");\n\t}\n\n\tif (matchesAny(policy.filesystem.denyWrite, candidate) || matchesAny(policy.filesystem.denyWrite, request.path)) {\n\t\treturn decision(false, \"path.deny_write\", \"Write path matches an unoverrideable denyWrite rule.\");\n\t}\n\tif (matchesAny(policy.filesystem.writeAllow, candidate) || matchesAny(policy.filesystem.tempWrite, candidate)) {\n\t\treturn decision(true, \"path.write_allow\", \"Write path is allowed.\");\n\t}\n\treturn decision(false, \"path.write_not_allowed\", \"Write path is not in an allow rule.\");\n}\n\nfunction hostFromRequest(request: NetworkAccessRequest): string | undefined {\n\tif (request.host) return request.host.toLowerCase();\n\tif (!request.url) return undefined;\n\ttry {\n\t\treturn new URL(request.url).hostname.toLowerCase();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction hostMatches(patterns: readonly string[], host: string): boolean {\n\treturn patterns.some((pattern) => {\n\t\tconst normalized = pattern.toLowerCase();\n\t\treturn host === normalized || host.endsWith(`.${normalized}`);\n\t});\n}\n\nexport function decideNetworkAccess(policy: SandboxPolicy, request: NetworkAccessRequest): SandboxDecision {\n\tif (policy.mode === \"off\") return decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\tif (request.browser || request.unixSocketPath) {\n\t\treturn decision(false, \"network.socket_or_browser\", \"Browser and Unix socket access is denied by default.\");\n\t}\n\tif (policy.network.mode === \"none\") {\n\t\treturn decision(false, \"network.none\", \"Network access is disabled by policy.\");\n\t}\n\tif (request.loopback) {\n\t\treturn policy.network.mode === \"loopback\" ||\n\t\t\tpolicy.network.mode === \"domain-allowlist\" ||\n\t\t\tpolicy.network.mode === \"all-explicit\"\n\t\t\t? decision(true, \"network.loopback\", \"Loopback access is allowed.\")\n\t\t\t: decision(false, \"network.loopback_denied\", \"Loopback access is denied.\");\n\t}\n\tconst host = hostFromRequest(request);\n\tif (!host) return decision(false, \"network.unknown_host\", \"Network host could not be resolved from request.\");\n\tif (hostMatches(policy.network.deniedDomains, host)) {\n\t\treturn decision(false, \"network.domain_deny\", \"Host matches a denied domain.\");\n\t}\n\tif (policy.network.mode === \"all-explicit\") {\n\t\treturn decision(true, \"network.explicit_all\", \"Explicit network profile allows outbound host access.\");\n\t}\n\tif (policy.network.mode === \"domain-allowlist\" && hostMatches(policy.network.allowedDomains, host)) {\n\t\treturn decision(true, \"network.domain_allow\", \"Host matches an allowed domain.\");\n\t}\n\treturn decision(false, \"network.domain_not_allowed\", \"Host is not allowed by network policy.\");\n}\n\nexport function decideSandboxFallback(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxFallbackDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.backend_available\", \"Sandbox backend is available.\"),\n\t\t\tallowShell: policy.process.allowShell,\n\t\t\tallowExec: policy.process.allowExec,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.audit_fallback\", \"Audit mode permits execution without backend.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tconst backendDetail = backend.unavailableReason ? ` ${backend.unavailableReason}` : \"\";\n\treturn {\n\t\t...decision(\n\t\t\tfalse,\n\t\t\t\"sandbox.backend_missing\",\n\t\t\t`Enforce mode blocks shell and exec when no backend is available.${backendDetail}`,\n\t\t),\n\t\tallowShell: false,\n\t\tallowExec: false,\n\t\tallowReadOnlyTools: true,\n\t};\n}\n\nexport function mergeSandboxPolicy(\n\tbase: SandboxPolicy,\n\toverride: Partial<SandboxPolicy>,\n\toptions: { allowBroaden?: boolean } = {},\n): SandboxPolicy {\n\tconst allowBroaden = options.allowBroaden === true;\n\treturn {\n\t\tmode: override.mode ?? base.mode,\n\t\tprofile: override.profile ?? base.profile,\n\t\tfilesystem: {\n\t\t\troot: override.filesystem?.root ?? base.filesystem.root,\n\t\t\treadAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.readAllow ?? base.filesystem.readAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.readAllow, override.filesystem?.readAllow),\n\t\t\treadDeny: union(base.filesystem.readDeny, override.filesystem?.readDeny),\n\t\t\twriteAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.writeAllow ?? base.filesystem.writeAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.writeAllow, override.filesystem?.writeAllow),\n\t\t\tdenyWrite: union(base.filesystem.denyWrite, override.filesystem?.denyWrite),\n\t\t\ttempWrite: allowBroaden\n\t\t\t\t? (override.filesystem?.tempWrite ?? base.filesystem.tempWrite)\n\t\t\t\t: intersectOrBase(base.filesystem.tempWrite, override.filesystem?.tempWrite),\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: allowBroaden\n\t\t\t\t? (override.network?.mode ?? base.network.mode)\n\t\t\t\t: narrowNetworkMode(base.network.mode, override.network?.mode),\n\t\t\tallowedDomains: allowBroaden\n\t\t\t\t? (override.network?.allowedDomains ?? base.network.allowedDomains)\n\t\t\t\t: intersectOrBase(base.network.allowedDomains, override.network?.allowedDomains),\n\t\t\tdeniedDomains: union(base.network.deniedDomains, override.network?.deniedDomains),\n\t\t\tallowUnixSockets: allowBroaden\n\t\t\t\t? (override.network?.allowUnixSockets ?? base.network.allowUnixSockets)\n\t\t\t\t: intersectOrBase(base.network.allowUnixSockets, override.network?.allowUnixSockets),\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: allowBroaden\n\t\t\t\t? (override.process?.allowExec ?? base.process.allowExec)\n\t\t\t\t: base.process.allowExec && (override.process?.allowExec ?? true),\n\t\t\tallowShell: allowBroaden\n\t\t\t\t? (override.process?.allowShell ?? base.process.allowShell)\n\t\t\t\t: base.process.allowShell && (override.process?.allowShell ?? true),\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n\nfunction union(base: readonly string[], override: readonly string[] | undefined): string[] {\n\treturn [...new Set([...base, ...(override ?? [])])];\n}\n\nfunction intersectOrBase(base: readonly string[], override: readonly string[] | undefined): string[] {\n\tif (override === undefined) return [...base];\n\tconst result: string[] = [];\n\tfor (const candidate of override) {\n\t\tfor (const entry of base) {\n\t\t\tif (normalize(candidate) === normalize(entry)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(entry, candidate)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(candidate, entry)) {\n\t\t\t\tresult.push(entry);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...new Set(result)];\n}\n\nfunction narrowNetworkMode(base: NetworkMode, override: NetworkMode | undefined): NetworkMode {\n\tif (override === undefined) return base;\n\tconst rank: Record<NetworkMode, number> = { none: 0, loopback: 1, \"domain-allowlist\": 2, \"all-explicit\": 3 };\n\treturn rank[override] <= rank[base] ? override : base;\n}\n\nexport interface BashSpawnPreflightContext {\n\tcommand: string;\n\tcwd: string;\n}\n\nexport interface BashSpawnPreflightDecision extends SandboxDecision {\n\tallowShell: boolean;\n}\n\n/**\n * Final pure preflight for a local bash spawn. Combines the backend-availability\n * fallback (deny shell when an OS sandbox backend is missing under enforce mode)\n * with a working-directory containment check (deny when cwd resolves outside the\n * sandbox root). This helper does not itself wrap the process in an OS sandbox; it\n * only decides whether the spawn may proceed.\n */\nexport function preflightBashSpawn(\n\tpolicy: SandboxPolicy,\n\tbackend: SandboxBackendStatus,\n\tcontext: BashSpawnPreflightContext,\n\tresolver?: SandboxPathResolver,\n): BashSpawnPreflightDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn { ...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"), allowShell: true };\n\t}\n\tif (!policy.process.allowShell) {\n\t\treturn {\n\t\t\t...decision(false, \"process.shell_denied\", \"Shell execution is disabled by policy.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst fallback = decideSandboxFallback(policy, backend);\n\tif (!fallback.allowShell) {\n\t\treturn { ...decision(false, fallback.rule, fallback.reason), allowShell: false };\n\t}\n\tif (context.cwd.includes(\"\\0\")) {\n\t\treturn { ...decision(false, \"cwd.invalid\", \"Working directory contains a NUL byte.\"), allowShell: false };\n\t}\n\tconst resolved = resolver?.(context.cwd);\n\tif (resolved?.error) {\n\t\treturn { ...decision(false, \"cwd.resolve_error\", resolved.error), allowShell: false };\n\t}\n\tif (resolved?.isSymlink) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.symlink\", \"Working directory is a symlink and traversal is denied.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst candidate = resolved ? (resolvedDecisionPath(resolved) ?? context.cwd) : context.cwd;\n\tif (!isInside(policy.filesystem.root, candidate)) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.root_escape\", \"Working directory is outside the sandbox root.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(true, \"sandbox.shell_preflight_ok\", \"Bash spawn is permitted by sandbox policy.\"),\n\t\tallowShell: true,\n\t};\n}\n"]}
|
package/dist/core/sdk.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface CreateAgentSessionOptions {
|
|
|
29
29
|
model: Model<any>;
|
|
30
30
|
thinkingLevel?: ThinkingLevel;
|
|
31
31
|
}>;
|
|
32
|
+
/** True when the user pinned `--model` / `--provider` for this process. */
|
|
33
|
+
modelPinned?: boolean;
|
|
32
34
|
/**
|
|
33
35
|
* Optional default tool suppression mode when no explicit allowlist is provided.
|
|
34
36
|
*
|