MergeDenyInto: prune allow entries shadowed by canonical denies #26

Closed
opened 2026-05-28 01:23:49 +00:00 by coilysiren · 0 comments
Owner

Symptom

cli-guard/lockdown.MergeDenyInto reasserts the canonical deny list at an ancestor settings.local.json but explicitly preserves every existing allow entry. This leaves shadowed allows in place — entries that are dead because the deny-before-allow rule within a file makes them unreachable.

Example: parent settings.local.json had Bash(gh issue *) and Bash(xargs ls -lt) as allows. After MergeDenyInto merged canonical denies (Bash(gh:*), Bash(xargs:*)), those allows are still in the file but effectively dead. They re-accumulate every session as Claude prompts for tools the lockdown denies and the user clicks Allow into local.

After a recent sweep, the parent had ~20 such residue entries.

Fix

Extend MergeDenyInto so that when merging a canonical deny Bash(<verb>:*), it also drops any allow Bash(<verb>...) in the same file. The match rule: allow's leading bash verb token matches a canonical deny's verb prefix.

  • Allow Bash(gh issue *) + canonical deny Bash(gh:*) → drop the allow
  • Allow Bash(xargs ls -lt) + canonical deny Bash(xargs:*) → drop the allow
  • Allow Bash(coily git :*) + canonical deny Bash(make:*) → keep (no match)

Non-Bash allows (Read/Write/MCP) are untouched.

Out of scope for this issue

  • Session-residue allows that aren't shadowed by canonical denies (specific file paths, one-off rm -rf, MCP tools for retired servers). Harder to detect programmatically. Leave for a separate cleanup.
  • User-level ~/.claude/settings.json handling. Lockdown today doesn't touch user-level. File separately.

Context

Surfaced during the same renormalization sweep that produced coilysiren/coily#124 and coilysiren/coily#125. The manual prune of the parent local file should be re-runnable via coily lockdown --apply --recursive rather than a one-time hand-edit.

**Symptom** `cli-guard/lockdown.MergeDenyInto` reasserts the canonical deny list at an ancestor `settings.local.json` but explicitly preserves every existing allow entry. This leaves shadowed allows in place — entries that are dead because the deny-before-allow rule within a file makes them unreachable. Example: parent `settings.local.json` had `Bash(gh issue *)` and `Bash(xargs ls -lt)` as allows. After `MergeDenyInto` merged canonical denies (`Bash(gh:*)`, `Bash(xargs:*)`), those allows are still in the file but effectively dead. They re-accumulate every session as Claude prompts for tools the lockdown denies and the user clicks Allow into local. After a recent sweep, the parent had ~20 such residue entries. **Fix** Extend `MergeDenyInto` so that when merging a canonical deny `Bash(<verb>:*)`, it also drops any allow `Bash(<verb>...)` in the same file. The match rule: allow's leading bash verb token matches a canonical deny's verb prefix. - Allow `Bash(gh issue *)` + canonical deny `Bash(gh:*)` → drop the allow - Allow `Bash(xargs ls -lt)` + canonical deny `Bash(xargs:*)` → drop the allow - Allow `Bash(coily git :*)` + canonical deny `Bash(make:*)` → keep (no match) Non-Bash allows (Read/Write/MCP) are untouched. **Out of scope for this issue** - Session-residue allows that aren't shadowed by canonical denies (specific file paths, one-off `rm -rf`, MCP tools for retired servers). Harder to detect programmatically. Leave for a separate cleanup. - User-level `~/.claude/settings.json` handling. Lockdown today doesn't touch user-level. File separately. **Context** Surfaced during the same renormalization sweep that produced coilysiren/coily#124 and coilysiren/coily#125. The manual prune of the parent local file should be re-runnable via `coily lockdown --apply --recursive` rather than a one-time hand-edit.
Sign in to join this conversation.
No labels
P0
P1
P2
P3
P4
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/cli-guard#26
No description provided.