Skip to content

fix(hook): respect Claude Code deny/ask permission rules on rewrite#576

Open
FlorianBruniaux wants to merge 18 commits intodevelopfrom
fix/hook-bypass-permission
Open

fix(hook): respect Claude Code deny/ask permission rules on rewrite#576
FlorianBruniaux wants to merge 18 commits intodevelopfrom
fix/hook-bypass-permission

Conversation

@FlorianBruniaux
Copy link
Collaborator

Problem

The PreToolUse hook (rtk-rewrite.sh) was emitting permissionDecision: "allow" on every rewritten command, silently bypassing deny and ask rules defined in .claude/settings.json.

Concrete example: user has Bash(git push --force) in deny rules → hook rewrites to rtk git push --force with auto-allow → command executes without confirmation. P1-critical bug reported by the community and confirmed by @pszymkowiak.

Closes #260

Solution

Permission logic lives in the Rust binary (rtk rewrite), not the shell. The hook interprets exit codes.

Exit code protocol:

Exit Stdout Meaning
0 rewritten No deny/ask rule matched — hook auto-allows
1 (none) No RTK equivalent — passthrough unchanged
2 (none) Deny rule matched — CC native deny handles it
3 rewritten Ask rule matched — rewrite without permissionDecision (CC prompts user)

Changes

  • src/permissions.rs (new) — loads Bash(...) deny/ask rules from all 4 Claude Code settings files ($PROJECT_ROOT/.claude/settings.json, .local.json, ~/.claude/settings.json, .local.json). Checks commands including compound (&&, ||, |, ;) and returns Allow / Deny / Ask verdict. Deny takes priority over Ask. 16 unit tests.
  • src/rewrite_cmd.rs — after finding a rewrite, calls check_command() on the original command. Exits 0/2/3 accordingly.
  • hooks/rtk-rewrite.sh + .claude/hooks/rtk-rewrite.sh — handle exit codes 2 (passthrough silently) and 3 (rewrite without permissionDecision). Hook version bumped 2→3.
  • src/hook_check.rsCURRENT_HOOK_VERSION 2→3 so existing users get the upgrade prompt.
  • ARCHITECTURE.md — updated module count.

Test plan

  • 16 unit tests in src/permissions.rs (exact match, prefix match, wildcard, deny precedence, compound commands, word boundary)
  • Full test suite: 915 passed
  • Manual: echo '{"permissions":{"deny":["Bash(git push --force)"]}}' > .claude/settings.jsonrtk rewrite "git push --force" exits 2
  • Manual: ask rule → exits 3 with rewritten command in stdout
  • Manual: rtk rewrite "git status" → exits 0 as before (no regression)
  • set -euo pipefail bug fixed in .claude/hooks/rtk-rewrite.sh (use || EXIT_CODE=$? pattern)

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings March 13, 2026 14:20
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Claude Code permission-awareness to the rtk rewrite flow so hooks can avoid auto-allowing rewritten commands when deny/ask rules match, preserving user intent while still enabling rewrites.

Changes:

  • Introduces src/permissions.rs to load Claude Code settings and evaluate deny/ask Bash rules against commands.
  • Updates rtk rewrite to return a richer exit-code protocol (0/1/2/3) to drive hook behavior (auto-allow vs prompt vs passthrough).
  • Updates hook scripts and hook versioning to handle the new exit codes, and documents the new module in ARCHITECTURE.md.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/rewrite_cmd.rs Adds permission checking and new exit-code protocol for rtk rewrite.
src/permissions.rs Implements settings discovery + deny/ask rule matching (with unit tests).
src/main.rs Registers the new permissions module.
src/hook_check.rs Bumps expected hook version to 3.
hooks/rtk-rewrite.sh Updates hook logic to respect deny/ask via rtk rewrite exit codes.
.claude/hooks/rtk-rewrite.sh Same as above, with audit logging retained.
ARCHITECTURE.md Documents the new module and updates module counts.
Comments suppressed due to low confidence (1)

ARCHITECTURE.md:301

  • The module counts in this section are internally inconsistent after the update: it now claims “Total: 65 modules (38 command + 27 infrastructure)”, but the breakdown immediately below still lists different numbers (e.g. “Command Modules: 34”). Please reconcile these counts (and the “Complete Module Map (30 Modules)” heading earlier in this section if it’s still accurate).
**Total: 65 modules** (38 command modules + 27 infrastructure modules)

### Module Count Breakdown

- **Command Modules**: 34 (directly exposed to users)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to +35
PermissionVerdict::Ask => {
print!("{}", rewritten);
std::process::exit(3);
}
mpecan added a commit to mpecan/tokf that referenced this pull request Mar 13, 2026
The PreToolUse hook was emitting permissionDecision: "allow" on every
rewritten command, silently bypassing deny and ask rules configured in
Claude Code settings.json. For example, a user with Bash(git push --force)
in their deny list would have the command rewritten and auto-allowed.

Add a permissions module that loads Bash(...) deny/ask rules from all 4
Claude Code settings files and checks the original command before deciding
the hook response:

- Deny match → pass through (return false), native deny handles it
- Ask match → rewrite but omit permissionDecision, tool prompts user
- No match → rewrite with permissionDecision: "allow" (existing behavior)

Same fix applied to Claude Code, Gemini CLI, and Cursor hook handlers.

Ref: rtk-ai/rtk#576

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mpecan added a commit to mpecan/tokf that referenced this pull request Mar 13, 2026
The PreToolUse hook was emitting permissionDecision: "allow" on every
rewritten command, silently bypassing deny and ask rules configured in
Claude Code settings.json. For example, a user with Bash(git push --force)
in their deny list would have the command rewritten and auto-allowed.

Add a permissions module that loads Bash(...) deny/ask rules from all 4
Claude Code settings files and checks the original command before deciding
the hook response:

- Deny match → pass through (return false), native deny handles it
- Ask match → rewrite but omit permissionDecision, tool prompts user
- No match → rewrite with permissionDecision: "allow" (existing behavior)

Same fix applied to Claude Code, Gemini CLI, and Cursor hook handlers.

Ref: rtk-ai/rtk#576

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aeppling
Copy link
Contributor

follow-up wildcards issue

The PR's pattern matching only handles * at the end of a pattern. Claude Code's permission system supports *
anywhere.

Works:

  • Bash(git push --force) — exact match
  • Bash(git push *) — trailing wildcard
  • Bash(sudo:*) — legacy colon syntax

Doesn't work:

  • Bash(* --force) — leading wildcard. User wants to deny any command ending with --force. The PR sees * --force,
    doesn't find a trailing *, falls through to exact match, matches nothing. Hook auto-allows.
  • Bash(git * main) — middle wildcard. User wants to deny git push main, git merge main, etc. Same problem — no
    trailing *, treated as literal string, matches nothing.
  • Bash(* --help *) — multiple wildcards. Same.

mpecan added a commit to mpecan/tokf that referenced this pull request Mar 13, 2026
…289)

## Summary

- The `PreToolUse` hook was emitting `permissionDecision: "allow"` on
every rewritten command, silently bypassing deny and ask rules
configured in Claude Code `settings.json`
- Adds a `permissions` module that loads `Bash(...)` deny/ask rules from
all 4 Claude Code settings files and checks the **original** command
before deciding the hook response
- **Deny** → pass through silently, letting Claude Code's native deny
handle it
- **Ask** → rewrite but omit `permissionDecision`, so the tool prompts
the user
- **Allow** → rewrite with `permissionDecision: "allow"` (existing
behavior)
- Same fix applied to Claude Code, Gemini CLI, and Cursor hook handlers

Ref: rtk-ai/rtk#576

## Test plan

- [x] 17 unit tests in `permissions.rs` (exact match, prefix match,
wildcard, deny precedence, compound commands, word boundary, settings
file integration)
- [x] Full test suite: 1324 passed, 0 failed
- [x] Clippy clean, fmt clean
- [x] Existing hook handle and install tests still pass

Credit: rtk-ai/rtk#576 - thank you
@FlorianBruniaux

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@pszymkowiak
Copy link
Collaborator

Ran a second deeper review focused on edge cases. Found 2 additional gaps beyond @aeppling's wildcard finding.

Bug 1 (Critical): deny rules skipped for non-RTK commands

check_command() only runs inside the Some(rewritten) branch (rewrite_cmd.rs:38). When rewrite_command returns None (no RTK equivalent), exit 1 is returned immediately — deny rules are never consulted.

Commands that return None include everything in IGNORED_PREFIXES: rm, mv, cp, chmod, kill, bash, python3 -c, etc.

Concrete failure: user adds Bash(rm -rf *) to deny list. Claude runs rm -rf /. Hook calls rtk rewrite "rm -rf /" → exit 1 (no rewrite) → hook passes through. Deny rule never checked.

Fix: run check_command before the rewrite match, not inside it.

Bug 2 (Critical): *:* pattern matches nothing

Bash(*:*) — a user expects this to block everything. strip_suffix('*')*:, trim_end_matches(':')*, then checks cmd == "*" → false. Pattern silently matches nothing.

Fix: add || prefix == "*" to the empty-prefix guard at permissions.rs:180.

@aeppling's wildcard finding confirmed

Leading (* --force), middle (git * main), and multiple (* --force *) wildcards all silently return false. Only trailing wildcards work.

Summary

Bug Impact Status
Deny skipped for non-RTK commands Security bypass for rm, mv, kill, etc. New finding
*:* matches nothing Catch-all deny rule is a no-op New finding
Leading/middle wildcards @aeppling's finding, confirmed Known

Core architecture is solid — these are fixable without redesign. Happy to re-review after fixes.

@FlorianBruniaux
Copy link
Collaborator Author

@pszymkowiak @aeppling All four findings addressed. Two commits on this branch:

53b8366 — rewrite_cmd.rs (Bug 1 + Bug 4)

  • check_command() now runs before registry::rewrite_command(), so non-RTK commands (rm, kill, python3 -c) hit deny rules instead of bypassing them.
  • Added explicit stdout().flush() before each process::exit().

6ddc805 — permissions.rs (Bug 2 + Bug 3)

  • *:* catch-all: after strip_suffix('*') + trim, we now detect an empty-or-* prefix and return true.
  • Leading/middle/multi wildcards: new glob_matches() function with segment anchoring (first = prefix, last = suffix, middle = str::find in order). * --force, git * main, git * --force * all work correctly.

8 new tests cover all the fixed cases, including integration variants with check_command_with_rules. 945 passing, 0 failures.

@FlorianBruniaux FlorianBruniaux force-pushed the fix/hook-bypass-permission branch 2 times, most recently from 6bf2100 to 0841ec3 Compare March 16, 2026 10:58
pszymkowiak and others added 4 commits March 16, 2026 14:58
* fix: P1 exit codes, grep regex perf, SQLite concurrency

Exit code propagation (same pattern as existing modules):
- wget_cmd: run() and run_stdout() now exit on failure
- container: docker_logs, kubectl_pods/services/logs now check
  status before parsing JSON (was showing "No pods found" on error)
- pnpm_cmd: replace bail!() with eprint + process::exit in
  run_list and run_install

Performance:
- grep_cmd: compile context regex once before loop instead of
  per-line in clean_line() (was N compilations per grep call)

Data integrity:
- tracking: add PRAGMA journal_mode=WAL and busy_timeout=5000
  to prevent SQLite corruption with concurrent Claude Code instances

Signed-off-by: Patrick <patrick@rtk.ai>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>

* fix: address review findings on P1 fixes

- tracking: WAL pragma non-fatal (NFS/read-only compat)
- wget: forward raw stderr on failure, track raw==raw (no fake savings)
- container: remove stderr shadow in docker_logs, add empty-stderr
  guard on all 4 new exit code paths for consistency with prisma pattern

Signed-off-by: Patrick <patrick@rtk.ai>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>

---------

Signed-off-by: Patrick <patrick@rtk.ai>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
* fix: raise output caps for grep, git status, and parser fallback (#617, #618, #620)

- grep: per-file match cap 10 → 25, global max 50 → 200
- git status: file list caps 5/5/3 → 15/15/10
- parser fallback: truncate 500 → 2000 chars across all modules

These P0 bugs caused LLM retry loops when RTK returned less signal
than the raw command, making RTK worse than not using it.

Fixes #617, #618, #620

Signed-off-by: Patrick <patrick@rtk.ai>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>

* fix: update README example and add truncation tests for modified/untracked

- parser/README.md: update example from 500 → 2000 to match code
- git.rs: add test_format_status_modified_truncation (cap 15)
- git.rs: add test_format_status_untracked_truncation (cap 10)

Signed-off-by: Patrick <patrick@rtk.ai>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>

* refactor: extract output caps into [limits] config section

Move hardcoded caps into config.toml so users can tune them:

  [limits]
  grep_max_results = 200      # global grep match limit
  grep_max_per_file = 25      # per-file match limit
  status_max_files = 15       # staged/modified file list cap
  status_max_untracked = 10   # untracked file list cap
  passthrough_max_chars = 2000 # parser fallback truncation

All 8 modules now read from config::limits() instead of hardcoded
values. Defaults unchanged from previous commit.

Signed-off-by: Patrick <patrick@rtk.ai>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>

---------

Signed-off-by: Patrick <patrick@rtk.ai>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
…es (#662)

* feat(.claude): add /rtk-triage skill — orchestrated PR+issue cross-analysis

New skill that runs issue-triage + pr-triage in parallel then produces
a cross-analysis layer that neither skill can do individually:

- Double coverage detection: identifies when 2+ PRs target the same issue
  (via body scan + file overlap), recommends which to keep/close
- Security gap detection: for security review issues, maps each finding
  to a PR (or flags it as uncovered)
- P0/P1 bugs without PR: groups by pattern to suggest sprint batching
- Our dirty PRs: identifies probable cause (conflict with sibling PR,
  needs rebase, missing linked issue)

Output is saved automatically to claudedocs/RTK-YYYY-MM-DD.md.

Usage: /rtk-triage           (French, auto-save)
       /rtk-triage en        (English output)

Signed-off-by: Florian Bruniaux <florian@bel-etage.com>
Signed-off-by: Florian BRUNIAUX <florian@bruniaux.com>

* docs(architecture): update module count to 66

Sync ARCHITECTURE.md with current main.rs state.
Previous count (60) was stale since several modules were added
(dotnet_cmd, dotnet_format_report, dotnet_trx, npm_cmd, gt_cmd, etc.).

Signed-off-by: Florian Bruniaux <florian@bel-etage.com>
Signed-off-by: Florian BRUNIAUX <florian@bruniaux.com>

---------

Signed-off-by: Florian Bruniaux <florian@bel-etage.com>
Signed-off-by: Florian BRUNIAUX <florian@bruniaux.com>
- git stash: pass unknown subcommands (save, branch, clear) through
  instead of silently falling back to git stash push
- git branch: add --show-current, --set-upstream-to, --format, --sort
  to flag detection so they don't get overridden by -a injection
- pip: replace bail!() with passthrough for unknown subcommands
  (freeze, download, wheel, etc.)

Fixes #600

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
@pszymkowiak
Copy link
Collaborator

@FlorianBruniaux This PR has a merge conflict with develop (likely src/main.rs). Could you rebase on develop and enable "Allow edits by maintainers" so we can merge faster?

git fetch upstream develop
git rebase upstream/develop
# resolve conflicts
git push --force-with-lease

This is P1 security — we'd like to get it in the next release. Thanks!

pszymkowiak and others added 13 commits March 17, 2026 17:18
cargo fmt diffs in config.rs, git.rs, playwright_cmd.rs were failing
the fmt CI check, which cascaded to block clippy/test/security on
PRs #632, #635, #638. Also fixes all clippy warnings: dead code
annotations, iterator simplifications, assert patterns, and
unnecessary allocations.

Signed-off-by: Patrick Szymkowiak <patrick@rtk-ai.app>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
* fix: discover classifies absolute paths like /usr/bin/grep (#485)

Normalize absolute binary paths before classification:
/usr/bin/grep → grep, /bin/ls → ls, /usr/local/bin/git → git

Adds strip_absolute_path() helper + 5 tests.

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>

* fix: discover and rewrite support git global options -C, --no-pager, etc. (#163)

Strip git global options (-C <path>, -c <key=val>, --git-dir, --work-tree,
--no-pager, --no-optional-locks, --bare, --literal-pathspecs) before
classification so git -C /tmp status is recognized as rtk git.

Rewrite preserves global options: git -C /tmp status → rtk git -C /tmp status

Adds GIT_GLOBAL_OPT lazy_static regex + strip_git_global_opts() helper + 6 tests.

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>

---------

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
When running `rtk cargo clippy -p my-crate -- -D warnings`, Clap with
`trailing_var_arg = true` preserves the `--` in parsed args when flags
precede it. `restore_double_dash()` then added a second `--`, producing
`cargo clippy -p my-crate -- -- -D warnings`. This caused rustc to
interpret `-D` as a filename instead of a lint flag.

Fix: skip restoration when args already contain `--` (Clap preserved it).

Fixes #496

Signed-off-by: Ousama Ben Younes <benyounes.ousama@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- PR template reminds contributors to target develop
- CI workflow labels PRs targeting master with 'wrong-base' and posts a comment
- Excludes develop→master PRs (maintainer releases)

Signed-off-by: Patrick <patrick@rtk-ai.com>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
Add Language::Data variant for data formats (JSON, YAML, TOML, XML, CSV, etc.)
with empty comment patterns to prevent comment stripping. AggressiveFilter
falls back to MinimalFilter for data files.

Fixes #464

Signed-off-by: Ousama Ben Younes <benyounes.ousama@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…439) (#563)

rtk find outputs a grouped format incompatible with pipe consumers
like xargs, grep, wc, sort. Skip rewrite when find/fd is followed
by a pipe, preserving native one-per-line output.

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
…gh (#427) (#564)

When compact_diff truncates output, append a hint line so Claude knows
how to get the full diff: [full diff: rtk git diff --no-compact]

Also fix --no-compact flag being passed to git (causing usage error)
and remove decorative emoji from compact_diff output.

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
#632)

4 P1 bugs where git exit codes were swallowed:
- git diff: failure silently printed empty stat output
- git status (with args): failure was filtered instead of propagated
- git commit: failure printed "FAILED" but returned Ok(()) breaking pre-commit hooks
- git branch (list mode): failure was silently ignored

All now follow the established pattern: eprint stderr, track raw==raw, process::exit(code).

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
…635)

* feat: add 5 new TOML built-in filters (ollama, nx, gradle, spring-boot, jira)

New filters for commands not covered by Rust modules:
- ollama: strip ANSI spinners, keep final text response (#624)
- nx: strip Nx monorepo noise, keep build results (#444)
- gradle/gradlew: strip UP-TO-DATE tasks, keep build summary (#147)
- spring-boot: strip banner and verbose logs, keep startup/errors (#147)
- jira: strip blanks, truncate wide columns (#524)

All 5 filters pass inline tests via rtk verify (123/123).
Updated builtin filter count: 47 -> 52.

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>

* feat: add 5 more TOML filters (turbo, mise, just, task, yadm)

New filters for task runners and git wrapper:
- turbo: strip cache/Tasks/Duration noise, keep task output (#531)
- mise: strip install/download progress, keep task results (#607)
- just: strip blanks and recipe headers, keep output (#607)
- task: strip task headers and up-to-date lines, keep results (#607)
- yadm: strip hint lines, compact git-like output (#567)

All verified with fake binaries through catch-all TOML engine.
137/137 TOML tests pass, 934 Rust tests pass.
Updated builtin filter count: 52 -> 57.

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>

---------

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
Git status output used emojis (📌, 📝, ❓, ✅, ⚠️) that confuse
non-Claude LLMs (GPT, etc.) causing retry loops. Replace with plain
text labels (branch:, modified:, staged:, untracked:, conflicts:).

Also add "clean — nothing to commit" when working tree is clean,
so LLMs understand the repo state without ambiguity.

Before: 📌 master
After:  branch: master
        clean — nothing to commit

Fixes #603

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
The PreToolUse hook was emitting `permissionDecision: "allow"` on every
rewritten command, bypassing deny and ask rules in .claude/settings.json.

- Add `src/permissions.rs`: loads Bash deny/ask rules from all 4 Claude
  Code settings files (project + global, settings.json + settings.local.json),
  checks commands (including compound && / || / | / ;) and returns
  Allow / Deny / Ask verdict. 16 unit tests.
- Modify `src/rewrite_cmd.rs`: after finding a rewrite, check the original
  command against permissions. Exit 0 = allow (auto-approve rewrite),
  exit 2 = deny (passthrough, let CC native deny handle it),
  exit 3 = ask (print rewrite but no permissionDecision, CC prompts user).
- Update both hook files to handle exit codes 2 and 3. Version bumped 2→3.
- Bump `CURRENT_HOOK_VERSION` 2→3 in `hook_check.rs` so users with the old
  hook get the upgrade prompt.
- Fix set -euo pipefail bug in .claude/hooks/rtk-rewrite.sh: capture exit
  code with `|| EXIT_CODE=$?` instead of bare assignment.

Fixes #260

Signed-off-by: Florian BRUNIAUX <florian@bruniaux.com>
Bug 1 (Critical): check_command() was called inside Some(rewritten),
so non-RTK commands (rm, kill, python3 -c) bypassed deny rules entirely.
Move verdict check before registry::rewrite_command() so all commands
are evaluated regardless of whether RTK has an equivalent.

Bug 4 (Medium): print!() before process::exit() could leave stdout
unflushed. Add explicit std::io::stdout().flush() after each print!().

Add Eq derive to PermissionVerdict (required for == comparison).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Florian BRUNIAUX <florian@bruniaux.com>
Bug 2 (Critical): *:* catch-all matched nothing. strip_suffix('*') left
"*:" which after trim became "*" (non-empty), so the branch returned
false instead of true. Fix: detect empty-or-star prefix after stripping.

Bug 3 (Medium): leading wildcards ("* --force"), middle wildcards
("git * main"), and multi-wildcard patterns ("git * --force *") fell
through to exact match, silently failing. Add glob_matches() with
character-level segment anchoring: first segment must be prefix, last
must be suffix, middle segments found via str::find in order.

Colon normalization in glob_matches(): "sudo:*" -> "sudo *" so both
fast path and glob path interpret colon syntax consistently.

New tests: test_star_colon_star_matches_everything,
test_leading_wildcard, test_leading_wildcard_no_partial,
test_middle_wildcard, test_middle_wildcard_no_match,
test_multiple_wildcards, test_deny_with_leading_wildcard,
test_deny_star_colon_star.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Florian BRUNIAUX <florian@bruniaux.com>
validate-docs.sh counts mod declarations in main.rs (66).
Update ARCHITECTURE.md total to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Florian BRUNIAUX <florian@bruniaux.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants