plugins/lisa/skills/lisa-parity-safety-net-rules/SKILL.md
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.
npx skillsauth add codyswanngt/lisa lisa-parity-safety-net-rulesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Manage the custom guard rules that Lisa's safety-net hook enforces on every
Bash command. The hook (hooks/parity-safety-net.sh, registered as a
PreToolUse matcher on Bash) ships with built-in guards against catastrophic
commands; this skill lets a project view, set, and verify additional
project-specific rules on top of those built-ins.
Lisa-native reimplementation. Upstream 0.9.0 shipped two rule-management skills (
set-custom-rules+verify-custom-rules), which this skill consolidates. Upstream 1.0.6 consolidated them too (intocc-safety-net) and moved custom rules to a JSON rulebook system driven by thenpx cc-safety-net ruleCLI. Lisa deliberately keeps its simpler ERE-lines-file design: the Lisa hook must run identically on Codex, agy, Copilot, Cursor, and Claude without an npx dependency, and a flat regex file is auditable in any of those runtimes. It is reimplemented from scratch against Lisa conventions — it does not port or invoke upstream plugin code.Drift tracking. Pinned to
safety-net@[email protected].scripts/plugin-parity-drift.mjscompares this pin against the upstream version in the plugin cache and flags staleness. Do not port or copy upstream plugin code.
# are
ignored. Matching is case-insensitive (grep -Ei).Resolved in this order:
$SAFETY_NET_RULES_FILE (explicit override), else${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txtRULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
rm -rf of a filesystem root, $HOME/~, or a top-level wildcard — with
quote-aware boundaries, so wrapper/interpreter forms like
bash -c "rm -rf /" and python -c "… os.system('rm -rf /')" match too.
Path-prefixed spellings (/bin/rm, ./rm) count as rm for every rm
guard.rm -rf target hardening: the cwd itself (././), ..-traversal paths,
home-anchored ~/… paths, absolute paths outside the project (/tmp,
/var/tmp, and $TMPDIR are allowed), $VAR targets other than $TMPDIR,
and any recursive forced delete while the working directory is $HOME.main/master/production/prod/release). --force-with-lease is
intentionally allowed, and so is force-pushing a feature branch (sanctioned
rebase workflow). Acceptable parity divergence: a refspec force-push
(git push origin +main) is not blocked — upstream 1.0.6 allows it too.git reset --hard / git reset --merge while the working tree is dirty
(would discard work). Clean-tree resets are intentionally allowed.git rebase --abort / git rebase --quit while the in-progress rebase
holds human-made conflict resolutions (they would be discarded). A clean or
untouched rebase state stays abortable — an agent-recoverable wedge; the
apply backend and a missing AUTO_MERGE ref fail closed. Deliberate
divergence: upstream blocks rebase --abort unconditionally.git checkout discards: the -- pathspec form (with or without a ref),
-f/--force, --pathspec-from-file, and bare git checkout ..
Branch switching and -b/-B creation stay allowed.git switch --discard-changes / -f/--force.git restore touching the worktree — only git restore --staged <path>
without --worktree is allowed (unstaging is safe).git stash drop / git stash clear (push/pop/list/apply stay allowed).git clean with a force flag and no dry-run — -n/--dry-run anywhere
makes it a safe preview.git branch -D (or -d combined with -f, clustered or split);
safe -d and rename -m stay allowed.git tag -d, git reflog delete, git worktree remove --force.find … -delete, find … -exec rm -rf, and xargs … rm -rf
(plain non-recursive rm on find/xargs output stays allowed).dd of=/dev/…, mkfs … /dev/…, shred.DROP DATABASE/SCHEMA/TABLE, TRUNCATE TABLE.Every git guard sees through leading git global options (-C <path>,
-c <k>=<v>, --git-dir[=…], --no-pager, …), so git -C /path <destructive>
is screened the same as git <destructive>.
Malformed hook input fails closed (exit 2 denies the command). A text-scan
hook cannot exempt display commands, so prose like
echo "docs about rm -rf /" can false-positive — quote-break the string or use
the gh-writer heredoc form (payload is stripped before the guards run). For the
same reason the rm guard treats substitution-wrapping as verdict-neutral (issue
#1982): an executable echo "$(rm -rf /)" is blocked, and so are inert twins the
shell would never expand — a single-quoted echo '$(rm -rf /)' or an escaped
echo "\$(rm -rf /)" — since the scan has no quote-context awareness. The same
quote-break or heredoc workaround applies. Spell that heredoc workaround with a
quote-pair delimiter (gh issue create --body-file - <<'EOF') — only that
spelling reaches the payload-strip path. <<"EOF" and <<\EOF do not, and a
gh … <<\EOF writer now fails closed instead.
Read this before treating the safety net as a wall.
What it is for: stopping accidental catastrophic commands — an agent (or a
human) that reaches for rm -rf /, force-pushes main, drops a table, or writes
to a raw device by mistake. Within that job the guards above are the floor and
they are always on.
What it is not: a security boundary against a deliberate adversary. Two structural reasons, both accepted rather than open bugs:
Why that is tolerable: a here-doc mis-classification degrades to the content
guards rather than bypassing them. Only the narrow gh issue|pr writer form ever
strips payload text; every other path passes the raw command to the guards,
and since issue #1982 those guards see through substitution wrappers. So a
mis-read here-doc costs no guard coverage for any destructive class listed above.
The honest bottom line: an agent that is actively trying to run something destructive has many paths that never involve a here-doc at all — the safety net does not claim to close them. Do not rely on it as the only control over what an agent may execute; treat it as the last line of defence against mistakes, and keep the real boundary (credentials, permissions, environment isolation) outside the agent.
RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
if [ -f "$RULES_FILE" ]; then
echo "Custom safety-net rules ($RULES_FILE):"
grep -vE '^[[:space:]]*(#|$)' "$RULES_FILE" || echo "(no active rules)"
else
echo "No custom rules file yet ($RULES_FILE). Only built-in guards are active."
fi
Read the file to show comments and structure as well.
A rule is an ERE matched against the full command string. Keep rules specific to avoid blocking legitimate work — anchor on the dangerous verb and its target.
Ensure the file exists, then append a commented rule (use Edit/Write,
or append from the shell):
RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
mkdir -p "$(dirname "$RULES_FILE")"
{
echo "# Block deleting a Kubernetes namespace"
echo 'kubectl[[:space:]]+delete[[:space:]]+namespace'
} >> "$RULES_FILE"
Always verify the new rule (next section) before considering it set — confirm it blocks what it should and allows what it shouldn't.
Editing/removing: open the file with Edit and change or delete the line.
Removing a rule never affects the built-in guards.
Two checks — both should pass before you trust a rule.
An invalid regex would make the hook error on every command. Validate it:
printf '%s' "$RULE" | grep -Eq -- "$RULE" 2>/dev/null && echo "valid ERE" \
|| echo "INVALID ERE — fix before saving"
Drive the actual hook with a fake PreToolUse payload and assert the exit
code (non-zero = blocked, 0 = allowed). Build the JSON with jq so the test
command line itself never contains the dangerous literal:
HOOK="${CLAUDE_PLUGIN_ROOT}/hooks/parity-safety-net.sh"
check() { # check <expect: block|allow> <command>
jq -nc --arg c "$2" '{tool_name:"Bash",tool_input:{command:$c}}' \
| bash "$HOOK" >/dev/null 2>&1
local code=$?
local got=allow; [ "$code" -ne 0 ] && got=block
printf '%-5s want=%-5s got=%-5s %s\n' \
"$([ "$got" = "$1" ] && echo OK || echo FAIL)" "$1" "$got" "$2"
}
# Should block (matches the new rule):
check block "kubectl delete namespace prod"
# Should allow (must not over-match):
check allow "kubectl get pods"
Report a table of cases with want/got/verdict. If any case disagrees, tighten the ERE and re-verify.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.