@codyswann/lisa 4.4.1 → 4.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +12 -29
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/scripts/check-delivery-deletion-conflicts.mjs +2 -2
- package/scripts/check-derived-artifacts.mjs +24 -5
- package/scripts/check-orphan-test-processes.mjs +2 -2
- package/scripts/check-security-floors.mjs +20 -9
- package/scripts/check-template-workflow-refs.mjs +23 -8
- package/scripts/check-workflow-package-paths.mjs +12 -8
- package/scripts/generate-lisa-owned-hash-ledger.mjs +12 -4
- package/scripts/generate-upstream-evidence-manifest.mjs +4 -4
- package/scripts/verify-health-agentic-built.mjs +4 -3
- package/scripts/verify-health-consumer-built.mjs +3 -2
- package/scripts/verify-health-contract-built.mjs +3 -2
- package/scripts/verify-health-deterministic-built.mjs +5 -4
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-safety-net-rules/SKILL.md +0 -263
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-safety-net-rules/agents/openai.yaml +0 -4
- package/plugins/lisa/commands/parity-safety-net-rules.md +0 -6
- package/plugins/lisa/skills/lisa-parity-safety-net-rules/SKILL.md +0 -263
- package/plugins/lisa/skills/lisa-parity-safety-net-rules/agents/openai.yaml +0 -4
- package/plugins/lisa-agy/commands/lisa/parity-safety-net-rules.md +0 -6
- package/plugins/lisa-agy/skills/lisa-parity-safety-net-rules/SKILL.md +0 -263
- package/plugins/lisa-copilot/commands/lisa/parity-safety-net-rules.md +0 -6
- package/plugins/lisa-copilot/skills/lisa-parity-safety-net-rules/SKILL.md +0 -263
- package/plugins/lisa-cursor/commands/lisa/parity-safety-net-rules.md +0 -6
- package/plugins/lisa-cursor/skills/lisa-parity-safety-net-rules/SKILL.md +0 -263
- package/plugins/src/base/commands/parity-safety-net-rules.md +0 -6
- package/plugins/src/base/skills/lisa-parity-safety-net-rules/SKILL.md +0 -263
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: lisa-parity-safety-net-rules
|
|
3
|
-
description: "View, set, and verify the custom guard rules enforced by Lisa's safety-net PreToolUse Bash hook (parity-safety-net.sh). The consolidated cross-agent equivalent of the upstream safety-net plugin's set-custom-rules + verify-custom-rules skills — manages a project-local list of extended-regex patterns that block destructive shell commands, on Codex, agy, Copilot, Cursor, and Claude."
|
|
4
|
-
allowed-tools: ["Read", "Edit", "Write", "Bash"]
|
|
5
|
-
synced-from: safety-net@cc-marketplace@1.0.6
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Parity Safety-Net Rules
|
|
9
|
-
|
|
10
|
-
Manage the **custom guard rules** that Lisa's safety-net hook enforces on every
|
|
11
|
-
Bash command. The hook (`hooks/parity-safety-net.sh`, registered as a
|
|
12
|
-
`PreToolUse` matcher on `Bash`) ships with built-in guards against catastrophic
|
|
13
|
-
commands; this skill lets a project **view**, **set**, and **verify** *additional*
|
|
14
|
-
project-specific rules on top of those built-ins.
|
|
15
|
-
|
|
16
|
-
> **Lisa-native reimplementation.** Upstream 0.9.0 shipped two rule-management
|
|
17
|
-
> skills (`set-custom-rules` + `verify-custom-rules`), which this skill
|
|
18
|
-
> consolidates. Upstream 1.0.6 consolidated them too (into `cc-safety-net`) and
|
|
19
|
-
> moved custom rules to a JSON rulebook system driven by the
|
|
20
|
-
> `npx cc-safety-net rule` CLI. Lisa **deliberately keeps** its simpler
|
|
21
|
-
> ERE-lines-file design: the Lisa hook must run identically on Codex, agy,
|
|
22
|
-
> Copilot, Cursor, and Claude without an npx dependency, and a flat regex file
|
|
23
|
-
> is auditable in any of those runtimes. It is reimplemented from scratch
|
|
24
|
-
> against Lisa conventions — it does **not** port or invoke upstream plugin
|
|
25
|
-
> code.
|
|
26
|
-
>
|
|
27
|
-
> **Drift tracking.** Pinned to `safety-net@cc-marketplace@1.0.6`.
|
|
28
|
-
> `scripts/plugin-parity-drift.mjs` compares this pin against the upstream
|
|
29
|
-
> version in the plugin cache and flags staleness. **Do not port or copy upstream
|
|
30
|
-
> plugin code.**
|
|
31
|
-
>
|
|
32
|
-
> **2.0.3 review.** Upstream 2.0.2–2.0.3 added Kimi Code and Amp installation
|
|
33
|
-
> support and release tooling. Those changes do not alter the upstream
|
|
34
|
-
> rule-management skill or Lisa's project-local ERE rule contract, and Lisa does
|
|
35
|
-
> not currently ship hooks for those runtimes, so no behavior is absorbed here.
|
|
36
|
-
>
|
|
37
|
-
> **2.0.4 review.** Method, so the next person can re-run it rather than trust
|
|
38
|
-
> it: hash the sorted (relative path, content) manifest of every rule-bearing
|
|
39
|
-
> directory in the plugin cache under both versions, then diff the trees
|
|
40
|
-
> excluding `dist/`, `.git/`, and `node_modules/`. Result — `src/` (230 files),
|
|
41
|
-
> `skills/` (1), and `hooks/` (1) are byte-identical across 2.0.3 and 2.0.4
|
|
42
|
-
> (`src` digest `7e346e205fbd1aa7` both sides). The only non-generated
|
|
43
|
-
> differences are the `version` string in `package.json`, `kimi.plugin.json`,
|
|
44
|
-
> and `.claude-plugin/plugin.json`, plus a test-harness fix in
|
|
45
|
-
> `tests/cli/cli-statusline.test.ts` that tolerates EPIPE when an oversized
|
|
46
|
-
> payload is flushed at the bounded reader's cap. Everything else is rebuilt
|
|
47
|
-
> `dist/` output. No rule, guard family, or skill surface moved, so nothing is
|
|
48
|
-
> absorbed here — and, critically, **2.0.4 does not close the gap recorded
|
|
49
|
-
> below**, which therefore carries forward unchanged rather than being dropped
|
|
50
|
-
> at the new pin.
|
|
51
|
-
>
|
|
52
|
-
> **Known gap at the 2.0.4 pin.** Upstream 2.0.0 rebuilt its engine and added two
|
|
53
|
-
> guard families this hook does **not** mirror: `secret.*` (blocks reading or
|
|
54
|
-
> copying SSH keys, `.env` files, cloud credentials, and coding-CLI credential
|
|
55
|
-
> stores) and `rm.git-metadata` (blocks deleting the `.git` control plane). Both
|
|
56
|
-
> were measured against this hook rather than assumed — see the probe table in
|
|
57
|
-
> `parity/plugin-routing/safety-net@cc-marketplace.md`. Absorbing `secret.*` also
|
|
58
|
-
> needs a matcher change, because this hook is registered on `Bash` only while
|
|
59
|
-
> upstream also screens file tools. That work is tracked separately in
|
|
60
|
-
> `parity/FOLLOWUPS.md` §5; **do not treat this skill's built-in guard list as
|
|
61
|
-
> covering secret access.**
|
|
62
|
-
|
|
63
|
-
## How the rules work
|
|
64
|
-
|
|
65
|
-
- The hook always enforces its **built-in guards** (see below). These cannot be
|
|
66
|
-
disabled from the rules file — they are the floor.
|
|
67
|
-
- **Custom rules** live in a project-local file, one **POSIX extended regular
|
|
68
|
-
expression (ERE)** per line. Blank lines and lines beginning with `#` are
|
|
69
|
-
ignored. Matching is case-insensitive (`grep -Ei`).
|
|
70
|
-
- If *any* built-in guard or custom rule matches the proposed command, the hook
|
|
71
|
-
exits non-zero and the Bash call is **blocked**, with the reason shown to the
|
|
72
|
-
agent.
|
|
73
|
-
|
|
74
|
-
### Rules file location
|
|
75
|
-
|
|
76
|
-
Resolved in this order:
|
|
77
|
-
|
|
78
|
-
1. `$SAFETY_NET_RULES_FILE` (explicit override), else
|
|
79
|
-
2. `${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt`
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Built-in guards (always on)
|
|
86
|
-
|
|
87
|
-
1. `rm -rf` of a filesystem root, `$HOME`/`~`, or a top-level wildcard — with
|
|
88
|
-
quote-aware boundaries, so wrapper/interpreter forms like
|
|
89
|
-
`bash -c "rm -rf /"` and `python -c "… os.system('rm -rf /')"` match too.
|
|
90
|
-
Path-prefixed spellings (`/bin/rm`, `./rm`) count as `rm` for every rm
|
|
91
|
-
guard.
|
|
92
|
-
2. `rm -rf` target hardening: the cwd itself (`.`/`./`), `..`-traversal paths,
|
|
93
|
-
home-anchored `~/…` paths, absolute paths outside the project (`/tmp`,
|
|
94
|
-
`/var/tmp`, and `$TMPDIR` are allowed), `$VAR` targets other than `$TMPDIR`,
|
|
95
|
-
and **any** recursive forced delete while the working directory is `$HOME`.
|
|
96
|
-
3. Force-pushing a protected branch
|
|
97
|
-
(`main`/`master`/`production`/`prod`/`release`). `--force-with-lease` is
|
|
98
|
-
intentionally allowed, and so is force-pushing a feature branch (sanctioned
|
|
99
|
-
rebase workflow). Acceptable parity divergence: a refspec force-push
|
|
100
|
-
(`git push origin +main`) is not blocked — upstream 1.0.6 allows it too.
|
|
101
|
-
4. `git reset --hard` / `git reset --merge` while the working tree is **dirty**
|
|
102
|
-
(would discard work). Clean-tree resets are intentionally allowed.
|
|
103
|
-
5. `git rebase --abort` / `git rebase --quit` while the in-progress rebase
|
|
104
|
-
holds human-made conflict resolutions (they would be discarded). A clean or
|
|
105
|
-
untouched rebase state stays abortable — an agent-recoverable wedge; the
|
|
106
|
-
apply backend and a missing `AUTO_MERGE` ref fail closed. Deliberate
|
|
107
|
-
divergence: upstream blocks `rebase --abort` unconditionally.
|
|
108
|
-
6. `git checkout` discards: the `--` pathspec form (with or without a ref),
|
|
109
|
-
`-f`/`--force`, `--pathspec-from-file`, and bare `git checkout .`.
|
|
110
|
-
Branch switching and `-b`/`-B` creation stay allowed.
|
|
111
|
-
7. `git switch --discard-changes` / `-f`/`--force`.
|
|
112
|
-
8. `git restore` touching the worktree — only `git restore --staged <path>`
|
|
113
|
-
without `--worktree` is allowed (unstaging is safe).
|
|
114
|
-
9. `git stash drop` / `git stash clear` (push/pop/list/apply stay allowed).
|
|
115
|
-
10. `git clean` with a force flag and no dry-run — `-n`/`--dry-run` anywhere
|
|
116
|
-
makes it a safe preview.
|
|
117
|
-
11. `git branch -D` (or `-d` combined with `-f`, clustered or split);
|
|
118
|
-
safe `-d` and rename `-m` stay allowed.
|
|
119
|
-
12. `git tag -d`, `git reflog delete`, `git worktree remove --force`.
|
|
120
|
-
13. Deletion via `find … -delete`, `find … -exec rm -rf`, and `xargs … rm -rf`
|
|
121
|
-
(plain non-recursive `rm` on find/xargs output stays allowed).
|
|
122
|
-
14. Disk destroyers: `dd of=/dev/…`, `mkfs … /dev/…`, `shred`.
|
|
123
|
-
15. Destructive SQL — `DROP DATABASE/SCHEMA/TABLE`, `TRUNCATE TABLE`.
|
|
124
|
-
|
|
125
|
-
Every git guard sees through leading git **global options** (`-C <path>`,
|
|
126
|
-
`-c <k>=<v>`, `--git-dir[=…]`, `--no-pager`, …), so `git -C /path <destructive>`
|
|
127
|
-
is screened the same as `git <destructive>`.
|
|
128
|
-
|
|
129
|
-
Malformed hook input fails **closed** (exit 2 denies the command). A text-scan
|
|
130
|
-
hook cannot exempt display commands, so prose like
|
|
131
|
-
`echo "docs about rm -rf /"` can false-positive — quote-break the string or use
|
|
132
|
-
the gh-writer heredoc form (payload is stripped before the guards run). For the
|
|
133
|
-
same reason the rm guard treats substitution-wrapping as verdict-neutral (issue
|
|
134
|
-
#1982): an executable `echo "$(rm -rf /)"` is blocked, and so are inert twins the
|
|
135
|
-
shell would never expand — a single-quoted `echo '$(rm -rf /)'` or an escaped
|
|
136
|
-
`echo "\$(rm -rf /)"` — since the scan has no quote-context awareness. The same
|
|
137
|
-
quote-break or heredoc workaround applies. Spell that heredoc workaround with a
|
|
138
|
-
**quote-pair** delimiter (`gh issue create --body-file - <<'EOF'`) — only that
|
|
139
|
-
spelling reaches the payload-strip path. `<<"EOF"` and `<<\EOF` do not, and a
|
|
140
|
-
`gh … <<\EOF` writer now fails closed instead.
|
|
141
|
-
|
|
142
|
-
## What this does and does not defend against
|
|
143
|
-
|
|
144
|
-
Read this before treating the safety net as a wall.
|
|
145
|
-
|
|
146
|
-
**What it is for:** stopping *accidental* catastrophic commands — an agent (or a
|
|
147
|
-
human) that reaches for `rm -rf /`, force-pushes `main`, drops a table, or writes
|
|
148
|
-
to a raw device by mistake. Within that job the guards above are the floor and
|
|
149
|
-
they are always on.
|
|
150
|
-
|
|
151
|
-
**What it is not:** a security boundary against a deliberate adversary. Two
|
|
152
|
-
structural reasons, both accepted rather than open bugs:
|
|
153
|
-
|
|
154
|
-
- **It is a text scan, not a shell engine.** It has no quote-context awareness,
|
|
155
|
-
which produces the false positives described above and means an operation
|
|
156
|
-
spelled in a way the patterns do not anticipate is not matched.
|
|
157
|
-
- **The here-doc classifier re-implements bash's lexer in Python** to decide
|
|
158
|
-
which payload text is inert data. Every divergence from real bash is a shape it
|
|
159
|
-
mis-classifies. Eight distinct divergences were found and fixed across issues
|
|
160
|
-
#1958 and #1993; the class is open-ended, so it is now closed as an accepted
|
|
161
|
-
limitation rather than chased indefinitely.
|
|
162
|
-
|
|
163
|
-
**Why that is tolerable:** a here-doc mis-classification degrades to the content
|
|
164
|
-
guards rather than bypassing them. Only the narrow `gh issue|pr` writer form ever
|
|
165
|
-
strips payload text; every other path passes the **raw** command to the guards,
|
|
166
|
-
and since issue #1982 those guards see through substitution wrappers. So a
|
|
167
|
-
mis-read here-doc costs no guard coverage for any destructive class listed above.
|
|
168
|
-
|
|
169
|
-
**The honest bottom line:** an agent that is actively trying to run something
|
|
170
|
-
destructive has many paths that never involve a here-doc at all — the safety net
|
|
171
|
-
does not claim to close them. Do not rely on it as the only control over what an
|
|
172
|
-
agent may execute; treat it as the last line of defence against mistakes, and
|
|
173
|
-
keep the real boundary (credentials, permissions, environment isolation) outside
|
|
174
|
-
the agent.
|
|
175
|
-
|
|
176
|
-
## View the current rules
|
|
177
|
-
|
|
178
|
-
```bash
|
|
179
|
-
RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
|
|
180
|
-
if [ -f "$RULES_FILE" ]; then
|
|
181
|
-
echo "Custom safety-net rules ($RULES_FILE):"
|
|
182
|
-
grep -vE '^[[:space:]]*(#|$)' "$RULES_FILE" || echo "(no active rules)"
|
|
183
|
-
else
|
|
184
|
-
echo "No custom rules file yet ($RULES_FILE). Only built-in guards are active."
|
|
185
|
-
fi
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
`Read` the file to show comments and structure as well.
|
|
189
|
-
|
|
190
|
-
## Set (add or edit) a rule
|
|
191
|
-
|
|
192
|
-
A rule is an ERE matched against the full command string. Keep rules **specific**
|
|
193
|
-
to avoid blocking legitimate work — anchor on the dangerous verb and its target.
|
|
194
|
-
|
|
195
|
-
1. Ensure the file exists, then **append** a commented rule (use `Edit`/`Write`,
|
|
196
|
-
or append from the shell):
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
|
|
200
|
-
mkdir -p "$(dirname "$RULES_FILE")"
|
|
201
|
-
{
|
|
202
|
-
echo "# Block deleting a Kubernetes namespace"
|
|
203
|
-
echo 'kubectl[[:space:]]+delete[[:space:]]+namespace'
|
|
204
|
-
} >> "$RULES_FILE"
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
2. **Always verify** the new rule (next section) before considering it set —
|
|
208
|
-
confirm it blocks what it should and allows what it shouldn't.
|
|
209
|
-
|
|
210
|
-
Editing/removing: open the file with `Edit` and change or delete the line.
|
|
211
|
-
Removing a rule never affects the built-in guards.
|
|
212
|
-
|
|
213
|
-
## Verify the rules
|
|
214
|
-
|
|
215
|
-
Two checks — both should pass before you trust a rule.
|
|
216
|
-
|
|
217
|
-
### 1. The ERE is valid
|
|
218
|
-
|
|
219
|
-
An invalid regex would make the hook error on every command. Validate it:
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
printf '%s' "$RULE" | grep -Eq -- "$RULE" 2>/dev/null && echo "valid ERE" \
|
|
223
|
-
|| echo "INVALID ERE — fix before saving"
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### 2. The rule behaves as intended
|
|
227
|
-
|
|
228
|
-
Drive the **actual hook** with a fake `PreToolUse` payload and assert the exit
|
|
229
|
-
code (non-zero = blocked, 0 = allowed). Build the JSON with `jq` so the test
|
|
230
|
-
command line itself never contains the dangerous literal:
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
HOOK="${CLAUDE_PLUGIN_ROOT}/hooks/parity-safety-net.sh"
|
|
234
|
-
|
|
235
|
-
check() { # check <expect: block|allow> <command>
|
|
236
|
-
jq -nc --arg c "$2" '{tool_name:"Bash",tool_input:{command:$c}}' \
|
|
237
|
-
| bash "$HOOK" >/dev/null 2>&1
|
|
238
|
-
local code=$?
|
|
239
|
-
local got=allow; [ "$code" -ne 0 ] && got=block
|
|
240
|
-
printf '%-5s want=%-5s got=%-5s %s\n' \
|
|
241
|
-
"$([ "$got" = "$1" ] && echo OK || echo FAIL)" "$1" "$got" "$2"
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
# Should block (matches the new rule):
|
|
245
|
-
check block "kubectl delete namespace prod"
|
|
246
|
-
# Should allow (must not over-match):
|
|
247
|
-
check allow "kubectl get pods"
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
Report a table of cases with want/got/verdict. If any case disagrees, tighten the
|
|
251
|
-
ERE and re-verify.
|
|
252
|
-
|
|
253
|
-
## Rules
|
|
254
|
-
|
|
255
|
-
- **Built-in guards are the floor** — custom rules only *add* blocks; they cannot
|
|
256
|
-
weaken the built-ins.
|
|
257
|
-
- **Prefer specific over broad** — a rule that blocks too much trains users to
|
|
258
|
-
bypass the safety net. Anchor on verb + target.
|
|
259
|
-
- **Verify every rule against the real hook** before saving — never ship an
|
|
260
|
-
unverified or syntactically invalid ERE.
|
|
261
|
-
- **Never weaken the net to unblock yourself.** If a built-in guard fires on a
|
|
262
|
-
command that is genuinely safe, run it manually outside the agent after the
|
|
263
|
-
user confirms — do not edit the hook to remove the guard.
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "View, set, and verify the custom guard rules enforced by Lisa's safety-net PreToolUse Bash hook — the cross-agent equivalent of the upstream safety-net plugin's set/verify-custom-rules skills."
|
|
3
|
-
argument-hint: "[view | set <regex> | verify]"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Use the /lisa-parity-safety-net-rules skill to view, set, or verify the project-local custom guard rules enforced by Lisa's safety-net Bash hook (`parity-safety-net.sh`). $ARGUMENTS
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: lisa-parity-safety-net-rules
|
|
3
|
-
description: "View, set, and verify the custom guard rules enforced by Lisa's safety-net PreToolUse Bash hook (parity-safety-net.sh). The consolidated cross-agent equivalent of the upstream safety-net plugin's set-custom-rules + verify-custom-rules skills — manages a project-local list of extended-regex patterns that block destructive shell commands, on Codex, agy, Copilot, Cursor, and Claude."
|
|
4
|
-
allowed-tools: ["Read", "Edit", "Write", "Bash"]
|
|
5
|
-
synced-from: safety-net@cc-marketplace@1.0.6
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Parity Safety-Net Rules
|
|
9
|
-
|
|
10
|
-
Manage the **custom guard rules** that Lisa's safety-net hook enforces on every
|
|
11
|
-
Bash command. The hook (`hooks/parity-safety-net.sh`, registered as a
|
|
12
|
-
`PreToolUse` matcher on `Bash`) ships with built-in guards against catastrophic
|
|
13
|
-
commands; this skill lets a project **view**, **set**, and **verify** *additional*
|
|
14
|
-
project-specific rules on top of those built-ins.
|
|
15
|
-
|
|
16
|
-
> **Lisa-native reimplementation.** Upstream 0.9.0 shipped two rule-management
|
|
17
|
-
> skills (`set-custom-rules` + `verify-custom-rules`), which this skill
|
|
18
|
-
> consolidates. Upstream 1.0.6 consolidated them too (into `cc-safety-net`) and
|
|
19
|
-
> moved custom rules to a JSON rulebook system driven by the
|
|
20
|
-
> `npx cc-safety-net rule` CLI. Lisa **deliberately keeps** its simpler
|
|
21
|
-
> ERE-lines-file design: the Lisa hook must run identically on Codex, agy,
|
|
22
|
-
> Copilot, Cursor, and Claude without an npx dependency, and a flat regex file
|
|
23
|
-
> is auditable in any of those runtimes. It is reimplemented from scratch
|
|
24
|
-
> against Lisa conventions — it does **not** port or invoke upstream plugin
|
|
25
|
-
> code.
|
|
26
|
-
>
|
|
27
|
-
> **Drift tracking.** Pinned to `safety-net@cc-marketplace@1.0.6`.
|
|
28
|
-
> `scripts/plugin-parity-drift.mjs` compares this pin against the upstream
|
|
29
|
-
> version in the plugin cache and flags staleness. **Do not port or copy upstream
|
|
30
|
-
> plugin code.**
|
|
31
|
-
>
|
|
32
|
-
> **2.0.3 review.** Upstream 2.0.2–2.0.3 added Kimi Code and Amp installation
|
|
33
|
-
> support and release tooling. Those changes do not alter the upstream
|
|
34
|
-
> rule-management skill or Lisa's project-local ERE rule contract, and Lisa does
|
|
35
|
-
> not currently ship hooks for those runtimes, so no behavior is absorbed here.
|
|
36
|
-
>
|
|
37
|
-
> **2.0.4 review.** Method, so the next person can re-run it rather than trust
|
|
38
|
-
> it: hash the sorted (relative path, content) manifest of every rule-bearing
|
|
39
|
-
> directory in the plugin cache under both versions, then diff the trees
|
|
40
|
-
> excluding `dist/`, `.git/`, and `node_modules/`. Result — `src/` (230 files),
|
|
41
|
-
> `skills/` (1), and `hooks/` (1) are byte-identical across 2.0.3 and 2.0.4
|
|
42
|
-
> (`src` digest `7e346e205fbd1aa7` both sides). The only non-generated
|
|
43
|
-
> differences are the `version` string in `package.json`, `kimi.plugin.json`,
|
|
44
|
-
> and `.claude-plugin/plugin.json`, plus a test-harness fix in
|
|
45
|
-
> `tests/cli/cli-statusline.test.ts` that tolerates EPIPE when an oversized
|
|
46
|
-
> payload is flushed at the bounded reader's cap. Everything else is rebuilt
|
|
47
|
-
> `dist/` output. No rule, guard family, or skill surface moved, so nothing is
|
|
48
|
-
> absorbed here — and, critically, **2.0.4 does not close the gap recorded
|
|
49
|
-
> below**, which therefore carries forward unchanged rather than being dropped
|
|
50
|
-
> at the new pin.
|
|
51
|
-
>
|
|
52
|
-
> **Known gap at the 2.0.4 pin.** Upstream 2.0.0 rebuilt its engine and added two
|
|
53
|
-
> guard families this hook does **not** mirror: `secret.*` (blocks reading or
|
|
54
|
-
> copying SSH keys, `.env` files, cloud credentials, and coding-CLI credential
|
|
55
|
-
> stores) and `rm.git-metadata` (blocks deleting the `.git` control plane). Both
|
|
56
|
-
> were measured against this hook rather than assumed — see the probe table in
|
|
57
|
-
> `parity/plugin-routing/safety-net@cc-marketplace.md`. Absorbing `secret.*` also
|
|
58
|
-
> needs a matcher change, because this hook is registered on `Bash` only while
|
|
59
|
-
> upstream also screens file tools. That work is tracked separately in
|
|
60
|
-
> `parity/FOLLOWUPS.md` §5; **do not treat this skill's built-in guard list as
|
|
61
|
-
> covering secret access.**
|
|
62
|
-
|
|
63
|
-
## How the rules work
|
|
64
|
-
|
|
65
|
-
- The hook always enforces its **built-in guards** (see below). These cannot be
|
|
66
|
-
disabled from the rules file — they are the floor.
|
|
67
|
-
- **Custom rules** live in a project-local file, one **POSIX extended regular
|
|
68
|
-
expression (ERE)** per line. Blank lines and lines beginning with `#` are
|
|
69
|
-
ignored. Matching is case-insensitive (`grep -Ei`).
|
|
70
|
-
- If *any* built-in guard or custom rule matches the proposed command, the hook
|
|
71
|
-
exits non-zero and the Bash call is **blocked**, with the reason shown to the
|
|
72
|
-
agent.
|
|
73
|
-
|
|
74
|
-
### Rules file location
|
|
75
|
-
|
|
76
|
-
Resolved in this order:
|
|
77
|
-
|
|
78
|
-
1. `$SAFETY_NET_RULES_FILE` (explicit override), else
|
|
79
|
-
2. `${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt`
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Built-in guards (always on)
|
|
86
|
-
|
|
87
|
-
1. `rm -rf` of a filesystem root, `$HOME`/`~`, or a top-level wildcard — with
|
|
88
|
-
quote-aware boundaries, so wrapper/interpreter forms like
|
|
89
|
-
`bash -c "rm -rf /"` and `python -c "… os.system('rm -rf /')"` match too.
|
|
90
|
-
Path-prefixed spellings (`/bin/rm`, `./rm`) count as `rm` for every rm
|
|
91
|
-
guard.
|
|
92
|
-
2. `rm -rf` target hardening: the cwd itself (`.`/`./`), `..`-traversal paths,
|
|
93
|
-
home-anchored `~/…` paths, absolute paths outside the project (`/tmp`,
|
|
94
|
-
`/var/tmp`, and `$TMPDIR` are allowed), `$VAR` targets other than `$TMPDIR`,
|
|
95
|
-
and **any** recursive forced delete while the working directory is `$HOME`.
|
|
96
|
-
3. Force-pushing a protected branch
|
|
97
|
-
(`main`/`master`/`production`/`prod`/`release`). `--force-with-lease` is
|
|
98
|
-
intentionally allowed, and so is force-pushing a feature branch (sanctioned
|
|
99
|
-
rebase workflow). Acceptable parity divergence: a refspec force-push
|
|
100
|
-
(`git push origin +main`) is not blocked — upstream 1.0.6 allows it too.
|
|
101
|
-
4. `git reset --hard` / `git reset --merge` while the working tree is **dirty**
|
|
102
|
-
(would discard work). Clean-tree resets are intentionally allowed.
|
|
103
|
-
5. `git rebase --abort` / `git rebase --quit` while the in-progress rebase
|
|
104
|
-
holds human-made conflict resolutions (they would be discarded). A clean or
|
|
105
|
-
untouched rebase state stays abortable — an agent-recoverable wedge; the
|
|
106
|
-
apply backend and a missing `AUTO_MERGE` ref fail closed. Deliberate
|
|
107
|
-
divergence: upstream blocks `rebase --abort` unconditionally.
|
|
108
|
-
6. `git checkout` discards: the `--` pathspec form (with or without a ref),
|
|
109
|
-
`-f`/`--force`, `--pathspec-from-file`, and bare `git checkout .`.
|
|
110
|
-
Branch switching and `-b`/`-B` creation stay allowed.
|
|
111
|
-
7. `git switch --discard-changes` / `-f`/`--force`.
|
|
112
|
-
8. `git restore` touching the worktree — only `git restore --staged <path>`
|
|
113
|
-
without `--worktree` is allowed (unstaging is safe).
|
|
114
|
-
9. `git stash drop` / `git stash clear` (push/pop/list/apply stay allowed).
|
|
115
|
-
10. `git clean` with a force flag and no dry-run — `-n`/`--dry-run` anywhere
|
|
116
|
-
makes it a safe preview.
|
|
117
|
-
11. `git branch -D` (or `-d` combined with `-f`, clustered or split);
|
|
118
|
-
safe `-d` and rename `-m` stay allowed.
|
|
119
|
-
12. `git tag -d`, `git reflog delete`, `git worktree remove --force`.
|
|
120
|
-
13. Deletion via `find … -delete`, `find … -exec rm -rf`, and `xargs … rm -rf`
|
|
121
|
-
(plain non-recursive `rm` on find/xargs output stays allowed).
|
|
122
|
-
14. Disk destroyers: `dd of=/dev/…`, `mkfs … /dev/…`, `shred`.
|
|
123
|
-
15. Destructive SQL — `DROP DATABASE/SCHEMA/TABLE`, `TRUNCATE TABLE`.
|
|
124
|
-
|
|
125
|
-
Every git guard sees through leading git **global options** (`-C <path>`,
|
|
126
|
-
`-c <k>=<v>`, `--git-dir[=…]`, `--no-pager`, …), so `git -C /path <destructive>`
|
|
127
|
-
is screened the same as `git <destructive>`.
|
|
128
|
-
|
|
129
|
-
Malformed hook input fails **closed** (exit 2 denies the command). A text-scan
|
|
130
|
-
hook cannot exempt display commands, so prose like
|
|
131
|
-
`echo "docs about rm -rf /"` can false-positive — quote-break the string or use
|
|
132
|
-
the gh-writer heredoc form (payload is stripped before the guards run). For the
|
|
133
|
-
same reason the rm guard treats substitution-wrapping as verdict-neutral (issue
|
|
134
|
-
#1982): an executable `echo "$(rm -rf /)"` is blocked, and so are inert twins the
|
|
135
|
-
shell would never expand — a single-quoted `echo '$(rm -rf /)'` or an escaped
|
|
136
|
-
`echo "\$(rm -rf /)"` — since the scan has no quote-context awareness. The same
|
|
137
|
-
quote-break or heredoc workaround applies. Spell that heredoc workaround with a
|
|
138
|
-
**quote-pair** delimiter (`gh issue create --body-file - <<'EOF'`) — only that
|
|
139
|
-
spelling reaches the payload-strip path. `<<"EOF"` and `<<\EOF` do not, and a
|
|
140
|
-
`gh … <<\EOF` writer now fails closed instead.
|
|
141
|
-
|
|
142
|
-
## What this does and does not defend against
|
|
143
|
-
|
|
144
|
-
Read this before treating the safety net as a wall.
|
|
145
|
-
|
|
146
|
-
**What it is for:** stopping *accidental* catastrophic commands — an agent (or a
|
|
147
|
-
human) that reaches for `rm -rf /`, force-pushes `main`, drops a table, or writes
|
|
148
|
-
to a raw device by mistake. Within that job the guards above are the floor and
|
|
149
|
-
they are always on.
|
|
150
|
-
|
|
151
|
-
**What it is not:** a security boundary against a deliberate adversary. Two
|
|
152
|
-
structural reasons, both accepted rather than open bugs:
|
|
153
|
-
|
|
154
|
-
- **It is a text scan, not a shell engine.** It has no quote-context awareness,
|
|
155
|
-
which produces the false positives described above and means an operation
|
|
156
|
-
spelled in a way the patterns do not anticipate is not matched.
|
|
157
|
-
- **The here-doc classifier re-implements bash's lexer in Python** to decide
|
|
158
|
-
which payload text is inert data. Every divergence from real bash is a shape it
|
|
159
|
-
mis-classifies. Eight distinct divergences were found and fixed across issues
|
|
160
|
-
#1958 and #1993; the class is open-ended, so it is now closed as an accepted
|
|
161
|
-
limitation rather than chased indefinitely.
|
|
162
|
-
|
|
163
|
-
**Why that is tolerable:** a here-doc mis-classification degrades to the content
|
|
164
|
-
guards rather than bypassing them. Only the narrow `gh issue|pr` writer form ever
|
|
165
|
-
strips payload text; every other path passes the **raw** command to the guards,
|
|
166
|
-
and since issue #1982 those guards see through substitution wrappers. So a
|
|
167
|
-
mis-read here-doc costs no guard coverage for any destructive class listed above.
|
|
168
|
-
|
|
169
|
-
**The honest bottom line:** an agent that is actively trying to run something
|
|
170
|
-
destructive has many paths that never involve a here-doc at all — the safety net
|
|
171
|
-
does not claim to close them. Do not rely on it as the only control over what an
|
|
172
|
-
agent may execute; treat it as the last line of defence against mistakes, and
|
|
173
|
-
keep the real boundary (credentials, permissions, environment isolation) outside
|
|
174
|
-
the agent.
|
|
175
|
-
|
|
176
|
-
## View the current rules
|
|
177
|
-
|
|
178
|
-
```bash
|
|
179
|
-
RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
|
|
180
|
-
if [ -f "$RULES_FILE" ]; then
|
|
181
|
-
echo "Custom safety-net rules ($RULES_FILE):"
|
|
182
|
-
grep -vE '^[[:space:]]*(#|$)' "$RULES_FILE" || echo "(no active rules)"
|
|
183
|
-
else
|
|
184
|
-
echo "No custom rules file yet ($RULES_FILE). Only built-in guards are active."
|
|
185
|
-
fi
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
`Read` the file to show comments and structure as well.
|
|
189
|
-
|
|
190
|
-
## Set (add or edit) a rule
|
|
191
|
-
|
|
192
|
-
A rule is an ERE matched against the full command string. Keep rules **specific**
|
|
193
|
-
to avoid blocking legitimate work — anchor on the dangerous verb and its target.
|
|
194
|
-
|
|
195
|
-
1. Ensure the file exists, then **append** a commented rule (use `Edit`/`Write`,
|
|
196
|
-
or append from the shell):
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
|
|
200
|
-
mkdir -p "$(dirname "$RULES_FILE")"
|
|
201
|
-
{
|
|
202
|
-
echo "# Block deleting a Kubernetes namespace"
|
|
203
|
-
echo 'kubectl[[:space:]]+delete[[:space:]]+namespace'
|
|
204
|
-
} >> "$RULES_FILE"
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
2. **Always verify** the new rule (next section) before considering it set —
|
|
208
|
-
confirm it blocks what it should and allows what it shouldn't.
|
|
209
|
-
|
|
210
|
-
Editing/removing: open the file with `Edit` and change or delete the line.
|
|
211
|
-
Removing a rule never affects the built-in guards.
|
|
212
|
-
|
|
213
|
-
## Verify the rules
|
|
214
|
-
|
|
215
|
-
Two checks — both should pass before you trust a rule.
|
|
216
|
-
|
|
217
|
-
### 1. The ERE is valid
|
|
218
|
-
|
|
219
|
-
An invalid regex would make the hook error on every command. Validate it:
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
printf '%s' "$RULE" | grep -Eq -- "$RULE" 2>/dev/null && echo "valid ERE" \
|
|
223
|
-
|| echo "INVALID ERE — fix before saving"
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### 2. The rule behaves as intended
|
|
227
|
-
|
|
228
|
-
Drive the **actual hook** with a fake `PreToolUse` payload and assert the exit
|
|
229
|
-
code (non-zero = blocked, 0 = allowed). Build the JSON with `jq` so the test
|
|
230
|
-
command line itself never contains the dangerous literal:
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
HOOK="${CLAUDE_PLUGIN_ROOT}/hooks/parity-safety-net.sh"
|
|
234
|
-
|
|
235
|
-
check() { # check <expect: block|allow> <command>
|
|
236
|
-
jq -nc --arg c "$2" '{tool_name:"Bash",tool_input:{command:$c}}' \
|
|
237
|
-
| bash "$HOOK" >/dev/null 2>&1
|
|
238
|
-
local code=$?
|
|
239
|
-
local got=allow; [ "$code" -ne 0 ] && got=block
|
|
240
|
-
printf '%-5s want=%-5s got=%-5s %s\n' \
|
|
241
|
-
"$([ "$got" = "$1" ] && echo OK || echo FAIL)" "$1" "$got" "$2"
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
# Should block (matches the new rule):
|
|
245
|
-
check block "kubectl delete namespace prod"
|
|
246
|
-
# Should allow (must not over-match):
|
|
247
|
-
check allow "kubectl get pods"
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
Report a table of cases with want/got/verdict. If any case disagrees, tighten the
|
|
251
|
-
ERE and re-verify.
|
|
252
|
-
|
|
253
|
-
## Rules
|
|
254
|
-
|
|
255
|
-
- **Built-in guards are the floor** — custom rules only *add* blocks; they cannot
|
|
256
|
-
weaken the built-ins.
|
|
257
|
-
- **Prefer specific over broad** — a rule that blocks too much trains users to
|
|
258
|
-
bypass the safety net. Anchor on verb + target.
|
|
259
|
-
- **Verify every rule against the real hook** before saving — never ship an
|
|
260
|
-
unverified or syntactically invalid ERE.
|
|
261
|
-
- **Never weaken the net to unblock yourself.** If a built-in guard fires on a
|
|
262
|
-
command that is genuinely safe, run it manually outside the agent after the
|
|
263
|
-
user confirms — do not edit the hook to remove the guard.
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "View, set, and verify the custom guard rules enforced by Lisa's safety-net PreToolUse Bash hook — the cross-agent equivalent of the upstream safety-net plugin's set/verify-custom-rules skills."
|
|
3
|
-
argument-hint: "[view | set <regex> | verify]"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Use the /lisa-parity-safety-net-rules skill to view, set, or verify the project-local custom guard rules enforced by Lisa's safety-net Bash hook (`parity-safety-net.sh`). $ARGUMENTS
|