claude-warden 1.7.0 → 1.8.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-warden",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Smart command safety filter for Claude Code — parses shell pipelines and evaluates per-command safety rules to auto-approve safe commands and block dangerous ones",
5
5
  "author": {
6
6
  "name": "banyudu"
package/dist/index.cjs CHANGED
@@ -18385,6 +18385,15 @@ function parseCommand(input) {
18385
18385
 
18386
18386
  // src/evaluator.ts
18387
18387
  var import_os = require("os");
18388
+ function safeRegexTest(pattern, input) {
18389
+ try {
18390
+ return new RegExp(pattern).test(input);
18391
+ } catch {
18392
+ process.stderr.write(`[warden] Warning: invalid regex pattern: ${pattern}
18393
+ `);
18394
+ return false;
18395
+ }
18396
+ }
18388
18397
  function commandMatchesName(cmd, name) {
18389
18398
  if (name.startsWith("/")) {
18390
18399
  return cmd.originalCommand === name;
@@ -18394,7 +18403,11 @@ function commandMatchesName(cmd, name) {
18394
18403
  }
18395
18404
  return cmd.command === name;
18396
18405
  }
18397
- function evaluate(parsed, config) {
18406
+ var MAX_RECURSION_DEPTH = 10;
18407
+ function evaluate(parsed, config, depth = 0) {
18408
+ if (depth > MAX_RECURSION_DEPTH) {
18409
+ return { decision: "ask", reason: "Maximum recursion depth exceeded", details: [] };
18410
+ }
18398
18411
  if (parsed.parseError) {
18399
18412
  return { decision: "ask", reason: "Could not parse command safely", details: [] };
18400
18413
  }
@@ -18404,7 +18417,7 @@ function evaluate(parsed, config) {
18404
18417
  if (parsed.hasSubshell && parsed.subshellCommands.length > 0) {
18405
18418
  for (const subCmd of parsed.subshellCommands) {
18406
18419
  const subParsed = parseCommand(subCmd);
18407
- const subResult = evaluate(subParsed, config);
18420
+ const subResult = evaluate(subParsed, config, depth + 1);
18408
18421
  if (subResult.decision === "deny") {
18409
18422
  return { decision: "deny", reason: `Subshell command: ${subResult.reason}`, details: subResult.details };
18410
18423
  }
@@ -18417,7 +18430,7 @@ function evaluate(parsed, config) {
18417
18430
  }
18418
18431
  const details = [];
18419
18432
  for (const cmd of parsed.commands) {
18420
- details.push(evaluateCommand(cmd, config));
18433
+ details.push(evaluateCommand(cmd, config, depth));
18421
18434
  }
18422
18435
  const decisions = details.map((d) => d.decision);
18423
18436
  if (decisions.includes("deny")) {
@@ -18438,7 +18451,7 @@ function evaluate(parsed, config) {
18438
18451
  }
18439
18452
  return { decision: "allow", reason: "All commands are safe", details };
18440
18453
  }
18441
- function evaluateCommand(cmd, config) {
18454
+ function evaluateCommand(cmd, config, depth = 0) {
18442
18455
  const { command, args: args2 } = cmd;
18443
18456
  for (const layer of config.layers) {
18444
18457
  if (layer.alwaysDeny.some((name) => commandMatchesName(cmd, name))) {
@@ -18449,28 +18462,52 @@ function evaluateCommand(cmd, config) {
18449
18462
  }
18450
18463
  }
18451
18464
  if ((command === "ssh" || command === "scp" || command === "rsync") && config.trustedSSHHosts?.length) {
18452
- const sshResult = evaluateSSHCommand(cmd, config);
18465
+ const sshResult = evaluateSSHCommand(cmd, config, depth);
18453
18466
  if (sshResult) return sshResult;
18454
18467
  }
18455
18468
  if (command === "docker" && config.trustedDockerContainers?.length) {
18456
- const dockerResult = evaluateDockerExec(cmd, config);
18469
+ const dockerResult = evaluateDockerExec(cmd, config, depth);
18457
18470
  if (dockerResult) return dockerResult;
18458
18471
  }
18459
18472
  if (command === "kubectl" && config.trustedKubectlContexts?.length) {
18460
- const kubectlResult = evaluateKubectlExec(cmd, config);
18473
+ const kubectlResult = evaluateKubectlExec(cmd, config, depth);
18461
18474
  if (kubectlResult) return kubectlResult;
18462
18475
  }
18463
18476
  if (command === "sprite" && config.trustedSprites?.length) {
18464
- const spriteResult = evaluateSpriteExec(cmd, config);
18477
+ const spriteResult = evaluateSpriteExec(cmd, config, depth);
18465
18478
  if (spriteResult) return spriteResult;
18466
18479
  }
18480
+ if (command === "xargs") {
18481
+ return evaluateXargsCommand(cmd, config, depth);
18482
+ }
18483
+ const mergedRule = collectMergedRule(cmd, config);
18484
+ if (mergedRule) {
18485
+ return evaluateRule(cmd, mergedRule);
18486
+ }
18487
+ return { command, args: args2, decision: config.defaultDecision, reason: `No rule for "${command}"`, matchedRule: "default" };
18488
+ }
18489
+ function collectMergedRule(cmd, config) {
18490
+ const matchingRules = [];
18467
18491
  for (const layer of config.layers) {
18468
18492
  const rule = layer.rules.find((r) => commandMatchesName(cmd, r.command));
18469
18493
  if (rule) {
18470
- return evaluateRule(cmd, rule);
18494
+ matchingRules.push(rule);
18495
+ if (rule.override) break;
18471
18496
  }
18472
18497
  }
18473
- return { command, args: args2, decision: config.defaultDecision, reason: `No rule for "${command}"`, matchedRule: "default" };
18498
+ if (matchingRules.length === 0) return null;
18499
+ if (matchingRules.length === 1) return matchingRules[0];
18500
+ const mergedPatterns = [];
18501
+ for (const rule of matchingRules) {
18502
+ if (rule.argPatterns) {
18503
+ mergedPatterns.push(...rule.argPatterns);
18504
+ }
18505
+ }
18506
+ return {
18507
+ command: matchingRules[0].command,
18508
+ default: matchingRules[0].default,
18509
+ argPatterns: mergedPatterns
18510
+ };
18474
18511
  }
18475
18512
  function evaluateRule(cmd, rule) {
18476
18513
  const { command, args: args2 } = cmd;
@@ -18482,10 +18519,10 @@ function evaluateRule(cmd, rule) {
18482
18519
  matched = matched && m.noArgs === (args2.length === 0);
18483
18520
  }
18484
18521
  if (m.argsMatch && matched) {
18485
- matched = m.argsMatch.some((re) => new RegExp(re).test(argsJoined));
18522
+ matched = m.argsMatch.some((re) => safeRegexTest(re, argsJoined));
18486
18523
  }
18487
18524
  if (m.anyArgMatches && matched) {
18488
- matched = args2.some((arg) => m.anyArgMatches.some((re) => new RegExp(re).test(arg)));
18525
+ matched = args2.some((arg) => m.anyArgMatches.some((re) => safeRegexTest(re, arg)));
18489
18526
  }
18490
18527
  if (m.argCount && matched) {
18491
18528
  if (m.argCount.min !== void 0) matched = matched && args2.length >= m.argCount.min;
@@ -18510,6 +18547,126 @@ function evaluateRule(cmd, rule) {
18510
18547
  matchedRule: `${command}:default`
18511
18548
  };
18512
18549
  }
18550
+ var XARGS_SHORT_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set(["E", "I", "L", "n", "P", "s", "S", "d", "a"]);
18551
+ var XARGS_SHORT_FLAGS_NO_VALUE = /* @__PURE__ */ new Set(["0", "e", "o", "p", "r", "t", "x"]);
18552
+ var XARGS_LONG_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
18553
+ "--eof",
18554
+ "--replace",
18555
+ "--max-lines",
18556
+ "--max-args",
18557
+ "--max-procs",
18558
+ "--max-chars",
18559
+ "--arg-file",
18560
+ "--delimiter"
18561
+ ]);
18562
+ var XARGS_LONG_FLAGS_NO_VALUE = /* @__PURE__ */ new Set([
18563
+ "--null",
18564
+ "--exit",
18565
+ "--open-tty",
18566
+ "--interactive",
18567
+ "--no-run-if-empty",
18568
+ "--verbose",
18569
+ "--show-limits"
18570
+ ]);
18571
+ function parseXargsSubcommand(args2) {
18572
+ let i = 0;
18573
+ while (i < args2.length) {
18574
+ const arg = args2[i];
18575
+ if (arg === "--") {
18576
+ i++;
18577
+ break;
18578
+ }
18579
+ if (!arg.startsWith("-") || arg === "-") {
18580
+ break;
18581
+ }
18582
+ if (arg.startsWith("--")) {
18583
+ const eqIndex = arg.indexOf("=");
18584
+ const longFlag = eqIndex === -1 ? arg : arg.slice(0, eqIndex);
18585
+ if (XARGS_LONG_FLAGS_WITH_VALUE.has(longFlag)) {
18586
+ if (eqIndex !== -1) {
18587
+ i++;
18588
+ continue;
18589
+ }
18590
+ if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
18591
+ i += 2;
18592
+ continue;
18593
+ }
18594
+ if (XARGS_LONG_FLAGS_NO_VALUE.has(longFlag)) {
18595
+ i++;
18596
+ continue;
18597
+ }
18598
+ return { subcommand: null, unresolved: true };
18599
+ }
18600
+ const short = arg[1];
18601
+ if (XARGS_SHORT_FLAGS_WITH_VALUE.has(short)) {
18602
+ if (arg.length > 2) {
18603
+ i++;
18604
+ continue;
18605
+ }
18606
+ if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
18607
+ i += 2;
18608
+ continue;
18609
+ }
18610
+ const grouped = arg.slice(1).split("");
18611
+ const allKnownNoValue = grouped.every((ch) => XARGS_SHORT_FLAGS_NO_VALUE.has(ch));
18612
+ if (allKnownNoValue) {
18613
+ i++;
18614
+ continue;
18615
+ }
18616
+ return { subcommand: null, unresolved: true };
18617
+ }
18618
+ if (i >= args2.length) {
18619
+ return {
18620
+ unresolved: false,
18621
+ subcommand: {
18622
+ command: "echo",
18623
+ originalCommand: "echo",
18624
+ args: [],
18625
+ envPrefixes: [],
18626
+ raw: "echo"
18627
+ }
18628
+ };
18629
+ }
18630
+ const subcommand = args2[i];
18631
+ const subArgs = args2.slice(i + 1);
18632
+ return {
18633
+ unresolved: false,
18634
+ subcommand: {
18635
+ command: subcommand,
18636
+ originalCommand: subcommand,
18637
+ args: subArgs,
18638
+ envPrefixes: [],
18639
+ raw: [subcommand, ...subArgs].join(" ")
18640
+ }
18641
+ };
18642
+ }
18643
+ function evaluateXargsCommand(cmd, config, depth = 0) {
18644
+ const { command, args: args2 } = cmd;
18645
+ const { subcommand, unresolved } = parseXargsSubcommand(args2);
18646
+ if (unresolved || !subcommand) {
18647
+ return {
18648
+ command,
18649
+ args: args2,
18650
+ decision: "ask",
18651
+ reason: "xargs subcommand could not be resolved safely",
18652
+ matchedRule: "xargs:subcommand"
18653
+ };
18654
+ }
18655
+ const parsed = {
18656
+ commands: [subcommand],
18657
+ hasSubshell: false,
18658
+ subshellCommands: [],
18659
+ parseError: false
18660
+ };
18661
+ const result = evaluate(parsed, config, depth + 1);
18662
+ return {
18663
+ command,
18664
+ args: args2,
18665
+ decision: result.decision,
18666
+ reason: `xargs subcommand "${subcommand.command}": ${result.reason}`,
18667
+ matchedRule: "xargs:subcommand"
18668
+ };
18669
+ }
18513
18670
  var SSH_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
18514
18671
  "-b",
18515
18672
  "-c",
@@ -18604,7 +18761,7 @@ function parseSSHArgs(args2) {
18604
18761
  }
18605
18762
  return {
18606
18763
  host,
18607
- remoteCommand: remoteArgs.length > 0 ? remoteArgs.join(" ") : null
18764
+ remoteCommand: remoteArgs.length > 0 ? remoteArgs.map(shellQuote).join(" ") : null
18608
18765
  };
18609
18766
  }
18610
18767
  function extractHostFromRemotePath(args2) {
@@ -18614,7 +18771,7 @@ function extractHostFromRemotePath(args2) {
18614
18771
  }
18615
18772
  return null;
18616
18773
  }
18617
- function evaluateSSHCommand(cmd, config) {
18774
+ function evaluateSSHCommand(cmd, config, depth = 0) {
18618
18775
  const { command, args: args2 } = cmd;
18619
18776
  const trustedHosts = config.trustedSSHHosts || [];
18620
18777
  if (command === "scp" || command === "rsync") {
@@ -18622,6 +18779,24 @@ function evaluateSSHCommand(cmd, config) {
18622
18779
  if (!host2) return null;
18623
18780
  const target2 = findMatchingTarget(host2, trustedHosts);
18624
18781
  if (!target2) return null;
18782
+ if (target2.allowAll || !target2.overrides) {
18783
+ return {
18784
+ command,
18785
+ args: args2,
18786
+ decision: "allow",
18787
+ reason: `Trusted SSH host "${host2}"${target2.allowAll ? " (allowAll)" : ""}`,
18788
+ matchedRule: "trustedSSHHosts"
18789
+ };
18790
+ }
18791
+ if (target2.overrides.alwaysDeny.some((name) => name === command)) {
18792
+ return {
18793
+ command,
18794
+ args: args2,
18795
+ decision: "deny",
18796
+ reason: `Trusted SSH host "${host2}": "${command}" blocked by overrides`,
18797
+ matchedRule: "trustedSSHHosts"
18798
+ };
18799
+ }
18625
18800
  return {
18626
18801
  command,
18627
18802
  args: args2,
@@ -18653,7 +18828,7 @@ function evaluateSSHCommand(cmd, config) {
18653
18828
  };
18654
18829
  }
18655
18830
  const parsed = parseCommand(remoteCommand);
18656
- const result = evaluate(parsed, configWithContextOverrides(config, target));
18831
+ const result = evaluate(parsed, configWithContextOverrides(config, target), depth + 1);
18657
18832
  return {
18658
18833
  command,
18659
18834
  args: args2,
@@ -18673,6 +18848,12 @@ var DOCKER_EXEC_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
18673
18848
  "--detach-keys"
18674
18849
  ]);
18675
18850
  var INTERACTIVE_SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh"]);
18851
+ function shellQuote(arg) {
18852
+ if (/[\s"'\\$`!#&|;()<>]/.test(arg)) {
18853
+ return `'${arg.replace(/'/g, "'\\''")}'`;
18854
+ }
18855
+ return arg;
18856
+ }
18676
18857
  function configWithContextOverrides(config, target) {
18677
18858
  const overrideLayers = [];
18678
18859
  if (target?.overrides) overrideLayers.push(target.overrides);
@@ -18683,7 +18864,7 @@ function configWithContextOverrides(config, target) {
18683
18864
  layers: [...overrideLayers, ...config.layers]
18684
18865
  };
18685
18866
  }
18686
- function evaluateRemoteCommand(remoteArgs, config, target) {
18867
+ function evaluateRemoteCommand(remoteArgs, config, target, depth = 0) {
18687
18868
  if (target?.allowAll) {
18688
18869
  return { decision: "allow", reason: "allowAll target", details: [] };
18689
18870
  }
@@ -18698,7 +18879,7 @@ function evaluateRemoteCommand(remoteArgs, config, target) {
18698
18879
  if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs[1] === "-c" && remoteArgs.length >= 3) {
18699
18880
  const innerCommand = remoteArgs.slice(2).join(" ");
18700
18881
  const parsed2 = parseCommand(innerCommand);
18701
- return evaluate(parsed2, overriddenConfig);
18882
+ return evaluate(parsed2, overriddenConfig, depth + 1);
18702
18883
  }
18703
18884
  const parsed = {
18704
18885
  commands: [{ command: remoteCmd, originalCommand: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
@@ -18706,7 +18887,7 @@ function evaluateRemoteCommand(remoteArgs, config, target) {
18706
18887
  subshellCommands: [],
18707
18888
  parseError: false
18708
18889
  };
18709
- return evaluate(parsed, overriddenConfig);
18890
+ return evaluate(parsed, overriddenConfig, depth + 1);
18710
18891
  }
18711
18892
  function parseDockerExecArgs(args2) {
18712
18893
  let target = null;
@@ -18735,14 +18916,14 @@ function parseDockerExecArgs(args2) {
18735
18916
  }
18736
18917
  return { target, remoteArgs };
18737
18918
  }
18738
- function evaluateDockerExec(cmd, config) {
18919
+ function evaluateDockerExec(cmd, config, depth = 0) {
18739
18920
  const { command, args: args2 } = cmd;
18740
18921
  if (args2[0] !== "exec") return null;
18741
18922
  const { target: containerName, remoteArgs } = parseDockerExecArgs(args2.slice(1));
18742
18923
  if (!containerName) return null;
18743
18924
  const matched = findMatchingTarget(containerName, config.trustedDockerContainers || []);
18744
18925
  if (!matched) return null;
18745
- const result = evaluateRemoteCommand(remoteArgs, config, matched);
18926
+ const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
18746
18927
  return {
18747
18928
  command,
18748
18929
  args: args2,
@@ -18815,14 +18996,14 @@ function parseKubectlExecArgs(args2) {
18815
18996
  }
18816
18997
  return { context, pod, remoteArgs };
18817
18998
  }
18818
- function evaluateKubectlExec(cmd, config) {
18999
+ function evaluateKubectlExec(cmd, config, depth = 0) {
18819
19000
  const { command, args: args2 } = cmd;
18820
19001
  if (args2[0] !== "exec") return null;
18821
19002
  const { context, pod, remoteArgs } = parseKubectlExecArgs(args2.slice(1));
18822
19003
  if (!context) return null;
18823
19004
  const matched = findMatchingTarget(context, config.trustedKubectlContexts || []);
18824
19005
  if (!matched) return null;
18825
- const result = evaluateRemoteCommand(remoteArgs, config, matched);
19006
+ const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
18826
19007
  return {
18827
19008
  command,
18828
19009
  args: args2,
@@ -18882,13 +19063,13 @@ function parseSpriteExecArgs(args2) {
18882
19063
  }
18883
19064
  return { spriteName, remoteArgs };
18884
19065
  }
18885
- function evaluateSpriteExec(cmd, config) {
19066
+ function evaluateSpriteExec(cmd, config, depth = 0) {
18886
19067
  const { command, args: args2 } = cmd;
18887
19068
  const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
18888
19069
  if (!spriteName) return null;
18889
19070
  const matched = findMatchingTarget(spriteName, config.trustedSprites || []);
18890
19071
  if (!matched) return null;
18891
- const result = evaluateRemoteCommand(remoteArgs, config, matched);
19072
+ const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
18892
19073
  return {
18893
19074
  command,
18894
19075
  args: args2,
@@ -19231,6 +19412,7 @@ var DEFAULT_CONFIG = {
19231
19412
  "xcode-select",
19232
19413
  "xcrun",
19233
19414
  "xcodebuild",
19415
+ "networkQuality",
19234
19416
  // Misc safe
19235
19417
  "cd",
19236
19418
  "pushd",
@@ -19339,6 +19521,7 @@ var DEFAULT_CONFIG = {
19339
19521
  // npx / bunx — package runners
19340
19522
  pkgRunnerRule("npx"),
19341
19523
  pkgRunnerRule("bunx"),
19524
+ pkgRunnerRule("pnpx"),
19342
19525
  // npm / pnpm / yarn — package managers
19343
19526
  pkgManagerRule("npm", ["ci", "search", "explain", "prefix", "root", "fund", "doctor", "diff", "pkg", "query", "shrinkwrap"]),
19344
19527
  pkgManagerRule("pnpm", ["store", "fetch", "doctor", "patch"]),
@@ -19592,6 +19775,12 @@ var DEFAULT_CONFIG = {
19592
19775
  { command: "codesign", default: "ask", argPatterns: [
19593
19776
  { match: { anyArgMatches: ["^(-vv|--verify|--display|-d)$"] }, decision: "allow", description: "Verify codesign" }
19594
19777
  ] },
19778
+ { command: "networksetup", default: "ask", argPatterns: [
19779
+ { match: { anyArgMatches: ["^-(get|list|show)"] }, decision: "allow", description: "Read-only network configuration queries" }
19780
+ ] },
19781
+ { command: "scutil", default: "ask", argPatterns: [
19782
+ { match: { anyArgMatches: ["^--get$", "^--dns$", "^--proxy$", "^--nwi$"] }, decision: "allow", description: "Read-only system configuration queries" }
19783
+ ] },
19595
19784
  { command: "osascript", default: "ask" },
19596
19785
  { command: "say", default: "ask" },
19597
19786
  // --- Process management ---
@@ -19604,6 +19793,10 @@ var DEFAULT_CONFIG = {
19604
19793
  };
19605
19794
 
19606
19795
  // src/rules.ts
19796
+ var VALID_DECISIONS = /* @__PURE__ */ new Set(["allow", "deny", "ask"]);
19797
+ function isValidDecision(value) {
19798
+ return VALID_DECISIONS.has(value);
19799
+ }
19607
19800
  var USER_CONFIG_PATHS = [
19608
19801
  (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.yaml"),
19609
19802
  (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.json")
@@ -19654,15 +19847,36 @@ function tryLoadFile(filePath) {
19654
19847
  if (parsed && typeof parsed === "object") {
19655
19848
  return parsed;
19656
19849
  }
19657
- } catch {
19850
+ } catch (err) {
19851
+ process.stderr.write(`[warden] Warning: failed to parse config ${filePath}: ${err instanceof Error ? err.message : String(err)}
19852
+ `);
19658
19853
  }
19659
19854
  return null;
19660
19855
  }
19661
19856
  function extractLayer(raw) {
19857
+ const rules = Array.isArray(raw.rules) ? raw.rules : [];
19858
+ for (const rule of rules) {
19859
+ if (rule && typeof rule === "object") {
19860
+ if (rule.default && !isValidDecision(rule.default)) {
19861
+ process.stderr.write(`[warden] Warning: invalid rule default "${rule.default}" for "${rule.command}", using "ask"
19862
+ `);
19863
+ rule.default = "ask";
19864
+ }
19865
+ if (Array.isArray(rule.argPatterns)) {
19866
+ for (const pattern of rule.argPatterns) {
19867
+ if (pattern?.decision && !isValidDecision(pattern.decision)) {
19868
+ process.stderr.write(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule.command}", using "ask"
19869
+ `);
19870
+ pattern.decision = "ask";
19871
+ }
19872
+ }
19873
+ }
19874
+ }
19875
+ }
19662
19876
  return {
19663
19877
  alwaysAllow: Array.isArray(raw.alwaysAllow) ? raw.alwaysAllow : [],
19664
19878
  alwaysDeny: Array.isArray(raw.alwaysDeny) ? raw.alwaysDeny : [],
19665
- rules: Array.isArray(raw.rules) ? raw.rules : []
19879
+ rules
19666
19880
  };
19667
19881
  }
19668
19882
  function parseTrustedList(raw) {
@@ -19694,7 +19908,12 @@ function mergeNonLayerFields(config, raw) {
19694
19908
  config.trustedSprites = [...config.trustedSprites || [], ...parseTrustedList(raw.trustedSprites)];
19695
19909
  }
19696
19910
  if (typeof raw.defaultDecision === "string") {
19697
- config.defaultDecision = raw.defaultDecision;
19911
+ if (isValidDecision(raw.defaultDecision)) {
19912
+ config.defaultDecision = raw.defaultDecision;
19913
+ } else {
19914
+ process.stderr.write(`[warden] Warning: invalid defaultDecision "${raw.defaultDecision}", ignoring
19915
+ `);
19916
+ }
19698
19917
  }
19699
19918
  if (typeof raw.askOnSubshell === "boolean") {
19700
19919
  config.askOnSubshell = raw.askOnSubshell;
@@ -19850,10 +20069,22 @@ function sendNotification(title, message, config) {
19850
20069
  }
19851
20070
 
19852
20071
  // src/index.ts
20072
+ var MAX_STDIN_SIZE = 1024 * 1024;
19853
20073
  async function main() {
19854
20074
  let raw = "";
19855
20075
  for await (const chunk of process.stdin) {
19856
20076
  raw += chunk;
20077
+ if (raw.length > MAX_STDIN_SIZE) {
20078
+ const output2 = {
20079
+ hookSpecificOutput: {
20080
+ hookEventName: "PreToolUse",
20081
+ permissionDecision: "ask",
20082
+ permissionDecisionReason: "[warden] Input exceeds size limit"
20083
+ }
20084
+ };
20085
+ process.stdout.write(JSON.stringify(output2));
20086
+ process.exit(0);
20087
+ }
19857
20088
  }
19858
20089
  let input;
19859
20090
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-warden",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Smart command safety filter for Claude Code — auto-approves safe commands, blocks dangerous ones",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -28,13 +28,15 @@
28
28
  "README.md",
29
29
  "LICENSE"
30
30
  ],
31
+ "dependencies": {
32
+ "bash-parser": "^0.5.0",
33
+ "yaml": "^2.4.0"
34
+ },
31
35
  "devDependencies": {
32
36
  "@types/node": "^20.0.0",
33
- "bash-parser": "^0.5.0",
34
37
  "tsup": "^8.0.0",
35
38
  "typescript": "^5.4.0",
36
- "vitest": "^1.6.0",
37
- "yaml": "^2.4.0"
39
+ "vitest": "^1.6.0"
38
40
  },
39
41
  "scripts": {
40
42
  "build": "tsup",