@orion-agents/orion-code 0.1.2 → 0.1.4
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/README.md +96 -3
- package/README.zh-CN.md +109 -4
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +168 -54
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/types.d.ts +3 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/framework/query.d.ts +3 -0
- package/dist/framework/query.d.ts.map +1 -1
- package/dist/framework/query.js +1 -0
- package/dist/framework/query.js.map +1 -1
- package/dist/framework/tool-scheduler.d.ts +61 -1
- package/dist/framework/tool-scheduler.d.ts.map +1 -1
- package/dist/framework/tool-scheduler.js +165 -47
- package/dist/framework/tool-scheduler.js.map +1 -1
- package/dist/framework/tool.d.ts +9 -0
- package/dist/framework/tool.d.ts.map +1 -1
- package/dist/framework/tool.js +2 -0
- package/dist/framework/tool.js.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts +2 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.js +65 -0
- package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
- package/dist/memory/embeddings.d.ts.map +1 -1
- package/dist/memory/embeddings.js +3 -2
- package/dist/memory/embeddings.js.map +1 -1
- package/dist/memory/semantic-search.d.ts.map +1 -1
- package/dist/memory/semantic-search.js +2 -1
- package/dist/memory/semantic-search.js.map +1 -1
- package/dist/memory/vector-store.d.ts.map +1 -1
- package/dist/memory/vector-store.js +7 -3
- package/dist/memory/vector-store.js.map +1 -1
- package/dist/migration/migrate.d.ts.map +1 -1
- package/dist/migration/migrate.js +7 -11
- package/dist/migration/migrate.js.map +1 -1
- package/dist/print-ui/launch.d.ts +2 -1
- package/dist/print-ui/launch.d.ts.map +1 -1
- package/dist/print-ui/launch.js +7 -0
- package/dist/print-ui/launch.js.map +1 -1
- package/dist/runtime/agent-runtime-controller.d.ts +1 -0
- package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-controller.js +24 -3
- package/dist/runtime/agent-runtime-controller.js.map +1 -1
- package/dist/runtime/agent-runtime-protocol.d.ts +16 -1
- package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-protocol.js +12 -0
- package/dist/runtime/agent-runtime-protocol.js.map +1 -1
- package/dist/runtime/chat-controller.d.ts +6 -0
- package/dist/runtime/chat-controller.d.ts.map +1 -1
- package/dist/runtime/chat-controller.js +65 -2
- package/dist/runtime/chat-controller.js.map +1 -1
- package/dist/runtime/goals/tools.d.ts.map +1 -1
- package/dist/runtime/goals/tools.js +14 -14
- package/dist/runtime/goals/tools.js.map +1 -1
- package/dist/runtime/goals/types.d.ts +6 -0
- package/dist/runtime/goals/types.d.ts.map +1 -1
- package/dist/runtime/goals/types.js.map +1 -1
- package/dist/runtime/model-coordinator.d.ts.map +1 -1
- package/dist/runtime/model-coordinator.js.map +1 -1
- package/dist/runtime/subagents/child-executor-guard.d.ts.map +1 -1
- package/dist/runtime/subagents/child-executor-guard.js +4 -0
- package/dist/runtime/subagents/child-executor-guard.js.map +1 -1
- package/dist/runtime/subagents/index.d.ts +10 -3
- package/dist/runtime/subagents/index.d.ts.map +1 -1
- package/dist/runtime/subagents/index.js +24 -1
- package/dist/runtime/subagents/index.js.map +1 -1
- package/dist/runtime/subagents/provider-gate.d.ts.map +1 -1
- package/dist/runtime/subagents/provider-gate.js +0 -1
- package/dist/runtime/subagents/provider-gate.js.map +1 -1
- package/dist/runtime/subagents/research-artifact.d.ts +94 -0
- package/dist/runtime/subagents/research-artifact.d.ts.map +1 -0
- package/dist/runtime/subagents/research-artifact.js +164 -0
- package/dist/runtime/subagents/research-artifact.js.map +1 -0
- package/dist/runtime/subagents/research-citation.d.ts +66 -0
- package/dist/runtime/subagents/research-citation.d.ts.map +1 -0
- package/dist/runtime/subagents/research-citation.js +185 -0
- package/dist/runtime/subagents/research-citation.js.map +1 -0
- package/dist/runtime/subagents/research-contract.d.ts +69 -0
- package/dist/runtime/subagents/research-contract.d.ts.map +1 -0
- package/dist/runtime/subagents/research-contract.js +232 -0
- package/dist/runtime/subagents/research-contract.js.map +1 -0
- package/dist/runtime/subagents/research-quality.d.ts +47 -0
- package/dist/runtime/subagents/research-quality.d.ts.map +1 -0
- package/dist/runtime/subagents/research-quality.js +125 -0
- package/dist/runtime/subagents/research-quality.js.map +1 -0
- package/dist/runtime/subagents/research-renderer.d.ts +79 -0
- package/dist/runtime/subagents/research-renderer.d.ts.map +1 -0
- package/dist/runtime/subagents/research-renderer.js +154 -0
- package/dist/runtime/subagents/research-renderer.js.map +1 -0
- package/dist/runtime/subagents/research-types.d.ts +105 -0
- package/dist/runtime/subagents/research-types.d.ts.map +1 -0
- package/dist/runtime/subagents/research-types.js +16 -0
- package/dist/runtime/subagents/research-types.js.map +1 -0
- package/dist/runtime/subagents/runtime-integration.d.ts +1 -1
- package/dist/runtime/subagents/runtime-integration.d.ts.map +1 -1
- package/dist/runtime/subagents/runtime-integration.js.map +1 -1
- package/dist/runtime/subagents/supervisor.d.ts +1 -1
- package/dist/runtime/subagents/supervisor.d.ts.map +1 -1
- package/dist/runtime/subagents/supervisor.js +1 -1
- package/dist/runtime/subagents/supervisor.js.map +1 -1
- package/dist/runtime/subagents/web-research-adapter.d.ts +83 -0
- package/dist/runtime/subagents/web-research-adapter.d.ts.map +1 -0
- package/dist/runtime/subagents/web-research-adapter.js +262 -0
- package/dist/runtime/subagents/web-research-adapter.js.map +1 -0
- package/dist/runtime/turn-controller.d.ts.map +1 -1
- package/dist/runtime/turn-controller.js +3 -1
- package/dist/runtime/turn-controller.js.map +1 -1
- package/dist/runtime/ui-events.d.ts +21 -0
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js.map +1 -1
- package/dist/services/auth/auth.d.ts.map +1 -1
- package/dist/services/auth/auth.js +17 -4
- package/dist/services/auth/auth.js.map +1 -1
- package/dist/services/concurrent-sessions.d.ts.map +1 -1
- package/dist/services/concurrent-sessions.js +33 -17
- package/dist/services/concurrent-sessions.js.map +1 -1
- package/dist/services/config-dir.d.ts.map +1 -1
- package/dist/services/config-dir.js +19 -5
- package/dist/services/config-dir.js.map +1 -1
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +2 -1
- package/dist/services/config.js.map +1 -1
- package/dist/services/global-config.d.ts +31 -0
- package/dist/services/global-config.d.ts.map +1 -1
- package/dist/services/global-config.js.map +1 -1
- package/dist/services/goal-storage.d.ts.map +1 -1
- package/dist/services/goal-storage.js +22 -8
- package/dist/services/goal-storage.js.map +1 -1
- package/dist/services/llm.d.ts +17 -0
- package/dist/services/llm.d.ts.map +1 -1
- package/dist/services/llm.js +88 -31
- package/dist/services/llm.js.map +1 -1
- package/dist/services/model-catalog.d.ts +2 -0
- package/dist/services/model-catalog.d.ts.map +1 -1
- package/dist/services/model-catalog.js +25 -15
- package/dist/services/model-catalog.js.map +1 -1
- package/dist/services/model-context.d.ts.map +1 -1
- package/dist/services/model-context.js +36 -0
- package/dist/services/model-context.js.map +1 -1
- package/dist/services/model-registry.d.ts.map +1 -1
- package/dist/services/model-registry.js +32 -0
- package/dist/services/model-registry.js.map +1 -1
- package/dist/services/provider-resilience/coordinator.d.ts +1 -1
- package/dist/services/provider-resilience/coordinator.d.ts.map +1 -1
- package/dist/services/provider-resilience/coordinator.js +4 -4
- package/dist/services/provider-resilience/coordinator.js.map +1 -1
- package/dist/services/provider-resilience/error-classifier.js +20 -8
- package/dist/services/provider-resilience/error-classifier.js.map +1 -1
- package/dist/services/provider-resilience/stream-recovery.d.ts +1 -1
- package/dist/services/provider-resilience/stream-recovery.d.ts.map +1 -1
- package/dist/services/provider-resilience/stream-recovery.js +1 -1
- package/dist/services/provider-resilience/stream-recovery.js.map +1 -1
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js +45 -14
- package/dist/services/session-storage.js.map +1 -1
- package/dist/services/storage-maintenance.d.ts.map +1 -1
- package/dist/services/storage-maintenance.js +44 -9
- package/dist/services/storage-maintenance.js.map +1 -1
- package/dist/services/task-manager.d.ts +3 -3
- package/dist/services/task-manager.d.ts.map +1 -1
- package/dist/services/tool-allowlist.d.ts +100 -0
- package/dist/services/tool-allowlist.d.ts.map +1 -0
- package/dist/services/tool-allowlist.js +250 -0
- package/dist/services/tool-allowlist.js.map +1 -0
- package/dist/services/web-search-adapters.d.ts.map +1 -1
- package/dist/services/web-search-adapters.js +48 -18
- package/dist/services/web-search-adapters.js.map +1 -1
- package/dist/services/web-search-mcp.d.ts.map +1 -1
- package/dist/services/web-search-mcp.js +27 -11
- package/dist/services/web-search-mcp.js.map +1 -1
- package/dist/services/yaml-config.d.ts.map +1 -1
- package/dist/services/yaml-config.js +2 -1
- package/dist/services/yaml-config.js.map +1 -1
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +3 -2
- package/dist/skills/loader.js.map +1 -1
- package/dist/terminal-ui/launch.d.ts +22 -2
- package/dist/terminal-ui/launch.d.ts.map +1 -1
- package/dist/terminal-ui/launch.js +150 -1
- package/dist/terminal-ui/launch.js.map +1 -1
- package/dist/terminal-ui/raw-editor.js.map +1 -1
- package/dist/tools/bash_security.d.ts +50 -18
- package/dist/tools/bash_security.d.ts.map +1 -1
- package/dist/tools/bash_security.js +394 -61
- package/dist/tools/bash_security.js.map +1 -1
- package/dist/tools/git.d.ts +4 -0
- package/dist/tools/git.d.ts.map +1 -1
- package/dist/tools/git.js +310 -3
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +87 -25
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp.d.ts +8 -2
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mcp.js +44 -23
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/sandbox.d.ts +139 -0
- package/dist/tools/sandbox.d.ts.map +1 -0
- package/dist/tools/sandbox.js +485 -0
- package/dist/tools/sandbox.js.map +1 -0
- package/dist/tools/web.d.ts.map +1 -1
- package/dist/tools/web.js +113 -42
- package/dist/tools/web.js.map +1 -1
- package/dist/tui-core/frame.js.map +1 -1
- package/dist/tui-ui/launch.d.ts.map +1 -1
- package/dist/tui-ui/launch.js +46 -8
- package/dist/tui-ui/launch.js.map +1 -1
- package/dist/tui-ui/layout.d.ts.map +1 -1
- package/dist/tui-ui/layout.js +62 -0
- package/dist/tui-ui/layout.js.map +1 -1
- package/dist/tui-ui/runner.d.ts +6 -0
- package/dist/tui-ui/runner.d.ts.map +1 -1
- package/dist/tui-ui/runner.js +81 -1
- package/dist/tui-ui/runner.js.map +1 -1
- package/dist/tui-ui/state.d.ts +19 -1
- package/dist/tui-ui/state.d.ts.map +1 -1
- package/dist/tui-ui/state.js +35 -7
- package/dist/tui-ui/state.js.map +1 -1
- package/dist/tui-ui/terminal-image.d.ts +3 -13
- package/dist/tui-ui/terminal-image.d.ts.map +1 -1
- package/dist/tui-ui/terminal-image.js +5 -160
- package/dist/tui-ui/terminal-image.js.map +1 -1
- package/dist/utils/debug-log.d.ts +52 -0
- package/dist/utils/debug-log.d.ts.map +1 -0
- package/dist/utils/debug-log.js +91 -0
- package/dist/utils/debug-log.js.map +1 -0
- package/dist/utils/errors.d.ts +25 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +47 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/mask.d.ts +28 -0
- package/dist/utils/mask.d.ts.map +1 -0
- package/dist/utils/mask.js +54 -0
- package/dist/utils/mask.js.map +1 -0
- package/package.json +2 -1
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
* These commands only read data and do not modify the filesystem.
|
|
9
9
|
*/
|
|
10
10
|
export declare const READ_ONLY_COMMANDS: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Sub-commands that only read state, keyed by the driver that runs them.
|
|
13
|
+
*
|
|
14
|
+
* The drivers themselves must never appear in READ_ONLY_COMMANDS: `npm run`,
|
|
15
|
+
* `npm exec`, `yarn <script>`, `pnpm run`, `cargo run` and `npx <pkg>` all
|
|
16
|
+
* execute arbitrary project-defined or freshly downloaded code, so the safe
|
|
17
|
+
* surface has to be enumerated rather than subtracted.
|
|
18
|
+
*/
|
|
19
|
+
export declare const READ_ONLY_SUBCOMMANDS: Record<string, readonly string[]>;
|
|
11
20
|
/**
|
|
12
21
|
* Validation commands that are safe enough to run without an interactive
|
|
13
22
|
* confirmation prompt. They may read the project and execute local test code,
|
|
@@ -25,14 +34,55 @@ export declare const DANGEROUS_PATTERNS: {
|
|
|
25
34
|
* Potentially destructive patterns that should ask for confirmation.
|
|
26
35
|
*/
|
|
27
36
|
export declare const POTENTIALLY_DESTRUCTIVE_PATTERNS: RegExp[];
|
|
37
|
+
/**
|
|
38
|
+
* Result of a quote-aware scan over a whole command line.
|
|
39
|
+
*/
|
|
40
|
+
export interface ShellCommandScan {
|
|
41
|
+
/** The individual commands on the line, split on unquoted separators. */
|
|
42
|
+
segments: string[];
|
|
43
|
+
/**
|
|
44
|
+
* False when the line uses a construct this scanner cannot reason about --
|
|
45
|
+
* command substitution, process substitution or an unbalanced quote. Such a
|
|
46
|
+
* line can hide an arbitrary command and must never be auto-approved.
|
|
47
|
+
*/
|
|
48
|
+
supported: boolean;
|
|
49
|
+
/** True when the line redirects output somewhere other than /dev/null. */
|
|
50
|
+
writesFile: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Split a command line into the individual commands it will actually run.
|
|
54
|
+
*
|
|
55
|
+
* The classifier below hands out "run without confirmation" verdicts, so it has
|
|
56
|
+
* to see every command on the line rather than just the leading token. Quote
|
|
57
|
+
* state is tracked so `echo "a && b"` stays one segment, and substitutions are
|
|
58
|
+
* reported as unsupported instead of being silently skipped over.
|
|
59
|
+
*/
|
|
60
|
+
export declare function scanShellCommand(cmd: string): ShellCommandScan;
|
|
28
61
|
/**
|
|
29
62
|
* Check if a command is read-only and safe to execute without confirmation.
|
|
63
|
+
*
|
|
64
|
+
* Every command on the line must be independently read-only.
|
|
30
65
|
*/
|
|
31
66
|
export declare function isReadOnlyCommand(cmd: string): boolean;
|
|
32
67
|
/**
|
|
33
68
|
* Check if a command is a bounded validation command.
|
|
69
|
+
*
|
|
70
|
+
* Segment-aware for the same reason as `isReadOnlyCommand`: without it,
|
|
71
|
+
* `npm test && rm -rf /tmp/x` matches the leading validation pattern and is
|
|
72
|
+
* auto-approved in full.
|
|
34
73
|
*/
|
|
35
74
|
export declare function isValidationCommand(cmd: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Structured replacement for the literal `rm -rf /` patterns.
|
|
77
|
+
*
|
|
78
|
+
* Returns the normalised target that makes the command catastrophic, or null.
|
|
79
|
+
* Matching on parsed flags and canonicalised operands closes the rewrites that
|
|
80
|
+
* defeat the anchored patterns: `rm -fr /`, `rm -r -f /`,
|
|
81
|
+
* `rm --recursive --force /`, `rm -rf /*` and, most importantly,
|
|
82
|
+
* `rm -rf / --no-preserve-root` -- the only form that actually deletes anything
|
|
83
|
+
* on GNU coreutils.
|
|
84
|
+
*/
|
|
85
|
+
export declare function findDestructiveRmTarget(cmd: string): string | null;
|
|
36
86
|
/**
|
|
37
87
|
* Check if a command matches dangerous patterns.
|
|
38
88
|
* Returns the first matched dangerous pattern, or null if safe.
|
|
@@ -53,22 +103,4 @@ export declare function assessCommandSecurity(cmd: string): {
|
|
|
53
103
|
reason?: string;
|
|
54
104
|
isReadOnly: boolean;
|
|
55
105
|
};
|
|
56
|
-
/**
|
|
57
|
-
* Sandbox execution options.
|
|
58
|
-
*/
|
|
59
|
-
export interface SandboxOptions {
|
|
60
|
-
mode: 'none' | 'docker' | 'bubblewrap';
|
|
61
|
-
container?: string;
|
|
62
|
-
timeout?: number;
|
|
63
|
-
network?: boolean;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Default sandbox options (disabled).
|
|
67
|
-
*/
|
|
68
|
-
export declare const DEFAULT_SANDBOX_OPTIONS: SandboxOptions;
|
|
69
|
-
/**
|
|
70
|
-
* Wrap command for sandbox execution.
|
|
71
|
-
* Returns the modified command for sandbox execution.
|
|
72
|
-
*/
|
|
73
|
-
export declare function wrapForSandbox(cmd: string, options: SandboxOptions): string;
|
|
74
106
|
//# sourceMappingURL=bash_security.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash_security.d.ts","sourceRoot":"","sources":["../../src/tools/bash_security.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"bash_security.d.ts","sourceRoot":"","sources":["../../src/tools/bash_security.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,UA4H9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAmBnE,CAAC;AAuBF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,UAOvC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;GAoC9B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gCAAgC,UAW5C,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAC;CACrB;AAKD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAuG9D;AAgBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEtD;AAuFD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIxD;AAkID;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBlE;AAiBD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAe7F;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO7D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAClD,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACrB,CA2CA"}
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
* Provides safety checks and sandbox options for command execution.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.
|
|
8
|
+
exports.POTENTIALLY_DESTRUCTIVE_PATTERNS = exports.DANGEROUS_PATTERNS = exports.VALIDATION_COMMAND_PATTERNS = exports.READ_ONLY_SUBCOMMANDS = exports.READ_ONLY_COMMANDS = void 0;
|
|
9
|
+
exports.scanShellCommand = scanShellCommand;
|
|
9
10
|
exports.isReadOnlyCommand = isReadOnlyCommand;
|
|
10
11
|
exports.isValidationCommand = isValidationCommand;
|
|
12
|
+
exports.findDestructiveRmTarget = findDestructiveRmTarget;
|
|
11
13
|
exports.checkDangerousCommand = checkDangerousCommand;
|
|
12
14
|
exports.isPotentiallyDestructive = isPotentiallyDestructive;
|
|
13
15
|
exports.assessCommandSecurity = assessCommandSecurity;
|
|
14
|
-
exports.wrapForSandbox = wrapForSandbox;
|
|
15
16
|
/**
|
|
16
17
|
* Commands that are considered read-only and safe to execute without confirmation.
|
|
17
18
|
* These commands only read data and do not modify the filesystem.
|
|
@@ -112,16 +113,11 @@ exports.READ_ONLY_COMMANDS = [
|
|
|
112
113
|
'htop',
|
|
113
114
|
'lsof',
|
|
114
115
|
'pgrep',
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
'pip',
|
|
121
|
-
'pip3',
|
|
122
|
-
'cargo',
|
|
123
|
-
'top',
|
|
124
|
-
'htop',
|
|
116
|
+
// Job control
|
|
117
|
+
// NOTE: the package-manager drivers (npm/npx/yarn/pnpm/pip/cargo) are
|
|
118
|
+
// deliberately NOT listed here. Whether they read or execute depends entirely
|
|
119
|
+
// on the sub-command, so they are allow-listed per sub-command in
|
|
120
|
+
// READ_ONLY_SUBCOMMANDS instead.
|
|
125
121
|
'jobs',
|
|
126
122
|
// JSON/YAML processing (read-only when no file write)
|
|
127
123
|
'jq',
|
|
@@ -136,6 +132,51 @@ exports.READ_ONLY_COMMANDS = [
|
|
|
136
132
|
'--version',
|
|
137
133
|
'-V',
|
|
138
134
|
];
|
|
135
|
+
/**
|
|
136
|
+
* Sub-commands that only read state, keyed by the driver that runs them.
|
|
137
|
+
*
|
|
138
|
+
* The drivers themselves must never appear in READ_ONLY_COMMANDS: `npm run`,
|
|
139
|
+
* `npm exec`, `yarn <script>`, `pnpm run`, `cargo run` and `npx <pkg>` all
|
|
140
|
+
* execute arbitrary project-defined or freshly downloaded code, so the safe
|
|
141
|
+
* surface has to be enumerated rather than subtracted.
|
|
142
|
+
*/
|
|
143
|
+
exports.READ_ONLY_SUBCOMMANDS = {
|
|
144
|
+
git: [
|
|
145
|
+
'status',
|
|
146
|
+
'log',
|
|
147
|
+
'diff',
|
|
148
|
+
'show',
|
|
149
|
+
'branch',
|
|
150
|
+
'tag',
|
|
151
|
+
'remote',
|
|
152
|
+
'rev-parse',
|
|
153
|
+
'ls-files',
|
|
154
|
+
'ls-tree',
|
|
155
|
+
],
|
|
156
|
+
npm: ['list', 'ls', 'info', 'view', 'search', 'outdated', 'why'],
|
|
157
|
+
yarn: ['list', 'info', 'why'],
|
|
158
|
+
pnpm: ['list', 'ls', 'info', 'why', 'outdated'],
|
|
159
|
+
cargo: ['tree', 'metadata', 'search'],
|
|
160
|
+
pip: ['list', 'show', 'freeze'],
|
|
161
|
+
pip3: ['list', 'show', 'freeze'],
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* git options that hand execution to an external program or write a file,
|
|
165
|
+
* whichever sub-command they are attached to.
|
|
166
|
+
*/
|
|
167
|
+
const GIT_UNSAFE_OPTIONS = /(^|\s)--(ext-diff|extcmd|output|exec-path|upload-pack|receive-pack)\b/;
|
|
168
|
+
/**
|
|
169
|
+
* `git branch` / `git tag` only list when they are given nothing but these
|
|
170
|
+
* flags. Any other argument may create, move or delete a ref, so the allow-list
|
|
171
|
+
* is deliberately valueless flags only -- `git branch --contains HEAD` reads,
|
|
172
|
+
* but prompting for it is far cheaper than letting `git branch -D main` through.
|
|
173
|
+
*/
|
|
174
|
+
const GIT_LISTING_FLAGS = {
|
|
175
|
+
branch: /^(-a|--all|-r|--remotes|-v|-vv|--verbose|-l|--list|--no-color|--color(=.+)?|--sort=.+|--format=.+)$/,
|
|
176
|
+
tag: /^(-l|--list|-n\d*|-i|--ignore-case|--no-color|--color(=.+)?|--sort=.+|--format=.+)$/,
|
|
177
|
+
};
|
|
178
|
+
/** `git remote` reads with no argument, or via these two sub-sub-commands. */
|
|
179
|
+
const GIT_REMOTE_READ_ONLY_ACTIONS = new Set(['show', 'get-url']);
|
|
139
180
|
/**
|
|
140
181
|
* Validation commands that are safe enough to run without an interactive
|
|
141
182
|
* confirmation prompt. They may read the project and execute local test code,
|
|
@@ -153,7 +194,10 @@ exports.VALIDATION_COMMAND_PATTERNS = [
|
|
|
153
194
|
* Dangerous command patterns that should always be blocked or require confirmation.
|
|
154
195
|
*/
|
|
155
196
|
exports.DANGEROUS_PATTERNS = [
|
|
156
|
-
// Filesystem destruction
|
|
197
|
+
// Filesystem destruction.
|
|
198
|
+
// NOTE: these literal forms are a backstop only. Flag order, long options and
|
|
199
|
+
// trailing arguments all defeat them, so the authoritative check is the
|
|
200
|
+
// structured one in findDestructiveRmTarget(); see checkDangerousCommand().
|
|
157
201
|
{ pattern: /rm\s+-rf\s+\/$/, reason: 'Attempting to delete root directory' },
|
|
158
202
|
{ pattern: /rm\s+-rf\s+~$/, reason: 'Attempting to delete home directory' },
|
|
159
203
|
{ pattern: /rm\s+-rf\s+\*/, reason: 'Attempting to delete all files in current directory' },
|
|
@@ -195,11 +239,136 @@ exports.POTENTIALLY_DESTRUCTIVE_PATTERNS = [
|
|
|
195
239
|
/curl.*>\s*\//,
|
|
196
240
|
/wget.*-O\s*\//,
|
|
197
241
|
];
|
|
242
|
+
/** The one redirect target that does not count as writing to the filesystem. */
|
|
243
|
+
const DISCARD_REDIRECT_TARGET = '/dev/null';
|
|
244
|
+
/**
|
|
245
|
+
* Split a command line into the individual commands it will actually run.
|
|
246
|
+
*
|
|
247
|
+
* The classifier below hands out "run without confirmation" verdicts, so it has
|
|
248
|
+
* to see every command on the line rather than just the leading token. Quote
|
|
249
|
+
* state is tracked so `echo "a && b"` stays one segment, and substitutions are
|
|
250
|
+
* reported as unsupported instead of being silently skipped over.
|
|
251
|
+
*/
|
|
252
|
+
function scanShellCommand(cmd) {
|
|
253
|
+
const segments = [];
|
|
254
|
+
let current = '';
|
|
255
|
+
let supported = true;
|
|
256
|
+
let writesFile = false;
|
|
257
|
+
let inSingleQuote = false;
|
|
258
|
+
let inDoubleQuote = false;
|
|
259
|
+
const pushSegment = () => {
|
|
260
|
+
const segment = current.trim();
|
|
261
|
+
if (segment.length > 0) {
|
|
262
|
+
segments.push(segment);
|
|
263
|
+
}
|
|
264
|
+
current = '';
|
|
265
|
+
};
|
|
266
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
267
|
+
const ch = cmd[i];
|
|
268
|
+
const next = cmd[i + 1];
|
|
269
|
+
if (ch === '\\' && !inSingleQuote && next !== undefined) {
|
|
270
|
+
current += ch + next;
|
|
271
|
+
i++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (ch === "'" && !inDoubleQuote) {
|
|
275
|
+
inSingleQuote = !inSingleQuote;
|
|
276
|
+
current += ch;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (ch === '"' && !inSingleQuote) {
|
|
280
|
+
inDoubleQuote = !inDoubleQuote;
|
|
281
|
+
current += ch;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (inSingleQuote || inDoubleQuote) {
|
|
285
|
+
current += ch;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
// Command and process substitution can hide an arbitrary command inside an
|
|
289
|
+
// otherwise innocent-looking line, and neither is worth modelling here.
|
|
290
|
+
if (ch === '`' || (ch === '$' && next === '(') || ((ch === '<' || ch === '>') && next === '(')) {
|
|
291
|
+
supported = false;
|
|
292
|
+
current += ch;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (ch === ';' || ch === '\n') {
|
|
296
|
+
pushSegment();
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if ((ch === '&' && next === '&') || (ch === '|' && next === '|')) {
|
|
300
|
+
pushSegment();
|
|
301
|
+
i++;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (ch === '&' || ch === '|') {
|
|
305
|
+
pushSegment();
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (ch === '>') {
|
|
309
|
+
let cursor = i + 1;
|
|
310
|
+
if (cmd[cursor] === '>') {
|
|
311
|
+
cursor++;
|
|
312
|
+
}
|
|
313
|
+
if (cmd[cursor] === '&') {
|
|
314
|
+
// `>&1` / `2>&1` duplicate a descriptor and `>&-` closes one; nothing
|
|
315
|
+
// reaches the filesystem. Consume the whole reference so its `&` is not
|
|
316
|
+
// mistaken for a background-job separator.
|
|
317
|
+
let end = cursor + 1;
|
|
318
|
+
while (end < cmd.length && cmd[end] >= '0' && cmd[end] <= '9') {
|
|
319
|
+
end++;
|
|
320
|
+
}
|
|
321
|
+
if (cmd[end] === '-') {
|
|
322
|
+
end++;
|
|
323
|
+
}
|
|
324
|
+
current += cmd.slice(i, end);
|
|
325
|
+
i = end - 1;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
while (cursor < cmd.length && (cmd[cursor] === ' ' || cmd[cursor] === '\t')) {
|
|
329
|
+
cursor++;
|
|
330
|
+
}
|
|
331
|
+
const target = cmd.slice(cursor).split(/[\s;&|<>]/)[0];
|
|
332
|
+
if (target !== DISCARD_REDIRECT_TARGET) {
|
|
333
|
+
writesFile = true;
|
|
334
|
+
}
|
|
335
|
+
current += ch;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
current += ch;
|
|
339
|
+
}
|
|
340
|
+
pushSegment();
|
|
341
|
+
return {
|
|
342
|
+
segments,
|
|
343
|
+
supported: supported && !inSingleQuote && !inDoubleQuote,
|
|
344
|
+
writesFile,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Require every command on the line to satisfy `predicate`.
|
|
349
|
+
*
|
|
350
|
+
* A single unrecognised segment is enough to withhold the auto-approval, which
|
|
351
|
+
* is what stops `echo hi && rm -rf ~` from inheriting `echo`'s verdict.
|
|
352
|
+
*/
|
|
353
|
+
function everySegmentSatisfies(cmd, predicate) {
|
|
354
|
+
const scan = scanShellCommand(cmd.trim());
|
|
355
|
+
if (!scan.supported || scan.writesFile || scan.segments.length === 0) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
return scan.segments.every(predicate);
|
|
359
|
+
}
|
|
198
360
|
/**
|
|
199
361
|
* Check if a command is read-only and safe to execute without confirmation.
|
|
362
|
+
*
|
|
363
|
+
* Every command on the line must be independently read-only.
|
|
200
364
|
*/
|
|
201
365
|
function isReadOnlyCommand(cmd) {
|
|
202
|
-
|
|
366
|
+
return everySegmentSatisfies(cmd, isReadOnlySegment);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Check whether a single command -- with no separators left in it -- is read-only.
|
|
370
|
+
*/
|
|
371
|
+
function isReadOnlySegment(trimmedCmd) {
|
|
203
372
|
// Check exact matches first
|
|
204
373
|
if (exports.READ_ONLY_COMMANDS.includes(trimmedCmd)) {
|
|
205
374
|
return true;
|
|
@@ -230,42 +399,217 @@ function isReadOnlyCommand(cmd) {
|
|
|
230
399
|
if (baseCmd === 'sort' && (/(^|\s)-o\b/.test(trimmedCmd) || trimmedCmd.includes('>'))) {
|
|
231
400
|
return false; // sort -o / redirect writes a file
|
|
232
401
|
}
|
|
233
|
-
// npm/pip/cargo/yarn install commands have side effects
|
|
234
|
-
if ((baseCmd === 'npm' || baseCmd === 'yarn' || baseCmd === 'pnpm') &&
|
|
235
|
-
/\b(install|add|remove|uninstall|publish|link|unlink|deprecate|audit fix|fund)\b/.test(trimmedCmd)) {
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
if ((baseCmd === 'pip' || baseCmd === 'pip3') && /\binstall\b/.test(trimmedCmd)) {
|
|
239
|
-
return false; // pip install modifies system
|
|
240
|
-
}
|
|
241
|
-
if (baseCmd === 'cargo' && /\b(install|publish|update|remove)\b/.test(trimmedCmd)) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
402
|
return true;
|
|
245
403
|
}
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
404
|
+
// Driver commands are read-only only for an explicitly allow-listed
|
|
405
|
+
// sub-command, matched on the exact token.
|
|
406
|
+
return isReadOnlyDriverInvocation(trimmedCmd);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Classify `git`, `npm`, `cargo` and friends by their sub-command.
|
|
410
|
+
*
|
|
411
|
+
* Matching is on the exact sub-command token. The previous implementation used
|
|
412
|
+
* `startsWith`, which let `git difftool --extcmd=...` match the `git diff`
|
|
413
|
+
* entry and run an arbitrary program.
|
|
414
|
+
*/
|
|
415
|
+
function isReadOnlyDriverInvocation(segment) {
|
|
416
|
+
const tokens = segment.split(/\s+/).filter(token => token.length > 0);
|
|
417
|
+
const driver = tokens[0];
|
|
418
|
+
const subcommand = tokens[1];
|
|
419
|
+
const allowed = driver ? exports.READ_ONLY_SUBCOMMANDS[driver] : undefined;
|
|
420
|
+
if (!allowed || !subcommand || !allowed.includes(subcommand)) {
|
|
421
|
+
return false;
|
|
254
422
|
}
|
|
255
|
-
|
|
423
|
+
if (driver !== 'git') {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
if (GIT_UNSAFE_OPTIONS.test(segment)) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
const args = tokens.slice(2);
|
|
430
|
+
if (subcommand === 'remote') {
|
|
431
|
+
// `git remote [-v]` lists; `git remote show|get-url <name>` reads.
|
|
432
|
+
// Everything else -- add, remove, rename, set-url, prune -- mutates.
|
|
433
|
+
const positional = args.filter(arg => arg !== '-v' && arg !== '--verbose');
|
|
434
|
+
return positional.length === 0 || GIT_REMOTE_READ_ONLY_ACTIONS.has(positional[0]);
|
|
435
|
+
}
|
|
436
|
+
const listingFlags = GIT_LISTING_FLAGS[subcommand];
|
|
437
|
+
if (listingFlags) {
|
|
438
|
+
return args.every(arg => listingFlags.test(arg));
|
|
439
|
+
}
|
|
440
|
+
return true;
|
|
256
441
|
}
|
|
257
442
|
/**
|
|
258
443
|
* Check if a command is a bounded validation command.
|
|
444
|
+
*
|
|
445
|
+
* Segment-aware for the same reason as `isReadOnlyCommand`: without it,
|
|
446
|
+
* `npm test && rm -rf /tmp/x` matches the leading validation pattern and is
|
|
447
|
+
* auto-approved in full.
|
|
259
448
|
*/
|
|
260
449
|
function isValidationCommand(cmd) {
|
|
261
|
-
|
|
262
|
-
|
|
450
|
+
return everySegmentSatisfies(cmd, segment => exports.VALIDATION_COMMAND_PATTERNS.some(pattern => pattern.test(segment)));
|
|
451
|
+
}
|
|
452
|
+
/** Wrappers that may precede `rm` without changing what it deletes. */
|
|
453
|
+
const COMMAND_WRAPPERS = new Set(['sudo', 'doas', 'command', 'builtin', 'nohup', 'time', 'exec']);
|
|
454
|
+
/**
|
|
455
|
+
* Targets whose deletion is never a legitimate agent action, in the normalised
|
|
456
|
+
* form produced by {@link normalizeRmTarget}.
|
|
457
|
+
*/
|
|
458
|
+
const CATASTROPHIC_RM_TARGETS = new Set([
|
|
459
|
+
'/',
|
|
460
|
+
'~',
|
|
461
|
+
'.',
|
|
462
|
+
'..',
|
|
463
|
+
'*',
|
|
464
|
+
// System roots: wiping any of these bricks the machine just as thoroughly as
|
|
465
|
+
// `/` does, and no build or cleanup step has a reason to touch them.
|
|
466
|
+
'/bin',
|
|
467
|
+
'/boot',
|
|
468
|
+
'/dev',
|
|
469
|
+
'/etc',
|
|
470
|
+
'/lib',
|
|
471
|
+
'/lib64',
|
|
472
|
+
'/opt',
|
|
473
|
+
'/proc',
|
|
474
|
+
'/root',
|
|
475
|
+
'/sbin',
|
|
476
|
+
'/srv',
|
|
477
|
+
'/sys',
|
|
478
|
+
'/usr',
|
|
479
|
+
'/var',
|
|
480
|
+
'/home',
|
|
481
|
+
'/Users',
|
|
482
|
+
'/System',
|
|
483
|
+
'/Library',
|
|
484
|
+
'/Applications',
|
|
485
|
+
]);
|
|
486
|
+
/**
|
|
487
|
+
* Reduce an `rm` operand to a canonical path so that every spelling of the same
|
|
488
|
+
* catastrophic target collapses onto one key: `/`, `/*`, `/**` and `///` all
|
|
489
|
+
* become `/`, and `~/`, `~/*`, `$HOME`, `${HOME}/` all become `~`.
|
|
490
|
+
*/
|
|
491
|
+
function normalizeRmTarget(raw) {
|
|
492
|
+
let target = raw.trim();
|
|
493
|
+
const quote = target[0];
|
|
494
|
+
if ((quote === "'" || quote === '"') && target.endsWith(quote) && target.length > 1) {
|
|
495
|
+
target = target.slice(1, -1).trim();
|
|
496
|
+
}
|
|
497
|
+
target = target.replace(/^\$\{HOME\}/, '~').replace(/^\$HOME/, '~');
|
|
498
|
+
// A trailing glob deletes the directory's contents, which is the same disaster
|
|
499
|
+
// as deleting the directory itself.
|
|
500
|
+
target = target.replace(/\/\*+$/, '/');
|
|
501
|
+
target = target.replace(/\/{2,}/g, '/');
|
|
502
|
+
if (target.length > 1) {
|
|
503
|
+
target = target.replace(/\/+$/, '');
|
|
504
|
+
}
|
|
505
|
+
return target;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Parse a single command segment as an `rm` invocation.
|
|
509
|
+
*
|
|
510
|
+
* Returns null when the segment does not run `rm`. Flags are collected
|
|
511
|
+
* order-independently, so `-rf`, `-fr`, `-r -f` and `--recursive --force` are
|
|
512
|
+
* all recognised as the same request.
|
|
513
|
+
*/
|
|
514
|
+
function parseRmInvocation(segment) {
|
|
515
|
+
const tokens = segment.split(/\s+/).filter(token => token.length > 0);
|
|
516
|
+
let index = 0;
|
|
517
|
+
while (index < tokens.length && COMMAND_WRAPPERS.has(tokens[index])) {
|
|
518
|
+
index++;
|
|
519
|
+
}
|
|
520
|
+
const binary = tokens[index];
|
|
521
|
+
if (!binary || !/(^|\/)rm$/.test(binary)) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
index++;
|
|
525
|
+
const invocation = {
|
|
526
|
+
recursive: false,
|
|
527
|
+
force: false,
|
|
528
|
+
noPreserveRoot: false,
|
|
529
|
+
targets: [],
|
|
530
|
+
};
|
|
531
|
+
let endOfOptions = false;
|
|
532
|
+
for (; index < tokens.length; index++) {
|
|
533
|
+
const token = tokens[index];
|
|
534
|
+
if (!endOfOptions && token === '--') {
|
|
535
|
+
endOfOptions = true;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (!endOfOptions && token.startsWith('--')) {
|
|
539
|
+
if (token === '--recursive')
|
|
540
|
+
invocation.recursive = true;
|
|
541
|
+
else if (token === '--force')
|
|
542
|
+
invocation.force = true;
|
|
543
|
+
else if (token === '--no-preserve-root')
|
|
544
|
+
invocation.noPreserveRoot = true;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (!endOfOptions && token.length > 1 && token.startsWith('-')) {
|
|
548
|
+
for (const flag of token.slice(1)) {
|
|
549
|
+
if (flag === 'r' || flag === 'R')
|
|
550
|
+
invocation.recursive = true;
|
|
551
|
+
else if (flag === 'f')
|
|
552
|
+
invocation.force = true;
|
|
553
|
+
}
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
invocation.targets.push(token);
|
|
557
|
+
}
|
|
558
|
+
return invocation;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Structured replacement for the literal `rm -rf /` patterns.
|
|
562
|
+
*
|
|
563
|
+
* Returns the normalised target that makes the command catastrophic, or null.
|
|
564
|
+
* Matching on parsed flags and canonicalised operands closes the rewrites that
|
|
565
|
+
* defeat the anchored patterns: `rm -fr /`, `rm -r -f /`,
|
|
566
|
+
* `rm --recursive --force /`, `rm -rf /*` and, most importantly,
|
|
567
|
+
* `rm -rf / --no-preserve-root` -- the only form that actually deletes anything
|
|
568
|
+
* on GNU coreutils.
|
|
569
|
+
*/
|
|
570
|
+
function findDestructiveRmTarget(cmd) {
|
|
571
|
+
for (const segment of scanShellCommand(cmd).segments) {
|
|
572
|
+
const invocation = parseRmInvocation(segment);
|
|
573
|
+
if (!invocation)
|
|
574
|
+
continue;
|
|
575
|
+
// `rm /` without -r or -f cannot remove a directory, so it is noise rather
|
|
576
|
+
// than a threat. Either flag turns the same operand into a real deletion.
|
|
577
|
+
if (!invocation.recursive && !invocation.force)
|
|
578
|
+
continue;
|
|
579
|
+
for (const target of invocation.targets) {
|
|
580
|
+
if (CATASTROPHIC_RM_TARGETS.has(normalizeRmTarget(target))) {
|
|
581
|
+
return target;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
/** Reported when the structured `rm` check fires rather than a literal pattern. */
|
|
588
|
+
const DESTRUCTIVE_RM_PATTERN = /\brm\b/;
|
|
589
|
+
/** Human-readable description of why a normalised target is off-limits. */
|
|
590
|
+
function describeCatastrophicTarget(raw) {
|
|
591
|
+
const normalized = normalizeRmTarget(raw);
|
|
592
|
+
if (normalized === '/')
|
|
593
|
+
return 'the root directory';
|
|
594
|
+
if (normalized === '~')
|
|
595
|
+
return 'the home directory';
|
|
596
|
+
if (normalized === '.' || normalized === '..' || normalized === '*') {
|
|
597
|
+
return 'the current directory contents';
|
|
598
|
+
}
|
|
599
|
+
return `the system directory ${normalized}`;
|
|
263
600
|
}
|
|
264
601
|
/**
|
|
265
602
|
* Check if a command matches dangerous patterns.
|
|
266
603
|
* Returns the first matched dangerous pattern, or null if safe.
|
|
267
604
|
*/
|
|
268
605
|
function checkDangerousCommand(cmd) {
|
|
606
|
+
const rmTarget = findDestructiveRmTarget(cmd);
|
|
607
|
+
if (rmTarget) {
|
|
608
|
+
return {
|
|
609
|
+
pattern: DESTRUCTIVE_RM_PATTERN,
|
|
610
|
+
reason: `Attempting to delete ${describeCatastrophicTarget(rmTarget)} (${rmTarget})`,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
269
613
|
for (const { pattern, reason } of exports.DANGEROUS_PATTERNS) {
|
|
270
614
|
if (pattern.test(cmd)) {
|
|
271
615
|
return { pattern, reason };
|
|
@@ -327,30 +671,19 @@ function assessCommandSecurity(cmd) {
|
|
|
327
671
|
isReadOnly: false,
|
|
328
672
|
};
|
|
329
673
|
}
|
|
330
|
-
|
|
331
|
-
*
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
*
|
|
338
|
-
*
|
|
674
|
+
/*
|
|
675
|
+
* The `SandboxOptions` / `DEFAULT_SANDBOX_OPTIONS` / `wrapForSandbox` trio that
|
|
676
|
+
* used to live here has been removed.
|
|
677
|
+
*
|
|
678
|
+
* `wrapForSandbox` advertised network isolation it never delivered: it emitted
|
|
679
|
+
* `docker exec --network none <container> sh -c '<cmd>'`, but `--network` is a
|
|
680
|
+
* `docker run` flag and `docker exec` rejects it with `unknown flag`. With
|
|
681
|
+
* `network: true` the flag was dropped altogether, so neither configuration
|
|
682
|
+
* isolated anything. Building the wrapper as a shell string was unsound anyway:
|
|
683
|
+
* every layer re-parses the command, so a quoting bug becomes a sandbox escape.
|
|
684
|
+
*
|
|
685
|
+
* `src/tools/sandbox.ts` supersedes it with an argv-based implementation that
|
|
686
|
+
* probes backend availability and fails closed. Use `planSandboxedCommand()`
|
|
687
|
+
* from there instead.
|
|
339
688
|
*/
|
|
340
|
-
function wrapForSandbox(cmd, options) {
|
|
341
|
-
if (options.mode === 'none') {
|
|
342
|
-
return cmd;
|
|
343
|
-
}
|
|
344
|
-
if (options.mode === 'docker') {
|
|
345
|
-
const container = options.container || 'orion-code-sandbox';
|
|
346
|
-
const networkFlag = options.network ? '' : '--network none';
|
|
347
|
-
return `docker exec ${networkFlag} ${container} sh -c '${cmd.replace(/'/g, "'\\''")}'`;
|
|
348
|
-
}
|
|
349
|
-
if (options.mode === 'bubblewrap') {
|
|
350
|
-
// bubblewrap (bwrap) for Linux sandboxing
|
|
351
|
-
const networkFlag = options.network ? '' : '--unshare-net';
|
|
352
|
-
return `bwrap ${networkFlag} --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /bin /bin --bind /tmp /tmp --proc /proc --dev-bind /dev /dev sh -c '${cmd.replace(/'/g, "'\\''")}'`;
|
|
353
|
-
}
|
|
354
|
-
return cmd;
|
|
355
|
-
}
|
|
356
689
|
//# sourceMappingURL=bash_security.js.map
|