claude-warden 2.5.4 → 2.7.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/config/warden.default.yaml +22 -0
- package/dist/cli.cjs +83 -7
- package/dist/codex-export.cjs +83 -7
- package/dist/copilot.cjs +83 -7
- package/dist/index.cjs +238 -80
- package/hooks/hooks.json +11 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "warden",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.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"
|
|
@@ -175,3 +175,25 @@ notifyOnDeny: true
|
|
|
175
175
|
# anyArgMatches: ['^(ps|images|logs)$']
|
|
176
176
|
# decision: allow
|
|
177
177
|
# description: Read-only docker commands
|
|
178
|
+
|
|
179
|
+
# Skill (slash command) rules — filter Claude Code skill invocations.
|
|
180
|
+
# Skill names use the short form (e.g. "commit", not "/commit").
|
|
181
|
+
# Glob patterns supported for namespace matching (e.g. "example-plugin:*").
|
|
182
|
+
# Built-in defaults auto-allow common safe skills (commit, review, simplify, init).
|
|
183
|
+
# skills:
|
|
184
|
+
# defaultDecision: ask
|
|
185
|
+
# alwaysAllow:
|
|
186
|
+
# - commit
|
|
187
|
+
# - review
|
|
188
|
+
# - simplify
|
|
189
|
+
# - "example-plugin:*"
|
|
190
|
+
# alwaysDeny:
|
|
191
|
+
# - deploy
|
|
192
|
+
# rules:
|
|
193
|
+
# - skill: release
|
|
194
|
+
# default: ask
|
|
195
|
+
# argPatterns:
|
|
196
|
+
# - match:
|
|
197
|
+
# argsMatch: ["--dry-run"]
|
|
198
|
+
# decision: allow
|
|
199
|
+
# description: Dry-run release is safe
|
package/dist/cli.cjs
CHANGED
|
@@ -18888,6 +18888,51 @@ function pkgRunnerRule(command) {
|
|
|
18888
18888
|
]
|
|
18889
18889
|
};
|
|
18890
18890
|
}
|
|
18891
|
+
var DEFAULT_SKILL_RULES = {
|
|
18892
|
+
defaultDecision: "ask",
|
|
18893
|
+
layers: [{
|
|
18894
|
+
alwaysAllow: [
|
|
18895
|
+
// Built-in review/analysis skills
|
|
18896
|
+
"review",
|
|
18897
|
+
"security-review",
|
|
18898
|
+
// Code review plugins
|
|
18899
|
+
"code-review:code-review",
|
|
18900
|
+
"pr-review-toolkit:review-pr",
|
|
18901
|
+
// Slack read-only skills
|
|
18902
|
+
"slack:find-discussions",
|
|
18903
|
+
"slack:summarize-channel",
|
|
18904
|
+
"slack:channel-digest",
|
|
18905
|
+
"slack:standup",
|
|
18906
|
+
"slack:draft-announcement",
|
|
18907
|
+
"slack:slack-messaging",
|
|
18908
|
+
"slack:slack-search",
|
|
18909
|
+
// Search/summarization
|
|
18910
|
+
"promptfolio-summarize",
|
|
18911
|
+
"promptfolio-search-skills",
|
|
18912
|
+
"promptfolio-search-people",
|
|
18913
|
+
// Informational/guidance skills
|
|
18914
|
+
"keybindings-help",
|
|
18915
|
+
"claude-api",
|
|
18916
|
+
"azure-tools:azure-usage",
|
|
18917
|
+
"gcloud-tools:gcloud-usage",
|
|
18918
|
+
"linear-tools:linear-usage",
|
|
18919
|
+
"tavily-tools:tavily-usage",
|
|
18920
|
+
"mongodb-tools:mongodb-usage",
|
|
18921
|
+
"supabase-tools:supabase-usage",
|
|
18922
|
+
"playwright-tools:playwright-testing",
|
|
18923
|
+
// Plugin development guidance (read-only context loading)
|
|
18924
|
+
"plugin-dev:agent-development",
|
|
18925
|
+
"plugin-dev:mcp-integration",
|
|
18926
|
+
"plugin-dev:skill-development",
|
|
18927
|
+
"plugin-dev:plugin-settings",
|
|
18928
|
+
"plugin-dev:command-development",
|
|
18929
|
+
"plugin-dev:plugin-structure",
|
|
18930
|
+
"plugin-dev:hook-development"
|
|
18931
|
+
],
|
|
18932
|
+
alwaysDeny: [],
|
|
18933
|
+
rules: []
|
|
18934
|
+
}]
|
|
18935
|
+
};
|
|
18891
18936
|
var DEFAULT_CONFIG = {
|
|
18892
18937
|
defaultDecision: "ask",
|
|
18893
18938
|
askOnSubshell: true,
|
|
@@ -18895,6 +18940,7 @@ var DEFAULT_CONFIG = {
|
|
|
18895
18940
|
notifyOnDeny: true,
|
|
18896
18941
|
trustedRemotes: [],
|
|
18897
18942
|
targetPolicies: [],
|
|
18943
|
+
skillRules: DEFAULT_SKILL_RULES,
|
|
18898
18944
|
layers: [{
|
|
18899
18945
|
alwaysAllow: [
|
|
18900
18946
|
// Read-only file operations
|
|
@@ -19400,16 +19446,21 @@ var DEFAULT_CONFIG = {
|
|
|
19400
19446
|
})),
|
|
19401
19447
|
// --- Cloud CLIs ---
|
|
19402
19448
|
{ command: "gcloud", default: "ask", argPatterns: [
|
|
19403
|
-
{ match: { anyArgMatches: ["^(info|version|help|
|
|
19404
|
-
{ match: { anyArgMatches: ["^
|
|
19449
|
+
{ match: { anyArgMatches: ["^(info|version|help|topic|components|feedback|survey)$"] }, decision: "allow", description: "Info/meta commands" },
|
|
19450
|
+
{ match: { anyArgMatches: ["^config$"] }, decision: "allow", description: "Config subcommands" },
|
|
19451
|
+
{ match: { anyArgMatches: ["^(list|describe|get|get-iam-policy|browse|tail|read|show|search|lookup|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19452
|
+
{ match: { anyArgMatches: ["^(list|get|describe|show|print|read|search|lookup|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun (list-enabled, get-value, print-access-token, etc.)" },
|
|
19405
19453
|
VERSION_HELP_FLAGS
|
|
19406
19454
|
] },
|
|
19407
19455
|
{ command: "az", default: "ask", argPatterns: [
|
|
19408
|
-
{ match: { anyArgMatches: ["^(list|show|get)$"] }, decision: "allow", description: "Read-only
|
|
19456
|
+
{ match: { anyArgMatches: ["^(list|show|get|search|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19457
|
+
{ match: { anyArgMatches: ["^(list|show|get|search|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun" },
|
|
19458
|
+
{ match: { anyArgMatches: ["^(version|help|account|feedback)$"] }, decision: "allow", description: "Info/meta commands" },
|
|
19409
19459
|
VERSION_HELP_FLAGS
|
|
19410
19460
|
] },
|
|
19411
19461
|
{ command: "aws", default: "ask", argPatterns: [
|
|
19412
|
-
{ match: { anyArgMatches: ["^(describe|list|get|sts)$"] }, decision: "allow", description: "Read-only
|
|
19462
|
+
{ match: { anyArgMatches: ["^(describe|list|get|sts|help|search|lookup|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19463
|
+
{ match: { anyArgMatches: ["^(describe|list|get|search|lookup|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun" },
|
|
19413
19464
|
VERSION_HELP_FLAGS
|
|
19414
19465
|
] },
|
|
19415
19466
|
// --- Helm ---
|
|
@@ -19520,6 +19571,14 @@ function loadConfig(cwd) {
|
|
|
19520
19571
|
...userLayer ? [userLayer] : [],
|
|
19521
19572
|
defaultLayer
|
|
19522
19573
|
];
|
|
19574
|
+
const defaultSkillLayer = config.skillRules.layers[0];
|
|
19575
|
+
const userSkillLayer = userRaw?.skills ? extractSkillLayer(userRaw.skills) : null;
|
|
19576
|
+
const workspaceSkillLayer = workspaceRaw?.skills ? extractSkillLayer(workspaceRaw.skills) : null;
|
|
19577
|
+
config.skillRules.layers = [
|
|
19578
|
+
...workspaceSkillLayer ? [workspaceSkillLayer] : [],
|
|
19579
|
+
...userSkillLayer ? [userSkillLayer] : [],
|
|
19580
|
+
defaultSkillLayer
|
|
19581
|
+
];
|
|
19523
19582
|
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19524
19583
|
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19525
19584
|
return config;
|
|
@@ -19538,19 +19597,19 @@ function tryLoadFile(filePath) {
|
|
|
19538
19597
|
}
|
|
19539
19598
|
return null;
|
|
19540
19599
|
}
|
|
19541
|
-
function
|
|
19600
|
+
function extractGenericLayer(raw, nameKey) {
|
|
19542
19601
|
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19543
19602
|
for (const rule of rules) {
|
|
19544
19603
|
if (rule && typeof rule === "object") {
|
|
19545
19604
|
if (rule.default && !isValidDecision(rule.default)) {
|
|
19546
|
-
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule
|
|
19605
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule[nameKey]}", using "ask"
|
|
19547
19606
|
`);
|
|
19548
19607
|
rule.default = "ask";
|
|
19549
19608
|
}
|
|
19550
19609
|
if (Array.isArray(rule.argPatterns)) {
|
|
19551
19610
|
for (const pattern of rule.argPatterns) {
|
|
19552
19611
|
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19553
|
-
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule
|
|
19612
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule[nameKey]}", using "ask"
|
|
19554
19613
|
`);
|
|
19555
19614
|
pattern.decision = "ask";
|
|
19556
19615
|
}
|
|
@@ -19564,6 +19623,12 @@ function extractLayer(raw) {
|
|
|
19564
19623
|
rules
|
|
19565
19624
|
};
|
|
19566
19625
|
}
|
|
19626
|
+
function extractSkillLayer(raw) {
|
|
19627
|
+
return extractGenericLayer(raw, "skill");
|
|
19628
|
+
}
|
|
19629
|
+
function extractLayer(raw) {
|
|
19630
|
+
return extractGenericLayer(raw, "command");
|
|
19631
|
+
}
|
|
19567
19632
|
function parseTrustedList(raw) {
|
|
19568
19633
|
return raw.map((entry) => {
|
|
19569
19634
|
if (typeof entry === "string") return { name: entry };
|
|
@@ -19744,6 +19809,17 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19744
19809
|
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19745
19810
|
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19746
19811
|
}
|
|
19812
|
+
if (raw.skills && typeof raw.skills === "object") {
|
|
19813
|
+
const skills = raw.skills;
|
|
19814
|
+
if (typeof skills.defaultDecision === "string") {
|
|
19815
|
+
if (isValidDecision(skills.defaultDecision)) {
|
|
19816
|
+
config.skillRules.defaultDecision = skills.defaultDecision;
|
|
19817
|
+
} else {
|
|
19818
|
+
warn(`[warden] Warning: invalid skills.defaultDecision "${skills.defaultDecision}", ignoring
|
|
19819
|
+
`);
|
|
19820
|
+
}
|
|
19821
|
+
}
|
|
19822
|
+
}
|
|
19747
19823
|
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19748
19824
|
const overrides = raw.trustedContextOverrides;
|
|
19749
19825
|
const layer = extractLayer(overrides);
|
package/dist/codex-export.cjs
CHANGED
|
@@ -18892,6 +18892,51 @@ function pkgRunnerRule(command) {
|
|
|
18892
18892
|
]
|
|
18893
18893
|
};
|
|
18894
18894
|
}
|
|
18895
|
+
var DEFAULT_SKILL_RULES = {
|
|
18896
|
+
defaultDecision: "ask",
|
|
18897
|
+
layers: [{
|
|
18898
|
+
alwaysAllow: [
|
|
18899
|
+
// Built-in review/analysis skills
|
|
18900
|
+
"review",
|
|
18901
|
+
"security-review",
|
|
18902
|
+
// Code review plugins
|
|
18903
|
+
"code-review:code-review",
|
|
18904
|
+
"pr-review-toolkit:review-pr",
|
|
18905
|
+
// Slack read-only skills
|
|
18906
|
+
"slack:find-discussions",
|
|
18907
|
+
"slack:summarize-channel",
|
|
18908
|
+
"slack:channel-digest",
|
|
18909
|
+
"slack:standup",
|
|
18910
|
+
"slack:draft-announcement",
|
|
18911
|
+
"slack:slack-messaging",
|
|
18912
|
+
"slack:slack-search",
|
|
18913
|
+
// Search/summarization
|
|
18914
|
+
"promptfolio-summarize",
|
|
18915
|
+
"promptfolio-search-skills",
|
|
18916
|
+
"promptfolio-search-people",
|
|
18917
|
+
// Informational/guidance skills
|
|
18918
|
+
"keybindings-help",
|
|
18919
|
+
"claude-api",
|
|
18920
|
+
"azure-tools:azure-usage",
|
|
18921
|
+
"gcloud-tools:gcloud-usage",
|
|
18922
|
+
"linear-tools:linear-usage",
|
|
18923
|
+
"tavily-tools:tavily-usage",
|
|
18924
|
+
"mongodb-tools:mongodb-usage",
|
|
18925
|
+
"supabase-tools:supabase-usage",
|
|
18926
|
+
"playwright-tools:playwright-testing",
|
|
18927
|
+
// Plugin development guidance (read-only context loading)
|
|
18928
|
+
"plugin-dev:agent-development",
|
|
18929
|
+
"plugin-dev:mcp-integration",
|
|
18930
|
+
"plugin-dev:skill-development",
|
|
18931
|
+
"plugin-dev:plugin-settings",
|
|
18932
|
+
"plugin-dev:command-development",
|
|
18933
|
+
"plugin-dev:plugin-structure",
|
|
18934
|
+
"plugin-dev:hook-development"
|
|
18935
|
+
],
|
|
18936
|
+
alwaysDeny: [],
|
|
18937
|
+
rules: []
|
|
18938
|
+
}]
|
|
18939
|
+
};
|
|
18895
18940
|
var DEFAULT_CONFIG = {
|
|
18896
18941
|
defaultDecision: "ask",
|
|
18897
18942
|
askOnSubshell: true,
|
|
@@ -18899,6 +18944,7 @@ var DEFAULT_CONFIG = {
|
|
|
18899
18944
|
notifyOnDeny: true,
|
|
18900
18945
|
trustedRemotes: [],
|
|
18901
18946
|
targetPolicies: [],
|
|
18947
|
+
skillRules: DEFAULT_SKILL_RULES,
|
|
18902
18948
|
layers: [{
|
|
18903
18949
|
alwaysAllow: [
|
|
18904
18950
|
// Read-only file operations
|
|
@@ -19404,16 +19450,21 @@ var DEFAULT_CONFIG = {
|
|
|
19404
19450
|
})),
|
|
19405
19451
|
// --- Cloud CLIs ---
|
|
19406
19452
|
{ command: "gcloud", default: "ask", argPatterns: [
|
|
19407
|
-
{ match: { anyArgMatches: ["^(info|version|help|
|
|
19408
|
-
{ match: { anyArgMatches: ["^
|
|
19453
|
+
{ match: { anyArgMatches: ["^(info|version|help|topic|components|feedback|survey)$"] }, decision: "allow", description: "Info/meta commands" },
|
|
19454
|
+
{ match: { anyArgMatches: ["^config$"] }, decision: "allow", description: "Config subcommands" },
|
|
19455
|
+
{ match: { anyArgMatches: ["^(list|describe|get|get-iam-policy|browse|tail|read|show|search|lookup|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19456
|
+
{ match: { anyArgMatches: ["^(list|get|describe|show|print|read|search|lookup|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun (list-enabled, get-value, print-access-token, etc.)" },
|
|
19409
19457
|
VERSION_HELP_FLAGS
|
|
19410
19458
|
] },
|
|
19411
19459
|
{ command: "az", default: "ask", argPatterns: [
|
|
19412
|
-
{ match: { anyArgMatches: ["^(list|show|get)$"] }, decision: "allow", description: "Read-only
|
|
19460
|
+
{ match: { anyArgMatches: ["^(list|show|get|search|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19461
|
+
{ match: { anyArgMatches: ["^(list|show|get|search|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun" },
|
|
19462
|
+
{ match: { anyArgMatches: ["^(version|help|account|feedback)$"] }, decision: "allow", description: "Info/meta commands" },
|
|
19413
19463
|
VERSION_HELP_FLAGS
|
|
19414
19464
|
] },
|
|
19415
19465
|
{ command: "aws", default: "ask", argPatterns: [
|
|
19416
|
-
{ match: { anyArgMatches: ["^(describe|list|get|sts)$"] }, decision: "allow", description: "Read-only
|
|
19466
|
+
{ match: { anyArgMatches: ["^(describe|list|get|sts|help|search|lookup|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19467
|
+
{ match: { anyArgMatches: ["^(describe|list|get|search|lookup|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun" },
|
|
19417
19468
|
VERSION_HELP_FLAGS
|
|
19418
19469
|
] },
|
|
19419
19470
|
// --- Helm ---
|
|
@@ -19524,6 +19575,14 @@ function loadConfig(cwd) {
|
|
|
19524
19575
|
...userLayer ? [userLayer] : [],
|
|
19525
19576
|
defaultLayer
|
|
19526
19577
|
];
|
|
19578
|
+
const defaultSkillLayer = config.skillRules.layers[0];
|
|
19579
|
+
const userSkillLayer = userRaw?.skills ? extractSkillLayer(userRaw.skills) : null;
|
|
19580
|
+
const workspaceSkillLayer = workspaceRaw?.skills ? extractSkillLayer(workspaceRaw.skills) : null;
|
|
19581
|
+
config.skillRules.layers = [
|
|
19582
|
+
...workspaceSkillLayer ? [workspaceSkillLayer] : [],
|
|
19583
|
+
...userSkillLayer ? [userSkillLayer] : [],
|
|
19584
|
+
defaultSkillLayer
|
|
19585
|
+
];
|
|
19527
19586
|
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19528
19587
|
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19529
19588
|
return config;
|
|
@@ -19542,19 +19601,19 @@ function tryLoadFile(filePath) {
|
|
|
19542
19601
|
}
|
|
19543
19602
|
return null;
|
|
19544
19603
|
}
|
|
19545
|
-
function
|
|
19604
|
+
function extractGenericLayer(raw, nameKey) {
|
|
19546
19605
|
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19547
19606
|
for (const rule of rules) {
|
|
19548
19607
|
if (rule && typeof rule === "object") {
|
|
19549
19608
|
if (rule.default && !isValidDecision(rule.default)) {
|
|
19550
|
-
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule
|
|
19609
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule[nameKey]}", using "ask"
|
|
19551
19610
|
`);
|
|
19552
19611
|
rule.default = "ask";
|
|
19553
19612
|
}
|
|
19554
19613
|
if (Array.isArray(rule.argPatterns)) {
|
|
19555
19614
|
for (const pattern of rule.argPatterns) {
|
|
19556
19615
|
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19557
|
-
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule
|
|
19616
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule[nameKey]}", using "ask"
|
|
19558
19617
|
`);
|
|
19559
19618
|
pattern.decision = "ask";
|
|
19560
19619
|
}
|
|
@@ -19568,6 +19627,12 @@ function extractLayer(raw) {
|
|
|
19568
19627
|
rules
|
|
19569
19628
|
};
|
|
19570
19629
|
}
|
|
19630
|
+
function extractSkillLayer(raw) {
|
|
19631
|
+
return extractGenericLayer(raw, "skill");
|
|
19632
|
+
}
|
|
19633
|
+
function extractLayer(raw) {
|
|
19634
|
+
return extractGenericLayer(raw, "command");
|
|
19635
|
+
}
|
|
19571
19636
|
function parseTrustedList(raw) {
|
|
19572
19637
|
return raw.map((entry) => {
|
|
19573
19638
|
if (typeof entry === "string") return { name: entry };
|
|
@@ -19748,6 +19813,17 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19748
19813
|
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19749
19814
|
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19750
19815
|
}
|
|
19816
|
+
if (raw.skills && typeof raw.skills === "object") {
|
|
19817
|
+
const skills = raw.skills;
|
|
19818
|
+
if (typeof skills.defaultDecision === "string") {
|
|
19819
|
+
if (isValidDecision(skills.defaultDecision)) {
|
|
19820
|
+
config.skillRules.defaultDecision = skills.defaultDecision;
|
|
19821
|
+
} else {
|
|
19822
|
+
warn(`[warden] Warning: invalid skills.defaultDecision "${skills.defaultDecision}", ignoring
|
|
19823
|
+
`);
|
|
19824
|
+
}
|
|
19825
|
+
}
|
|
19826
|
+
}
|
|
19751
19827
|
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19752
19828
|
const overrides = raw.trustedContextOverrides;
|
|
19753
19829
|
const layer = extractLayer(overrides);
|
package/dist/copilot.cjs
CHANGED
|
@@ -18888,6 +18888,51 @@ function pkgRunnerRule(command) {
|
|
|
18888
18888
|
]
|
|
18889
18889
|
};
|
|
18890
18890
|
}
|
|
18891
|
+
var DEFAULT_SKILL_RULES = {
|
|
18892
|
+
defaultDecision: "ask",
|
|
18893
|
+
layers: [{
|
|
18894
|
+
alwaysAllow: [
|
|
18895
|
+
// Built-in review/analysis skills
|
|
18896
|
+
"review",
|
|
18897
|
+
"security-review",
|
|
18898
|
+
// Code review plugins
|
|
18899
|
+
"code-review:code-review",
|
|
18900
|
+
"pr-review-toolkit:review-pr",
|
|
18901
|
+
// Slack read-only skills
|
|
18902
|
+
"slack:find-discussions",
|
|
18903
|
+
"slack:summarize-channel",
|
|
18904
|
+
"slack:channel-digest",
|
|
18905
|
+
"slack:standup",
|
|
18906
|
+
"slack:draft-announcement",
|
|
18907
|
+
"slack:slack-messaging",
|
|
18908
|
+
"slack:slack-search",
|
|
18909
|
+
// Search/summarization
|
|
18910
|
+
"promptfolio-summarize",
|
|
18911
|
+
"promptfolio-search-skills",
|
|
18912
|
+
"promptfolio-search-people",
|
|
18913
|
+
// Informational/guidance skills
|
|
18914
|
+
"keybindings-help",
|
|
18915
|
+
"claude-api",
|
|
18916
|
+
"azure-tools:azure-usage",
|
|
18917
|
+
"gcloud-tools:gcloud-usage",
|
|
18918
|
+
"linear-tools:linear-usage",
|
|
18919
|
+
"tavily-tools:tavily-usage",
|
|
18920
|
+
"mongodb-tools:mongodb-usage",
|
|
18921
|
+
"supabase-tools:supabase-usage",
|
|
18922
|
+
"playwright-tools:playwright-testing",
|
|
18923
|
+
// Plugin development guidance (read-only context loading)
|
|
18924
|
+
"plugin-dev:agent-development",
|
|
18925
|
+
"plugin-dev:mcp-integration",
|
|
18926
|
+
"plugin-dev:skill-development",
|
|
18927
|
+
"plugin-dev:plugin-settings",
|
|
18928
|
+
"plugin-dev:command-development",
|
|
18929
|
+
"plugin-dev:plugin-structure",
|
|
18930
|
+
"plugin-dev:hook-development"
|
|
18931
|
+
],
|
|
18932
|
+
alwaysDeny: [],
|
|
18933
|
+
rules: []
|
|
18934
|
+
}]
|
|
18935
|
+
};
|
|
18891
18936
|
var DEFAULT_CONFIG = {
|
|
18892
18937
|
defaultDecision: "ask",
|
|
18893
18938
|
askOnSubshell: true,
|
|
@@ -18895,6 +18940,7 @@ var DEFAULT_CONFIG = {
|
|
|
18895
18940
|
notifyOnDeny: true,
|
|
18896
18941
|
trustedRemotes: [],
|
|
18897
18942
|
targetPolicies: [],
|
|
18943
|
+
skillRules: DEFAULT_SKILL_RULES,
|
|
18898
18944
|
layers: [{
|
|
18899
18945
|
alwaysAllow: [
|
|
18900
18946
|
// Read-only file operations
|
|
@@ -19400,16 +19446,21 @@ var DEFAULT_CONFIG = {
|
|
|
19400
19446
|
})),
|
|
19401
19447
|
// --- Cloud CLIs ---
|
|
19402
19448
|
{ command: "gcloud", default: "ask", argPatterns: [
|
|
19403
|
-
{ match: { anyArgMatches: ["^(info|version|help|
|
|
19404
|
-
{ match: { anyArgMatches: ["^
|
|
19449
|
+
{ match: { anyArgMatches: ["^(info|version|help|topic|components|feedback|survey)$"] }, decision: "allow", description: "Info/meta commands" },
|
|
19450
|
+
{ match: { anyArgMatches: ["^config$"] }, decision: "allow", description: "Config subcommands" },
|
|
19451
|
+
{ match: { anyArgMatches: ["^(list|describe|get|get-iam-policy|browse|tail|read|show|search|lookup|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19452
|
+
{ match: { anyArgMatches: ["^(list|get|describe|show|print|read|search|lookup|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun (list-enabled, get-value, print-access-token, etc.)" },
|
|
19405
19453
|
VERSION_HELP_FLAGS
|
|
19406
19454
|
] },
|
|
19407
19455
|
{ command: "az", default: "ask", argPatterns: [
|
|
19408
|
-
{ match: { anyArgMatches: ["^(list|show|get)$"] }, decision: "allow", description: "Read-only
|
|
19456
|
+
{ match: { anyArgMatches: ["^(list|show|get|search|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19457
|
+
{ match: { anyArgMatches: ["^(list|show|get|search|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun" },
|
|
19458
|
+
{ match: { anyArgMatches: ["^(version|help|account|feedback)$"] }, decision: "allow", description: "Info/meta commands" },
|
|
19409
19459
|
VERSION_HELP_FLAGS
|
|
19410
19460
|
] },
|
|
19411
19461
|
{ command: "aws", default: "ask", argPatterns: [
|
|
19412
|
-
{ match: { anyArgMatches: ["^(describe|list|get|sts)$"] }, decision: "allow", description: "Read-only
|
|
19462
|
+
{ match: { anyArgMatches: ["^(describe|list|get|sts|help|search|lookup|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19463
|
+
{ match: { anyArgMatches: ["^(describe|list|get|search|lookup|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun" },
|
|
19413
19464
|
VERSION_HELP_FLAGS
|
|
19414
19465
|
] },
|
|
19415
19466
|
// --- Helm ---
|
|
@@ -19517,6 +19568,14 @@ function loadConfig(cwd) {
|
|
|
19517
19568
|
...userLayer ? [userLayer] : [],
|
|
19518
19569
|
defaultLayer
|
|
19519
19570
|
];
|
|
19571
|
+
const defaultSkillLayer = config.skillRules.layers[0];
|
|
19572
|
+
const userSkillLayer = userRaw?.skills ? extractSkillLayer(userRaw.skills) : null;
|
|
19573
|
+
const workspaceSkillLayer = workspaceRaw?.skills ? extractSkillLayer(workspaceRaw.skills) : null;
|
|
19574
|
+
config.skillRules.layers = [
|
|
19575
|
+
...workspaceSkillLayer ? [workspaceSkillLayer] : [],
|
|
19576
|
+
...userSkillLayer ? [userSkillLayer] : [],
|
|
19577
|
+
defaultSkillLayer
|
|
19578
|
+
];
|
|
19520
19579
|
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19521
19580
|
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19522
19581
|
return config;
|
|
@@ -19535,19 +19594,19 @@ function tryLoadFile(filePath) {
|
|
|
19535
19594
|
}
|
|
19536
19595
|
return null;
|
|
19537
19596
|
}
|
|
19538
|
-
function
|
|
19597
|
+
function extractGenericLayer(raw, nameKey) {
|
|
19539
19598
|
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19540
19599
|
for (const rule of rules) {
|
|
19541
19600
|
if (rule && typeof rule === "object") {
|
|
19542
19601
|
if (rule.default && !isValidDecision(rule.default)) {
|
|
19543
|
-
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule
|
|
19602
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule[nameKey]}", using "ask"
|
|
19544
19603
|
`);
|
|
19545
19604
|
rule.default = "ask";
|
|
19546
19605
|
}
|
|
19547
19606
|
if (Array.isArray(rule.argPatterns)) {
|
|
19548
19607
|
for (const pattern of rule.argPatterns) {
|
|
19549
19608
|
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19550
|
-
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule
|
|
19609
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule[nameKey]}", using "ask"
|
|
19551
19610
|
`);
|
|
19552
19611
|
pattern.decision = "ask";
|
|
19553
19612
|
}
|
|
@@ -19561,6 +19620,12 @@ function extractLayer(raw) {
|
|
|
19561
19620
|
rules
|
|
19562
19621
|
};
|
|
19563
19622
|
}
|
|
19623
|
+
function extractSkillLayer(raw) {
|
|
19624
|
+
return extractGenericLayer(raw, "skill");
|
|
19625
|
+
}
|
|
19626
|
+
function extractLayer(raw) {
|
|
19627
|
+
return extractGenericLayer(raw, "command");
|
|
19628
|
+
}
|
|
19564
19629
|
function parseTrustedList(raw) {
|
|
19565
19630
|
return raw.map((entry) => {
|
|
19566
19631
|
if (typeof entry === "string") return { name: entry };
|
|
@@ -19741,6 +19806,17 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19741
19806
|
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19742
19807
|
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19743
19808
|
}
|
|
19809
|
+
if (raw.skills && typeof raw.skills === "object") {
|
|
19810
|
+
const skills = raw.skills;
|
|
19811
|
+
if (typeof skills.defaultDecision === "string") {
|
|
19812
|
+
if (isValidDecision(skills.defaultDecision)) {
|
|
19813
|
+
config.skillRules.defaultDecision = skills.defaultDecision;
|
|
19814
|
+
} else {
|
|
19815
|
+
warn(`[warden] Warning: invalid skills.defaultDecision "${skills.defaultDecision}", ignoring
|
|
19816
|
+
`);
|
|
19817
|
+
}
|
|
19818
|
+
}
|
|
19819
|
+
}
|
|
19744
19820
|
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19745
19821
|
const overrides = raw.trustedContextOverrides;
|
|
19746
19822
|
const layer = extractLayer(overrides);
|
package/dist/index.cjs
CHANGED
|
@@ -18888,6 +18888,51 @@ function pkgRunnerRule(command) {
|
|
|
18888
18888
|
]
|
|
18889
18889
|
};
|
|
18890
18890
|
}
|
|
18891
|
+
var DEFAULT_SKILL_RULES = {
|
|
18892
|
+
defaultDecision: "ask",
|
|
18893
|
+
layers: [{
|
|
18894
|
+
alwaysAllow: [
|
|
18895
|
+
// Built-in review/analysis skills
|
|
18896
|
+
"review",
|
|
18897
|
+
"security-review",
|
|
18898
|
+
// Code review plugins
|
|
18899
|
+
"code-review:code-review",
|
|
18900
|
+
"pr-review-toolkit:review-pr",
|
|
18901
|
+
// Slack read-only skills
|
|
18902
|
+
"slack:find-discussions",
|
|
18903
|
+
"slack:summarize-channel",
|
|
18904
|
+
"slack:channel-digest",
|
|
18905
|
+
"slack:standup",
|
|
18906
|
+
"slack:draft-announcement",
|
|
18907
|
+
"slack:slack-messaging",
|
|
18908
|
+
"slack:slack-search",
|
|
18909
|
+
// Search/summarization
|
|
18910
|
+
"promptfolio-summarize",
|
|
18911
|
+
"promptfolio-search-skills",
|
|
18912
|
+
"promptfolio-search-people",
|
|
18913
|
+
// Informational/guidance skills
|
|
18914
|
+
"keybindings-help",
|
|
18915
|
+
"claude-api",
|
|
18916
|
+
"azure-tools:azure-usage",
|
|
18917
|
+
"gcloud-tools:gcloud-usage",
|
|
18918
|
+
"linear-tools:linear-usage",
|
|
18919
|
+
"tavily-tools:tavily-usage",
|
|
18920
|
+
"mongodb-tools:mongodb-usage",
|
|
18921
|
+
"supabase-tools:supabase-usage",
|
|
18922
|
+
"playwright-tools:playwright-testing",
|
|
18923
|
+
// Plugin development guidance (read-only context loading)
|
|
18924
|
+
"plugin-dev:agent-development",
|
|
18925
|
+
"plugin-dev:mcp-integration",
|
|
18926
|
+
"plugin-dev:skill-development",
|
|
18927
|
+
"plugin-dev:plugin-settings",
|
|
18928
|
+
"plugin-dev:command-development",
|
|
18929
|
+
"plugin-dev:plugin-structure",
|
|
18930
|
+
"plugin-dev:hook-development"
|
|
18931
|
+
],
|
|
18932
|
+
alwaysDeny: [],
|
|
18933
|
+
rules: []
|
|
18934
|
+
}]
|
|
18935
|
+
};
|
|
18891
18936
|
var DEFAULT_CONFIG = {
|
|
18892
18937
|
defaultDecision: "ask",
|
|
18893
18938
|
askOnSubshell: true,
|
|
@@ -18895,6 +18940,7 @@ var DEFAULT_CONFIG = {
|
|
|
18895
18940
|
notifyOnDeny: true,
|
|
18896
18941
|
trustedRemotes: [],
|
|
18897
18942
|
targetPolicies: [],
|
|
18943
|
+
skillRules: DEFAULT_SKILL_RULES,
|
|
18898
18944
|
layers: [{
|
|
18899
18945
|
alwaysAllow: [
|
|
18900
18946
|
// Read-only file operations
|
|
@@ -19400,16 +19446,21 @@ var DEFAULT_CONFIG = {
|
|
|
19400
19446
|
})),
|
|
19401
19447
|
// --- Cloud CLIs ---
|
|
19402
19448
|
{ command: "gcloud", default: "ask", argPatterns: [
|
|
19403
|
-
{ match: { anyArgMatches: ["^(info|version|help|
|
|
19404
|
-
{ match: { anyArgMatches: ["^
|
|
19449
|
+
{ match: { anyArgMatches: ["^(info|version|help|topic|components|feedback|survey)$"] }, decision: "allow", description: "Info/meta commands" },
|
|
19450
|
+
{ match: { anyArgMatches: ["^config$"] }, decision: "allow", description: "Config subcommands" },
|
|
19451
|
+
{ match: { anyArgMatches: ["^(list|describe|get|get-iam-policy|browse|tail|read|show|search|lookup|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19452
|
+
{ match: { anyArgMatches: ["^(list|get|describe|show|print|read|search|lookup|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun (list-enabled, get-value, print-access-token, etc.)" },
|
|
19405
19453
|
VERSION_HELP_FLAGS
|
|
19406
19454
|
] },
|
|
19407
19455
|
{ command: "az", default: "ask", argPatterns: [
|
|
19408
|
-
{ match: { anyArgMatches: ["^(list|show|get)$"] }, decision: "allow", description: "Read-only
|
|
19456
|
+
{ match: { anyArgMatches: ["^(list|show|get|search|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19457
|
+
{ match: { anyArgMatches: ["^(list|show|get|search|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun" },
|
|
19458
|
+
{ match: { anyArgMatches: ["^(version|help|account|feedback)$"] }, decision: "allow", description: "Info/meta commands" },
|
|
19409
19459
|
VERSION_HELP_FLAGS
|
|
19410
19460
|
] },
|
|
19411
19461
|
{ command: "aws", default: "ask", argPatterns: [
|
|
19412
|
-
{ match: { anyArgMatches: ["^(describe|list|get|sts)$"] }, decision: "allow", description: "Read-only
|
|
19462
|
+
{ match: { anyArgMatches: ["^(describe|list|get|sts|help|search|lookup|check)$"] }, decision: "allow", description: "Read-only verbs" },
|
|
19463
|
+
{ match: { anyArgMatches: ["^(describe|list|get|search|lookup|check)-[a-z][a-z0-9-]*$"] }, decision: "allow", description: "Read-only verb-noun" },
|
|
19413
19464
|
VERSION_HELP_FLAGS
|
|
19414
19465
|
] },
|
|
19415
19466
|
// --- Helm ---
|
|
@@ -19517,6 +19568,14 @@ function loadConfig(cwd) {
|
|
|
19517
19568
|
...userLayer ? [userLayer] : [],
|
|
19518
19569
|
defaultLayer
|
|
19519
19570
|
];
|
|
19571
|
+
const defaultSkillLayer = config.skillRules.layers[0];
|
|
19572
|
+
const userSkillLayer = userRaw?.skills ? extractSkillLayer(userRaw.skills) : null;
|
|
19573
|
+
const workspaceSkillLayer = workspaceRaw?.skills ? extractSkillLayer(workspaceRaw.skills) : null;
|
|
19574
|
+
config.skillRules.layers = [
|
|
19575
|
+
...workspaceSkillLayer ? [workspaceSkillLayer] : [],
|
|
19576
|
+
...userSkillLayer ? [userSkillLayer] : [],
|
|
19577
|
+
defaultSkillLayer
|
|
19578
|
+
];
|
|
19520
19579
|
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19521
19580
|
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19522
19581
|
return config;
|
|
@@ -19535,19 +19594,19 @@ function tryLoadFile(filePath) {
|
|
|
19535
19594
|
}
|
|
19536
19595
|
return null;
|
|
19537
19596
|
}
|
|
19538
|
-
function
|
|
19597
|
+
function extractGenericLayer(raw, nameKey) {
|
|
19539
19598
|
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19540
19599
|
for (const rule of rules) {
|
|
19541
19600
|
if (rule && typeof rule === "object") {
|
|
19542
19601
|
if (rule.default && !isValidDecision(rule.default)) {
|
|
19543
|
-
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule
|
|
19602
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule[nameKey]}", using "ask"
|
|
19544
19603
|
`);
|
|
19545
19604
|
rule.default = "ask";
|
|
19546
19605
|
}
|
|
19547
19606
|
if (Array.isArray(rule.argPatterns)) {
|
|
19548
19607
|
for (const pattern of rule.argPatterns) {
|
|
19549
19608
|
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19550
|
-
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule
|
|
19609
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule[nameKey]}", using "ask"
|
|
19551
19610
|
`);
|
|
19552
19611
|
pattern.decision = "ask";
|
|
19553
19612
|
}
|
|
@@ -19561,6 +19620,12 @@ function extractLayer(raw) {
|
|
|
19561
19620
|
rules
|
|
19562
19621
|
};
|
|
19563
19622
|
}
|
|
19623
|
+
function extractSkillLayer(raw) {
|
|
19624
|
+
return extractGenericLayer(raw, "skill");
|
|
19625
|
+
}
|
|
19626
|
+
function extractLayer(raw) {
|
|
19627
|
+
return extractGenericLayer(raw, "command");
|
|
19628
|
+
}
|
|
19564
19629
|
function parseTrustedList(raw) {
|
|
19565
19630
|
return raw.map((entry) => {
|
|
19566
19631
|
if (typeof entry === "string") return { name: entry };
|
|
@@ -19741,6 +19806,17 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19741
19806
|
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19742
19807
|
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19743
19808
|
}
|
|
19809
|
+
if (raw.skills && typeof raw.skills === "object") {
|
|
19810
|
+
const skills = raw.skills;
|
|
19811
|
+
if (typeof skills.defaultDecision === "string") {
|
|
19812
|
+
if (isValidDecision(skills.defaultDecision)) {
|
|
19813
|
+
config.skillRules.defaultDecision = skills.defaultDecision;
|
|
19814
|
+
} else {
|
|
19815
|
+
warn(`[warden] Warning: invalid skills.defaultDecision "${skills.defaultDecision}", ignoring
|
|
19816
|
+
`);
|
|
19817
|
+
}
|
|
19818
|
+
}
|
|
19819
|
+
}
|
|
19744
19820
|
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19745
19821
|
const overrides = raw.trustedContextOverrides;
|
|
19746
19822
|
const layer = extractLayer(overrides);
|
|
@@ -20594,6 +20670,110 @@ function wardenEvalWithConfig(command, config, cwd) {
|
|
|
20594
20670
|
return evaluate(parsed, config, cwd);
|
|
20595
20671
|
}
|
|
20596
20672
|
|
|
20673
|
+
// src/skill-evaluator.ts
|
|
20674
|
+
function skillMatchesName(skillName, pattern) {
|
|
20675
|
+
return globToRegex(pattern).test(skillName);
|
|
20676
|
+
}
|
|
20677
|
+
function evaluateSkill(skillName, args2, config) {
|
|
20678
|
+
const detail = evaluateSkillDetail(skillName, args2, config);
|
|
20679
|
+
return {
|
|
20680
|
+
decision: detail.decision,
|
|
20681
|
+
reason: detail.reason,
|
|
20682
|
+
details: [detail]
|
|
20683
|
+
};
|
|
20684
|
+
}
|
|
20685
|
+
function evaluateSkillDetail(skillName, args2, config) {
|
|
20686
|
+
const { skillRules } = config;
|
|
20687
|
+
for (const layer of skillRules.layers) {
|
|
20688
|
+
if (layer.alwaysDeny.some((p) => skillMatchesName(skillName, p))) {
|
|
20689
|
+
return {
|
|
20690
|
+
command: skillName,
|
|
20691
|
+
args: args2 ? [args2] : [],
|
|
20692
|
+
decision: "deny",
|
|
20693
|
+
reason: `Skill "${skillName}" is blocked`,
|
|
20694
|
+
matchedRule: "alwaysDeny"
|
|
20695
|
+
};
|
|
20696
|
+
}
|
|
20697
|
+
if (layer.alwaysAllow.some((p) => skillMatchesName(skillName, p))) {
|
|
20698
|
+
return {
|
|
20699
|
+
command: skillName,
|
|
20700
|
+
args: args2 ? [args2] : [],
|
|
20701
|
+
decision: "allow",
|
|
20702
|
+
reason: `Skill "${skillName}" is safe`,
|
|
20703
|
+
matchedRule: "alwaysAllow"
|
|
20704
|
+
};
|
|
20705
|
+
}
|
|
20706
|
+
}
|
|
20707
|
+
const mergedRule = collectMergedSkillRule(skillName, skillRules.layers.map((l) => l.rules));
|
|
20708
|
+
if (mergedRule) {
|
|
20709
|
+
return evaluateSkillRule(skillName, args2, mergedRule);
|
|
20710
|
+
}
|
|
20711
|
+
return {
|
|
20712
|
+
command: skillName,
|
|
20713
|
+
args: args2 ? [args2] : [],
|
|
20714
|
+
decision: skillRules.defaultDecision,
|
|
20715
|
+
reason: `No rule for skill "${skillName}"`,
|
|
20716
|
+
matchedRule: "default"
|
|
20717
|
+
};
|
|
20718
|
+
}
|
|
20719
|
+
function collectMergedSkillRule(skillName, layerRules) {
|
|
20720
|
+
const matching = [];
|
|
20721
|
+
for (const rules of layerRules) {
|
|
20722
|
+
const rule = rules.find((r) => skillMatchesName(skillName, r.skill));
|
|
20723
|
+
if (rule) {
|
|
20724
|
+
matching.push(rule);
|
|
20725
|
+
if (rule.override) break;
|
|
20726
|
+
}
|
|
20727
|
+
}
|
|
20728
|
+
if (matching.length === 0) return null;
|
|
20729
|
+
if (matching.length === 1) return matching[0];
|
|
20730
|
+
const mergedPatterns = [];
|
|
20731
|
+
for (const rule of matching) {
|
|
20732
|
+
if (rule.argPatterns) {
|
|
20733
|
+
mergedPatterns.push(...rule.argPatterns);
|
|
20734
|
+
}
|
|
20735
|
+
}
|
|
20736
|
+
return {
|
|
20737
|
+
skill: matching[0].skill,
|
|
20738
|
+
default: matching[0].default,
|
|
20739
|
+
argPatterns: mergedPatterns
|
|
20740
|
+
};
|
|
20741
|
+
}
|
|
20742
|
+
function evaluateSkillRule(skillName, args2, rule) {
|
|
20743
|
+
const argsArray = args2 ? [args2] : [];
|
|
20744
|
+
const argsJoined = args2 || "";
|
|
20745
|
+
for (const pattern of rule.argPatterns || []) {
|
|
20746
|
+
const m = pattern.match;
|
|
20747
|
+
let matched = true;
|
|
20748
|
+
if (m.noArgs !== void 0) {
|
|
20749
|
+
matched = matched && m.noArgs === !args2;
|
|
20750
|
+
}
|
|
20751
|
+
if (m.argsMatch && matched) {
|
|
20752
|
+
matched = m.argsMatch.some((re) => safeRegexTest(re, argsJoined));
|
|
20753
|
+
}
|
|
20754
|
+
if (m.anyArgMatches && matched) {
|
|
20755
|
+
matched = argsArray.some((arg) => m.anyArgMatches.some((re) => safeRegexTest(re, arg)));
|
|
20756
|
+
}
|
|
20757
|
+
if (m.not) matched = !matched;
|
|
20758
|
+
if (matched) {
|
|
20759
|
+
return {
|
|
20760
|
+
command: skillName,
|
|
20761
|
+
args: argsArray,
|
|
20762
|
+
decision: pattern.decision,
|
|
20763
|
+
reason: pattern.reason || pattern.description || `Matched pattern for skill "${skillName}"`,
|
|
20764
|
+
matchedRule: `${skillName}:argPattern`
|
|
20765
|
+
};
|
|
20766
|
+
}
|
|
20767
|
+
}
|
|
20768
|
+
return {
|
|
20769
|
+
command: skillName,
|
|
20770
|
+
args: argsArray,
|
|
20771
|
+
decision: rule.default,
|
|
20772
|
+
reason: `Default for skill "${skillName}"`,
|
|
20773
|
+
matchedRule: `${skillName}:default`
|
|
20774
|
+
};
|
|
20775
|
+
}
|
|
20776
|
+
|
|
20597
20777
|
// src/suggest.ts
|
|
20598
20778
|
function generateAllowSnippet(details) {
|
|
20599
20779
|
const lines = [];
|
|
@@ -20794,20 +20974,35 @@ function deactivateYolo(sessionId) {
|
|
|
20794
20974
|
|
|
20795
20975
|
// src/index.ts
|
|
20796
20976
|
var MAX_STDIN_SIZE = 1024 * 1024;
|
|
20977
|
+
function emitDecision(decision, reason, stderrMessage) {
|
|
20978
|
+
const output = {
|
|
20979
|
+
hookSpecificOutput: {
|
|
20980
|
+
hookEventName: "PreToolUse",
|
|
20981
|
+
permissionDecision: decision,
|
|
20982
|
+
permissionDecisionReason: reason
|
|
20983
|
+
}
|
|
20984
|
+
};
|
|
20985
|
+
process.stdout.write(JSON.stringify(output));
|
|
20986
|
+
if (decision === "deny") {
|
|
20987
|
+
process.stderr.write(`${stderrMessage ?? reason}
|
|
20988
|
+
`);
|
|
20989
|
+
process.exit(2);
|
|
20990
|
+
}
|
|
20991
|
+
process.exit(0);
|
|
20992
|
+
}
|
|
20993
|
+
function handleYoloMode(sessionId, result) {
|
|
20994
|
+
const yoloState = getYoloState(sessionId);
|
|
20995
|
+
if (!yoloState) return;
|
|
20996
|
+
if (result.decision === "deny" && !yoloState.bypassDeny) return;
|
|
20997
|
+
const expiryInfo = yoloState.expiresAt ? `expires ${new Date(yoloState.expiresAt).toLocaleTimeString()}` : "full session";
|
|
20998
|
+
emitDecision("allow", `[warden] YOLO mode active (${expiryInfo})`);
|
|
20999
|
+
}
|
|
20797
21000
|
async function main() {
|
|
20798
21001
|
let raw = "";
|
|
20799
21002
|
for await (const chunk of process.stdin) {
|
|
20800
21003
|
raw += chunk;
|
|
20801
21004
|
if (raw.length > MAX_STDIN_SIZE) {
|
|
20802
|
-
|
|
20803
|
-
hookSpecificOutput: {
|
|
20804
|
-
hookEventName: "PreToolUse",
|
|
20805
|
-
permissionDecision: "ask",
|
|
20806
|
-
permissionDecisionReason: "[warden] Input exceeds size limit"
|
|
20807
|
-
}
|
|
20808
|
-
};
|
|
20809
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20810
|
-
process.exit(0);
|
|
21005
|
+
emitDecision("ask", "[warden] Input exceeds size limit");
|
|
20811
21006
|
}
|
|
20812
21007
|
}
|
|
20813
21008
|
let input;
|
|
@@ -20816,7 +21011,7 @@ async function main() {
|
|
|
20816
21011
|
} catch {
|
|
20817
21012
|
process.exit(0);
|
|
20818
21013
|
}
|
|
20819
|
-
if (input.tool_name !== "Bash") {
|
|
21014
|
+
if (input.tool_name !== "Bash" && input.tool_name !== "Skill") {
|
|
20820
21015
|
process.exit(0);
|
|
20821
21016
|
}
|
|
20822
21017
|
if (input.permission_mode === "bypassPermissions") {
|
|
@@ -20825,100 +21020,63 @@ async function main() {
|
|
|
20825
21020
|
if (process.env.WARDEN_YOLO === "true" || process.env.WARDEN_YOLO === "1") {
|
|
20826
21021
|
process.exit(0);
|
|
20827
21022
|
}
|
|
21023
|
+
if (input.tool_name === "Skill") {
|
|
21024
|
+
const skillName = input.tool_input?.skill;
|
|
21025
|
+
if (!skillName || typeof skillName !== "string") process.exit(0);
|
|
21026
|
+
const args2 = typeof input.tool_input?.args === "string" ? input.tool_input.args : void 0;
|
|
21027
|
+
const config2 = loadConfig(input.cwd);
|
|
21028
|
+
const result2 = evaluateSkill(skillName, args2, config2);
|
|
21029
|
+
handleYoloMode(input.session_id, result2);
|
|
21030
|
+
emitResult(result2, `skill:${skillName}`, config2);
|
|
21031
|
+
}
|
|
20828
21032
|
const command = input.tool_input?.command;
|
|
20829
21033
|
if (!command || typeof command !== "string") {
|
|
20830
21034
|
process.exit(0);
|
|
20831
21035
|
}
|
|
20832
21036
|
const yoloCmd = parseYoloCommand(command);
|
|
20833
21037
|
if (yoloCmd) {
|
|
20834
|
-
let
|
|
21038
|
+
let msg;
|
|
20835
21039
|
if (yoloCmd.action === "activate") {
|
|
20836
21040
|
const state = activateYolo(input.session_id, yoloCmd.durationMinutes);
|
|
20837
21041
|
const expiryInfo = state.expiresAt ? `expires at ${new Date(state.expiresAt).toLocaleTimeString()}` : "full session, no expiry";
|
|
20838
|
-
|
|
21042
|
+
msg = `[warden] YOLO mode activated (${expiryInfo}). Always-deny commands are still blocked. Use \`echo __WARDEN_YOLO_DEACTIVATE__\` to turn off.`;
|
|
20839
21043
|
} else if (yoloCmd.action === "deactivate") {
|
|
20840
21044
|
deactivateYolo(input.session_id);
|
|
20841
|
-
|
|
21045
|
+
msg = "[warden] YOLO mode deactivated. Normal rule evaluation resumed.";
|
|
20842
21046
|
} else {
|
|
20843
21047
|
const state = getYoloState(input.session_id);
|
|
20844
21048
|
if (state) {
|
|
20845
21049
|
const expiryInfo = state.expiresAt ? `expires at ${new Date(state.expiresAt).toLocaleTimeString()}` : "full session";
|
|
20846
|
-
|
|
21050
|
+
msg = `[warden] YOLO mode is active (${expiryInfo})`;
|
|
20847
21051
|
} else {
|
|
20848
|
-
|
|
21052
|
+
msg = "[warden] YOLO mode is not active";
|
|
20849
21053
|
}
|
|
20850
21054
|
}
|
|
20851
|
-
|
|
20852
|
-
hookSpecificOutput: {
|
|
20853
|
-
hookEventName: "PreToolUse",
|
|
20854
|
-
permissionDecision: "allow",
|
|
20855
|
-
permissionDecisionReason: msg2
|
|
20856
|
-
}
|
|
20857
|
-
};
|
|
20858
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20859
|
-
process.exit(0);
|
|
21055
|
+
emitDecision("allow", msg);
|
|
20860
21056
|
}
|
|
20861
21057
|
const config = loadConfig(input.cwd);
|
|
20862
21058
|
const result = wardenEvalWithConfig(command, config, input.cwd);
|
|
20863
|
-
|
|
20864
|
-
|
|
20865
|
-
|
|
20866
|
-
|
|
20867
|
-
const expiryInfo = yoloState.expiresAt ? `expires ${new Date(yoloState.expiresAt).toLocaleTimeString()}` : "full session";
|
|
20868
|
-
const output2 = {
|
|
20869
|
-
hookSpecificOutput: {
|
|
20870
|
-
hookEventName: "PreToolUse",
|
|
20871
|
-
permissionDecision: "allow",
|
|
20872
|
-
permissionDecisionReason: `[warden] YOLO mode active (${expiryInfo})`
|
|
20873
|
-
}
|
|
20874
|
-
};
|
|
20875
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20876
|
-
process.exit(0);
|
|
20877
|
-
}
|
|
20878
|
-
}
|
|
21059
|
+
handleYoloMode(input.session_id, result);
|
|
21060
|
+
emitResult(result, command, config);
|
|
21061
|
+
}
|
|
21062
|
+
function emitResult(result, label, config) {
|
|
20879
21063
|
if (result.decision === "allow") {
|
|
20880
|
-
|
|
20881
|
-
hookSpecificOutput: {
|
|
20882
|
-
hookEventName: "PreToolUse",
|
|
20883
|
-
permissionDecision: "allow",
|
|
20884
|
-
permissionDecisionReason: `[warden] ${result.reason}`
|
|
20885
|
-
}
|
|
20886
|
-
};
|
|
20887
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20888
|
-
process.exit(0);
|
|
21064
|
+
emitDecision("allow", `[warden] ${result.reason}`);
|
|
20889
21065
|
}
|
|
20890
21066
|
if (result.decision === "deny") {
|
|
20891
21067
|
if (config.notifyOnDeny) {
|
|
20892
|
-
const truncated =
|
|
21068
|
+
const truncated = label.length > 80 ? label.slice(0, 77) + "..." : label;
|
|
20893
21069
|
sendNotification("Claude Warden", `Blocked: ${truncated}`, config);
|
|
20894
21070
|
}
|
|
20895
|
-
const msg2 = formatSystemMessage("deny",
|
|
20896
|
-
|
|
20897
|
-
hookSpecificOutput: {
|
|
20898
|
-
hookEventName: "PreToolUse",
|
|
20899
|
-
permissionDecision: "deny",
|
|
20900
|
-
permissionDecisionReason: msg2
|
|
20901
|
-
}
|
|
20902
|
-
};
|
|
20903
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20904
|
-
process.stderr.write(`[warden] Blocked: ${result.reason}
|
|
20905
|
-
`);
|
|
20906
|
-
process.exit(2);
|
|
21071
|
+
const msg2 = formatSystemMessage("deny", label, result.details);
|
|
21072
|
+
emitDecision("deny", msg2, `[warden] Blocked: ${result.reason}`);
|
|
20907
21073
|
}
|
|
20908
21074
|
if (config.notifyOnAsk) {
|
|
20909
|
-
const truncated =
|
|
21075
|
+
const truncated = label.length > 80 ? label.slice(0, 77) + "..." : label;
|
|
20910
21076
|
sendNotification("Claude Warden", `Permission needed: ${truncated}`, config);
|
|
20911
21077
|
}
|
|
20912
|
-
const msg = formatSystemMessage("ask",
|
|
20913
|
-
|
|
20914
|
-
hookSpecificOutput: {
|
|
20915
|
-
hookEventName: "PreToolUse",
|
|
20916
|
-
permissionDecision: "ask",
|
|
20917
|
-
permissionDecisionReason: msg
|
|
20918
|
-
}
|
|
20919
|
-
};
|
|
20920
|
-
process.stdout.write(JSON.stringify(output));
|
|
20921
|
-
process.exit(0);
|
|
21078
|
+
const msg = formatSystemMessage("ask", label, result.details);
|
|
21079
|
+
emitDecision("ask", msg);
|
|
20922
21080
|
}
|
|
20923
21081
|
main().catch(() => process.exit(0));
|
|
20924
21082
|
/*! Bundled license information:
|
package/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Smart command safety filter — evaluates Bash commands against configurable safety rules",
|
|
2
|
+
"description": "Smart command safety filter — evaluates Bash commands and Skill invocations against configurable safety rules",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"PreToolUse": [
|
|
5
5
|
{
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
"timeout": 5
|
|
12
12
|
}
|
|
13
13
|
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"matcher": "Skill",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/dist/index.cjs",
|
|
21
|
+
"timeout": 5
|
|
22
|
+
}
|
|
23
|
+
]
|
|
14
24
|
}
|
|
15
25
|
]
|
|
16
26
|
}
|