claude-warden 1.0.2 → 1.1.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/README.md +32 -19
- package/config/warden.default.yaml +13 -15
- package/dist/index.cjs +442 -331
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,8 @@ This AST-based approach enables:
|
|
|
19
19
|
- **Recursive evaluation of remote commands**: `ssh devserver 'cat /etc/hosts'` → Warden extracts the remote command, parses it through the same pipeline, and allows it. `ssh devserver 'sudo rm -rf /'` → denied. Same for `docker exec`, `kubectl exec`, and `sprite exec`.
|
|
20
20
|
- **Shell wrapper unwrapping**: `sh -c "npm run build && npm test"` → the inner command is extracted and recursively parsed/evaluated, not treated as an opaque string.
|
|
21
21
|
- **Env prefix handling**: `NODE_ENV=production npm run build` → correctly evaluates `npm run build`, ignoring the env prefix.
|
|
22
|
-
- **
|
|
22
|
+
- **Recursive subshell evaluation**: Commands with `$()` or backticks are extracted, parsed, and recursively evaluated through the same pipeline. `echo $(cat file.txt)` → both `echo` and `cat` are evaluated individually. Only unparseable constructs (heredocs, complex shell syntax) fall back to prompting when `askOnSubshell` is enabled.
|
|
23
|
+
- **Feedback on blocked commands**: When a command is blocked or flagged, Warden provides a system message explaining why and a YAML snippet showing how to allow it in your config.
|
|
23
24
|
|
|
24
25
|
The result: **100+ common dev commands auto-approved**, dangerous commands auto-denied, everything else configurable — with zero changes to how you use Claude Code.
|
|
25
26
|
|
|
@@ -70,6 +71,21 @@ Warden works out of the box with sensible defaults. To customize, create a confi
|
|
|
70
71
|
|
|
71
72
|
Copy [config/warden.default.yaml](config/warden.default.yaml) as a starting point.
|
|
72
73
|
|
|
74
|
+
### Config priority (scoped layers)
|
|
75
|
+
|
|
76
|
+
Config is evaluated in layers with **project > user > default** priority:
|
|
77
|
+
|
|
78
|
+
1. **Project-level** (`.claude/warden.yaml`) — highest priority
|
|
79
|
+
2. **User-level** (`~/.claude/warden.yaml`)
|
|
80
|
+
3. **Built-in defaults**
|
|
81
|
+
|
|
82
|
+
Within each layer, `alwaysDeny` is checked before `alwaysAllow`. The first layer with a matching entry wins. For command-specific rules, the first layer that defines a rule for a given command wins.
|
|
83
|
+
|
|
84
|
+
This means:
|
|
85
|
+
- A project `alwaysDeny` for `curl` overrides a user `alwaysAllow` for `curl`
|
|
86
|
+
- A user `alwaysAllow` for `sudo` overrides the default `alwaysDeny` for `sudo`
|
|
87
|
+
- A project rule for `npm` overrides the default rule for `npm`
|
|
88
|
+
|
|
73
89
|
### Config options
|
|
74
90
|
|
|
75
91
|
```yaml
|
|
@@ -79,18 +95,13 @@ defaultDecision: ask
|
|
|
79
95
|
# Trigger "ask" for commands with $() or backticks
|
|
80
96
|
askOnSubshell: true
|
|
81
97
|
|
|
82
|
-
# Add commands to always allow/deny
|
|
98
|
+
# Add commands to always allow/deny (scoped to this config level)
|
|
83
99
|
alwaysAllow:
|
|
84
100
|
- terraform
|
|
85
101
|
- flyctl
|
|
86
102
|
alwaysDeny:
|
|
87
103
|
- nc
|
|
88
104
|
|
|
89
|
-
# Block patterns (regex against full command string)
|
|
90
|
-
globalDeny:
|
|
91
|
-
- pattern: 'curl.*evil\.com'
|
|
92
|
-
reason: 'Blocked domain'
|
|
93
|
-
|
|
94
105
|
# Trusted remote targets (auto-allow connection, evaluate remote commands)
|
|
95
106
|
trustedSSHHosts:
|
|
96
107
|
- devserver
|
|
@@ -103,7 +114,7 @@ trustedKubectlContexts:
|
|
|
103
114
|
trustedSprites:
|
|
104
115
|
- my-sprite
|
|
105
116
|
|
|
106
|
-
# Per-command rules (override built-in defaults)
|
|
117
|
+
# Per-command rules (override built-in defaults for this scope)
|
|
107
118
|
rules:
|
|
108
119
|
- command: npx
|
|
109
120
|
default: allow
|
|
@@ -116,9 +127,14 @@ rules:
|
|
|
116
127
|
description: Read-only docker commands
|
|
117
128
|
```
|
|
118
129
|
|
|
119
|
-
|
|
130
|
+
## Feedback and `/warden-allow`
|
|
131
|
+
|
|
132
|
+
When Warden blocks or flags a command, it includes a system message explaining:
|
|
120
133
|
|
|
121
|
-
|
|
134
|
+
1. **Why** the command was blocked/flagged (per-command reasons)
|
|
135
|
+
2. **How to allow it** — a ready-to-use YAML snippet for your config
|
|
136
|
+
|
|
137
|
+
Use the `/warden-allow` slash command to apply the suggested config change. It will ask which scope (project or user) to use.
|
|
122
138
|
|
|
123
139
|
## Built-in defaults
|
|
124
140
|
|
|
@@ -128,14 +144,11 @@ File readers (`cat`, `head`, `tail`, `less`), search tools (`grep`, `rg`, `find`
|
|
|
128
144
|
### Always denied
|
|
129
145
|
`sudo`, `su`, `mkfs`, `fdisk`, `dd`, `shutdown`, `reboot`, `iptables`, `crontab`, `systemctl`, `launchctl`
|
|
130
146
|
|
|
131
|
-
### Global deny patterns
|
|
132
|
-
- `rm -rf` (recursive force delete)
|
|
133
|
-
- Direct writes to block devices
|
|
134
|
-
- `chmod -R 777`
|
|
135
|
-
- Fork bombs
|
|
136
|
-
|
|
137
147
|
### Conditional rules
|
|
138
|
-
Commands like `node`, `npx`, `docker`, `ssh`, `git push --force`, `rm` have argument-aware rules. For example
|
|
148
|
+
Commands like `node`, `npx`, `docker`, `ssh`, `git push --force`, `rm`, `chmod` have argument-aware rules. For example:
|
|
149
|
+
- `git` is allowed but `git push --force` triggers a prompt
|
|
150
|
+
- `rm temp.txt` is allowed but `rm -rf /` is denied
|
|
151
|
+
- `chmod 644 file` prompts but `chmod -R 777 /var` is denied
|
|
139
152
|
|
|
140
153
|
### Trusted remote targets
|
|
141
154
|
Configure trusted hosts/containers/contexts to auto-allow connections and recursively evaluate remote commands:
|
|
@@ -151,9 +164,9 @@ All support glob patterns: `*`, `?`, `[...]`, `[!...]`, `{a,b,c}`
|
|
|
151
164
|
1. Claude Code calls the `PreToolUse` hook before every Bash command
|
|
152
165
|
2. Warden parses the command into an AST via [bash-parser](https://github.com/vorpaljs/bash-parser), walking the tree to extract individual commands from pipes, chains, logical expressions, and subshells
|
|
153
166
|
3. Shell wrappers (`sh -c`, `bash -c`) and remote commands (`ssh`, `docker exec`, `kubectl exec`, `sprite exec`) are recursively parsed and evaluated
|
|
154
|
-
4. Each command is evaluated through the rule hierarchy:
|
|
167
|
+
4. Each command is evaluated through the rule hierarchy: alwaysDeny → alwaysAllow → trusted remote targets → command-specific rules with argument matching → default decision (checked per layer in priority order)
|
|
155
168
|
5. Results are combined: any deny → deny whole pipeline, any ask → ask, all allow → allow
|
|
156
|
-
6. Returns the decision via stdout JSON (allow/ask) or exit code 2 (deny)
|
|
169
|
+
6. Returns the decision via stdout JSON (allow/ask) or exit code 2 (deny), with a system message explaining the reasoning for deny/ask decisions
|
|
157
170
|
|
|
158
171
|
## Development
|
|
159
172
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# Claude Warden - Default Configuration Reference
|
|
2
2
|
#
|
|
3
|
-
# Copy to ~/.claude/warden.yaml or .claude/warden.yaml (project-level) to customize.
|
|
4
|
-
#
|
|
3
|
+
# Copy to ~/.claude/warden.yaml (user-level) or .claude/warden.yaml (project-level) to customize.
|
|
4
|
+
#
|
|
5
|
+
# Config priority: project > user > built-in defaults.
|
|
6
|
+
# Within each scope, alwaysDeny is checked before alwaysAllow, and the first
|
|
7
|
+
# scope with a matching rule wins (e.g. a project rule overrides a user rule
|
|
8
|
+
# for the same command).
|
|
5
9
|
|
|
6
10
|
# Default decision for commands not covered by any rule: allow | deny | ask
|
|
7
11
|
defaultDecision: ask
|
|
@@ -9,22 +13,17 @@ defaultDecision: ask
|
|
|
9
13
|
# If true, commands containing $() or backticks trigger "ask"
|
|
10
14
|
askOnSubshell: true
|
|
11
15
|
|
|
12
|
-
# Additional commands to always allow (
|
|
16
|
+
# Additional commands to always allow (checked after alwaysDeny within this scope)
|
|
13
17
|
# alwaysAllow:
|
|
14
18
|
# - terraform
|
|
15
19
|
# - flyctl
|
|
16
20
|
# - my-safe-tool
|
|
17
21
|
|
|
18
|
-
# Additional commands to always deny (
|
|
22
|
+
# Additional commands to always deny (checked first within this scope)
|
|
19
23
|
# alwaysDeny:
|
|
20
24
|
# - nc
|
|
21
25
|
# - ncat
|
|
22
26
|
|
|
23
|
-
# Additional global deny patterns (regex against full command string)
|
|
24
|
-
# globalDeny:
|
|
25
|
-
# - pattern: 'curl.*evil\\.com'
|
|
26
|
-
# reason: 'Blocked domain'
|
|
27
|
-
|
|
28
27
|
# Trusted SSH hosts — ssh/scp/rsync to these hosts are auto-allowed.
|
|
29
28
|
# Remote commands on trusted hosts are recursively evaluated through warden rules.
|
|
30
29
|
# Supports glob patterns (* wildcards).
|
|
@@ -52,16 +51,15 @@ askOnSubshell: true
|
|
|
52
51
|
# - my-sprite
|
|
53
52
|
# - dev-*
|
|
54
53
|
|
|
55
|
-
# Command-specific rules (override built-in rules by command name)
|
|
54
|
+
# Command-specific rules (override built-in rules by command name).
|
|
55
|
+
# The first scope (project > user > default) with a rule for a given command wins.
|
|
56
56
|
# rules:
|
|
57
57
|
# - command: npx
|
|
58
58
|
# default: allow # Trust all npx in this project
|
|
59
59
|
# - command: docker
|
|
60
|
-
# default:
|
|
61
|
-
# - command: ssh
|
|
62
|
-
# default: allow
|
|
60
|
+
# default: ask
|
|
63
61
|
# argPatterns:
|
|
64
62
|
# - match:
|
|
65
|
-
# anyArgMatches: ['^
|
|
63
|
+
# anyArgMatches: ['^(ps|images|logs)$']
|
|
66
64
|
# decision: allow
|
|
67
|
-
# description:
|
|
65
|
+
# description: Read-only docker commands
|
package/dist/index.cjs
CHANGED
|
@@ -18335,16 +18335,13 @@ function evaluate(parsed, config) {
|
|
|
18335
18335
|
}
|
|
18336
18336
|
function evaluateCommand(cmd, config) {
|
|
18337
18337
|
const { command, args: args2 } = cmd;
|
|
18338
|
-
for (const
|
|
18339
|
-
if (
|
|
18340
|
-
return { command, args: args2, decision: "deny", reason:
|
|
18338
|
+
for (const layer of config.layers) {
|
|
18339
|
+
if (layer.alwaysDeny.includes(command)) {
|
|
18340
|
+
return { command, args: args2, decision: "deny", reason: `"${command}" is blocked`, matchedRule: "alwaysDeny" };
|
|
18341
|
+
}
|
|
18342
|
+
if (layer.alwaysAllow.includes(command)) {
|
|
18343
|
+
return { command, args: args2, decision: "allow", reason: `"${command}" is safe`, matchedRule: "alwaysAllow" };
|
|
18341
18344
|
}
|
|
18342
|
-
}
|
|
18343
|
-
if (config.alwaysDeny?.includes(command)) {
|
|
18344
|
-
return { command, args: args2, decision: "deny", reason: `"${command}" is blocked`, matchedRule: "alwaysDeny" };
|
|
18345
|
-
}
|
|
18346
|
-
if (config.alwaysAllow?.includes(command)) {
|
|
18347
|
-
return { command, args: args2, decision: "allow", reason: `"${command}" is safe`, matchedRule: "alwaysAllow" };
|
|
18348
18345
|
}
|
|
18349
18346
|
if ((command === "ssh" || command === "scp" || command === "rsync") && config.trustedSSHHosts?.length) {
|
|
18350
18347
|
const sshResult = evaluateSSHCommand(cmd, config);
|
|
@@ -18362,9 +18359,11 @@ function evaluateCommand(cmd, config) {
|
|
|
18362
18359
|
const spriteResult = evaluateSpriteExec(cmd, config);
|
|
18363
18360
|
if (spriteResult) return spriteResult;
|
|
18364
18361
|
}
|
|
18365
|
-
const
|
|
18366
|
-
|
|
18367
|
-
|
|
18362
|
+
for (const layer of config.layers) {
|
|
18363
|
+
const rule = layer.rules.find((r) => r.command === command);
|
|
18364
|
+
if (rule) {
|
|
18365
|
+
return evaluateRule(cmd, rule);
|
|
18366
|
+
}
|
|
18368
18367
|
}
|
|
18369
18368
|
return { command, args: args2, decision: config.defaultDecision, reason: `No rule for "${command}"`, matchedRule: "default" };
|
|
18370
18369
|
}
|
|
@@ -18438,7 +18437,6 @@ function globToRegex(pattern) {
|
|
|
18438
18437
|
} else if (ch === "?") {
|
|
18439
18438
|
regex += ".";
|
|
18440
18439
|
} else if (ch === "[") {
|
|
18441
|
-
const start = i;
|
|
18442
18440
|
i++;
|
|
18443
18441
|
if (i < pattern.length && pattern[i] === "!") {
|
|
18444
18442
|
regex += "[^";
|
|
@@ -18793,288 +18791,327 @@ var DEFAULT_CONFIG = {
|
|
|
18793
18791
|
trustedDockerContainers: [],
|
|
18794
18792
|
trustedKubectlContexts: [],
|
|
18795
18793
|
trustedSprites: [],
|
|
18796
|
-
|
|
18797
|
-
|
|
18798
|
-
|
|
18799
|
-
|
|
18800
|
-
|
|
18801
|
-
|
|
18802
|
-
|
|
18803
|
-
|
|
18804
|
-
|
|
18805
|
-
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
18812
|
-
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
18822
|
-
|
|
18823
|
-
|
|
18824
|
-
|
|
18825
|
-
|
|
18826
|
-
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
|
|
18831
|
-
|
|
18832
|
-
|
|
18833
|
-
|
|
18834
|
-
|
|
18835
|
-
|
|
18836
|
-
|
|
18837
|
-
|
|
18838
|
-
|
|
18839
|
-
|
|
18840
|
-
|
|
18841
|
-
|
|
18842
|
-
|
|
18843
|
-
|
|
18844
|
-
|
|
18845
|
-
|
|
18846
|
-
|
|
18847
|
-
|
|
18848
|
-
|
|
18849
|
-
|
|
18850
|
-
|
|
18851
|
-
|
|
18852
|
-
|
|
18853
|
-
|
|
18854
|
-
|
|
18855
|
-
|
|
18856
|
-
|
|
18857
|
-
|
|
18858
|
-
|
|
18859
|
-
|
|
18860
|
-
|
|
18861
|
-
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
|
|
18867
|
-
|
|
18868
|
-
|
|
18869
|
-
|
|
18870
|
-
|
|
18871
|
-
|
|
18872
|
-
|
|
18873
|
-
|
|
18874
|
-
|
|
18875
|
-
|
|
18876
|
-
|
|
18877
|
-
|
|
18878
|
-
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
18883
|
-
|
|
18884
|
-
|
|
18885
|
-
|
|
18886
|
-
|
|
18887
|
-
|
|
18888
|
-
|
|
18889
|
-
|
|
18890
|
-
|
|
18891
|
-
|
|
18892
|
-
|
|
18893
|
-
|
|
18894
|
-
|
|
18895
|
-
|
|
18896
|
-
|
|
18897
|
-
|
|
18898
|
-
|
|
18899
|
-
|
|
18900
|
-
|
|
18901
|
-
|
|
18902
|
-
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
18914
|
-
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
|
|
18921
|
-
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
18925
|
-
|
|
18926
|
-
|
|
18927
|
-
|
|
18928
|
-
|
|
18929
|
-
|
|
18930
|
-
|
|
18931
|
-
|
|
18932
|
-
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
|
|
18936
|
-
|
|
18937
|
-
|
|
18938
|
-
|
|
18939
|
-
|
|
18940
|
-
|
|
18941
|
-
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
|
|
18955
|
-
|
|
18956
|
-
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
18962
|
-
|
|
18963
|
-
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
18978
|
-
|
|
18979
|
-
|
|
18980
|
-
|
|
18981
|
-
|
|
18982
|
-
|
|
18983
|
-
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
|
|
18987
|
-
|
|
18988
|
-
|
|
18989
|
-
|
|
18990
|
-
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
|
|
18995
|
-
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
|
|
19001
|
-
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
|
|
19009
|
-
|
|
19010
|
-
|
|
19011
|
-
|
|
19012
|
-
|
|
19013
|
-
|
|
19014
|
-
default: "allow",
|
|
19015
|
-
|
|
19016
|
-
|
|
19017
|
-
|
|
19018
|
-
|
|
19019
|
-
|
|
19020
|
-
|
|
19021
|
-
|
|
19022
|
-
|
|
19023
|
-
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19027
|
-
|
|
19028
|
-
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
|
|
19053
|
-
|
|
19054
|
-
|
|
19055
|
-
|
|
19056
|
-
|
|
19057
|
-
|
|
19058
|
-
|
|
19059
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19067
|
-
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
|
|
18794
|
+
layers: [{
|
|
18795
|
+
alwaysAllow: [
|
|
18796
|
+
// Read-only file operations
|
|
18797
|
+
"cat",
|
|
18798
|
+
"head",
|
|
18799
|
+
"tail",
|
|
18800
|
+
"less",
|
|
18801
|
+
"more",
|
|
18802
|
+
"wc",
|
|
18803
|
+
"sort",
|
|
18804
|
+
"uniq",
|
|
18805
|
+
"tee",
|
|
18806
|
+
"diff",
|
|
18807
|
+
"comm",
|
|
18808
|
+
"cut",
|
|
18809
|
+
"paste",
|
|
18810
|
+
"tr",
|
|
18811
|
+
"fold",
|
|
18812
|
+
"expand",
|
|
18813
|
+
"unexpand",
|
|
18814
|
+
"column",
|
|
18815
|
+
"rev",
|
|
18816
|
+
"tac",
|
|
18817
|
+
"nl",
|
|
18818
|
+
"od",
|
|
18819
|
+
"xxd",
|
|
18820
|
+
"file",
|
|
18821
|
+
"stat",
|
|
18822
|
+
// Search/find
|
|
18823
|
+
"grep",
|
|
18824
|
+
"egrep",
|
|
18825
|
+
"fgrep",
|
|
18826
|
+
"rg",
|
|
18827
|
+
"ag",
|
|
18828
|
+
"ack",
|
|
18829
|
+
"find",
|
|
18830
|
+
"fd",
|
|
18831
|
+
"fzf",
|
|
18832
|
+
"locate",
|
|
18833
|
+
"which",
|
|
18834
|
+
"whereis",
|
|
18835
|
+
"type",
|
|
18836
|
+
"command",
|
|
18837
|
+
// Directory listing
|
|
18838
|
+
"ls",
|
|
18839
|
+
"dir",
|
|
18840
|
+
"tree",
|
|
18841
|
+
"exa",
|
|
18842
|
+
"eza",
|
|
18843
|
+
"lsd",
|
|
18844
|
+
// Path/string utilities
|
|
18845
|
+
"basename",
|
|
18846
|
+
"dirname",
|
|
18847
|
+
"realpath",
|
|
18848
|
+
"readlink",
|
|
18849
|
+
"echo",
|
|
18850
|
+
"printf",
|
|
18851
|
+
"true",
|
|
18852
|
+
"false",
|
|
18853
|
+
"test",
|
|
18854
|
+
"[",
|
|
18855
|
+
// Date/time
|
|
18856
|
+
"date",
|
|
18857
|
+
"cal",
|
|
18858
|
+
// Environment info
|
|
18859
|
+
"env",
|
|
18860
|
+
"printenv",
|
|
18861
|
+
"uname",
|
|
18862
|
+
"hostname",
|
|
18863
|
+
"whoami",
|
|
18864
|
+
"id",
|
|
18865
|
+
"pwd",
|
|
18866
|
+
// Process viewing (read-only)
|
|
18867
|
+
"ps",
|
|
18868
|
+
"top",
|
|
18869
|
+
"htop",
|
|
18870
|
+
"uptime",
|
|
18871
|
+
"free",
|
|
18872
|
+
"df",
|
|
18873
|
+
"du",
|
|
18874
|
+
"lsof",
|
|
18875
|
+
// Text processing
|
|
18876
|
+
"sed",
|
|
18877
|
+
"awk",
|
|
18878
|
+
"jq",
|
|
18879
|
+
"yq",
|
|
18880
|
+
"xargs",
|
|
18881
|
+
"seq",
|
|
18882
|
+
// Pagers and formatters
|
|
18883
|
+
"bat",
|
|
18884
|
+
"pygmentize",
|
|
18885
|
+
"highlight",
|
|
18886
|
+
// Version managers (read-only)
|
|
18887
|
+
"nvm",
|
|
18888
|
+
"fnm",
|
|
18889
|
+
"rbenv",
|
|
18890
|
+
"pyenv",
|
|
18891
|
+
// Misc safe
|
|
18892
|
+
"cd",
|
|
18893
|
+
"pushd",
|
|
18894
|
+
"popd",
|
|
18895
|
+
"dirs",
|
|
18896
|
+
"hash",
|
|
18897
|
+
"alias",
|
|
18898
|
+
"sleep",
|
|
18899
|
+
"wait",
|
|
18900
|
+
"time",
|
|
18901
|
+
"md5",
|
|
18902
|
+
"md5sum",
|
|
18903
|
+
"sha256sum",
|
|
18904
|
+
"shasum",
|
|
18905
|
+
"cksum",
|
|
18906
|
+
"base64",
|
|
18907
|
+
"openssl"
|
|
18908
|
+
],
|
|
18909
|
+
alwaysDeny: [
|
|
18910
|
+
"sudo",
|
|
18911
|
+
"su",
|
|
18912
|
+
"doas",
|
|
18913
|
+
"mkfs",
|
|
18914
|
+
"fdisk",
|
|
18915
|
+
"dd",
|
|
18916
|
+
"shutdown",
|
|
18917
|
+
"reboot",
|
|
18918
|
+
"halt",
|
|
18919
|
+
"poweroff",
|
|
18920
|
+
"iptables",
|
|
18921
|
+
"ip6tables",
|
|
18922
|
+
"nft",
|
|
18923
|
+
"useradd",
|
|
18924
|
+
"userdel",
|
|
18925
|
+
"usermod",
|
|
18926
|
+
"groupadd",
|
|
18927
|
+
"groupdel",
|
|
18928
|
+
"crontab",
|
|
18929
|
+
"systemctl",
|
|
18930
|
+
"service",
|
|
18931
|
+
"launchctl"
|
|
18932
|
+
],
|
|
18933
|
+
rules: [
|
|
18934
|
+
// --- Node.js ecosystem ---
|
|
18935
|
+
{
|
|
18936
|
+
command: "node",
|
|
18937
|
+
default: "ask",
|
|
18938
|
+
argPatterns: [
|
|
18939
|
+
{ match: { anyArgMatches: ["^-e$", "^--eval", "^-p$", "^--print"] }, decision: "ask", reason: "Evaluating inline code" },
|
|
18940
|
+
{ match: { anyArgMatches: ["^--(version|help)$", "^-[vh]$"] }, decision: "allow", description: "Version/help flags" },
|
|
18941
|
+
{ match: { noArgs: true }, decision: "ask", reason: "Interactive REPL" }
|
|
18942
|
+
]
|
|
18943
|
+
},
|
|
18944
|
+
{
|
|
18945
|
+
command: "npx",
|
|
18946
|
+
default: "ask",
|
|
18947
|
+
argPatterns: [
|
|
18948
|
+
{
|
|
18949
|
+
match: { anyArgMatches: ["^(jest|vitest|tsx|ts-node|tsc|eslint|prettier|mkdirp|concurrently|turbo|next|nuxt|vite|astro|playwright|cypress|mocha|nyc|c8|nodemon|ts-jest|tsup|esbuild|rollup|webpack|prisma|drizzle-kit|typeorm|knex|sequelize-cli|tailwindcss|postcss|autoprefixer|lint-staged|husky|changeset|semantic-release|lerna|nx|create-react-app|create-next-app|create-vite|degit|storybook|wrangler|netlify|vercel)$"] },
|
|
18950
|
+
decision: "allow",
|
|
18951
|
+
description: "Well-known dev tools"
|
|
18952
|
+
},
|
|
18953
|
+
{ match: { anyArgMatches: ["^--(version|help)$", "^-[vh]$"] }, decision: "allow", description: "Version/help flags" }
|
|
18954
|
+
]
|
|
18955
|
+
},
|
|
18956
|
+
{
|
|
18957
|
+
command: "bunx",
|
|
18958
|
+
default: "ask",
|
|
18959
|
+
argPatterns: [
|
|
18960
|
+
{
|
|
18961
|
+
match: { anyArgMatches: ["^(jest|vitest|tsx|ts-node|tsc|eslint|prettier|mkdirp|concurrently|turbo|next|nuxt|vite|astro|playwright|cypress|mocha|nyc|c8|nodemon|ts-jest|tsup|esbuild|rollup|webpack|prisma|drizzle-kit|typeorm|knex|sequelize-cli|tailwindcss|postcss|autoprefixer|lint-staged|husky|changeset|semantic-release|lerna|nx|create-react-app|create-next-app|create-vite|degit|storybook|wrangler|netlify|vercel)$"] },
|
|
18962
|
+
decision: "allow",
|
|
18963
|
+
description: "Well-known dev tools"
|
|
18964
|
+
},
|
|
18965
|
+
{ match: { anyArgMatches: ["^--(version|help)$", "^-[vh]$"] }, decision: "allow", description: "Version/help flags" }
|
|
18966
|
+
]
|
|
18967
|
+
},
|
|
18968
|
+
{
|
|
18969
|
+
command: "npm",
|
|
18970
|
+
default: "allow",
|
|
18971
|
+
argPatterns: [
|
|
18972
|
+
{ match: { anyArgMatches: ["^(publish|unpublish|deprecate|owner|access|token|adduser|login)$"] }, decision: "ask", reason: "Registry modification" }
|
|
18973
|
+
]
|
|
18974
|
+
},
|
|
18975
|
+
{
|
|
18976
|
+
command: "pnpm",
|
|
18977
|
+
default: "allow",
|
|
18978
|
+
argPatterns: [
|
|
18979
|
+
{ match: { anyArgMatches: ["^(publish|unpublish|deprecate|owner|access|token|adduser|login)$"] }, decision: "ask", reason: "Registry modification" }
|
|
18980
|
+
]
|
|
18981
|
+
},
|
|
18982
|
+
{
|
|
18983
|
+
command: "yarn",
|
|
18984
|
+
default: "allow",
|
|
18985
|
+
argPatterns: [
|
|
18986
|
+
{ match: { anyArgMatches: ["^(publish|unpublish|owner|access|token|login|logout)$"] }, decision: "ask", reason: "Registry modification" }
|
|
18987
|
+
]
|
|
18988
|
+
},
|
|
18989
|
+
{
|
|
18990
|
+
command: "bun",
|
|
18991
|
+
default: "ask",
|
|
18992
|
+
argPatterns: [
|
|
18993
|
+
{ match: { anyArgMatches: ["^(install|add|remove|run|test|build|init|create|pm|x|upgrade|link|unlink)$"] }, decision: "allow", description: "Standard bun commands" },
|
|
18994
|
+
{ match: { anyArgMatches: ["^--(version|help)$"] }, decision: "allow" }
|
|
18995
|
+
]
|
|
18996
|
+
},
|
|
18997
|
+
// --- Python ---
|
|
18998
|
+
{
|
|
18999
|
+
command: "python",
|
|
19000
|
+
default: "ask",
|
|
19001
|
+
argPatterns: [
|
|
19002
|
+
{ match: { anyArgMatches: ["^--(version|help)$", "^-V$"] }, decision: "allow" }
|
|
19003
|
+
]
|
|
19004
|
+
},
|
|
19005
|
+
{
|
|
19006
|
+
command: "python3",
|
|
19007
|
+
default: "ask",
|
|
19008
|
+
argPatterns: [
|
|
19009
|
+
{ match: { anyArgMatches: ["^--(version|help)$", "^-V$"] }, decision: "allow" }
|
|
19010
|
+
]
|
|
19011
|
+
},
|
|
19012
|
+
{ command: "pip", default: "allow" },
|
|
19013
|
+
{ command: "pip3", default: "allow" },
|
|
19014
|
+
{
|
|
19015
|
+
command: "uv",
|
|
19016
|
+
default: "allow",
|
|
19017
|
+
argPatterns: [
|
|
19018
|
+
{ match: { anyArgMatches: ["^publish$"] }, decision: "ask", reason: "Publishing to PyPI" }
|
|
19019
|
+
]
|
|
19020
|
+
},
|
|
19021
|
+
{ command: "pipx", default: "ask" },
|
|
19022
|
+
// --- Git ---
|
|
19023
|
+
{
|
|
19024
|
+
command: "git",
|
|
19025
|
+
default: "allow",
|
|
19026
|
+
argPatterns: [
|
|
19027
|
+
{ match: { argsMatch: ["push\\s+--force", "push\\s+-f\\b"] }, decision: "ask", reason: "Force push can overwrite remote history" },
|
|
19028
|
+
{ match: { argsMatch: ["reset\\s+--hard"] }, decision: "ask", reason: "Hard reset discards changes" },
|
|
19029
|
+
{ match: { anyArgMatches: ["^clean$"] }, decision: "ask", reason: "git clean removes untracked files" }
|
|
19030
|
+
]
|
|
19031
|
+
},
|
|
19032
|
+
{
|
|
19033
|
+
command: "gh",
|
|
19034
|
+
default: "allow",
|
|
19035
|
+
argPatterns: [
|
|
19036
|
+
{ match: { argsMatch: ["repo\\s+delete", "repo\\s+archive"] }, decision: "ask", reason: "Destructive repo operation" }
|
|
19037
|
+
]
|
|
19038
|
+
},
|
|
19039
|
+
// --- Build tools ---
|
|
19040
|
+
{ command: "make", default: "allow" },
|
|
19041
|
+
{ command: "cmake", default: "allow" },
|
|
19042
|
+
{
|
|
19043
|
+
command: "cargo",
|
|
19044
|
+
default: "allow",
|
|
19045
|
+
argPatterns: [
|
|
19046
|
+
{ match: { anyArgMatches: ["^(publish|login|logout|owner|yank)$"] }, decision: "ask", reason: "Registry modification" }
|
|
19047
|
+
]
|
|
19048
|
+
},
|
|
19049
|
+
{
|
|
19050
|
+
command: "go",
|
|
19051
|
+
default: "allow",
|
|
19052
|
+
argPatterns: [
|
|
19053
|
+
{ match: { anyArgMatches: ["^generate$"] }, decision: "ask", reason: "go generate runs arbitrary commands" }
|
|
19054
|
+
]
|
|
19055
|
+
},
|
|
19056
|
+
{ command: "rustup", default: "allow" },
|
|
19057
|
+
{ command: "tsc", default: "allow" },
|
|
19058
|
+
{ command: "turbo", default: "allow" },
|
|
19059
|
+
{ command: "nx", default: "allow" },
|
|
19060
|
+
{ command: "lerna", default: "allow" },
|
|
19061
|
+
// --- Docker ---
|
|
19062
|
+
{
|
|
19063
|
+
command: "docker",
|
|
19064
|
+
default: "ask",
|
|
19065
|
+
argPatterns: [
|
|
19066
|
+
{ match: { anyArgMatches: ["^(ps|images|logs|inspect|stats|top|version|info)$"] }, decision: "allow", description: "Read-only docker commands" },
|
|
19067
|
+
{ match: { anyArgMatches: ["^(build|run|compose|exec|pull|stop|start|restart|create)$"] }, decision: "ask", reason: "Docker state-changing operation" },
|
|
19068
|
+
{ match: { anyArgMatches: ["^(system\\s+prune|container\\s+prune|image\\s+prune)$"] }, decision: "ask", reason: "Docker prune operations" }
|
|
19069
|
+
]
|
|
19070
|
+
},
|
|
19071
|
+
{ command: "docker-compose", default: "ask" },
|
|
19072
|
+
{ command: "kubectl", default: "ask" },
|
|
19073
|
+
// --- File operations ---
|
|
19074
|
+
{
|
|
19075
|
+
command: "rm",
|
|
19076
|
+
default: "ask",
|
|
19077
|
+
argPatterns: [
|
|
19078
|
+
{ match: { argsMatch: ["-[^\\s]*r[^\\s]*f|-[^\\s]*f[^\\s]*r"] }, decision: "deny", reason: "Recursive force delete (rm -rf)" },
|
|
19079
|
+
{ match: { argsMatch: ["-[^\\s]*r"] }, decision: "ask", reason: "Recursive delete" },
|
|
19080
|
+
{ match: { argCount: { max: 3 }, not: false }, decision: "allow", description: "Deleting a small number of non-recursive files" }
|
|
19081
|
+
]
|
|
19082
|
+
},
|
|
19083
|
+
{ command: "mkdir", default: "allow" },
|
|
19084
|
+
{ command: "touch", default: "allow" },
|
|
19085
|
+
{ command: "cp", default: "allow" },
|
|
19086
|
+
{ command: "mv", default: "allow" },
|
|
19087
|
+
{ command: "ln", default: "allow" },
|
|
19088
|
+
{
|
|
19089
|
+
command: "chmod",
|
|
19090
|
+
default: "ask",
|
|
19091
|
+
argPatterns: [
|
|
19092
|
+
{ match: { argsMatch: ["-R\\s+777"] }, decision: "deny", reason: "Recursively setting world-writable permissions" }
|
|
19093
|
+
]
|
|
19094
|
+
},
|
|
19095
|
+
{ command: "chown", default: "ask" },
|
|
19096
|
+
// --- Network ---
|
|
19097
|
+
{ command: "curl", default: "allow" },
|
|
19098
|
+
{ command: "wget", default: "allow" },
|
|
19099
|
+
{ command: "ssh", default: "ask" },
|
|
19100
|
+
{ command: "scp", default: "ask" },
|
|
19101
|
+
{ command: "rsync", default: "ask" },
|
|
19102
|
+
// --- Package managers ---
|
|
19103
|
+
{ command: "brew", default: "allow" },
|
|
19104
|
+
{ command: "apt", default: "ask" },
|
|
19105
|
+
{ command: "apt-get", default: "ask" },
|
|
19106
|
+
{ command: "yum", default: "ask" },
|
|
19107
|
+
{ command: "dnf", default: "ask" },
|
|
19108
|
+
{ command: "pacman", default: "ask" },
|
|
19109
|
+
// --- Terraform / IaC ---
|
|
19110
|
+
{ command: "terraform", default: "ask", argPatterns: [
|
|
19111
|
+
{ match: { anyArgMatches: ["^(plan|validate|fmt|show|state|output|providers|version|graph|console)$"] }, decision: "allow", description: "Read-only terraform commands" }
|
|
19112
|
+
] }
|
|
19113
|
+
]
|
|
19114
|
+
}]
|
|
19078
19115
|
};
|
|
19079
19116
|
|
|
19080
19117
|
// src/rules.ts
|
|
@@ -19088,67 +19125,135 @@ var PROJECT_CONFIG_NAMES = [
|
|
|
19088
19125
|
];
|
|
19089
19126
|
function loadConfig(cwd) {
|
|
19090
19127
|
const config = structuredClone(DEFAULT_CONFIG);
|
|
19128
|
+
const defaultLayer = config.layers[0];
|
|
19129
|
+
let userLayer = null;
|
|
19130
|
+
let userRaw = null;
|
|
19091
19131
|
for (const configPath of USER_CONFIG_PATHS) {
|
|
19092
|
-
|
|
19132
|
+
const result = tryLoadFile(configPath);
|
|
19133
|
+
if (result) {
|
|
19134
|
+
userLayer = extractLayer(result);
|
|
19135
|
+
userRaw = result;
|
|
19136
|
+
break;
|
|
19137
|
+
}
|
|
19093
19138
|
}
|
|
19139
|
+
let workspaceLayer = null;
|
|
19140
|
+
let workspaceRaw = null;
|
|
19094
19141
|
if (cwd) {
|
|
19095
19142
|
for (const name of PROJECT_CONFIG_NAMES) {
|
|
19096
|
-
|
|
19143
|
+
const result = tryLoadFile((0, import_path2.join)(cwd, name));
|
|
19144
|
+
if (result) {
|
|
19145
|
+
workspaceLayer = extractLayer(result);
|
|
19146
|
+
workspaceRaw = result;
|
|
19147
|
+
break;
|
|
19148
|
+
}
|
|
19097
19149
|
}
|
|
19098
19150
|
}
|
|
19151
|
+
config.layers = [
|
|
19152
|
+
...workspaceLayer ? [workspaceLayer] : [],
|
|
19153
|
+
...userLayer ? [userLayer] : [],
|
|
19154
|
+
defaultLayer
|
|
19155
|
+
];
|
|
19156
|
+
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19157
|
+
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19099
19158
|
return config;
|
|
19100
19159
|
}
|
|
19101
|
-
function
|
|
19102
|
-
if (!(0, import_fs.existsSync)(filePath)) return
|
|
19160
|
+
function tryLoadFile(filePath) {
|
|
19161
|
+
if (!(0, import_fs.existsSync)(filePath)) return null;
|
|
19103
19162
|
try {
|
|
19104
19163
|
const raw = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
19105
19164
|
const parsed = filePath.endsWith(".yaml") || filePath.endsWith(".yml") ? (0, import_yaml.parse)(raw) : JSON.parse(raw);
|
|
19106
19165
|
if (parsed && typeof parsed === "object") {
|
|
19107
|
-
|
|
19108
|
-
return true;
|
|
19166
|
+
return parsed;
|
|
19109
19167
|
}
|
|
19110
19168
|
} catch {
|
|
19111
19169
|
}
|
|
19112
|
-
return
|
|
19170
|
+
return null;
|
|
19113
19171
|
}
|
|
19114
|
-
function
|
|
19115
|
-
|
|
19116
|
-
|
|
19172
|
+
function extractLayer(raw) {
|
|
19173
|
+
return {
|
|
19174
|
+
alwaysAllow: Array.isArray(raw.alwaysAllow) ? raw.alwaysAllow : [],
|
|
19175
|
+
alwaysDeny: Array.isArray(raw.alwaysDeny) ? raw.alwaysDeny : [],
|
|
19176
|
+
rules: Array.isArray(raw.rules) ? raw.rules : []
|
|
19177
|
+
};
|
|
19178
|
+
}
|
|
19179
|
+
function mergeNonLayerFields(config, raw) {
|
|
19180
|
+
if (Array.isArray(raw.trustedSSHHosts)) {
|
|
19181
|
+
config.trustedSSHHosts = [...config.trustedSSHHosts || [], ...raw.trustedSSHHosts];
|
|
19117
19182
|
}
|
|
19118
|
-
if (
|
|
19119
|
-
|
|
19183
|
+
if (Array.isArray(raw.trustedDockerContainers)) {
|
|
19184
|
+
config.trustedDockerContainers = [...config.trustedDockerContainers || [], ...raw.trustedDockerContainers];
|
|
19120
19185
|
}
|
|
19121
|
-
if (
|
|
19122
|
-
|
|
19186
|
+
if (Array.isArray(raw.trustedKubectlContexts)) {
|
|
19187
|
+
config.trustedKubectlContexts = [...config.trustedKubectlContexts || [], ...raw.trustedKubectlContexts];
|
|
19123
19188
|
}
|
|
19124
|
-
if (
|
|
19125
|
-
|
|
19189
|
+
if (Array.isArray(raw.trustedSprites)) {
|
|
19190
|
+
config.trustedSprites = [...config.trustedSprites || [], ...raw.trustedSprites];
|
|
19126
19191
|
}
|
|
19127
|
-
if (
|
|
19128
|
-
|
|
19192
|
+
if (typeof raw.defaultDecision === "string") {
|
|
19193
|
+
config.defaultDecision = raw.defaultDecision;
|
|
19129
19194
|
}
|
|
19130
|
-
if (
|
|
19131
|
-
|
|
19195
|
+
if (typeof raw.askOnSubshell === "boolean") {
|
|
19196
|
+
config.askOnSubshell = raw.askOnSubshell;
|
|
19132
19197
|
}
|
|
19133
|
-
|
|
19134
|
-
|
|
19198
|
+
}
|
|
19199
|
+
|
|
19200
|
+
// src/suggest.ts
|
|
19201
|
+
function generateAllowSnippet(details) {
|
|
19202
|
+
const lines = [];
|
|
19203
|
+
const alwaysAllowCmds = [];
|
|
19204
|
+
const ruleCmds = [];
|
|
19205
|
+
for (const d of details) {
|
|
19206
|
+
if (d.decision === "allow") continue;
|
|
19207
|
+
if (d.matchedRule === "alwaysDeny" || d.matchedRule === "default") {
|
|
19208
|
+
if (!alwaysAllowCmds.includes(d.command)) {
|
|
19209
|
+
alwaysAllowCmds.push(d.command);
|
|
19210
|
+
}
|
|
19211
|
+
} else if (d.matchedRule?.endsWith(":default") || d.matchedRule?.endsWith(":argPattern")) {
|
|
19212
|
+
if (!ruleCmds.includes(d.command)) {
|
|
19213
|
+
ruleCmds.push(d.command);
|
|
19214
|
+
}
|
|
19215
|
+
}
|
|
19135
19216
|
}
|
|
19136
|
-
if (
|
|
19137
|
-
|
|
19217
|
+
if (alwaysAllowCmds.length > 0) {
|
|
19218
|
+
lines.push("alwaysAllow:");
|
|
19219
|
+
for (const cmd of alwaysAllowCmds) {
|
|
19220
|
+
lines.push(` - "${cmd}"`);
|
|
19221
|
+
}
|
|
19138
19222
|
}
|
|
19139
|
-
if (
|
|
19140
|
-
|
|
19223
|
+
if (ruleCmds.length > 0) {
|
|
19224
|
+
lines.push("rules:");
|
|
19225
|
+
for (const cmd of ruleCmds) {
|
|
19226
|
+
lines.push(` - command: "${cmd}"`);
|
|
19227
|
+
lines.push(" default: allow");
|
|
19228
|
+
}
|
|
19141
19229
|
}
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19148
|
-
|
|
19149
|
-
|
|
19230
|
+
return lines.join("\n");
|
|
19231
|
+
}
|
|
19232
|
+
function formatSystemMessage(decision, rawCommand, details) {
|
|
19233
|
+
const header = decision === "deny" ? "[warden] Command blocked" : "[warden] Command flagged for review";
|
|
19234
|
+
const lines = [header, ""];
|
|
19235
|
+
const relevant = details.filter((d) => d.decision !== "allow");
|
|
19236
|
+
if (relevant.length > 0) {
|
|
19237
|
+
for (const d of relevant) {
|
|
19238
|
+
lines.push(`- \`${d.command}\`: ${d.reason}`);
|
|
19150
19239
|
}
|
|
19240
|
+
lines.push("");
|
|
19241
|
+
}
|
|
19242
|
+
const snippet = generateAllowSnippet(details);
|
|
19243
|
+
if (snippet) {
|
|
19244
|
+
lines.push("To allow this in the future, add to your warden config:");
|
|
19245
|
+
lines.push("");
|
|
19246
|
+
lines.push("```yaml");
|
|
19247
|
+
lines.push(snippet);
|
|
19248
|
+
lines.push("```");
|
|
19249
|
+
lines.push("");
|
|
19250
|
+
lines.push("Config locations:");
|
|
19251
|
+
lines.push("- User-level (all projects): `~/.claude/warden.yaml`");
|
|
19252
|
+
lines.push("- Project-level (this project): `.claude/warden.yaml`");
|
|
19253
|
+
lines.push("");
|
|
19254
|
+
lines.push("Project config takes priority over user config.");
|
|
19151
19255
|
}
|
|
19256
|
+
return lines.join("\n");
|
|
19152
19257
|
}
|
|
19153
19258
|
|
|
19154
19259
|
// src/index.ts
|
|
@@ -19174,21 +19279,27 @@ async function main() {
|
|
|
19174
19279
|
const parsed = parseCommand(command);
|
|
19175
19280
|
const result = evaluate(parsed, config);
|
|
19176
19281
|
if (result.decision === "allow") {
|
|
19177
|
-
const
|
|
19282
|
+
const output2 = {
|
|
19178
19283
|
hookSpecificOutput: {
|
|
19179
19284
|
hookEventName: "PreToolUse",
|
|
19180
19285
|
permissionDecision: "allow",
|
|
19181
19286
|
permissionDecisionReason: `[warden] ${result.reason}`
|
|
19182
19287
|
}
|
|
19183
19288
|
};
|
|
19184
|
-
process.stdout.write(JSON.stringify(
|
|
19289
|
+
process.stdout.write(JSON.stringify(output2));
|
|
19185
19290
|
process.exit(0);
|
|
19186
19291
|
}
|
|
19187
19292
|
if (result.decision === "deny") {
|
|
19293
|
+
const msg2 = formatSystemMessage("deny", command, result.details);
|
|
19294
|
+
const output2 = { systemMessage: msg2 };
|
|
19295
|
+
process.stdout.write(JSON.stringify(output2));
|
|
19188
19296
|
process.stderr.write(`[warden] Blocked: ${result.reason}
|
|
19189
19297
|
`);
|
|
19190
19298
|
process.exit(2);
|
|
19191
19299
|
}
|
|
19300
|
+
const msg = formatSystemMessage("ask", command, result.details);
|
|
19301
|
+
const output = { systemMessage: msg };
|
|
19302
|
+
process.stdout.write(JSON.stringify(output));
|
|
19192
19303
|
process.exit(0);
|
|
19193
19304
|
}
|
|
19194
19305
|
main().catch(() => process.exit(0));
|