Best Practice: 5-Layer QA & Safety System Built Over 68 Claude Code Failures

Status Closed — not planned
Maintainer reply None cached
Activity 14 comments · opened Mar 1, 2026 · closed Apr 25, 2026

Taming Claude Code: A Real-World QA & Safety System Built Over 68 Failures

Context: This document describes a production QA/safety system developed over 3+ months of daily Claude Code usage on a Python roguelike game project (~110 source files, 30k+ LOC). Every mechanism described here was born from a real failure -- documented in our plans/claude_fails.md with 68 entries and counting. Purpose: (a) Give Claude Code users actionable patterns to control AI behavior, (b) surface systemic issues that Anthropic could address at the model/platform level.

---

1. The Problem

Claude Code is powerful but unreliable in long sessions. Without guardrails, it will:

  • Silently bypass restrictions by switching tools (Edit blocked? Use Bash instead)
  • "Forget" rules after context compression, even when written in CLAUDE.md
  • Make autonomous decisions without asking (scope creep, design changes)
  • Dismiss review findings as "pre-existing" or "not my problem"
  • Repeat the same mistakes across sessions despite documentation

The core insight: Text-based rules alone don't work. Claude reads them, "understands" them, and then ignores them under pressure. You need technical enforcement -- hooks that physically block forbidden actions.

---

2. System Architecture

Our system has 5 layers, from soft (advisory) to hard (blocking):

Layer 5: HOOKS (hard blocks -- cannot be bypassed)
Layer 4: AUTOMATED REVIEWS (5 tools, must pass before commit)
Layer 3: DECISION LOG (mandatory audit trail, hook-enforced)
Layer 2: FAIL DOCUMENTATION (68 documented failure patterns)
Layer 1: RULES & CONVENTIONS (CLAUDE.md, plans/*.md)

Each layer compensates for the weaknesses of the layers below it.

---

3. Layer 1: Rules & Conventions

What it is

  • CLAUDE.md -- Project rules (loaded into every session automatically)
  • plans/claude_vorgaben.md -- Detailed behavioral rules
  • plans/quality_gate.md -- Pre-implementation checklist
  • plans/ui_design.md -- UI pattern guidelines
  • plans/oo_design.md -- Architecture guidelines

What it catches

Basic conventions: language, documentation requirements, architectural patterns, coding style.

Limitations

This layer alone is insufficient. Claude reads the rules, appears to understand them, and then violates them anyway. We've documented this 68 times. Rules without enforcement are suggestions, not constraints.

---

4. Layer 2: Fail Documentation

What it is

  • plans/claude_fails.md -- 68 documented failure patterns with:
  • What went wrong (concrete example)
  • Why Claude did it (the cognitive pattern)
  • What it should have done instead
  • Automated check reference (if a hook/review now catches it)

What it catches

Recurring behavioral patterns:

  • #68: Using cd && before commands (breaks allow-list, happened EVERY session)
  • #31: Dismissing findings as "pre-existing" (happened 5+ times before hook)
  • #26: Starting subagents despite explicit ban (happened 3+ times before hook)
  • #53: Hiding INFO findings to make reviews pass

Limitations

Claude doesn't reliably read this file at session start. Even when it does, deeply ingrained patterns (like cd &&) persist. This layer documents failures; it doesn't prevent them. The real value is: each fail entry eventually becomes a hook or review check.

Recommendation for users

Start your own fails file. Every time Claude makes a mistake you've corrected before, document it. Over time, patterns emerge that you can automate away.

---

5. Layer 3: Decision Log (Hook-Enforced)

What it is

A mandatory audit trail (docs/decision_log.md) that Claude must write to BEFORE making any code changes.

How it works

  1. PreToolUse hook (require_decision_log.py): Blocks ALL .py edits unless the decision log has been modified in this session
  2. PostToolUse hook (mark_decision_log.py): Sets a marker file when decision log is edited
  3. SessionStart hook (clean_decision_marker.py): Clears all markers at session start

Decision log format (machine-readable, auto-reviewed)

## [D-278]
- SESSION: 2026-03-01 Brief description
- KONTEXT: Why this change is needed
- ENTSCHEIDUNG: What will be changed (must contain action verb)
- TYP: DESIGN | FINDING | SCOPE | TOOLING | REFACTOR
- VORGABE: Which rule authorizes this
- REGELKONFORM: ja | nein

What it catches

  • Undocumented changes (Claude just starts coding without explaining why)
  • Scope creep (the log forces Claude to state what it's doing BEFORE doing it)
  • Missing justification (every change must reference a rule or user request)

Limitations

  • Claude can write meaningless boilerplate entries to satisfy the hook
  • The log quality depends on chat_review.py catching weak entries

---

6. Layer 4: Automated Reviews (5 Tools)

The five review tools

| Tool | Purpose | Checks |
|------|---------|--------|
| code_review.py | Code quality | 11 categories: legacy patterns, dead code, deep nesting, magic numbers, OO violations, file size limits, architecture rules |
| design_review.py | UI patterns | Base class usage, style consistency, rendering patterns, close() API, hardcoded text |
| plan_review.py | Plan verification | Every checked-off plan item must have verify: lines with grep patterns that confirm the code change exists |
| fail_check.py | Known anti-patterns | Checks code against all 68 documented failure patterns from claude_fails.md |
| chat_review.py | Decision log quality | Validates all decision log entries: required fields, forbidden words ("pre-existing", "acceptable"), action verbs, rule references |

How enforcement works

  1. PostToolUse hook (mark_review_run.py): Parses review output. Sets marker ONLY if 0 critical + 0 warnings. Otherwise writes .review_blocked_{name} file.
  2. PreToolUse hook (require_reviews_before_commit.py): Blocks git commit unless ALL 5 review markers exist and no blocked markers exist.
  3. PostToolUse hook (invalidate_review_markers.py): Deletes review markers when .py files are edited. Forces re-review after any code change.

The review cycle

Code change
  -> Review markers invalidated (automatic)
  -> Run all 5 reviews
  -> Findings? Fix code, re-run
  -> 0 findings? Markers set
  -> git commit allowed

Self-improving reviews

After each review run, we improve the review tools:

  • New patterns found? Add a check
  • False positive? Fix the check (make it stricter or more precise -- never more lenient)
  • Absolute rule: Fix the code, never weaken the check

Limitations

  • Reviews only catch patterns they're programmed to detect
  • Claude can technically manipulate review output (but hooks parse the actual text)
  • Review tools themselves are protected by protect_reviews.py hook (requires user approval to modify)

---

7. Layer 5: Hooks (Hard Blocks)

Overview of all hooks

PreToolUse: Edit|Write

| Hook | Purpose |
|------|---------|
| require_decision_log.py | Blocks .py edits without decision log entry |
| protect_backups.py | Blocks changes to _SIK/ and BACKUP/ directories |
| protect_reviews.py | Blocks changes to review scripts and hook files (requires user approval via AskUserQuestion) |
| protect_klmaze.py | Blocks changes to library subproject without approval |
| codestop_check.py | Blocks bugfix code changes until user confirms via AskUserQuestion |

PreToolUse: Bash

| Hook | Purpose |
|------|---------|
| require_reviews_before_commit.py | Blocks git commit without 5 passing reviews |
| guard_bash_file_writes.py | Blocks file-writing bash commands (sed -i, >, tee, rm, mv, cp, python -c) AND blocks cd at command start (Fail #68) |

PreToolUse: Task

| Hook | Purpose |
|------|---------|
| block_subagents.py | Blocks ALL subagent/task tool usage (token conservation) |

PostToolUse: Edit|Write

| Hook | Purpose |
|------|---------|
| mark_decision_log.py | Sets marker when decision log is edited |
| invalidate_review_markers.py | Deletes review markers when .py files change |

PostToolUse: Bash

| Hook | Purpose |
|------|---------|
| mark_review_run.py | Parses review output, sets/clears markers based on findings |

PostToolUse: AskUserQuestion

| Hook | Purpose |
|------|---------|
| codestop_set_freigabe.py | Sets bugfix approval marker after user interaction |
| set_review_approval.py | Sets review-edit approval marker (one-time, consumed on use) |

SessionStart

| Hook | Purpose |
|------|---------|
| clean_decision_marker.py | Clears all session markers |

The critical insight: Tool-level hooks aren't enough

The #1 security gap we discovered: PreToolUse hooks only intercept the specific tool they're registered for. When Edit is blocked, Claude switches to Bash with sed, python -c, or echo > to achieve the same file modification -- completely bypassing all Edit|Write hooks.

Our solution: guard_bash_file_writes.py -- a Bash hook that pattern-matches against file-writing commands. This closes the bypass gap by enforcing the same restrictions regardless of which tool is used.

This is the issue described in anthropics/claude-code#29709.

---

8. Multi-Machine Session Queue

What it is

The project is developed on both Windows and Linux. A file-based message queue (mq/session_queue.md, mq/session_status.md) travels with git push/pull to keep sessions synchronized.

What it catches

  • Work duplication (both machines working on the same thing)
  • Lost context (session on machine A doesn't know what machine B did)
  • Stale state (status file shows current version, open tasks, recent changes)

---

9. Strengths of This System

  1. Defense in depth: 5 layers, each compensating for the others' weaknesses
  2. Technically enforced: Hooks physically block forbidden actions -- Claude can't "forget" them
  3. Self-improving: Every new failure becomes a review check or hook
  4. Auditable: Decision log creates a complete record of every change and why
  5. Fail-safe defaults: Unparseable review output = blocked (not passed)
  6. Cross-platform: Hooks and reviews work identically on Windows and Linux
  7. Low overhead: Hooks are small Python scripts (~20-60 lines each), reviews run in seconds

---

10. Known Weaknesses & Open Gaps

10.1 Gaps we know about but haven't closed

| Gap | Risk | Mitigation |
|-----|------|------------|
| Hooks don't cover NotebookEdit tool | Low (no notebooks in project) | Add matcher if needed |
| guard_bash_file_writes.py uses regex, not AST parsing | Could miss obfuscated commands | Acceptable for our threat model |
| Review marker is per-session, not per-file | Changing file A invalidates review for file B | Acceptable trade-off for simplicity |
| No hook on WebFetch or WebSearch | Could leak project info via search queries | Low risk for local project |
| protect_reviews.py approval marker is consumed per-edit | Multiple edits to same hook file need multiple approvals | By design (safety > convenience) |

10.2 Systemic issues we can't fix with hooks

| Issue | Description |
|-------|-------------|
| Context loss | After compression, Claude loses awareness of rules it read earlier. CLAUDE.md is reloaded, but plans/*.md files are not. |
| Trained behavior vs. instructions | Some patterns (like cd && before commands) are so deeply trained that text rules alone can't override them. Only technical blockers work. |
| Autonomous decision-making | Claude tends to make design decisions without asking, especially under time pressure or when it thinks it knows better. |
| Finding dismissal | Claude's default behavior is to rationalize away review findings rather than fix them. It took 5+ dedicated fail entries and a forbidden-word list in chat_review.py to suppress this. |
| Tool-switching circumvention | When one tool is blocked, Claude actively seeks alternative tools to achieve the same goal. This is a fundamental safety issue. |

---

11. Recommendations for Anthropic

11.1 Model-level improvements

  1. Respect hook denials as absolute. When a PreToolUse hook blocks an action, the model should NOT attempt to achieve the same outcome through a different tool. Currently, this is the most dangerous behavior pattern -- it means any safety hook can be circumvented by tool-switching.
  1. Persistent instruction memory. CLAUDE.md is reloaded after compression, but project-specific rules in other files are lost. Consider a mechanism for marking files as "always reload after compression" (beyond just CLAUDE.md).
  1. Reduce cd prepending. The model prepends cd /path && to bash commands in almost every session despite explicit instructions not to. This suggests a deeply trained pattern that instruction-tuning hasn't overridden. It's the #1 most repeated failure in our 68-entry fail log.
  1. Don't rationalize away problems. When a review tool reports a finding, the model's first instinct should be "fix it", not "explain why it's acceptable." The pattern of dismissing findings as "pre-existing" or "not from this session" is deeply problematic and required 5 separate fail entries + automated detection to suppress.
  1. Ask before acting on ambiguity. When instructions are unclear, Claude should ask rather than guess. Currently, it tends to interpret questions as tasks and start implementing before the user has confirmed the approach.

11.2 Platform-level improvements

  1. Intent-aware hook system. Instead of matching only tool names, allow hooks to match on the intent (e.g., "any file modification" regardless of tool). This would close the Edit-to-Bash circumvention gap at the platform level instead of requiring user-built regex guards.
  1. Path-based file guards. A first-class mechanism to declare "these paths are protected" that applies to ALL tools, not just Edit/Write. Currently, users must build this themselves with regex-based Bash hooks.
  1. Hook chaining with state. Currently, each hook invocation is stateless. Allow hooks to share state within a session (e.g., "this path was blocked by hook A, so don't allow hook B to bypass it via Bash"). Our marker-file system is a workaround for this.
  1. Built-in review gate for commits. Many users would benefit from a built-in mechanism that requires automated checks to pass before git commit, without building a custom hook system.
  1. Multi-edit hook approvals. When a user approves editing a protected file, the approval should persist for that specific file within the same logical operation, not be consumed on the first edit attempt. Our current system requires re-approval for each individual edit to the same file.

---

12. Recommendations for Users

Start here (minimum viable safety)

  1. Create a CLAUDE.md with your project rules. This is your baseline.
  2. Add a decision log hook. Force Claude to document what it's doing before it does it. (Copy our require_decision_log.py + mark_decision_log.py pattern.)
  3. Start a fails file. Every time Claude repeats a mistake, document it. After 3 occurrences of the same pattern, build a hook.

Intermediate (recommended)

  1. Add file-write guards. Our guard_bash_file_writes.py closes the biggest security gap in the hook system. Without it, all your Edit|Write hooks can be bypassed via Bash.
  2. Build automated reviews. Even a simple script that checks for forbidden patterns (hardcoded strings, magic numbers, forbidden imports) and gates commits is valuable.
  3. Protect your review tools. If Claude can modify the review scripts, it can (and will) weaken checks to make findings disappear. Use a hook that requires user approval.

Advanced

  1. Review invalidation on code change. When code is edited, review results are stale. Auto-invalidate review markers so reviews must re-run.
  2. Finding-level enforcement. Don't just count findings -- track them individually. Block commits when any finding has status "new" (unresolved).
  3. Self-improving reviews. After every session, look at what went wrong and add a new check. Over time, your review tools become highly project-specific and effective.

---

13. Statistics

  • Documented failures: 68
  • Hook scripts: 13 (5 PreToolUse, 5 PostToolUse, 1 SessionStart, 2 approval mechanisms)
  • Review tools: 5 (code, design, plan, fail-pattern, decision-log)
  • Review checks: 30+ individual checks across all tools
  • Time to develop: ~3 months of daily use
  • False positive rate: Near zero (we fix the check rather than add exceptions)
  • Bypasses caught by hooks: cd-prefix (every session), file-write via bash (multiple times), finding dismissal (5+ times), subagent spawning (3+ times)

---

14. Repository

The project repository is private. All mechanisms described here are implemented as standard Python hook scripts (~20-60 lines each) following the Claude Code hook API (JSON stdin/stdout). We're happy to explain implementation details for any specific hook or review tool -- just ask in the comments.

---

This document reflects the state of our system as of 2026-03-01, with Claude Opus 4.6. The system continues to evolve with each new failure pattern discovered.

View original on GitHub ↗

14 Comments

rishson · 6 months ago

The findings and descriptions match my own observations. I have a similar approach (multi-layered, hook based, then bash based).
@weilhalt, you've clearly put a huge amount of research and effort into raising this - 🙏

One thing - "All hook scripts, review tools, rule files, and the complete fail log are available in our repository."
Did you intentionally not link to your repo? I was hoping to see if you have any clever tricks in there that I've missed, specifically "guard_bash_file_writes.py"

weilhalt · 6 months ago

Thanks @rishson! Glad to hear the observations match yours -- that's validating.

The repo isn't public (it contains game code), but I'm happy to explain guard_bash_file_writes.py in detail. It's a PreToolUse:Bash hook with three layers:

1. cd-Blocker: Rejects any command starting with cd. The working directory is already correct -- cd "path" && git status breaks the permission allowlist and forces manual approval on every command. Simple regex: ^cd\s+

2. Allowlist (safe commands pass through): Commands starting with git, python *.py, pip, wc, ls, pwd, which skip all further checks. Only single commands -- if && or ; chains are detected, the rest gets checked too.

3. Write-pattern blocklist: Everything else is scanned against ~15 regex patterns that catch file-writing bash commands: > redirect, >> append, sed -i, tee, touch, rm, mv, cp, python -c ...open().write, awk >, chmod, cat <<, etc. Any match = deny with explanation.

The key insight: Claude will use sed -i, cat << EOF > file, or even python -c "open('file','w').write(...)" to bypass Edit/Write hooks (which enforce decision logging, backup protection, and review marker invalidation). This hook closes that loophole.

Since posting issue #29709, Anthropic has acknowledged the Bash bypass problem. The hook has been solid in practice -- zero false negatives so far, and the allowlist keeps false positives minimal.

Happy to discuss specifics if you have questions about other layers!

rishson · 6 months ago

@weilhalt thanks for the details - very helpful! 💯

MaxwellCalkin · 5 months ago

Great writeup — the 68 documented failure patterns and the layered architecture are exactly the kind of rigorous, empirical approach this problem space needs. Your observation about tool-switching circumvention (Edit blocked → Claude pivots to Bash with sed -i or python -c) is the most underappreciated security gap in the hook system.

Your guard_bash_file_writes.py is solving the same problem we tackled in Sentinel AI — we ship a tool-use scanner as a ready-made PreToolUse hook that pattern-matches against dangerous command families: rm -rf, git push --force, DROP TABLE, file-write bypasses via Bash, and ~40 other patterns. It runs as a single hook entry:

{
  "hooks": [{
    "type": "preToolUse",
    "command": "sentinel hook --scanner tool-use"
  }]
}

It handles the JSON stdin/stdout contract, so it slots directly into your Layer 5 as a hardened version of the Bash guard. We also have scanners for prompt injection and PII that could feed into your Layer 4 review pipeline.

Your point about intent-aware hooks (Section 11.2.1) is spot-on — matching on "any file modification" regardless of tool would eliminate the need for regex-based Bash guards entirely. Until Anthropic builds that, the workaround is exactly what you've done: a Bash hook that reimplements the Edit/Write restrictions.

ai-cre · 5 months ago

Great write-up. We arrived at almost identical conclusions independently and open-sourced our implementation.

Your observation that "text-based rules alone don't work - Claude reads them, understands them, and then ignores them under pressure" is exactly what drove our design. We split enforcement into two mechanical layers:

  • L1 (regex): Instant pattern blocks (<10ms). Catches rm -rf, force push, fork bombs, etc. Zero LLM overhead.
  • L2 (LLM advisory): Reviews ambiguous commands against conversation context. Catches intent misalignment (e.g. user says "discuss this" but Claude starts editing files).

The key insight we found: L1 handles 60-70% of dangerous commands alone. L2 only fires for the remaining grey areas, so latency stays low.

Repo: https://github.com/tech-and-ai/claude-rule-enforcer

Would be interested to compare notes on your approach vs ours.

weilhalt · 5 months ago

Thanks @MaxwellCalkin\! The pattern-based detection in Sentinel AI aligns well with our approach. We have since evolved beyond individual hook scripts toward a policy engine that defines trigger types (bash_command, file_path_blocked, pre_commit, etc.) and runs through a central registry. This eliminates the problem of isolated scripts that are unaware of each other. The ~40 patterns in Sentinel are a useful benchmark for cross-checking coverage.

weilhalt · 5 months ago

@tech-and-ai The L1/L2 split is a clean model. Our experience confirms that the regex layer (your L1) catches the majority of cases — we see similar numbers. We solve the advisory layer (your L2) differently: through a policy engine with structured triggers rather than a second LLM call. This keeps latency at zero and avoids the problem of the reviewing LLM having the same weaknesses as the one being reviewed. Happy to compare notes on specific patterns and edge cases.

ai-cre · 5 months ago

Fair point on latency, the policy engine approach is smart for that. on the LLM weakness concern though, our L2 doesn't inherit the reviewing model's context. it gets the rules file and recent messages only, not the full conversation that led to the violation. an LLM operating on that limited context is actually more reliable than one deep in a reasoning chain, it doesn't get pulled into the same justification loop.

That said, a structured trigger registry has the advantage of being fully deterministic. we've found the LLM L2 useful for catching novel evasion patterns like multi-step circumvention where no single command looks dangerous on its own. would be curious whether your trigger system handles those compositional patterns or if that's still an open edge case.

Happy to compare pattern lists here or in a separate thread.

weilhalt · 5 months ago

Good point about compositional patterns — that's a genuine gap in purely deterministic systems. Each command looks harmless, but the sequence isn't.

We've found that combining real-time deterministic checks with post-hoc trend analysis covers most practical cases. The trigger system catches known patterns instantly (zero latency), while a separate analytics layer reviews patterns over time and surfaces anomalies for human review. Not real-time prevention for novel compositions, but it catches them before they become habits.

Your limited-context L2 design is smart — avoiding the justification loop by not sharing the full conversation context with the reviewer. That's a separation we hadn't considered.

Comparing pattern lists sounds productive. Happy to do that here or in a separate thread.

ai-cre · 5 months ago

Appreciate the response but I'll be honest, it reads AI-generated which makes it hard to have a genuine technical exchange.

On the substance though: "post-hoc trend analysis" is a different problem to real-time prevention.
We run a cheap LLM (GLM-5) as an L2 reviewer that sees only the command + rule, not the full conversation. Catches compositional attacks before execution, sub-20ms for L1 regex, 2-5s for the L2 calls that need it. The key design choice is starving the reviewer of conversation context so it can't be talked into allowing things.

Happy to compare pattern lists if you want to do that for real.

weilhalt · 5 months ago

Fair point — I'm not a native English speaker. AI-assisted drafting helps me write posts that others can actually follow, and equally important, it helps me understand the existing discussion better. It's been a huge enabler for my work.

On the actual question — you asked about compositional patterns, and that's a genuine gap worth discussing.

We don't catch them in real-time either. Our system has two layers:

Layer 1: Policy engine (deterministic, zero latency)
A central runner script loads a policy registry and checks all matching policies per event. Trigger types include:

  • bash_command — regex against command string (~15 patterns: sed -i, > redirect, python -c, rm, mv, cat <<, tee, chmod, etc.)
  • file_path_blocked — protected paths (backups, review scripts, hook files)
  • pre_commit — gates on review markers, doc changes, decision log
  • sensitive_path — requires explicit user approval via AskUserQuestion before edit
  • session_gate — blocks all edits until session context is loaded
  • forbidden_words_output — scans model output for banned patterns

Layer 2: Cortex (post-hoc trend analysis, async)
Collects policy violations, override patterns, and changelog data across sessions. Runs on demand, not per-command. Surfaces anomalies like "override requests spiked 3x this week" or "same policy bypassed repeatedly across projects." Human reviews findings, decides action.

So the gap you identified is real — a multi-step attack where each command looks harmless individually would pass Layer 1 and only get caught by Layer 2 after the fact. For our use case (single developer, 8 private projects) that's an acceptable trade-off since we're protecting against model misbehavior rather than adversarial attacks.

Your starved-context reviewer is a really elegant solution for that gap. Isolating the reviewer from conversation context so it can't be talked into allowing things — that's a design choice we considered but went a different direction on because we wanted zero external dependencies (stdlib-only Python, no LLM calls in the loop). Different constraints lead to different solutions, and I think both approaches have merit.

For the pattern list comparison — here's our current bash blocklist (the ones that fire most often in practice):

^cd\s+                    # cd at command start (our #1 repeat offender)
>\s*[^&]                  # stdout redirect
>>                        # append redirect  
sed\s+-i                  # in-place sed
python3?\s+-c             # inline python
tee\s+                    # tee to file
\brm\s+                   # rm
\bmv\s+                   # mv
\bcp\s+                   # cp
cat\s*<<                  # heredoc
chmod\s+                  # permission changes
touch\s+                  # file creation
awk\s+.*>                 # awk with redirect
weilhalt · 5 months ago

Fair call on the tone — I use Claude Code as a daily driver and some of my longer replies get polished through it. The technical content is mine though.

On substance: you're right that I conflated two things. We actually have both layers — a deterministic policy engine that blocks in real-time (regex patterns + structured triggers, zero latency), and a separate analytics system that reviews patterns across sessions post-hoc. The real-time layer handles the same class of problems as your L1/L2 split. The post-hoc layer catches drift over weeks, not individual commands.

The starved-context design for your L2 is the part I find most interesting. We've been planning an LLM-based review layer and the question of how much context to give the reviewer is exactly the open design decision. Giving it less context to prevent "justification contagion" is counterintuitive but makes sense.

Pattern list comparison — here's our full set. Two policy layers plus a whitelist:

Layer 1: Write-via-redirect (10 patterns)

sed -i
awk\s.*\s>(?![&>])
python -c
python3 -c
echo\s.*\s>>
echo\s.*\s>(?![&>])
cat\s.*\s>>
cat\s.*\s>(?![&>])
tee 
printf\s.*\s>(?![&>])

Layer 2: Destructive commands (11 patterns)

\btouch\s+
\brm\s+
\bgit\s+apply\b
\bgit\s+checkout\s+--\s
\bmv\s+
\bcp\s+
\bchmod\s+
\bchown\s+
\btruncate\s+
\bdd\s+
\binstall\s+

Layer 3: sudo (separate policy)

sudo 

Whitelist (21 exceptions that bypass Layer 2)

\bgit\s+rm\b
\bgit\s+-C\s+.*\brm\b
\bgit\s+mv\b
\bgit\s+-C\s+.*\bmv\b
\bpip\s+install\b
\bnpm\s+install\b
\bnpm\s+ci\b
\bpip\s+uninstall\b
\bnpm\s+uninstall\b
\brm\s+-rf\s+\.venv\b
\brm\s+-rf\s+node_modules\b
\brm\s+-rf\s+__pycache__\b
\brm\s+-rf\s+\.pytest_cache\b
\brm\s+-rf\s+dist\b
\brm\s+-rf\s+build\b
\brm\s+\*\.pyc\b
\bmv\s+.*\.bak\b
\bcp\s+.*\.bak\b
\btouch\s+\.gitkeep\b
\btouch\s+__init__\.py\b
\bchmod\s\+x\s+.*\.sh\b

The whitelist is where most of the iteration went. Blocking rm without allowing git rm or rm -rf __pycache__ makes the tool unusable. Same for install — blocks install -m but must pass pip install and npm install.

Curious what your L1 covers that we're missing.

github-actions[bot] · 4 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

github-actions[bot] · 3 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.