Skip to content

Commit fe1cb78

Browse files
authored
feat(gatekeeper): v2 — all-tool coverage + auto-mode rules (#136)
* docs(track): add gatekeeper-v2-20260331 track * chore(track): gatekeeper-v2-20260331 start implementation * feat(gatekeeper): add 3-tier decision system and all-tool coverage - Rename DENY_RULES to HARD_DENY_RULES, add SOFT_DENY_RULES for Bash - Add classifyWriteEdit() with path-based soft_deny for .env, .claude/settings, CI configs - Add classifyWebFetch() with URL-based soft_deny for paste services, script downloads - Add SAFE_TOOLS instant-allow list (Read, Glob, Grep, etc.) - Refactor evaluate() to dispatch by tool_name to per-tool classifiers - soft_deny returns null (passthrough to PermissionRequest AI hook) - Add comprehensive tests: 246 tests, 835 assertions, 0 failures Closes: #135 * feat(gatekeeper): rewrite PermissionRequest prompt with auto-mode rules - Change both matchers from "Bash" to "" (all tools) - Add intent judgment as core principle - Add full auto-mode ALLOW/DENY rule coverage (7 ALLOW, 25+ DENY) - Add tool-specific guidance for Write/Edit, WebFetch, Agent - Switch model from sonnet to haiku for faster classification * docs(gatekeeper): rewrite README with 3-tier architecture and all-tool coverage * chore(gatekeeper): rebuild dist bundle for v2 * chore(track): update gatekeeper-v2-20260331 progress — all tasks complete * fix(gatekeeper): fix dead .claude/settings soft_deny pattern Split combined pattern into two rules because \b word boundary doesn't match before `.` (non-word character), making the .claude/settings branch unreachable. * docs(track): gatekeeper-v2-20260331 retrospective and PR finalization * fix(gatekeeper): fix regexp lint errors in pre-tool-use rules Resolve eslint regexp plugin errors: - Replace .* patterns with \S+ to eliminate super-linear backtracking (regexp/no-super-linear-backtracking on \s+ combined with .*) - Remove duplicate A-Z range from case-insensitive character class (regexp/no-dupe-characters-character-class on [a-zA-Z] with /i flag) - Convert capturing groups to non-capturing in localhost URL pattern (regexp/no-unused-capturing-group) * chore: apply AI code review suggestions - Fix gcloud IAM regex to match multi-segment commands (e.g., gcloud alpha projects add-iam-policy-binding) - Fix localhost allow-rule to add boundary after hostname/port to prevent bypasses like localhost.evil.com - Fix path traversal in classifyWriteEdit by using path.resolve before checking project membership - Fix URL domain patterns for paste/file services to anchor to authority section - Fix raw.githubusercontent.com regex to handle query parameters (not just $ anchor) - Broaden chmod 777 detection to match flags before the mode (e.g., chmod -R 777) * chore: fix cross-platform path check in classifyWriteEdit Remove POSIX-only startsWith('/') guard so absolute Windows paths (e.g. C:\Users\...) are no longer auto-allowed outside the project root. Now relies solely on resolvedPath.startsWith(process.cwd()) and uses path.sep for node_modules detection, making the check portable. * chore: apply AI code review suggestions Fix path traversal vulnerability in classifyWriteEdit: use strict boundary check (resolvedPath === cwd || startsWith(cwd + sep)) instead of broad prefix match, and remove the overly permissive node_modules allow rule.
1 parent 733303b commit fe1cb78

9 files changed

Lines changed: 932 additions & 97 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"track_id": "gatekeeper-v2-20260331",
3+
"type": "feature",
4+
"status": "in_progress",
5+
"created_at": "2026-03-31T21:18:46+09:00",
6+
"updated_at": "2026-03-31T21:35:00+09:00",
7+
"issue": "#135",
8+
"pr": "#136",
9+
"project": ""
10+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Plan: Gatekeeper v2 — All-Tool Coverage + Auto-Mode Rules
2+
3+
> Track: gatekeeper-v2-20260331
4+
> Spec: [spec.md](./spec.md)
5+
6+
## Overview
7+
8+
- **Source**: pleaseai/claude-code-plugins#135
9+
- **Issue**: #135
10+
- **Created**: 2026-03-31
11+
- **Approach**: Modular Classifier — extend existing pattern-matching architecture with 3-tier decisions and per-tool classifiers
12+
13+
## Purpose
14+
15+
After this change, Claude Code users with Gatekeeper installed will have comprehensive security coverage across all tools (not just Bash). They can verify it works by observing that Write/Edit to `.env` triggers an AI review, while Read/Glob are instantly allowed, and `rm -rf /` is still hard-blocked.
16+
17+
## Context
18+
19+
Gatekeeper v1 only covers Bash commands, leaving Write, Edit, WebFetch, Agent and other tools unprotected. All denials are treated equally — there's no distinction between absolutely dangerous commands (rm -rf /) and commands that are dangerous but sometimes intentionally requested (git push --force). Analysis of Claude Code's built-in auto-mode classifier (`yoloClassifier.ts`) reveals a comprehensive 25+ rule set for DENY decisions and 7 ALLOW rules that Gatekeeper should match for full coverage.
20+
21+
The issue comment provides a detailed gap analysis showing current coverage at ~40% for SOFT_DENY rules and ~57% for ALLOW rules. The goal is 100% coverage of auto-mode defaults through a combination of Layer 1 static rules and Layer 2 AI prompt improvements.
22+
23+
Key constraints: Layer 1 must remain <5ms (no AI calls), existing Bash DENY/ALLOW behavior must not regress, and the `g` flag must not be used on RegExp patterns.
24+
25+
## Architecture Decision
26+
27+
**Chosen approach: Modular Classifier Pattern**
28+
29+
The existing `pre-tool-use.ts` architecture (DENY_RULES → ALLOW_RULES → passthrough) extends naturally to a 3-tier system. Rather than a full rewrite, we rename `DENY_RULES` to `HARD_DENY_RULES`, add `SOFT_DENY_RULES`, and introduce per-tool classifier functions that dispatch from the main `evaluate()` function based on `tool_name`. The `chain-parser.ts` remains unchanged as it handles only shell command parsing.
30+
31+
For soft_deny, returning `null` from the PreToolUse hook causes Claude Code to proceed to the PermissionRequest hook, where the AI agent evaluates user intent. This matches the issue's proposed flow exactly.
32+
33+
The PermissionRequest prompt is rewritten with the full auto-mode rule set from the issue comment, structured as Core Principle (intent judgment) → ALLOW rules → hard DENY rules → soft DENY rules → tool-specific guidance.
34+
35+
## Tasks
36+
37+
- [x] T001 Refactor DENY_RULES to HARD_DENY_RULES and add SOFT_DENY_RULES for Bash (file: plugins/gatekeeper/src/pre-tool-use.ts)
38+
- [x] T002 Add Write/Edit classifier with path-based rules (file: plugins/gatekeeper/src/pre-tool-use.ts) (depends on T001)
39+
- [x] T003 Add WebFetch classifier with URL-based rules (file: plugins/gatekeeper/src/pre-tool-use.ts) (depends on T001)
40+
- [x] T004 Add safe tools instant-allow list and refactor evaluate() dispatcher (file: plugins/gatekeeper/src/pre-tool-use.ts) (depends on T001)
41+
- [x] T005 Add tests for 3-tier Bash decisions (hard_deny, soft_deny, allow) (file: plugins/gatekeeper/src/pre-tool-use.test.ts) (depends on T001)
42+
- [x] T006 [P] Add tests for Write/Edit classifier (file: plugins/gatekeeper/src/pre-tool-use.test.ts) (depends on T002)
43+
- [x] T007 [P] Add tests for WebFetch classifier (file: plugins/gatekeeper/src/pre-tool-use.test.ts) (depends on T003)
44+
- [x] T008 [P] Add tests for safe tools allowlist and unknown tool passthrough (file: plugins/gatekeeper/src/pre-tool-use.test.ts) (depends on T004)
45+
- [x] T009 Rewrite PermissionRequest prompt with full auto-mode coverage and intent judgment (file: plugins/gatekeeper/hooks/hooks.json) (depends on T004)
46+
- [x] T010 Update hook matchers from Bash to empty string and evaluate model change (file: plugins/gatekeeper/hooks/hooks.json) (depends on T009)
47+
- [x] T011 Rewrite README.md with 3-tier architecture, all-tool coverage tables, and soft_deny documentation (file: plugins/gatekeeper/README.md) (depends on T010)
48+
- [x] T012 Build dist bundle and verify all tests pass (depends on T005, T006, T007, T008, T010)
49+
50+
## Key Files
51+
52+
### Modify
53+
54+
- `plugins/gatekeeper/src/pre-tool-use.ts` — Main hook logic: add 3-tier system, per-tool classifiers, safe tools allowlist
55+
- `plugins/gatekeeper/src/pre-tool-use.test.ts` — Tests: add soft_deny, Write/Edit, WebFetch, safe tools test suites
56+
- `plugins/gatekeeper/hooks/hooks.json` — Hook config: update matchers, rewrite PermissionRequest prompt, evaluate model
57+
- `plugins/gatekeeper/README.md` — Documentation: full rewrite with 3-tier architecture
58+
59+
### Reuse (unchanged)
60+
61+
- `plugins/gatekeeper/src/chain-parser.ts` — Shell parser: no changes needed
62+
- `plugins/gatekeeper/package.json` — Build config: no changes needed
63+
- `plugins/gatekeeper/CLAUDE.md` — Dev instructions: no changes needed
64+
65+
## Verification
66+
67+
### Automated Tests
68+
69+
- [ ] All existing Bash DENY/ALLOW tests pass unchanged (regression check)
70+
- [ ] soft_deny Bash commands (git push --force, npm publish, kubectl apply) return null
71+
- [ ] hard_deny Bash commands (rm -rf /, mkfs) return deny decision
72+
- [ ] Write/Edit to .env returns null (soft_deny → AI review)
73+
- [ ] Write/Edit to project-relative path returns allow decision
74+
- [ ] WebFetch to paste services returns null (soft_deny → AI review)
75+
- [ ] Read, Glob, Grep return allow decision (safe tools)
76+
- [ ] Unknown tools return null (passthrough to AI)
77+
78+
### Observable Outcomes
79+
80+
- Running `echo '{"tool_name":"Bash","tool_input":{"command":"git push --force"}}' | node dist/pre-tool-use.js` produces no stdout (passthrough)
81+
- Running `echo '{"tool_name":"Read","tool_input":{}}' | node dist/pre-tool-use.js` produces allow JSON
82+
- Running `echo '{"tool_name":"Write","tool_input":{"file_path":".env"}}' | node dist/pre-tool-use.js` produces no stdout (passthrough to AI)
83+
84+
### Acceptance Criteria Check
85+
86+
- [ ] AC-1: hard_deny commands blocked immediately with stderr
87+
- [ ] AC-2: soft_deny commands pass through to PermissionRequest AI
88+
- [ ] AC-3: Write/Edit to .env/.claude/settings triggers soft_deny
89+
- [ ] AC-4: Write/Edit to project paths instantly allowed
90+
- [ ] AC-5: Safe tools instantly allowed
91+
- [ ] AC-6: Unknown tools pass through (fail-open)
92+
- [ ] AC-7: AI prompt judges user intent
93+
- [ ] AC-8: All existing tests pass
94+
- [ ] AC-9: New tests cover all classifications
95+
- [ ] AC-10: README reflects new architecture
96+
97+
## Progress
98+
99+
- [x] (2026-03-31 21:40 KST) T001 Refactor DENY_RULES to HARD_DENY_RULES and add SOFT_DENY_RULES for Bash
100+
- [x] (2026-03-31 21:40 KST) T002 Add Write/Edit classifier with path-based rules
101+
- [x] (2026-03-31 21:40 KST) T003 Add WebFetch classifier with URL-based rules
102+
- [x] (2026-03-31 21:40 KST) T004 Add safe tools instant-allow list and refactor evaluate() dispatcher
103+
- [x] (2026-03-31 21:40 KST) T005 Add tests for 3-tier Bash decisions
104+
- [x] (2026-03-31 21:40 KST) T006 Add tests for Write/Edit classifier
105+
- [x] (2026-03-31 21:40 KST) T007 Add tests for WebFetch classifier
106+
- [x] (2026-03-31 21:40 KST) T008 Add tests for safe tools allowlist and unknown tool passthrough
107+
Evidence: `bun test` → 246 pass, 0 fail, 835 assertions (51ms)
108+
- [x] (2026-03-31 21:43 KST) T009 Rewrite PermissionRequest prompt with full auto-mode coverage
109+
- [x] (2026-03-31 21:43 KST) T010 Update hook matchers from Bash to empty string, switch model to haiku
110+
- [x] (2026-03-31 21:43 KST) T011 Rewrite README.md with 3-tier architecture
111+
- [x] (2026-03-31 21:45 KST) T012 Build dist bundle and verify all tests pass
112+
Evidence: `bun build` → pre-tool-use.js 15.47 KB; `bun test` → 246 pass, 0 fail
113+
114+
## Decision Log
115+
116+
- Decision: Modular Classifier pattern — extend existing architecture rather than rewrite
117+
Rationale: Minimizes risk, preserves existing test coverage, clear migration path from 2-tier to 3-tier
118+
Date/Author: 2026-03-31 / Claude
119+
120+
- Decision: soft_deny returns null (passthrough) rather than a new decision type
121+
Rationale: Claude Code hook protocol already supports null = passthrough to next hook. No SDK changes needed.
122+
Date/Author: 2026-03-31 / Claude
123+
124+
## Surprises & Discoveries
125+
126+
- Observation: `\b` word boundary fails before `.` (non-word character) in regex patterns
127+
Evidence: `/\b\.claude\/settings/` never matches because `\b` requires word↔non-word boundary, but `.` preceded by space/start is non-word↔non-word. Fixed by using `(?:^|\s)` instead.
128+
129+
## Outcomes & Retrospective
130+
131+
### What Was Shipped
132+
- 3-tier decision system (hard_deny / soft_deny / allow) in PreToolUse hook
133+
- All-tool coverage: Bash, Write/Edit, WebFetch, safe tools allowlist
134+
- Rewritten PermissionRequest AI prompt with full auto-mode coverage (7 ALLOW, 25+ DENY rules)
135+
- Switched Layer 2 model from sonnet to haiku
136+
- Comprehensive README rewrite
137+
138+
### What Went Well
139+
- Existing test suite caught regression immediately when `evaluateSingleCommand` return type changed
140+
- Modular classifier pattern allowed incremental implementation without breaking existing behavior
141+
- Issue #135 with detailed gap analysis made rule coverage straightforward
142+
143+
### What Could Improve
144+
- Regex word boundary behavior with non-word characters caught by review — could add a regex-specific test helper
145+
146+
### Tech Debt Created
147+
- None identified
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Gatekeeper v2: All-Tool Coverage + Auto-Mode Rules
2+
3+
> Track: gatekeeper-v2-20260331
4+
> Issue: pleaseai/claude-code-plugins#135
5+
6+
## Overview
7+
8+
Extend Gatekeeper from Bash-only coverage to all Claude Code tools and introduce a 3-tier decision system (hard_deny / soft_deny / allow) based on Claude Code's built-in auto-mode classifier. Improve the PermissionRequest AI prompt with intent judgment and comprehensive security rules derived from auto-mode defaults analysis.
9+
10+
## Requirements
11+
12+
### Functional Requirements
13+
14+
- [ ] FR-1: Implement 3-tier decision system — `hard_deny` (exit code 2, immediate block), `soft_deny` (passthrough to PermissionRequest AI), `allow` (instant approve)
15+
- [ ] FR-2: Define soft_deny rules for Bash — git force push, push to main, hard reset, git clean, npm publish, terraform/kubectl apply, self-modification (.claude/settings, CLAUDE.md), --no-verify, chmod 777, exposed services, unauthorized persistence, permission grants, logging tampering
16+
- [ ] FR-3: Extend PreToolUse to handle Write/Edit tools — project-relative paths allowed, `.env`/`.claude/settings`/CI configs soft_deny
17+
- [ ] FR-4: Extend PreToolUse to handle WebFetch tool — paste services and script downloads soft_deny
18+
- [ ] FR-5: Add safe tools instant-allow list — Read, Glob, Grep, TaskCreate, and other read-only tools
19+
- [ ] FR-6: Add missing Bash hard_deny rules from auto-mode analysis
20+
- [ ] FR-7: Add missing Bash ALLOW rules — declared dependency install (npm install without args), toolchain bootstrap patterns
21+
- [ ] FR-8: Update PermissionRequest prompt with full auto-mode coverage: intent judgment, soft_deny context, 25+ DENY rules, 7 ALLOW rules, tool-specific guidance
22+
- [ ] FR-9: Change both hook matchers from `"Bash"` to `""` (all tools)
23+
- [ ] FR-10: Evaluate and switch PermissionRequest model from sonnet to haiku
24+
- [ ] FR-11: Rewrite README.md to document 3-tier system, all-tool coverage, soft_deny rules, and updated architecture diagram
25+
26+
### Non-functional Requirements
27+
28+
- [ ] NFR-1: Layer 1 (PreToolUse) must remain <5ms — static pattern matching only, no AI calls
29+
- [ ] NFR-2: No RegExp `g` flag on any pattern (existing invariant)
30+
- [ ] NFR-3: Existing DENY/ALLOW behavior for Bash commands must not regress
31+
32+
## Acceptance Criteria
33+
34+
- [ ] AC-1: `hard_deny` commands (rm -rf /, mkfs, dd) are blocked immediately with stderr message
35+
- [ ] AC-2: `soft_deny` commands (git push --force, npm publish, kubectl apply) pass through to PermissionRequest AI hook
36+
- [ ] AC-3: Write/Edit to `.env` or `.claude/settings` triggers soft_deny (passthrough to AI)
37+
- [ ] AC-4: Write/Edit to project-relative paths is instantly allowed
38+
- [ ] AC-5: Safe tools (Read, Glob, Grep) are instantly allowed without AI review
39+
- [ ] AC-6: Unknown tools pass through to PermissionRequest (fail-open)
40+
- [ ] AC-7: PermissionRequest AI prompt judges user intent ("did the user explicitly request this?")
41+
- [ ] AC-8: All existing tests pass without modification
42+
- [ ] AC-9: New tests cover hard_deny, soft_deny, and allow classifications for all tool types
43+
- [ ] AC-10: README.md accurately reflects the new 3-tier architecture
44+
45+
## Out of Scope
46+
47+
- Sandbox integration (complementary but separate concern)
48+
- PostToolUse hooks (this track focuses on PreToolUse + PermissionRequest)
49+
- Custom user rule configuration (future enhancement)
50+
- Per-project rule overrides
51+
52+
## Assumptions
53+
54+
- The issue comment's gap analysis (7 ALLOW + 25 SOFT_DENY auto-mode rules) is the authoritative reference for rule coverage
55+
- `soft_deny` returns null from PreToolUse, which causes Claude Code to proceed to PermissionRequest hook
56+
- The `@anthropic-ai/claude-agent-sdk` types support the current hook protocol
57+
- haiku model is sufficient for Layer 2 classification (to be validated in FR-10)

.please/docs/tracks/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
| [web-nuxt-update-20260328](active/web-nuxt-update-20260328/plan.md) | Web App Dependency Update | chore | #126 | 2026-03-28 | in_progress |
1414
| [fix-setup-glob-pattern-20260329](active/fix-setup-glob-pattern-20260329/plan.md) | Fix setup command package.json discovery | bugfix | #129 | 2026-03-29 | in_progress |
1515
| [setup-monorepo-scan-20260329](active/setup-monorepo-scan-20260329/plan.md) | Monorepo Workspace Dependency Scanning | feature | #132 | 2026-03-29 | in_progress |
16+
| [gatekeeper-v2-20260331](active/gatekeeper-v2-20260331/plan.md) | Gatekeeper v2: All-Tool Coverage + Auto-Mode Rules | feature | #135 | 2026-03-31 | in_progress |
1617

1718
## Recently Completed
1819

0 commit comments

Comments
 (0)