claude-warden 1.5.1 → 1.5.3
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/.claude-plugin/plugin.json +1 -1
- package/README.md +6 -0
- package/dist/index.cjs +68 -9
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-warden",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
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/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Claude Warden
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/claude-warden)
|
|
4
|
+
[](https://www.npmjs.com/package/claude-warden)
|
|
5
|
+
[](https://github.com/banyudu/claude-warden/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/banyudu/claude-warden/stargazers)
|
|
7
|
+
[](https://github.com/banyudu/claude-warden/actions)
|
|
8
|
+
|
|
3
9
|
Smart command safety filter for [Claude Code](https://claude.ai/code). Parses shell commands, evaluates each against configurable safety rules, and returns allow/deny/ask decisions — eliminating unnecessary permission prompts while blocking dangerous commands.
|
|
4
10
|
|
|
5
11
|
## The problem
|
package/dist/index.cjs
CHANGED
|
@@ -18143,9 +18143,57 @@ function preprocessCatHeredocs(input) {
|
|
|
18143
18143
|
const regex = /\$\(cat\s+<<-?\s*['"]?(\w+)['"]?\n([\s\S]*?)\n\1\s*\)/g;
|
|
18144
18144
|
return input.replace(regex, "__HEREDOC_TEXT__");
|
|
18145
18145
|
}
|
|
18146
|
+
function preprocessPathParentheses(input) {
|
|
18147
|
+
const result = [];
|
|
18148
|
+
let i = 0;
|
|
18149
|
+
while (i < input.length) {
|
|
18150
|
+
const ch = input[i];
|
|
18151
|
+
if (ch === '"' || ch === "'") {
|
|
18152
|
+
const quote = ch;
|
|
18153
|
+
let j = i + 1;
|
|
18154
|
+
while (j < input.length && input[j] !== quote) {
|
|
18155
|
+
if (input[j] === "\\" && quote === '"') j++;
|
|
18156
|
+
j++;
|
|
18157
|
+
}
|
|
18158
|
+
result.push(input.slice(i, j + 1));
|
|
18159
|
+
i = j + 1;
|
|
18160
|
+
continue;
|
|
18161
|
+
}
|
|
18162
|
+
if (ch === "$" && i + 1 < input.length && input[i + 1] === "(") {
|
|
18163
|
+
let depth = 1;
|
|
18164
|
+
let j = i + 2;
|
|
18165
|
+
while (j < input.length && depth > 0) {
|
|
18166
|
+
if (input[j] === "(") depth++;
|
|
18167
|
+
else if (input[j] === ")") depth--;
|
|
18168
|
+
if (depth > 0) j++;
|
|
18169
|
+
}
|
|
18170
|
+
result.push(input.slice(i, j + 1));
|
|
18171
|
+
i = j + 1;
|
|
18172
|
+
continue;
|
|
18173
|
+
}
|
|
18174
|
+
if (ch !== " " && ch !== " " && ch !== "\n") {
|
|
18175
|
+
let j = i;
|
|
18176
|
+
while (j < input.length && !" \n".includes(input[j]) && input[j] !== '"' && input[j] !== "'" && !(input[j] === "$" && j + 1 < input.length && input[j + 1] === "(")) {
|
|
18177
|
+
j++;
|
|
18178
|
+
}
|
|
18179
|
+
const token = input.slice(i, j);
|
|
18180
|
+
if (token.includes("/") && /[()]/.test(token) && !/^[<>|;&]/.test(token)) {
|
|
18181
|
+
result.push('"' + token + '"');
|
|
18182
|
+
} else {
|
|
18183
|
+
result.push(token);
|
|
18184
|
+
}
|
|
18185
|
+
i = j;
|
|
18186
|
+
continue;
|
|
18187
|
+
}
|
|
18188
|
+
result.push(ch);
|
|
18189
|
+
i++;
|
|
18190
|
+
}
|
|
18191
|
+
return result.join("");
|
|
18192
|
+
}
|
|
18146
18193
|
function convertCommand(node) {
|
|
18147
18194
|
if (!node.name) return null;
|
|
18148
|
-
const
|
|
18195
|
+
const originalCommand = node.name.text;
|
|
18196
|
+
const command = originalCommand.includes("/") ? (0, import_path.basename)(originalCommand) : originalCommand;
|
|
18149
18197
|
const envPrefixes = [];
|
|
18150
18198
|
if (node.prefix) {
|
|
18151
18199
|
for (const p of node.prefix) {
|
|
@@ -18168,7 +18216,7 @@ function convertCommand(node) {
|
|
|
18168
18216
|
...args2
|
|
18169
18217
|
];
|
|
18170
18218
|
const raw = rawParts.join(" ");
|
|
18171
|
-
return { command, args: args2, envPrefixes, raw };
|
|
18219
|
+
return { command, originalCommand, args: args2, envPrefixes, raw };
|
|
18172
18220
|
}
|
|
18173
18221
|
function collectCommandExpansions(node) {
|
|
18174
18222
|
const commands = [];
|
|
@@ -18289,6 +18337,7 @@ function parseCommand(input) {
|
|
|
18289
18337
|
return { commands: [], hasSubshell: false, subshellCommands: [], parseError: false };
|
|
18290
18338
|
}
|
|
18291
18339
|
input = preprocessCatHeredocs(input);
|
|
18340
|
+
input = preprocessPathParentheses(input);
|
|
18292
18341
|
try {
|
|
18293
18342
|
const ast = (0, import_bash_parser.default)(input);
|
|
18294
18343
|
const result = { commands: [], hasSubshell: false, subshellCommands: [] };
|
|
@@ -18335,6 +18384,16 @@ function parseCommand(input) {
|
|
|
18335
18384
|
}
|
|
18336
18385
|
|
|
18337
18386
|
// src/evaluator.ts
|
|
18387
|
+
var import_os = require("os");
|
|
18388
|
+
function commandMatchesName(cmd, name) {
|
|
18389
|
+
if (name.startsWith("/")) {
|
|
18390
|
+
return cmd.originalCommand === name;
|
|
18391
|
+
}
|
|
18392
|
+
if (name.startsWith("~/")) {
|
|
18393
|
+
return cmd.originalCommand === (0, import_os.homedir)() + name.slice(1);
|
|
18394
|
+
}
|
|
18395
|
+
return cmd.command === name;
|
|
18396
|
+
}
|
|
18338
18397
|
function evaluate(parsed, config) {
|
|
18339
18398
|
if (parsed.parseError) {
|
|
18340
18399
|
return { decision: "ask", reason: "Could not parse command safely", details: [] };
|
|
@@ -18382,10 +18441,10 @@ function evaluate(parsed, config) {
|
|
|
18382
18441
|
function evaluateCommand(cmd, config) {
|
|
18383
18442
|
const { command, args: args2 } = cmd;
|
|
18384
18443
|
for (const layer of config.layers) {
|
|
18385
|
-
if (layer.alwaysDeny.
|
|
18444
|
+
if (layer.alwaysDeny.some((name) => commandMatchesName(cmd, name))) {
|
|
18386
18445
|
return { command, args: args2, decision: "deny", reason: `"${command}" is blocked`, matchedRule: "alwaysDeny" };
|
|
18387
18446
|
}
|
|
18388
|
-
if (layer.alwaysAllow.
|
|
18447
|
+
if (layer.alwaysAllow.some((name) => commandMatchesName(cmd, name))) {
|
|
18389
18448
|
return { command, args: args2, decision: "allow", reason: `"${command}" is safe`, matchedRule: "alwaysAllow" };
|
|
18390
18449
|
}
|
|
18391
18450
|
}
|
|
@@ -18406,7 +18465,7 @@ function evaluateCommand(cmd, config) {
|
|
|
18406
18465
|
if (spriteResult) return spriteResult;
|
|
18407
18466
|
}
|
|
18408
18467
|
for (const layer of config.layers) {
|
|
18409
|
-
const rule = layer.rules.find((r) => r.command
|
|
18468
|
+
const rule = layer.rules.find((r) => commandMatchesName(cmd, r.command));
|
|
18410
18469
|
if (rule) {
|
|
18411
18470
|
return evaluateRule(cmd, rule);
|
|
18412
18471
|
}
|
|
@@ -18642,7 +18701,7 @@ function evaluateRemoteCommand(remoteArgs, config, target) {
|
|
|
18642
18701
|
return evaluate(parsed2, overriddenConfig);
|
|
18643
18702
|
}
|
|
18644
18703
|
const parsed = {
|
|
18645
|
-
commands: [{ command: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
|
|
18704
|
+
commands: [{ command: remoteCmd, originalCommand: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
|
|
18646
18705
|
hasSubshell: false,
|
|
18647
18706
|
subshellCommands: [],
|
|
18648
18707
|
parseError: false
|
|
@@ -18842,7 +18901,7 @@ function evaluateSpriteExec(cmd, config) {
|
|
|
18842
18901
|
// src/rules.ts
|
|
18843
18902
|
var import_fs = require("fs");
|
|
18844
18903
|
var import_yaml = __toESM(require_dist2(), 1);
|
|
18845
|
-
var
|
|
18904
|
+
var import_os2 = require("os");
|
|
18846
18905
|
var import_path2 = require("path");
|
|
18847
18906
|
|
|
18848
18907
|
// src/defaults.ts
|
|
@@ -19486,8 +19545,8 @@ var DEFAULT_CONFIG = {
|
|
|
19486
19545
|
|
|
19487
19546
|
// src/rules.ts
|
|
19488
19547
|
var USER_CONFIG_PATHS = [
|
|
19489
|
-
(0, import_path2.join)((0,
|
|
19490
|
-
(0, import_path2.join)((0,
|
|
19548
|
+
(0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.yaml"),
|
|
19549
|
+
(0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.json")
|
|
19491
19550
|
];
|
|
19492
19551
|
var PROJECT_CONFIG_NAMES = [
|
|
19493
19552
|
".claude/warden.yaml",
|