verify-project-tighten-only
Skill1.9k starsChanged 12 days agoMITView on GitHub
Use in the cc-safety-net repo before finishing any change to how a project policy merges with the user policy or how a merged policy turns into protections: src/core/policy/merge.ts, safety-level.ts, env.ts (levels, capabilities, sessionSafetyLevel), effective-rules.ts, the merge call and normalizePolicyConfig in store.ts, resolveSecretDisabledRules, rule metadata flags (catastrophic, activationCapability, secret defaultOff), or a new field in GuiPolicy / ProjectPolicyProjection. Runs an exhaustive check that CC_SAFETY_NET_PROJECT_TIGHTEN_ONLY really stops a repo''s .safety-net policy from weakening the user''s protection, and explains how to read and update it.
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 37
Every combination in each group below is tried.
- Distinctive: line 69
If the check passes after your change, still read the "Update it" list: a pass says nothing about a field the check does not look at.
- Very specific: line 75
New secret rule category with its own default: Add a representative id to SECRET_RULE_CLASSES.
- Very specific: line 80
After changing the check, break merge.ts on a scratch copy and confirm that the check catches it, then restore the file and confirm git …
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Verify: a project policy cannot weaken the user policy under tighten-only
The promise
Property. With CC_SAFETY_NET_PROJECT_TIGHTEN_ONLY=1, every protection the user policy gives on its own is still given after mergeProjectPolicy merges in the project policy.
Harm if broken. A user opts into tighten-only so that a cloned repo cannot relax their guard. If the merge misses one weakening, a repo's project policy file silently turns off a destructive-command rule, a capability such as fail_closed, or secret protection for a path, and the agent runs what the user meant to block.
Run it
bun test tests/core/policy/merge-tighten-only.test.tsIt takes about 2 seconds and needs no tool beyond Bun. It also runs in bun run check (the pre-push hook and CI), so a break fails the push even if nobody runs it by hand.
What it checks
The check calls the real code, not a copy: mergeProjectPolicy(user, project, true, sessionSafetyLevel(user.safety.level, env)) exactly as loadPolicyConfig in src/core/policy/store.ts calls it. It then turns both the user policy alone and the merged policy into a set of protections with the same functions the hook uses: getCCSafetyNetEnvModes (capabilities, worktree mode), resolveEffectiveDestructiveCommandRules (which destructive rules are on), and resolveSecretDisabledRules. It fails if any protection of the user policy is missing from the merged one, and prints at most three counterexamples.
A protection is one of these strings:
| Protection | Present when |
fail_closed, paranoid_rm, paranoid_interpreters | that capability is enabled |
no worktree relaxations | worktree mode is off |
rule <id> | that destructive rule is enabled (catastrophic rules always are) |
recursive delete checked in <path> | destructive protection is on and <path> is not in allow_paths |
secret deny <path> | secret protection is on and <path> is in deny_paths |
secret rule <id> in <path> | secret protection is on, the rule is not disabled, and <path> is not in allow_paths |
Every combination in each group below is tried; nothing is sampled:
| Test | Varied together | Cases |
| safety level and capabilities | CC_SAFETY_NET_LEVEL (unset or each level), user level, all 27 user override sets, project level (absent or each), all 27 project override sets | 34,992 |
destructive rule <id>, once per rule class | session level, both levels, that rule's own capability override in each scope, both protection switches, the rule's on/off/absent override in each scope | 23,328 each |
| recursive delete allow paths | user switch (on/off), user allow list (every subset of two paths), project switch (on/off/absent), project allow list (every subset or absent) | 120 |
| secret rules, deny paths, allow paths | both switches, overrides for one default-on and one default-off secret rule in each scope, both deny lists and both allow lists over two paths | 194,400 |
| worktree mode | user value, project value or absent | 6 |
A rule class is one (catastrophic, activationCapability) pair in DESTRUCTIVE_COMMAND_RULE_METADATA; the check picks the last rule of each class, so a new class is covered without editing the test.
What it does not cover
- Path matching. Paths are two strings. This is sound only because the analyzer's matching only adds coverage per list entry:
isAllowedPathTargetinsrc/gate/analyzer/recursive-delete-targets.tsandmatchesAllowedPath/matchesPolicyPathinsrc/gate/secret/secret-protection.tsusesome(...)over the list. If either starts to let one entry cancel another, this check no longer covers paths. - Allow paths and catastrophic rules. The check treats destructive
allow_pathsas reaching only non-catastrophic outcomes, becauserecursive-delete-targets.tsreturnsgit_metadata_targetand root/home results before it consults allow paths. Secretdeny_pathswin overallow_pathsinfindSensitivePolicyPathTarget, and the check models the secret rules that way. - Fields across groups. Each group fixes every other field at the default (
createDefaultGuiPolicy(), project section absent). A bug where one section's weakening test reads another section's field is caught only if it shows within one group. - Other environment flags.
CC_SAFETY_NET_STRICT,_PARANOID,_PARANOID_RM,_PARANOID_INTERPRETERS, and_WORKTREEare not varied. The first four only switch capabilities on, after overrides, in both the user and the merged policy. - Reading the files.
projectPolicyProjectionand the user-policy validation are not exercised. The check starts from already parsed policies. - The command analyzer itself. The check proves that the merged policy is at least as strict as the user policy. It does not prove that the analyzer blocks what a strict policy says to block.
- The merge without the flag. Without tighten-only, weakenings are reported, not ignored, and this check does not apply.
When it fails
Read the counterexample: user and project are the two policies, env is the environment, and lost names the protections the merge dropped. Retell it in the code's words before acting, for example: "the user is at standard with CC_SAFETY_NET_LEVEL=paranoid; the project sets safety.overrides.paranoid_rm: false; the merged policy has paranoid_rm off".
Then decide which of these it is:
- A real weakening in product code. The merged policy really is weaker, and
collectWeakeningsinmerge.tsdid not list it, ormergeProjectPolicykept a field it should have dropped. This is a security bug. Fix the product code, or report it to the user if the change was not meant to touch the merge. Never edit the check to make it pass. - An intended change the check does not model yet. For example, a new policy field, a new kind of protection, or a capability whose meaning changed. Update the check (see below), and say in the commit message which protection you added or changed and why.
- A modeling error in the check. The protection set says something the hook does not do. Confirm it by reading the hook code named in the table above, then fix
computeProtections. Do not loosen it so that it stops describing the hook.
If the check passes after your change, still read the "Update it" list: a pass says nothing about a field the check does not look at.
Update it when the code changes on purpose
- New field in
GuiPolicyorProjectPolicyProjectionthat can relax protection. Add its protection tocomputeProtections, then add a group (or extend the closest one) that varies the field in both scopes alongside the switch that gates it. - New capability in
SAFETY_OVERRIDE_KEYS.CAPABILITY_OVERRIDE_SETSand the rule classes pick it up throughSAFETY_OVERRIDE_KEYSand the rule metadata, so no edit is needed. Check that the case count stays small enough to keep the run near 2 seconds. - New secret rule category with its own default. Add a representative id to
SECRET_RULE_CLASSES. - A path matcher that is no longer a plain
some(...)over the list. The path abstraction above is no longer sound. ExtendPATHSwith the cases the new matcher can tell apart, or tell the user that the path part is no longer verified.
Make sure it can still fail
After changing the check, break merge.ts on a scratch copy and confirm that the check catches it, then restore the file and confirm git diff --quiet src/core/policy/merge.ts. Each of these changes must make at least one test fail:
- In
collectWeakenings, replaceSAFETY_LEVEL_CAPABILITIES[sessionLevel]withSAFETY_LEVEL_CAPABILITIES[user.safety.level]. This fails the capability group and the capability-gated rules. - Replace the destructive
wasEnabledconditionoverrides[id] !== 'off'withoverrides[id] === 'on'. This fails the default-on rule classes. - In
mergeProjectPolicy, passproject.secret_protection?.allow_pathsinstead ofkeptPaths(...). This fails the secret group.
Trending
Steady: 0 new projects took it in the last 4 weeks, none the 4 weeks before.thin data
Who's talking about kenryu42/claude-code-safety-net
- A Hacker News story“Show HN: A Claude Code plugin that catch destructive Git and filesystem commands”Hacker News · Dec 26, 2025 · 61 points
- @solimaniac.com“safety net should be standard issue 😭 it's all about tooling”Bluesky · Mar 16, 2026 · 1 likes
- VytCepas/project-init“Phase 1: Replace custom safety hooks with maintained plugin; move enforcement to git/CI level”GitHub · Jun 11, 2026
- OpenSIN-Code/OpenSIN-Code“🔒 P0: @sin/safety-net — Destructive Command Protection Plugin”GitHub · Apr 5, 2026
- OpenSIN-Code/OpenSIN-Code“🔒 P0: @sin/safety-net — Destructive Command Protection Plugin”GitHub · Apr 5, 2026
The biggest 5 of 8 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 8
Talked about on GitHub, Hacker News and Bluesky (8 times).
On GitHub · 6
- Jun 11, 2026Phase 1: Replace custom safety hooks with maintained plugin; move enforcement to git/CI level — VytCepas/project-init0 reactions
- Apr 5, 2026🔒 P0: @sin/safety-net — Destructive Command Protection Plugin — OpenSIN-Code/OpenSIN-Code0 reactions · 1 comment
- Apr 5, 2026🔒 P0: @sin/safety-net — Destructive Command Protection Plugin — OpenSIN-Code/OpenSIN-Code0 reactions · 1 comment
- Mar 24, 2026[Resource]: Claude Code Safety Net — hesreallyhim/awesome-claude-code0 reactions · 2 comments
- Mar 23, 2026Research: Notable concepts & workflows from the Claude Code plugin ecosystem — aiocean/claude-plugins0 reactions · 1 comment
1 more on GitHub
- Jan 8, 2026Error with Claude Code for VS Code plugin? — kenryu42/cc-safety-net0 reactions · 5 comments
On Hacker News · 1
- Dec 26, 2025Show HN: A Claude Code plugin that catch destructive Git and filesystem commands61 points · 69 comments
On Bluesky · 1
- Mar 16, 2026safety net should be standard issue 😭 it's all about tooling — @solimaniac.com1 likes and repost · 1 reply
Other versions1 project uses a version of this.
Word for word · 1
- kenryu42/cc-safety-net★ 1.9k100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository7 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- ccsn-find-simplificationsSkill
- ccsn-no-commentsSkill
- verify-ccsnSkill
- release-notesSkill
- cc-safety-netSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

