claude-warden 2.6.0 → 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 +74 -3
- package/dist/codex-export.cjs +74 -3
- package/dist/copilot.cjs +74 -3
- package/dist/index.cjs +229 -76
- 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
|
|
@@ -19525,6 +19571,14 @@ function loadConfig(cwd) {
|
|
|
19525
19571
|
...userLayer ? [userLayer] : [],
|
|
19526
19572
|
defaultLayer
|
|
19527
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
|
+
];
|
|
19528
19582
|
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19529
19583
|
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19530
19584
|
return config;
|
|
@@ -19543,19 +19597,19 @@ function tryLoadFile(filePath) {
|
|
|
19543
19597
|
}
|
|
19544
19598
|
return null;
|
|
19545
19599
|
}
|
|
19546
|
-
function
|
|
19600
|
+
function extractGenericLayer(raw, nameKey) {
|
|
19547
19601
|
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19548
19602
|
for (const rule of rules) {
|
|
19549
19603
|
if (rule && typeof rule === "object") {
|
|
19550
19604
|
if (rule.default && !isValidDecision(rule.default)) {
|
|
19551
|
-
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule
|
|
19605
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule[nameKey]}", using "ask"
|
|
19552
19606
|
`);
|
|
19553
19607
|
rule.default = "ask";
|
|
19554
19608
|
}
|
|
19555
19609
|
if (Array.isArray(rule.argPatterns)) {
|
|
19556
19610
|
for (const pattern of rule.argPatterns) {
|
|
19557
19611
|
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19558
|
-
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule
|
|
19612
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule[nameKey]}", using "ask"
|
|
19559
19613
|
`);
|
|
19560
19614
|
pattern.decision = "ask";
|
|
19561
19615
|
}
|
|
@@ -19569,6 +19623,12 @@ function extractLayer(raw) {
|
|
|
19569
19623
|
rules
|
|
19570
19624
|
};
|
|
19571
19625
|
}
|
|
19626
|
+
function extractSkillLayer(raw) {
|
|
19627
|
+
return extractGenericLayer(raw, "skill");
|
|
19628
|
+
}
|
|
19629
|
+
function extractLayer(raw) {
|
|
19630
|
+
return extractGenericLayer(raw, "command");
|
|
19631
|
+
}
|
|
19572
19632
|
function parseTrustedList(raw) {
|
|
19573
19633
|
return raw.map((entry) => {
|
|
19574
19634
|
if (typeof entry === "string") return { name: entry };
|
|
@@ -19749,6 +19809,17 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19749
19809
|
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19750
19810
|
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19751
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
|
+
}
|
|
19752
19823
|
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19753
19824
|
const overrides = raw.trustedContextOverrides;
|
|
19754
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
|
|
@@ -19529,6 +19575,14 @@ function loadConfig(cwd) {
|
|
|
19529
19575
|
...userLayer ? [userLayer] : [],
|
|
19530
19576
|
defaultLayer
|
|
19531
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
|
+
];
|
|
19532
19586
|
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19533
19587
|
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19534
19588
|
return config;
|
|
@@ -19547,19 +19601,19 @@ function tryLoadFile(filePath) {
|
|
|
19547
19601
|
}
|
|
19548
19602
|
return null;
|
|
19549
19603
|
}
|
|
19550
|
-
function
|
|
19604
|
+
function extractGenericLayer(raw, nameKey) {
|
|
19551
19605
|
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19552
19606
|
for (const rule of rules) {
|
|
19553
19607
|
if (rule && typeof rule === "object") {
|
|
19554
19608
|
if (rule.default && !isValidDecision(rule.default)) {
|
|
19555
|
-
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule
|
|
19609
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule[nameKey]}", using "ask"
|
|
19556
19610
|
`);
|
|
19557
19611
|
rule.default = "ask";
|
|
19558
19612
|
}
|
|
19559
19613
|
if (Array.isArray(rule.argPatterns)) {
|
|
19560
19614
|
for (const pattern of rule.argPatterns) {
|
|
19561
19615
|
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19562
|
-
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule
|
|
19616
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule[nameKey]}", using "ask"
|
|
19563
19617
|
`);
|
|
19564
19618
|
pattern.decision = "ask";
|
|
19565
19619
|
}
|
|
@@ -19573,6 +19627,12 @@ function extractLayer(raw) {
|
|
|
19573
19627
|
rules
|
|
19574
19628
|
};
|
|
19575
19629
|
}
|
|
19630
|
+
function extractSkillLayer(raw) {
|
|
19631
|
+
return extractGenericLayer(raw, "skill");
|
|
19632
|
+
}
|
|
19633
|
+
function extractLayer(raw) {
|
|
19634
|
+
return extractGenericLayer(raw, "command");
|
|
19635
|
+
}
|
|
19576
19636
|
function parseTrustedList(raw) {
|
|
19577
19637
|
return raw.map((entry) => {
|
|
19578
19638
|
if (typeof entry === "string") return { name: entry };
|
|
@@ -19753,6 +19813,17 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19753
19813
|
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19754
19814
|
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19755
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
|
+
}
|
|
19756
19827
|
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19757
19828
|
const overrides = raw.trustedContextOverrides;
|
|
19758
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
|
|
@@ -19522,6 +19568,14 @@ function loadConfig(cwd) {
|
|
|
19522
19568
|
...userLayer ? [userLayer] : [],
|
|
19523
19569
|
defaultLayer
|
|
19524
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
|
+
];
|
|
19525
19579
|
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19526
19580
|
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19527
19581
|
return config;
|
|
@@ -19540,19 +19594,19 @@ function tryLoadFile(filePath) {
|
|
|
19540
19594
|
}
|
|
19541
19595
|
return null;
|
|
19542
19596
|
}
|
|
19543
|
-
function
|
|
19597
|
+
function extractGenericLayer(raw, nameKey) {
|
|
19544
19598
|
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19545
19599
|
for (const rule of rules) {
|
|
19546
19600
|
if (rule && typeof rule === "object") {
|
|
19547
19601
|
if (rule.default && !isValidDecision(rule.default)) {
|
|
19548
|
-
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule
|
|
19602
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule[nameKey]}", using "ask"
|
|
19549
19603
|
`);
|
|
19550
19604
|
rule.default = "ask";
|
|
19551
19605
|
}
|
|
19552
19606
|
if (Array.isArray(rule.argPatterns)) {
|
|
19553
19607
|
for (const pattern of rule.argPatterns) {
|
|
19554
19608
|
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19555
|
-
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule
|
|
19609
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule[nameKey]}", using "ask"
|
|
19556
19610
|
`);
|
|
19557
19611
|
pattern.decision = "ask";
|
|
19558
19612
|
}
|
|
@@ -19566,6 +19620,12 @@ function extractLayer(raw) {
|
|
|
19566
19620
|
rules
|
|
19567
19621
|
};
|
|
19568
19622
|
}
|
|
19623
|
+
function extractSkillLayer(raw) {
|
|
19624
|
+
return extractGenericLayer(raw, "skill");
|
|
19625
|
+
}
|
|
19626
|
+
function extractLayer(raw) {
|
|
19627
|
+
return extractGenericLayer(raw, "command");
|
|
19628
|
+
}
|
|
19569
19629
|
function parseTrustedList(raw) {
|
|
19570
19630
|
return raw.map((entry) => {
|
|
19571
19631
|
if (typeof entry === "string") return { name: entry };
|
|
@@ -19746,6 +19806,17 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19746
19806
|
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19747
19807
|
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19748
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
|
+
}
|
|
19749
19820
|
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19750
19821
|
const overrides = raw.trustedContextOverrides;
|
|
19751
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
|
|
@@ -19522,6 +19568,14 @@ function loadConfig(cwd) {
|
|
|
19522
19568
|
...userLayer ? [userLayer] : [],
|
|
19523
19569
|
defaultLayer
|
|
19524
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
|
+
];
|
|
19525
19579
|
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19526
19580
|
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19527
19581
|
return config;
|
|
@@ -19540,19 +19594,19 @@ function tryLoadFile(filePath) {
|
|
|
19540
19594
|
}
|
|
19541
19595
|
return null;
|
|
19542
19596
|
}
|
|
19543
|
-
function
|
|
19597
|
+
function extractGenericLayer(raw, nameKey) {
|
|
19544
19598
|
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19545
19599
|
for (const rule of rules) {
|
|
19546
19600
|
if (rule && typeof rule === "object") {
|
|
19547
19601
|
if (rule.default && !isValidDecision(rule.default)) {
|
|
19548
|
-
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule
|
|
19602
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule[nameKey]}", using "ask"
|
|
19549
19603
|
`);
|
|
19550
19604
|
rule.default = "ask";
|
|
19551
19605
|
}
|
|
19552
19606
|
if (Array.isArray(rule.argPatterns)) {
|
|
19553
19607
|
for (const pattern of rule.argPatterns) {
|
|
19554
19608
|
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19555
|
-
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule
|
|
19609
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule[nameKey]}", using "ask"
|
|
19556
19610
|
`);
|
|
19557
19611
|
pattern.decision = "ask";
|
|
19558
19612
|
}
|
|
@@ -19566,6 +19620,12 @@ function extractLayer(raw) {
|
|
|
19566
19620
|
rules
|
|
19567
19621
|
};
|
|
19568
19622
|
}
|
|
19623
|
+
function extractSkillLayer(raw) {
|
|
19624
|
+
return extractGenericLayer(raw, "skill");
|
|
19625
|
+
}
|
|
19626
|
+
function extractLayer(raw) {
|
|
19627
|
+
return extractGenericLayer(raw, "command");
|
|
19628
|
+
}
|
|
19569
19629
|
function parseTrustedList(raw) {
|
|
19570
19630
|
return raw.map((entry) => {
|
|
19571
19631
|
if (typeof entry === "string") return { name: entry };
|
|
@@ -19746,6 +19806,17 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19746
19806
|
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19747
19807
|
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19748
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
|
+
}
|
|
19749
19820
|
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19750
19821
|
const overrides = raw.trustedContextOverrides;
|
|
19751
19822
|
const layer = extractLayer(overrides);
|
|
@@ -20599,6 +20670,110 @@ function wardenEvalWithConfig(command, config, cwd) {
|
|
|
20599
20670
|
return evaluate(parsed, config, cwd);
|
|
20600
20671
|
}
|
|
20601
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
|
+
|
|
20602
20777
|
// src/suggest.ts
|
|
20603
20778
|
function generateAllowSnippet(details) {
|
|
20604
20779
|
const lines = [];
|
|
@@ -20799,20 +20974,35 @@ function deactivateYolo(sessionId) {
|
|
|
20799
20974
|
|
|
20800
20975
|
// src/index.ts
|
|
20801
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
|
+
}
|
|
20802
21000
|
async function main() {
|
|
20803
21001
|
let raw = "";
|
|
20804
21002
|
for await (const chunk of process.stdin) {
|
|
20805
21003
|
raw += chunk;
|
|
20806
21004
|
if (raw.length > MAX_STDIN_SIZE) {
|
|
20807
|
-
|
|
20808
|
-
hookSpecificOutput: {
|
|
20809
|
-
hookEventName: "PreToolUse",
|
|
20810
|
-
permissionDecision: "ask",
|
|
20811
|
-
permissionDecisionReason: "[warden] Input exceeds size limit"
|
|
20812
|
-
}
|
|
20813
|
-
};
|
|
20814
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20815
|
-
process.exit(0);
|
|
21005
|
+
emitDecision("ask", "[warden] Input exceeds size limit");
|
|
20816
21006
|
}
|
|
20817
21007
|
}
|
|
20818
21008
|
let input;
|
|
@@ -20821,7 +21011,7 @@ async function main() {
|
|
|
20821
21011
|
} catch {
|
|
20822
21012
|
process.exit(0);
|
|
20823
21013
|
}
|
|
20824
|
-
if (input.tool_name !== "Bash") {
|
|
21014
|
+
if (input.tool_name !== "Bash" && input.tool_name !== "Skill") {
|
|
20825
21015
|
process.exit(0);
|
|
20826
21016
|
}
|
|
20827
21017
|
if (input.permission_mode === "bypassPermissions") {
|
|
@@ -20830,100 +21020,63 @@ async function main() {
|
|
|
20830
21020
|
if (process.env.WARDEN_YOLO === "true" || process.env.WARDEN_YOLO === "1") {
|
|
20831
21021
|
process.exit(0);
|
|
20832
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
|
+
}
|
|
20833
21032
|
const command = input.tool_input?.command;
|
|
20834
21033
|
if (!command || typeof command !== "string") {
|
|
20835
21034
|
process.exit(0);
|
|
20836
21035
|
}
|
|
20837
21036
|
const yoloCmd = parseYoloCommand(command);
|
|
20838
21037
|
if (yoloCmd) {
|
|
20839
|
-
let
|
|
21038
|
+
let msg;
|
|
20840
21039
|
if (yoloCmd.action === "activate") {
|
|
20841
21040
|
const state = activateYolo(input.session_id, yoloCmd.durationMinutes);
|
|
20842
21041
|
const expiryInfo = state.expiresAt ? `expires at ${new Date(state.expiresAt).toLocaleTimeString()}` : "full session, no expiry";
|
|
20843
|
-
|
|
21042
|
+
msg = `[warden] YOLO mode activated (${expiryInfo}). Always-deny commands are still blocked. Use \`echo __WARDEN_YOLO_DEACTIVATE__\` to turn off.`;
|
|
20844
21043
|
} else if (yoloCmd.action === "deactivate") {
|
|
20845
21044
|
deactivateYolo(input.session_id);
|
|
20846
|
-
|
|
21045
|
+
msg = "[warden] YOLO mode deactivated. Normal rule evaluation resumed.";
|
|
20847
21046
|
} else {
|
|
20848
21047
|
const state = getYoloState(input.session_id);
|
|
20849
21048
|
if (state) {
|
|
20850
21049
|
const expiryInfo = state.expiresAt ? `expires at ${new Date(state.expiresAt).toLocaleTimeString()}` : "full session";
|
|
20851
|
-
|
|
21050
|
+
msg = `[warden] YOLO mode is active (${expiryInfo})`;
|
|
20852
21051
|
} else {
|
|
20853
|
-
|
|
21052
|
+
msg = "[warden] YOLO mode is not active";
|
|
20854
21053
|
}
|
|
20855
21054
|
}
|
|
20856
|
-
|
|
20857
|
-
hookSpecificOutput: {
|
|
20858
|
-
hookEventName: "PreToolUse",
|
|
20859
|
-
permissionDecision: "allow",
|
|
20860
|
-
permissionDecisionReason: msg2
|
|
20861
|
-
}
|
|
20862
|
-
};
|
|
20863
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20864
|
-
process.exit(0);
|
|
21055
|
+
emitDecision("allow", msg);
|
|
20865
21056
|
}
|
|
20866
21057
|
const config = loadConfig(input.cwd);
|
|
20867
21058
|
const result = wardenEvalWithConfig(command, config, input.cwd);
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
const expiryInfo = yoloState.expiresAt ? `expires ${new Date(yoloState.expiresAt).toLocaleTimeString()}` : "full session";
|
|
20873
|
-
const output2 = {
|
|
20874
|
-
hookSpecificOutput: {
|
|
20875
|
-
hookEventName: "PreToolUse",
|
|
20876
|
-
permissionDecision: "allow",
|
|
20877
|
-
permissionDecisionReason: `[warden] YOLO mode active (${expiryInfo})`
|
|
20878
|
-
}
|
|
20879
|
-
};
|
|
20880
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20881
|
-
process.exit(0);
|
|
20882
|
-
}
|
|
20883
|
-
}
|
|
21059
|
+
handleYoloMode(input.session_id, result);
|
|
21060
|
+
emitResult(result, command, config);
|
|
21061
|
+
}
|
|
21062
|
+
function emitResult(result, label, config) {
|
|
20884
21063
|
if (result.decision === "allow") {
|
|
20885
|
-
|
|
20886
|
-
hookSpecificOutput: {
|
|
20887
|
-
hookEventName: "PreToolUse",
|
|
20888
|
-
permissionDecision: "allow",
|
|
20889
|
-
permissionDecisionReason: `[warden] ${result.reason}`
|
|
20890
|
-
}
|
|
20891
|
-
};
|
|
20892
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20893
|
-
process.exit(0);
|
|
21064
|
+
emitDecision("allow", `[warden] ${result.reason}`);
|
|
20894
21065
|
}
|
|
20895
21066
|
if (result.decision === "deny") {
|
|
20896
21067
|
if (config.notifyOnDeny) {
|
|
20897
|
-
const truncated =
|
|
21068
|
+
const truncated = label.length > 80 ? label.slice(0, 77) + "..." : label;
|
|
20898
21069
|
sendNotification("Claude Warden", `Blocked: ${truncated}`, config);
|
|
20899
21070
|
}
|
|
20900
|
-
const msg2 = formatSystemMessage("deny",
|
|
20901
|
-
|
|
20902
|
-
hookSpecificOutput: {
|
|
20903
|
-
hookEventName: "PreToolUse",
|
|
20904
|
-
permissionDecision: "deny",
|
|
20905
|
-
permissionDecisionReason: msg2
|
|
20906
|
-
}
|
|
20907
|
-
};
|
|
20908
|
-
process.stdout.write(JSON.stringify(output2));
|
|
20909
|
-
process.stderr.write(`[warden] Blocked: ${result.reason}
|
|
20910
|
-
`);
|
|
20911
|
-
process.exit(2);
|
|
21071
|
+
const msg2 = formatSystemMessage("deny", label, result.details);
|
|
21072
|
+
emitDecision("deny", msg2, `[warden] Blocked: ${result.reason}`);
|
|
20912
21073
|
}
|
|
20913
21074
|
if (config.notifyOnAsk) {
|
|
20914
|
-
const truncated =
|
|
21075
|
+
const truncated = label.length > 80 ? label.slice(0, 77) + "..." : label;
|
|
20915
21076
|
sendNotification("Claude Warden", `Permission needed: ${truncated}`, config);
|
|
20916
21077
|
}
|
|
20917
|
-
const msg = formatSystemMessage("ask",
|
|
20918
|
-
|
|
20919
|
-
hookSpecificOutput: {
|
|
20920
|
-
hookEventName: "PreToolUse",
|
|
20921
|
-
permissionDecision: "ask",
|
|
20922
|
-
permissionDecisionReason: msg
|
|
20923
|
-
}
|
|
20924
|
-
};
|
|
20925
|
-
process.stdout.write(JSON.stringify(output));
|
|
20926
|
-
process.exit(0);
|
|
21078
|
+
const msg = formatSystemMessage("ask", label, result.details);
|
|
21079
|
+
emitDecision("ask", msg);
|
|
20927
21080
|
}
|
|
20928
21081
|
main().catch(() => process.exit(0));
|
|
20929
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
|
}
|