Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .please/docs/tracks/active/gatekeeper-v2-20260331/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"track_id": "gatekeeper-v2-20260331",
"type": "feature",
"status": "in_progress",
"created_at": "2026-03-31T21:18:46+09:00",
"updated_at": "2026-03-31T21:35:00+09:00",
"issue": "#135",
"pr": "#136",
"project": ""
}
147 changes: 147 additions & 0 deletions .please/docs/tracks/active/gatekeeper-v2-20260331/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Plan: Gatekeeper v2 — All-Tool Coverage + Auto-Mode Rules

> Track: gatekeeper-v2-20260331
> Spec: [spec.md](./spec.md)

## Overview

- **Source**: pleaseai/claude-code-plugins#135
- **Issue**: #135
- **Created**: 2026-03-31
- **Approach**: Modular Classifier — extend existing pattern-matching architecture with 3-tier decisions and per-tool classifiers

## Purpose

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.

## Context

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.

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.

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.

## Architecture Decision

**Chosen approach: Modular Classifier Pattern**

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.

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.

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.

## Tasks

- [x] T001 Refactor DENY_RULES to HARD_DENY_RULES and add SOFT_DENY_RULES for Bash (file: plugins/gatekeeper/src/pre-tool-use.ts)
- [x] T002 Add Write/Edit classifier with path-based rules (file: plugins/gatekeeper/src/pre-tool-use.ts) (depends on T001)
- [x] T003 Add WebFetch classifier with URL-based rules (file: plugins/gatekeeper/src/pre-tool-use.ts) (depends on T001)
- [x] T004 Add safe tools instant-allow list and refactor evaluate() dispatcher (file: plugins/gatekeeper/src/pre-tool-use.ts) (depends on T001)
- [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)
- [x] T006 [P] Add tests for Write/Edit classifier (file: plugins/gatekeeper/src/pre-tool-use.test.ts) (depends on T002)
- [x] T007 [P] Add tests for WebFetch classifier (file: plugins/gatekeeper/src/pre-tool-use.test.ts) (depends on T003)
- [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)
- [x] T009 Rewrite PermissionRequest prompt with full auto-mode coverage and intent judgment (file: plugins/gatekeeper/hooks/hooks.json) (depends on T004)
- [x] T010 Update hook matchers from Bash to empty string and evaluate model change (file: plugins/gatekeeper/hooks/hooks.json) (depends on T009)
- [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)
- [x] T012 Build dist bundle and verify all tests pass (depends on T005, T006, T007, T008, T010)

## Key Files

### Modify

- `plugins/gatekeeper/src/pre-tool-use.ts` — Main hook logic: add 3-tier system, per-tool classifiers, safe tools allowlist
- `plugins/gatekeeper/src/pre-tool-use.test.ts` — Tests: add soft_deny, Write/Edit, WebFetch, safe tools test suites
- `plugins/gatekeeper/hooks/hooks.json` — Hook config: update matchers, rewrite PermissionRequest prompt, evaluate model
- `plugins/gatekeeper/README.md` — Documentation: full rewrite with 3-tier architecture

### Reuse (unchanged)

- `plugins/gatekeeper/src/chain-parser.ts` — Shell parser: no changes needed
- `plugins/gatekeeper/package.json` — Build config: no changes needed
- `plugins/gatekeeper/CLAUDE.md` — Dev instructions: no changes needed

## Verification

### Automated Tests

- [ ] All existing Bash DENY/ALLOW tests pass unchanged (regression check)
- [ ] soft_deny Bash commands (git push --force, npm publish, kubectl apply) return null
- [ ] hard_deny Bash commands (rm -rf /, mkfs) return deny decision
- [ ] Write/Edit to .env returns null (soft_deny → AI review)
- [ ] Write/Edit to project-relative path returns allow decision
- [ ] WebFetch to paste services returns null (soft_deny → AI review)
- [ ] Read, Glob, Grep return allow decision (safe tools)
- [ ] Unknown tools return null (passthrough to AI)

### Observable Outcomes

- Running `echo '{"tool_name":"Bash","tool_input":{"command":"git push --force"}}' | node dist/pre-tool-use.js` produces no stdout (passthrough)
- Running `echo '{"tool_name":"Read","tool_input":{}}' | node dist/pre-tool-use.js` produces allow JSON
- Running `echo '{"tool_name":"Write","tool_input":{"file_path":".env"}}' | node dist/pre-tool-use.js` produces no stdout (passthrough to AI)

### Acceptance Criteria Check

- [ ] AC-1: hard_deny commands blocked immediately with stderr
- [ ] AC-2: soft_deny commands pass through to PermissionRequest AI
- [ ] AC-3: Write/Edit to .env/.claude/settings triggers soft_deny
- [ ] AC-4: Write/Edit to project paths instantly allowed
- [ ] AC-5: Safe tools instantly allowed
- [ ] AC-6: Unknown tools pass through (fail-open)
- [ ] AC-7: AI prompt judges user intent
- [ ] AC-8: All existing tests pass
- [ ] AC-9: New tests cover all classifications
- [ ] AC-10: README reflects new architecture

## Progress

- [x] (2026-03-31 21:40 KST) T001 Refactor DENY_RULES to HARD_DENY_RULES and add SOFT_DENY_RULES for Bash
- [x] (2026-03-31 21:40 KST) T002 Add Write/Edit classifier with path-based rules
- [x] (2026-03-31 21:40 KST) T003 Add WebFetch classifier with URL-based rules
- [x] (2026-03-31 21:40 KST) T004 Add safe tools instant-allow list and refactor evaluate() dispatcher
- [x] (2026-03-31 21:40 KST) T005 Add tests for 3-tier Bash decisions
- [x] (2026-03-31 21:40 KST) T006 Add tests for Write/Edit classifier
- [x] (2026-03-31 21:40 KST) T007 Add tests for WebFetch classifier
- [x] (2026-03-31 21:40 KST) T008 Add tests for safe tools allowlist and unknown tool passthrough
Evidence: `bun test` → 246 pass, 0 fail, 835 assertions (51ms)
- [x] (2026-03-31 21:43 KST) T009 Rewrite PermissionRequest prompt with full auto-mode coverage
- [x] (2026-03-31 21:43 KST) T010 Update hook matchers from Bash to empty string, switch model to haiku
- [x] (2026-03-31 21:43 KST) T011 Rewrite README.md with 3-tier architecture
- [x] (2026-03-31 21:45 KST) T012 Build dist bundle and verify all tests pass
Evidence: `bun build` → pre-tool-use.js 15.47 KB; `bun test` → 246 pass, 0 fail

## Decision Log

- Decision: Modular Classifier pattern — extend existing architecture rather than rewrite
Rationale: Minimizes risk, preserves existing test coverage, clear migration path from 2-tier to 3-tier
Date/Author: 2026-03-31 / Claude

- Decision: soft_deny returns null (passthrough) rather than a new decision type
Rationale: Claude Code hook protocol already supports null = passthrough to next hook. No SDK changes needed.
Date/Author: 2026-03-31 / Claude

## Surprises & Discoveries

- Observation: `\b` word boundary fails before `.` (non-word character) in regex patterns
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.

## Outcomes & Retrospective

### What Was Shipped
- 3-tier decision system (hard_deny / soft_deny / allow) in PreToolUse hook
- All-tool coverage: Bash, Write/Edit, WebFetch, safe tools allowlist
- Rewritten PermissionRequest AI prompt with full auto-mode coverage (7 ALLOW, 25+ DENY rules)
- Switched Layer 2 model from sonnet to haiku
- Comprehensive README rewrite

### What Went Well
- Existing test suite caught regression immediately when `evaluateSingleCommand` return type changed
- Modular classifier pattern allowed incremental implementation without breaking existing behavior
- Issue #135 with detailed gap analysis made rule coverage straightforward

### What Could Improve
- Regex word boundary behavior with non-word characters caught by review — could add a regex-specific test helper

### Tech Debt Created
- None identified
57 changes: 57 additions & 0 deletions .please/docs/tracks/active/gatekeeper-v2-20260331/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Gatekeeper v2: All-Tool Coverage + Auto-Mode Rules

> Track: gatekeeper-v2-20260331
> Issue: pleaseai/claude-code-plugins#135

## Overview

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.

## Requirements

### Functional Requirements

- [ ] FR-1: Implement 3-tier decision system — `hard_deny` (exit code 2, immediate block), `soft_deny` (passthrough to PermissionRequest AI), `allow` (instant approve)
- [ ] 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
- [ ] FR-3: Extend PreToolUse to handle Write/Edit tools — project-relative paths allowed, `.env`/`.claude/settings`/CI configs soft_deny
- [ ] FR-4: Extend PreToolUse to handle WebFetch tool — paste services and script downloads soft_deny
- [ ] FR-5: Add safe tools instant-allow list — Read, Glob, Grep, TaskCreate, and other read-only tools
- [ ] FR-6: Add missing Bash hard_deny rules from auto-mode analysis
- [ ] FR-7: Add missing Bash ALLOW rules — declared dependency install (npm install without args), toolchain bootstrap patterns
- [ ] FR-8: Update PermissionRequest prompt with full auto-mode coverage: intent judgment, soft_deny context, 25+ DENY rules, 7 ALLOW rules, tool-specific guidance
- [ ] FR-9: Change both hook matchers from `"Bash"` to `""` (all tools)
- [ ] FR-10: Evaluate and switch PermissionRequest model from sonnet to haiku
- [ ] FR-11: Rewrite README.md to document 3-tier system, all-tool coverage, soft_deny rules, and updated architecture diagram

### Non-functional Requirements

- [ ] NFR-1: Layer 1 (PreToolUse) must remain <5ms — static pattern matching only, no AI calls
- [ ] NFR-2: No RegExp `g` flag on any pattern (existing invariant)
- [ ] NFR-3: Existing DENY/ALLOW behavior for Bash commands must not regress

## Acceptance Criteria

- [ ] AC-1: `hard_deny` commands (rm -rf /, mkfs, dd) are blocked immediately with stderr message
- [ ] AC-2: `soft_deny` commands (git push --force, npm publish, kubectl apply) pass through to PermissionRequest AI hook
- [ ] AC-3: Write/Edit to `.env` or `.claude/settings` triggers soft_deny (passthrough to AI)
- [ ] AC-4: Write/Edit to project-relative paths is instantly allowed
- [ ] AC-5: Safe tools (Read, Glob, Grep) are instantly allowed without AI review
- [ ] AC-6: Unknown tools pass through to PermissionRequest (fail-open)
- [ ] AC-7: PermissionRequest AI prompt judges user intent ("did the user explicitly request this?")
- [ ] AC-8: All existing tests pass without modification
- [ ] AC-9: New tests cover hard_deny, soft_deny, and allow classifications for all tool types
- [ ] AC-10: README.md accurately reflects the new 3-tier architecture

## Out of Scope

- Sandbox integration (complementary but separate concern)
- PostToolUse hooks (this track focuses on PreToolUse + PermissionRequest)
- Custom user rule configuration (future enhancement)
- Per-project rule overrides

## Assumptions

- The issue comment's gap analysis (7 ALLOW + 25 SOFT_DENY auto-mode rules) is the authoritative reference for rule coverage
- `soft_deny` returns null from PreToolUse, which causes Claude Code to proceed to PermissionRequest hook
- The `@anthropic-ai/claude-agent-sdk` types support the current hook protocol
- haiku model is sufficient for Layer 2 classification (to be validated in FR-10)
1 change: 1 addition & 0 deletions .please/docs/tracks/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
| [web-nuxt-update-20260328](active/web-nuxt-update-20260328/plan.md) | Web App Dependency Update | chore | #126 | 2026-03-28 | in_progress |
| [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 |
| [setup-monorepo-scan-20260329](active/setup-monorepo-scan-20260329/plan.md) | Monorepo Workspace Dependency Scanning | feature | #132 | 2026-03-29 | in_progress |
| [gatekeeper-v2-20260331](active/gatekeeper-v2-20260331/plan.md) | Gatekeeper v2: All-Tool Coverage + Auto-Mode Rules | feature | #135 | 2026-03-31 | in_progress |

## Recently Completed

Expand Down
Loading
Loading