@wrongstack/tools 0.277.1 → 0.280.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,121 @@
1
+ import { Tool } from '@wrongstack/core';
2
+
3
+ /**
4
+ * Heuristic danger detection for `exec` tool commands.
5
+ *
6
+ * Layered on top of `BLOCKED_ARG_PATTERNS` (which is a hard-deny list for
7
+ * clear sandbox escapes) and `bash-kill-guard.ts` (which protects WrongStack
8
+ * itself from kill). This module assigns a danger level to a command/arg
9
+ * pair so the caller can decide whether to:
10
+ *
11
+ * - 'safe' → execute normally
12
+ * - 'caution' → execute and emit a warning line to the tool output
13
+ * - 'destructive' → route through the existing confirm flow
14
+ * (`execTool.permission === 'confirm'`) instead of
15
+ * hard-deny, so the user can still proceed if intentional
16
+ *
17
+ * Design constraints:
18
+ * - Deterministic: no randomness, no I/O, no time. Same input → same output.
19
+ * - No LLM calls. Patterns are regex / exact-match.
20
+ * - Per-rule `id` so config can override specific rules via
21
+ * `tools.exec.danger.bypass`.
22
+ * - Reasons are human-readable, joined with "; " for the confirm prompt.
23
+ *
24
+ * Caution rules are deliberately permissive — they execute and emit a
25
+ * warning rather than blocking. The rationale: many of these patterns
26
+ * (python -c, sudo, curl | bash) are part of legitimate dev workflows,
27
+ * so a hard deny would block too much. A warning gives the user a
28
+ * chance to notice "wait, I didn't mean to do that" without forcing
29
+ * them to add a config override for every script.
30
+ */
31
+ type DangerLevel = 'safe' | 'caution' | 'destructive';
32
+ interface DangerAssessment {
33
+ level: DangerLevel;
34
+ reasons: string[];
35
+ /** Stable id of the matched rule, for tests and config-override. */
36
+ matchedRule?: string;
37
+ }
38
+ /**
39
+ * Evaluate the danger level of a (cmd, args) pair.
40
+ *
41
+ * Returns 'safe' if no rule fires, otherwise the highest level among all
42
+ * matching rules. The 'matchedRule' field is the *last* rule that fired
43
+ * (stable, since rules are evaluated in declaration order).
44
+ *
45
+ * Optional `bypass` argument: a set of rule ids that should be SKIPPED
46
+ * even if they would otherwise match. Wired from
47
+ * `config.tools.exec.danger.bypass` (see `ExecDangerConfig` in
48
+ * `@wrongstack/core/src/types/config.ts`). Unknown ids are silently
49
+ * ignored — forward-compat: a rule added in a future version can be
50
+ * referenced before the user upgrades their config schema.
51
+ *
52
+ * This function is the single source of truth for danger classification;
53
+ * it is pure (no side effects) and unit-tested in `danger-detect.test.ts`.
54
+ */
55
+ declare function detectDanger(cmd: string, args: readonly string[], bypass?: ReadonlySet<string>): DangerAssessment;
56
+
57
+ /**
58
+ * Apply the configured exec command policy. Recomputes the effective allowlist
59
+ * as `DEFAULT ∪ allow − deny`. Call once at boot from
60
+ * `config.tools.exec.{allow,deny}`. Idempotent (always rebuilt from defaults).
61
+ *
62
+ * SECURITY: `allow` must originate from TRUSTED config only — the config loader
63
+ * strips `tools.exec.allow` from the untrusted in-project repo config before it
64
+ * reaches here. `deny` is safe from any source (it only narrows).
65
+ */
66
+ declare function configureExecPolicy(opts?: {
67
+ allow?: readonly string[] | undefined;
68
+ deny?: readonly string[] | undefined;
69
+ }): void;
70
+ /** Reset the exec allowlist to the built-in defaults (tests / re-init). */
71
+ declare function resetExecPolicy(): void;
72
+ /**
73
+ * Apply the configured danger-bypass policy. Each id in `bypass` is
74
+ * added to the effective skip set; duplicates are fine. Idempotent.
75
+ *
76
+ * Call once at boot from `config.tools.exec.danger.bypass`.
77
+ */
78
+ declare function configureDangerBypass(opts?: {
79
+ bypass?: readonly string[] | undefined;
80
+ }): void;
81
+ /** Reset the danger-bypass set to empty (tests / re-init). */
82
+ declare function resetDangerBypass(): void;
83
+ /**
84
+ * Read-only view of the active bypass set. `detectDanger()` takes a
85
+ * `bypass` argument directly, so consumers should prefer passing this
86
+ * rather than reading the set and matching themselves.
87
+ */
88
+ declare function getDangerBypass(): ReadonlySet<string>;
89
+ /** Whether `cmd` is currently in the effective exec allowlist. */
90
+ declare function isExecCommandAllowed(cmd: string): boolean;
91
+ /** Snapshot of the effective allowlist (sorted) — for tests / diagnostics. */
92
+ declare function getExecAllowlist(): string[];
93
+ interface ExecInput {
94
+ command: string;
95
+ args?: string[] | undefined;
96
+ cwd?: string | undefined;
97
+ timeout?: number | undefined;
98
+ }
99
+ interface ExecOutput {
100
+ command: string;
101
+ args: string[];
102
+ stdout: string;
103
+ stderr: string;
104
+ exitCode: number;
105
+ truncated: boolean;
106
+ allowed: boolean;
107
+ /**
108
+ * Heuristic danger assessment of the (cmd, args) pair. Populated for every
109
+ * call (not just blocked ones) so the UI/TUI can render a banner when the
110
+ * level is 'caution' or 'destructive'. See `_danger-detect.ts` for the
111
+ * rule set.
112
+ *
113
+ * Pre-execution error returns (allowlist miss, circuit breaker, etc.)
114
+ * report `level: 'safe'` because the command never actually ran; the UI
115
+ * should surface the error separately and not also a danger warning.
116
+ */
117
+ danger: DangerAssessment;
118
+ }
119
+ declare const execTool: Tool<ExecInput, ExecOutput>;
120
+
121
+ export { type DangerAssessment as D, type DangerLevel as a, configureExecPolicy as b, configureDangerBypass as c, detectDanger as d, execTool as e, getExecAllowlist as f, getDangerBypass as g, resetExecPolicy as h, isExecCommandAllowed as i, resetDangerBypass as r };
package/dist/exec.d.ts CHANGED
@@ -1,39 +1,2 @@
1
- import { Tool } from '@wrongstack/core';
2
-
3
- /**
4
- * Apply the configured exec command policy. Recomputes the effective allowlist
5
- * as `DEFAULT ∪ allow − deny`. Call once at boot from
6
- * `config.tools.exec.{allow,deny}`. Idempotent (always rebuilt from defaults).
7
- *
8
- * SECURITY: `allow` must originate from TRUSTED config only — the config loader
9
- * strips `tools.exec.allow` from the untrusted in-project repo config before it
10
- * reaches here. `deny` is safe from any source (it only narrows).
11
- */
12
- declare function configureExecPolicy(opts?: {
13
- allow?: readonly string[] | undefined;
14
- deny?: readonly string[] | undefined;
15
- }): void;
16
- /** Reset the exec allowlist to the built-in defaults (tests / re-init). */
17
- declare function resetExecPolicy(): void;
18
- /** Whether `cmd` is currently in the effective exec allowlist. */
19
- declare function isExecCommandAllowed(cmd: string): boolean;
20
- /** Snapshot of the effective allowlist (sorted) — for tests / diagnostics. */
21
- declare function getExecAllowlist(): string[];
22
- interface ExecInput {
23
- command: string;
24
- args?: string[] | undefined;
25
- cwd?: string | undefined;
26
- timeout?: number | undefined;
27
- }
28
- interface ExecOutput {
29
- command: string;
30
- args: string[];
31
- stdout: string;
32
- stderr: string;
33
- exitCode: number;
34
- truncated: boolean;
35
- allowed: boolean;
36
- }
37
- declare const execTool: Tool<ExecInput, ExecOutput>;
38
-
39
- export { configureExecPolicy, execTool, getExecAllowlist, isExecCommandAllowed, resetExecPolicy };
1
+ import '@wrongstack/core';
2
+ export { c as configureDangerBypass, b as configureExecPolicy, e as execTool, g as getDangerBypass, f as getExecAllowlist, i as isExecCommandAllowed, r as resetDangerBypass, h as resetExecPolicy } from './exec-Ca3fnpUh.js';
package/dist/exec.js CHANGED
@@ -832,6 +832,292 @@ function quoteWin32CmdArg(arg) {
832
832
  return `"${arg}"`;
833
833
  }
834
834
 
835
+ // src/_danger-detect.ts
836
+ var argHas = (args, value) => args.includes(value);
837
+ var argMatches = (args, re) => args.some((a) => re.test(a));
838
+ var hasShortFlags = (args, letters) => {
839
+ const seen = /* @__PURE__ */ new Set();
840
+ for (const a of args) {
841
+ if (!a.startsWith("-") || a.startsWith("--")) continue;
842
+ for (const ch of a.replace(/^-+/, "")) seen.add(ch);
843
+ }
844
+ return letters.split("").every((l) => seen.has(l));
845
+ };
846
+ var RULES = [
847
+ // ----- rm / rmdir: recursive force delete (any path) -----
848
+ // Note: BLOCKED_ARG_PATTERNS already hard-denies root/home/glob paths,
849
+ // but `rm -rf ./build` is a normal dev workflow that the user might
850
+ // want to do intentionally. We downgrade it to 'destructive' so the
851
+ // confirm prompt can approve.
852
+ {
853
+ id: "rm-recursive",
854
+ level: "destructive",
855
+ test: (cmd, args) => (cmd === "rm" || cmd === "rmdir") && hasShortFlags(args, "rf"),
856
+ reason: "recursive force-delete"
857
+ },
858
+ // ----- Windows PowerShell Remove-Item: -Recurse -Force -----
859
+ {
860
+ id: "powershell-remove-item-recursive-force",
861
+ level: "destructive",
862
+ test: (cmd, args) => {
863
+ if (cmd !== "powershell" && cmd !== "pwsh") return false;
864
+ const hasRecurse = argMatches(args, /^-(?:R|Recurse|Recurse\s)/);
865
+ const hasForce = argHas(args, "-Force") || argHas(args, "-F");
866
+ if (argHas(args, "-WhatIf")) return false;
867
+ return hasRecurse && hasForce;
868
+ },
869
+ reason: "Remove-Item with -Recurse -Force"
870
+ },
871
+ // ----- find -exec / -ok / -execdir -----
872
+ {
873
+ id: "find-exec",
874
+ level: "destructive",
875
+ test: (cmd, args) => {
876
+ if (cmd !== "find") return false;
877
+ return args.some(
878
+ (a) => a === "-exec" || a === "-exec;" || a === "-ok" || a === "-ok;" || a === "-execdir" || a === "-execdir;" || a.startsWith("-exec=") || a.startsWith("-ok=") || a.startsWith("-execdir=")
879
+ );
880
+ },
881
+ reason: "find with -exec/-ok (executes arbitrary command on matches)"
882
+ },
883
+ // ----- git --exec= / --upload-pack= / --receive-pack= -----
884
+ // These run arbitrary commands via the git transport layer.
885
+ {
886
+ id: "git-exec",
887
+ level: "destructive",
888
+ test: (cmd, args) => cmd === "git" && args.some(
889
+ (a) => a.startsWith("--exec=") || a.startsWith("--upload-pack=") || a.startsWith("--receive-pack=") || a === "--exec" || a === "--upload-pack" || a === "--receive-pack"
890
+ ),
891
+ reason: "git with --exec/--upload-pack/--receive-pack (runs arbitrary code)"
892
+ },
893
+ // ----- Windows: format / diskpart / bcdedit -----
894
+ {
895
+ id: "win32-format",
896
+ level: "destructive",
897
+ test: (cmd) => cmd === "format" || cmd === "format.exe",
898
+ reason: "format (Windows disk format)"
899
+ },
900
+ {
901
+ id: "win32-diskpart",
902
+ level: "destructive",
903
+ test: (cmd) => cmd === "diskpart" || cmd === "diskpart.exe",
904
+ reason: "diskpart (Windows partition editor)"
905
+ },
906
+ {
907
+ id: "win32-bcdedit",
908
+ level: "destructive",
909
+ test: (cmd) => cmd === "bcdedit" || cmd === "bcdedit.exe",
910
+ reason: "bcdedit (Windows boot config editor)"
911
+ },
912
+ // ----- mkfs family -----
913
+ {
914
+ id: "mkfs",
915
+ level: "destructive",
916
+ test: (cmd) => /^mkfs(\.[a-z0-9]+)?$/.test(cmd) || cmd === "mkswap",
917
+ reason: "mkfs (filesystem creation \u2014 destroys existing data)"
918
+ },
919
+ // ----- dd writing to a block device -----
920
+ {
921
+ id: "dd-to-block-device",
922
+ level: "destructive",
923
+ test: (cmd, args) => {
924
+ if (cmd !== "dd") return false;
925
+ return args.some((a) => /of=\/dev\/(sd|hd|nvme|vd|mmcblk|xvd|loop|disk)/.test(a));
926
+ },
927
+ reason: "dd writing to a block device"
928
+ },
929
+ // ----- Secure-erase tools -----
930
+ {
931
+ id: "shred",
932
+ level: "destructive",
933
+ test: (cmd) => cmd === "shred" || cmd === "shred.exe",
934
+ reason: "shred (secure file delete)"
935
+ },
936
+ {
937
+ id: "wipefs",
938
+ level: "destructive",
939
+ test: (cmd) => cmd === "wipefs" || cmd === "wipefs.exe",
940
+ reason: "wipefs (signature wipe \u2014 destroys filesystem headers)"
941
+ },
942
+ {
943
+ id: "sdelete",
944
+ level: "destructive",
945
+ test: (cmd) => cmd === "sdelete" || cmd === "sdelete.exe",
946
+ reason: "sdelete (Sysinternals secure delete)"
947
+ },
948
+ // ----- VCS history rewrite (destructive) -----
949
+ // `git push --force` / `-f` rewrites remote history. `--force-with-lease`
950
+ // is the safer variant (checks remote hasn't moved) but still rewrites.
951
+ {
952
+ id: "git-push-force",
953
+ level: "destructive",
954
+ test: (cmd, args) => {
955
+ if (cmd !== "git") return false;
956
+ const pushIdx = args.indexOf("push");
957
+ if (pushIdx < 0) return false;
958
+ for (let i = pushIdx + 1; i < args.length; i++) {
959
+ const a = args[i];
960
+ if (a === "--force" || a === "-f" || a === "--force-with-lease") return true;
961
+ if (!a.startsWith("-") && !a.includes("=")) continue;
962
+ if (a.startsWith("--force")) return true;
963
+ }
964
+ return false;
965
+ },
966
+ reason: "git push with --force / -f (rewrites remote history)"
967
+ },
968
+ // ----- git reset --hard (destructive) -----
969
+ {
970
+ id: "git-reset-hard",
971
+ level: "destructive",
972
+ test: (cmd, args) => cmd === "git" && args.some((a) => a === "--hard" || a.startsWith("--hard=")),
973
+ reason: "git reset --hard (discards working tree + index)"
974
+ },
975
+ // ----- git clean -f / -fd (destructive) -----
976
+ {
977
+ id: "git-clean-force",
978
+ level: "destructive",
979
+ test: (cmd, args) => {
980
+ if (cmd !== "git") return false;
981
+ const cleanIdx = args.indexOf("clean");
982
+ if (cleanIdx < 0) return false;
983
+ return args.slice(cleanIdx + 1).some(
984
+ (a) => a === "-f" || a === "--force" || a.startsWith("-f") || a.startsWith("--force=")
985
+ );
986
+ },
987
+ reason: "git clean -f (deletes untracked files)"
988
+ },
989
+ // ----- package publish (destructive — public, irreversible) -----
990
+ {
991
+ id: "npm-publish",
992
+ level: "destructive",
993
+ test: (cmd, args) => {
994
+ if (!["npm", "pnpm", "yarn", "bun", "cargo"].includes(cmd)) return false;
995
+ return args.includes("publish") || cmd === "cargo" && args.includes("yank");
996
+ },
997
+ reason: "publishing to a public package registry (hard to reverse)"
998
+ },
999
+ // ----- k8s cluster-wide destructive ops (destructive) -----
1000
+ {
1001
+ id: "kubectl-delete-namespace",
1002
+ level: "destructive",
1003
+ test: (cmd, args) => {
1004
+ if (cmd !== "kubectl") return false;
1005
+ const delIdx = args.indexOf("delete");
1006
+ if (delIdx < 0) return false;
1007
+ const after = args.slice(delIdx + 1);
1008
+ return after[0] === "namespace" || after[0] === "ns";
1009
+ },
1010
+ reason: "kubectl delete namespace (deletes all resources in the namespace)"
1011
+ },
1012
+ {
1013
+ id: "kubectl-drain",
1014
+ level: "destructive",
1015
+ test: (cmd, args) => cmd === "kubectl" && args.includes("drain"),
1016
+ reason: "kubectl drain (evicts pods, marks node unschedulable)"
1017
+ },
1018
+ // ----- inline code evaluation (caution — high false-positive) -----
1019
+ // Common in scripts: `python -c "..."`, `node -e "..."`, `bash -c "..."`.
1020
+ // We tag 'caution' rather than 'destructive' because these are used in
1021
+ // many legitimate one-liners (e.g. `python -c "print(1)"`).
1022
+ {
1023
+ id: "inline-eval",
1024
+ level: "caution",
1025
+ test: (cmd, args) => {
1026
+ if (![
1027
+ "python",
1028
+ "python3",
1029
+ "python2",
1030
+ "node",
1031
+ "bash",
1032
+ "sh",
1033
+ "zsh",
1034
+ "ruby",
1035
+ "perl",
1036
+ "lua"
1037
+ ].includes(cmd)) {
1038
+ return false;
1039
+ }
1040
+ return args.some(
1041
+ (a) => a === "-c" || a === "-e" || a === "--eval" || a === "-eval" || a === "-E"
1042
+ );
1043
+ },
1044
+ reason: "inline script evaluation (-c / -e / --eval)"
1045
+ },
1046
+ // ----- pipe-to-shell (caution — well-known exfil pattern) -----
1047
+ // The classic `curl https://... | sh` download-and-run vector. Detected by
1048
+ // looking for a known fetcher followed by a shell sink. We use a simple
1049
+ // substring scan; false positives are limited because both tokens must
1050
+ // appear in the same argv.
1051
+ {
1052
+ id: "pipe-to-shell",
1053
+ level: "caution",
1054
+ test: (_cmd, args) => {
1055
+ const hasFetcher = args.some(
1056
+ (a) => /^(curl|wget|fetch|httpie|http)$/i.test(a) || a.startsWith("curl") || a.startsWith("wget")
1057
+ );
1058
+ const hasShellSink = args.some(
1059
+ (a) => a === "sh" || a === "bash" || a === "zsh" || a === "fish" || a === "pwsh" || a === "powershell" || a.endsWith("/sh") || a.endsWith("/bash") || a.endsWith("/zsh") || a.endsWith("/pwsh")
1060
+ );
1061
+ return hasFetcher && hasShellSink;
1062
+ },
1063
+ reason: "network fetch piped to a shell (download-and-run pattern)"
1064
+ },
1065
+ // ----- privilege escalation (caution) -----
1066
+ {
1067
+ id: "sudo",
1068
+ level: "caution",
1069
+ test: (cmd) => cmd === "sudo" || cmd === "doas",
1070
+ reason: "privilege escalation (sudo / doas)"
1071
+ },
1072
+ {
1073
+ id: "runas",
1074
+ level: "caution",
1075
+ test: (cmd) => cmd === "runas" || cmd === "runas.exe",
1076
+ reason: "Windows runas (run as different user)"
1077
+ },
1078
+ // ----- world-writable permissions (caution) -----
1079
+ // `chmod 777` is rarely correct. `chmod -R 777` is almost always wrong.
1080
+ // We only flag octal modes; symbolic modes like `chmod o+w` are
1081
+ // left to the operator's discretion.
1082
+ {
1083
+ id: "chmod-world-writable",
1084
+ level: "caution",
1085
+ test: (cmd, args) => {
1086
+ if (cmd !== "chmod") return false;
1087
+ return args.some((a) => /^[0-7]{3,4}$/.test(a) && /7/.test(a));
1088
+ },
1089
+ reason: "chmod with world-writable octal mode (e.g. 777)"
1090
+ }
1091
+ ];
1092
+ function detectDanger(cmd, args, bypass) {
1093
+ const reasons = [];
1094
+ let level = "safe";
1095
+ let matchedRule;
1096
+ for (const rule of RULES) {
1097
+ if (bypass?.has(rule.id)) continue;
1098
+ if (!rule.test(cmd, args)) continue;
1099
+ reasons.push(rule.reason);
1100
+ matchedRule = rule.id;
1101
+ if (levelRank(rule.level) > levelRank(level)) {
1102
+ level = rule.level;
1103
+ }
1104
+ }
1105
+ if (level === "safe") return { level: "safe", reasons: [] };
1106
+ const result = { level, reasons };
1107
+ if (matchedRule !== void 0) result.matchedRule = matchedRule;
1108
+ return result;
1109
+ }
1110
+ function levelRank(level) {
1111
+ switch (level) {
1112
+ case "safe":
1113
+ return 0;
1114
+ case "caution":
1115
+ return 1;
1116
+ case "destructive":
1117
+ return 2;
1118
+ }
1119
+ }
1120
+
835
1121
  // src/exec.ts
836
1122
  var isWin = process.platform === "win32";
837
1123
  var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
@@ -1452,6 +1738,21 @@ function configureExecPolicy(opts = {}) {
1452
1738
  function resetExecPolicy() {
1453
1739
  allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
1454
1740
  }
1741
+ var dangerBypass = /* @__PURE__ */ new Set();
1742
+ function configureDangerBypass(opts = {}) {
1743
+ const next = /* @__PURE__ */ new Set();
1744
+ for (const id of opts.bypass ?? []) {
1745
+ const trimmed = id.trim();
1746
+ if (trimmed) next.add(trimmed);
1747
+ }
1748
+ dangerBypass = next;
1749
+ }
1750
+ function resetDangerBypass() {
1751
+ dangerBypass = /* @__PURE__ */ new Set();
1752
+ }
1753
+ function getDangerBypass() {
1754
+ return dangerBypass;
1755
+ }
1455
1756
  function isExecCommandAllowed(cmd) {
1456
1757
  return allowedCommands.has(normalizeCmd(cmd));
1457
1758
  }
@@ -1543,6 +1844,7 @@ function validateArgs(cmd, args) {
1543
1844
  }
1544
1845
  return null;
1545
1846
  }
1847
+ var SAFE_DANGER = { level: "safe", reasons: [] };
1546
1848
  var execTool = {
1547
1849
  name: "exec",
1548
1850
  category: "Shell",
@@ -1587,7 +1889,8 @@ var execTool = {
1587
1889
  stderr: "Circuit breaker is open \u2014 too many consecutive failures. Use /kill reset to recover.",
1588
1890
  exitCode: 1,
1589
1891
  truncated: false,
1590
- allowed: false
1892
+ allowed: false,
1893
+ danger: SAFE_DANGER
1591
1894
  };
1592
1895
  }
1593
1896
  const cmd = input.command.trim();
@@ -1599,7 +1902,8 @@ var execTool = {
1599
1902
  stderr: "Empty command",
1600
1903
  exitCode: 1,
1601
1904
  truncated: false,
1602
- allowed: false
1905
+ allowed: false,
1906
+ danger: SAFE_DANGER
1603
1907
  };
1604
1908
  if (!isExecCommandAllowed(cmd)) {
1605
1909
  return {
@@ -1609,11 +1913,13 @@ var execTool = {
1609
1913
  stderr: `Command "${cmd}" not in allowlist. Add it to your ~/.wrongstack/config.json under "tools": { "exec": { "allow": ["${cmd}"] } }, or use the bash tool for one-off arbitrary commands.`,
1610
1914
  exitCode: 1,
1611
1915
  truncated: false,
1612
- allowed: false
1916
+ allowed: false,
1917
+ danger: SAFE_DANGER
1613
1918
  };
1614
1919
  }
1615
1920
  const args = (input.args ?? []).slice(0, MAX_ARGS);
1616
1921
  const timeout = Math.max(1, Math.min(input.timeout ?? DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS));
1922
+ const danger = detectDanger(cmd, args, dangerBypass);
1617
1923
  const argError = validateArgs(cmd, args);
1618
1924
  if (argError) {
1619
1925
  return {
@@ -1623,7 +1929,8 @@ var execTool = {
1623
1929
  stderr: argError,
1624
1930
  exitCode: 1,
1625
1931
  truncated: false,
1626
- allowed: false
1932
+ allowed: false,
1933
+ danger
1627
1934
  };
1628
1935
  }
1629
1936
  let cwd;
@@ -1637,14 +1944,15 @@ var execTool = {
1637
1944
  stderr: `cwd "${input.cwd ?? ctx.cwd}" resolves outside project root`,
1638
1945
  exitCode: 1,
1639
1946
  truncated: false,
1640
- allowed: false
1947
+ allowed: false,
1948
+ danger
1641
1949
  };
1642
1950
  }
1643
1951
  const signal = opts.signal;
1644
- return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id);
1952
+ return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id, danger);
1645
1953
  }
1646
1954
  };
1647
- function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1955
+ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
1648
1956
  return new Promise((resolve2) => {
1649
1957
  let stdout = "";
1650
1958
  let stderr = "";
@@ -1681,7 +1989,8 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1681
1989
  stderr: `spawn failed: ${toErrorMessage(err)}`,
1682
1990
  exitCode: 1,
1683
1991
  truncated: false,
1684
- allowed: true
1992
+ allowed: true,
1993
+ danger
1685
1994
  });
1686
1995
  return;
1687
1996
  }
@@ -1700,7 +2009,8 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1700
2009
  stderr: stderrText,
1701
2010
  exitCode: isAbort ? 124 : 1,
1702
2011
  truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
1703
- allowed: true
2012
+ allowed: true,
2013
+ danger
1704
2014
  });
1705
2015
  });
1706
2016
  const registry = getProcessRegistry();
@@ -1748,12 +2058,13 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1748
2058
  stderr: normalizeCommandOutput(stderr),
1749
2059
  exitCode,
1750
2060
  truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES || Buffer.byteLength(stderr, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
1751
- allowed: true
2061
+ allowed: true,
2062
+ danger
1752
2063
  });
1753
2064
  });
1754
2065
  });
1755
2066
  }
1756
2067
 
1757
- export { configureExecPolicy, execTool, getExecAllowlist, isExecCommandAllowed, resetExecPolicy };
2068
+ export { configureDangerBypass, configureExecPolicy, execTool, getDangerBypass, getExecAllowlist, isExecCommandAllowed, resetDangerBypass, resetExecPolicy };
1758
2069
  //# sourceMappingURL=exec.js.map
1759
2070
  //# sourceMappingURL=exec.js.map