claude-warden 2.8.1 → 2.10.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +62 -1
- package/dist/cli.cjs +14 -2
- package/dist/codex-export.cjs +14 -2
- package/dist/copilot.cjs +14 -2
- package/dist/index.cjs +19 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "warden",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.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/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://github.com/banyudu/claude-warden/stargazers)
|
|
7
7
|
[](https://github.com/banyudu/claude-warden/actions)
|
|
8
8
|
|
|
9
|
-
Smart command safety filter for [Claude Code](https://claude.ai/code), [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/customize-copilot-cli/use-hooks), and other AI coding agents. Parses shell commands, evaluates each against configurable safety rules, and returns allow/deny/ask decisions — eliminating unnecessary permission prompts while blocking dangerous commands.
|
|
9
|
+
Smart command safety filter for [Claude Code](https://claude.ai/code), [OpenAI Codex CLI](https://developers.openai.com/codex/hooks), [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/customize-copilot-cli/use-hooks), and other AI coding agents. Parses shell commands, evaluates each against configurable safety rules, and returns allow/deny/ask decisions — eliminating unnecessary permission prompts while blocking dangerous commands.
|
|
10
10
|
|
|
11
11
|
## The problem
|
|
12
12
|
|
|
@@ -283,8 +283,69 @@ rules:
|
|
|
283
283
|
anyArgMatches: ['^(ps|images|logs)$']
|
|
284
284
|
decision: allow
|
|
285
285
|
description: Read-only docker commands
|
|
286
|
+
|
|
287
|
+
# Skill (slash command) filtering — gate Claude Code skill invocations.
|
|
288
|
+
# Skill names use the short form ("commit", not "/commit"). Glob patterns
|
|
289
|
+
# are supported so you can whitelist an entire plugin namespace.
|
|
290
|
+
skills:
|
|
291
|
+
defaultDecision: ask
|
|
292
|
+
alwaysAllow:
|
|
293
|
+
- commit
|
|
294
|
+
- review
|
|
295
|
+
- simplify
|
|
296
|
+
- "plugin-dev:*" # allow every skill in the "plugin-dev" plugin
|
|
297
|
+
alwaysDeny:
|
|
298
|
+
- deploy
|
|
299
|
+
rules:
|
|
300
|
+
- skill: release
|
|
301
|
+
default: ask
|
|
302
|
+
argPatterns:
|
|
303
|
+
- match:
|
|
304
|
+
argsMatch: ["--dry-run"]
|
|
305
|
+
decision: allow
|
|
306
|
+
description: Dry-run release is safe
|
|
286
307
|
```
|
|
287
308
|
|
|
309
|
+
## Skill (slash command) filtering
|
|
310
|
+
|
|
311
|
+
Warden also intercepts Claude Code's `Skill` tool (the mechanism behind `/slash-command` invocations) using the same layered rule engine as shell commands. This lets you whitelist safe skills (read-only helpers, code review, summarization) while still prompting for anything that could modify state.
|
|
312
|
+
|
|
313
|
+
Skill names are the identifier Claude Code uses internally — **without the leading `/`**. Built-in skills use a bare name (`commit`, `review`), plugin skills use `<plugin>:<skill>` (e.g. `plugin-dev:agent-development`, `code-review:code-review`). Glob patterns `*`, `?`, `[...]`, `{a,b,c}` are supported, so `"plugin-dev:*"` matches every skill in that plugin.
|
|
314
|
+
|
|
315
|
+
### Configure
|
|
316
|
+
|
|
317
|
+
```yaml
|
|
318
|
+
skills:
|
|
319
|
+
# Default for skills with no matching rule: allow | deny | ask
|
|
320
|
+
defaultDecision: ask
|
|
321
|
+
|
|
322
|
+
# Auto-allow these skills (scoped to this config layer)
|
|
323
|
+
alwaysAllow:
|
|
324
|
+
- commit
|
|
325
|
+
- review
|
|
326
|
+
- "plugin-dev:*"
|
|
327
|
+
|
|
328
|
+
# Auto-deny these skills
|
|
329
|
+
alwaysDeny:
|
|
330
|
+
- deploy
|
|
331
|
+
|
|
332
|
+
# Per-skill rules with argument-aware matching
|
|
333
|
+
rules:
|
|
334
|
+
- skill: release
|
|
335
|
+
default: ask
|
|
336
|
+
argPatterns:
|
|
337
|
+
- match:
|
|
338
|
+
argsMatch: ["--dry-run"]
|
|
339
|
+
decision: allow
|
|
340
|
+
description: Dry-run release is safe
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Layering follows the same **project > user > default** priority as shell rules (see [Config priority](#config-priority-scoped-layers)).
|
|
344
|
+
|
|
345
|
+
### Built-in skill defaults
|
|
346
|
+
|
|
347
|
+
Warden ships with a curated allow-list of skills that are read-only or informational — review tools (`review`, `security-review`, `code-review:code-review`), search/summarization helpers (`promptfolio-*`, `slack:find-discussions`, `slack:summarize-channel`), plugin-development guidance (`plugin-dev:*-development`), and `*-usage` docs skills. Everything else falls through to `defaultDecision: ask`.
|
|
348
|
+
|
|
288
349
|
## YOLO mode
|
|
289
350
|
|
|
290
351
|
Need to temporarily bypass all permission prompts? YOLO mode auto-allows all commands for a limited time or the full session — while still blocking always-deny commands (like `sudo`, `shutdown`) for safety.
|
package/dist/cli.cjs
CHANGED
|
@@ -18280,7 +18280,6 @@ function walkNode(node, result) {
|
|
|
18280
18280
|
break;
|
|
18281
18281
|
}
|
|
18282
18282
|
case "Subshell": {
|
|
18283
|
-
result.hasSubshell = true;
|
|
18284
18283
|
const subshell = node;
|
|
18285
18284
|
if (subshell.list?.commands) {
|
|
18286
18285
|
for (const cmd of subshell.list.commands) {
|
|
@@ -18740,6 +18739,7 @@ var SAFE_DEV_TOOLS = [
|
|
|
18740
18739
|
"jest",
|
|
18741
18740
|
"vitest",
|
|
18742
18741
|
"tsc",
|
|
18742
|
+
"tsgo",
|
|
18743
18743
|
"eslint",
|
|
18744
18744
|
"prettier",
|
|
18745
18745
|
"mkdirp",
|
|
@@ -19407,6 +19407,7 @@ var DEFAULT_CONFIG = {
|
|
|
19407
19407
|
},
|
|
19408
19408
|
{ command: "rustup", default: "allow" },
|
|
19409
19409
|
{ command: "tsc", default: "allow" },
|
|
19410
|
+
{ command: "tsgo", default: "allow" },
|
|
19410
19411
|
{ command: "turbo", default: "allow" },
|
|
19411
19412
|
{ command: "nx", default: "allow" },
|
|
19412
19413
|
{ command: "lerna", default: "allow" },
|
|
@@ -19517,7 +19518,18 @@ var DEFAULT_CONFIG = {
|
|
|
19517
19518
|
})),
|
|
19518
19519
|
// --- Scripting languages ---
|
|
19519
19520
|
{ command: "ruby", default: "ask", argPatterns: [...inlineExecPatterns("Ruby", ["^-e$", "^--eval"]), VERSION_HELP_FLAGS] },
|
|
19520
|
-
|
|
19521
|
+
// `[npa]` bundles perl's common read-only short flags (`-pe`, `-ne`, `-ane`).
|
|
19522
|
+
// `-i` (in-place edit) mutates files — detected separately so it's caught whether
|
|
19523
|
+
// bundled (`-pie`, `-pi`) or passed as its own arg (`-i -pe`, `-i.bak -pe`).
|
|
19524
|
+
{
|
|
19525
|
+
command: "perl",
|
|
19526
|
+
default: "ask",
|
|
19527
|
+
argPatterns: [
|
|
19528
|
+
{ match: { anyArgMatches: ["^-[a-z]*i"] }, decision: "ask", reason: "Perl `-i` does in-place file edits. Save the script to scripts/*.pl and run it." },
|
|
19529
|
+
...inlineExecPatterns("Perl", ["^-[npa]*[eE]$"]),
|
|
19530
|
+
VERSION_HELP_FLAGS
|
|
19531
|
+
]
|
|
19532
|
+
},
|
|
19521
19533
|
{ command: "php", default: "ask", argPatterns: [...inlineExecPatterns("PHP", ["^-r$"]), VERSION_HELP_FLAGS] },
|
|
19522
19534
|
// --- Java ecosystem ---
|
|
19523
19535
|
{ command: "java", default: "ask", argPatterns: [VERSION_HELP_FLAGS] },
|
package/dist/codex-export.cjs
CHANGED
|
@@ -18284,7 +18284,6 @@ function walkNode(node, result) {
|
|
|
18284
18284
|
break;
|
|
18285
18285
|
}
|
|
18286
18286
|
case "Subshell": {
|
|
18287
|
-
result.hasSubshell = true;
|
|
18288
18287
|
const subshell = node;
|
|
18289
18288
|
if (subshell.list?.commands) {
|
|
18290
18289
|
for (const cmd of subshell.list.commands) {
|
|
@@ -18744,6 +18743,7 @@ var SAFE_DEV_TOOLS = [
|
|
|
18744
18743
|
"jest",
|
|
18745
18744
|
"vitest",
|
|
18746
18745
|
"tsc",
|
|
18746
|
+
"tsgo",
|
|
18747
18747
|
"eslint",
|
|
18748
18748
|
"prettier",
|
|
18749
18749
|
"mkdirp",
|
|
@@ -19411,6 +19411,7 @@ var DEFAULT_CONFIG = {
|
|
|
19411
19411
|
},
|
|
19412
19412
|
{ command: "rustup", default: "allow" },
|
|
19413
19413
|
{ command: "tsc", default: "allow" },
|
|
19414
|
+
{ command: "tsgo", default: "allow" },
|
|
19414
19415
|
{ command: "turbo", default: "allow" },
|
|
19415
19416
|
{ command: "nx", default: "allow" },
|
|
19416
19417
|
{ command: "lerna", default: "allow" },
|
|
@@ -19521,7 +19522,18 @@ var DEFAULT_CONFIG = {
|
|
|
19521
19522
|
})),
|
|
19522
19523
|
// --- Scripting languages ---
|
|
19523
19524
|
{ command: "ruby", default: "ask", argPatterns: [...inlineExecPatterns("Ruby", ["^-e$", "^--eval"]), VERSION_HELP_FLAGS] },
|
|
19524
|
-
|
|
19525
|
+
// `[npa]` bundles perl's common read-only short flags (`-pe`, `-ne`, `-ane`).
|
|
19526
|
+
// `-i` (in-place edit) mutates files — detected separately so it's caught whether
|
|
19527
|
+
// bundled (`-pie`, `-pi`) or passed as its own arg (`-i -pe`, `-i.bak -pe`).
|
|
19528
|
+
{
|
|
19529
|
+
command: "perl",
|
|
19530
|
+
default: "ask",
|
|
19531
|
+
argPatterns: [
|
|
19532
|
+
{ match: { anyArgMatches: ["^-[a-z]*i"] }, decision: "ask", reason: "Perl `-i` does in-place file edits. Save the script to scripts/*.pl and run it." },
|
|
19533
|
+
...inlineExecPatterns("Perl", ["^-[npa]*[eE]$"]),
|
|
19534
|
+
VERSION_HELP_FLAGS
|
|
19535
|
+
]
|
|
19536
|
+
},
|
|
19525
19537
|
{ command: "php", default: "ask", argPatterns: [...inlineExecPatterns("PHP", ["^-r$"]), VERSION_HELP_FLAGS] },
|
|
19526
19538
|
// --- Java ecosystem ---
|
|
19527
19539
|
{ command: "java", default: "ask", argPatterns: [VERSION_HELP_FLAGS] },
|
package/dist/copilot.cjs
CHANGED
|
@@ -18280,7 +18280,6 @@ function walkNode(node, result) {
|
|
|
18280
18280
|
break;
|
|
18281
18281
|
}
|
|
18282
18282
|
case "Subshell": {
|
|
18283
|
-
result.hasSubshell = true;
|
|
18284
18283
|
const subshell = node;
|
|
18285
18284
|
if (subshell.list?.commands) {
|
|
18286
18285
|
for (const cmd of subshell.list.commands) {
|
|
@@ -18740,6 +18739,7 @@ var SAFE_DEV_TOOLS = [
|
|
|
18740
18739
|
"jest",
|
|
18741
18740
|
"vitest",
|
|
18742
18741
|
"tsc",
|
|
18742
|
+
"tsgo",
|
|
18743
18743
|
"eslint",
|
|
18744
18744
|
"prettier",
|
|
18745
18745
|
"mkdirp",
|
|
@@ -19407,6 +19407,7 @@ var DEFAULT_CONFIG = {
|
|
|
19407
19407
|
},
|
|
19408
19408
|
{ command: "rustup", default: "allow" },
|
|
19409
19409
|
{ command: "tsc", default: "allow" },
|
|
19410
|
+
{ command: "tsgo", default: "allow" },
|
|
19410
19411
|
{ command: "turbo", default: "allow" },
|
|
19411
19412
|
{ command: "nx", default: "allow" },
|
|
19412
19413
|
{ command: "lerna", default: "allow" },
|
|
@@ -19517,7 +19518,18 @@ var DEFAULT_CONFIG = {
|
|
|
19517
19518
|
})),
|
|
19518
19519
|
// --- Scripting languages ---
|
|
19519
19520
|
{ command: "ruby", default: "ask", argPatterns: [...inlineExecPatterns("Ruby", ["^-e$", "^--eval"]), VERSION_HELP_FLAGS] },
|
|
19520
|
-
|
|
19521
|
+
// `[npa]` bundles perl's common read-only short flags (`-pe`, `-ne`, `-ane`).
|
|
19522
|
+
// `-i` (in-place edit) mutates files — detected separately so it's caught whether
|
|
19523
|
+
// bundled (`-pie`, `-pi`) or passed as its own arg (`-i -pe`, `-i.bak -pe`).
|
|
19524
|
+
{
|
|
19525
|
+
command: "perl",
|
|
19526
|
+
default: "ask",
|
|
19527
|
+
argPatterns: [
|
|
19528
|
+
{ match: { anyArgMatches: ["^-[a-z]*i"] }, decision: "ask", reason: "Perl `-i` does in-place file edits. Save the script to scripts/*.pl and run it." },
|
|
19529
|
+
...inlineExecPatterns("Perl", ["^-[npa]*[eE]$"]),
|
|
19530
|
+
VERSION_HELP_FLAGS
|
|
19531
|
+
]
|
|
19532
|
+
},
|
|
19521
19533
|
{ command: "php", default: "ask", argPatterns: [...inlineExecPatterns("PHP", ["^-r$"]), VERSION_HELP_FLAGS] },
|
|
19522
19534
|
// --- Java ecosystem ---
|
|
19523
19535
|
{ command: "java", default: "ask", argPatterns: [VERSION_HELP_FLAGS] },
|
package/dist/index.cjs
CHANGED
|
@@ -18280,7 +18280,6 @@ function walkNode(node, result) {
|
|
|
18280
18280
|
break;
|
|
18281
18281
|
}
|
|
18282
18282
|
case "Subshell": {
|
|
18283
|
-
result.hasSubshell = true;
|
|
18284
18283
|
const subshell = node;
|
|
18285
18284
|
if (subshell.list?.commands) {
|
|
18286
18285
|
for (const cmd of subshell.list.commands) {
|
|
@@ -18740,6 +18739,7 @@ var SAFE_DEV_TOOLS = [
|
|
|
18740
18739
|
"jest",
|
|
18741
18740
|
"vitest",
|
|
18742
18741
|
"tsc",
|
|
18742
|
+
"tsgo",
|
|
18743
18743
|
"eslint",
|
|
18744
18744
|
"prettier",
|
|
18745
18745
|
"mkdirp",
|
|
@@ -19407,6 +19407,7 @@ var DEFAULT_CONFIG = {
|
|
|
19407
19407
|
},
|
|
19408
19408
|
{ command: "rustup", default: "allow" },
|
|
19409
19409
|
{ command: "tsc", default: "allow" },
|
|
19410
|
+
{ command: "tsgo", default: "allow" },
|
|
19410
19411
|
{ command: "turbo", default: "allow" },
|
|
19411
19412
|
{ command: "nx", default: "allow" },
|
|
19412
19413
|
{ command: "lerna", default: "allow" },
|
|
@@ -19517,7 +19518,18 @@ var DEFAULT_CONFIG = {
|
|
|
19517
19518
|
})),
|
|
19518
19519
|
// --- Scripting languages ---
|
|
19519
19520
|
{ command: "ruby", default: "ask", argPatterns: [...inlineExecPatterns("Ruby", ["^-e$", "^--eval"]), VERSION_HELP_FLAGS] },
|
|
19520
|
-
|
|
19521
|
+
// `[npa]` bundles perl's common read-only short flags (`-pe`, `-ne`, `-ane`).
|
|
19522
|
+
// `-i` (in-place edit) mutates files — detected separately so it's caught whether
|
|
19523
|
+
// bundled (`-pie`, `-pi`) or passed as its own arg (`-i -pe`, `-i.bak -pe`).
|
|
19524
|
+
{
|
|
19525
|
+
command: "perl",
|
|
19526
|
+
default: "ask",
|
|
19527
|
+
argPatterns: [
|
|
19528
|
+
{ match: { anyArgMatches: ["^-[a-z]*i"] }, decision: "ask", reason: "Perl `-i` does in-place file edits. Save the script to scripts/*.pl and run it." },
|
|
19529
|
+
...inlineExecPatterns("Perl", ["^-[npa]*[eE]$"]),
|
|
19530
|
+
VERSION_HELP_FLAGS
|
|
19531
|
+
]
|
|
19532
|
+
},
|
|
19521
19533
|
{ command: "php", default: "ask", argPatterns: [...inlineExecPatterns("PHP", ["^-r$"]), VERSION_HELP_FLAGS] },
|
|
19522
19534
|
// --- Java ecosystem ---
|
|
19523
19535
|
{ command: "java", default: "ask", argPatterns: [VERSION_HELP_FLAGS] },
|
|
@@ -20911,11 +20923,12 @@ function generateAllowSnippet(details) {
|
|
|
20911
20923
|
}
|
|
20912
20924
|
return lines.join("\n");
|
|
20913
20925
|
}
|
|
20914
|
-
function formatSystemMessage(decision, rawCommand, details) {
|
|
20926
|
+
function formatSystemMessage(decision, rawCommand, details, fallbackReason) {
|
|
20915
20927
|
const relevant = details.filter((d) => d.decision !== "allow");
|
|
20916
20928
|
if (decision === "ask") {
|
|
20917
20929
|
const parts = relevant.map((d) => `\`${d.command}\`: ${d.reason}`);
|
|
20918
|
-
const
|
|
20930
|
+
const body = parts.length > 0 ? parts.join(" | ") : fallbackReason || "";
|
|
20931
|
+
const header = `[warden] ${body}`;
|
|
20919
20932
|
const subcommandHints = relevant.filter((d) => d.args.length > 0).map((d) => {
|
|
20920
20933
|
const sub = d.args[0];
|
|
20921
20934
|
return ` Option A: Allow all \`${d.command}\` \u2192 \`/warden:allow ${d.command}\`
|
|
@@ -21189,14 +21202,14 @@ function emitResult(result, label, config) {
|
|
|
21189
21202
|
const truncated = label.length > 80 ? label.slice(0, 77) + "..." : label;
|
|
21190
21203
|
sendNotification("Claude Warden", `Blocked: ${truncated}`, config);
|
|
21191
21204
|
}
|
|
21192
|
-
const msg2 = formatSystemMessage("deny", label, result.details);
|
|
21205
|
+
const msg2 = formatSystemMessage("deny", label, result.details, result.reason);
|
|
21193
21206
|
emitDecision("deny", msg2, `[warden] Blocked: ${result.reason}`);
|
|
21194
21207
|
}
|
|
21195
21208
|
if (config.notifyOnAsk) {
|
|
21196
21209
|
const truncated = label.length > 80 ? label.slice(0, 77) + "..." : label;
|
|
21197
21210
|
sendNotification("Claude Warden", `Permission needed: ${truncated}`, config);
|
|
21198
21211
|
}
|
|
21199
|
-
const msg = formatSystemMessage("ask", label, result.details);
|
|
21212
|
+
const msg = formatSystemMessage("ask", label, result.details, result.reason);
|
|
21200
21213
|
emitDecision("ask", msg);
|
|
21201
21214
|
}
|
|
21202
21215
|
main().catch(() => process.exit(0));
|