You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat(security): bash command security + auto-allow hardening
Compound command splitting: add split_bash_command() in Perl that
tokenizes Bash commands respecting quotes, subshells, and backticks,
then splits by unquoted |, &&, ||, ;, & operators. Redirections are
stripped from each segment. For deny rules, ANY segment match denies the
whole command. For allow rules, ALL segments must be covered.
Read-only auto-allow: mirror CC's readonly command list and safety regex
pattern. Simple commands (cat, head, tail, wc, stat, etc.) and custom
regex commands (echo, ls, find, cd, jq, etc.) are auto-allowed when all
path arguments resolve inside cwd or allowed directories.
Internal tool auto-allow: Agent, Skill, and Glob now get explicit allow
decisions (permissionDecision: allow) instead of passthrough, preventing
CC's native confirmation dialogs.
Overlay proposal anchoring: Bash proposals changed from ^<cmd>\s to
^<cmd>(\s[safe-chars]*)?$ using CC's safe character class to block
compound operator injection in proposed rules.
Additional allowed directories: new optional allowed_dirs field in
passthru.json v2. Bootstrap imports additionalAllowedWorkingDirs from
CC settings. Path validation for Read/Edit/Write/Grep auto-allow and
readonly Bash commands checks cwd plus all allowed dirs.
* docs(plan): bash command security + auto-allow hardening
* feat(overlay): desktop notification via OSC 777 before overlay prompt
* feat(overlay): WebSearch auto-allow + overlay queue lock for concurrent prompts
in read-only Bash commands are checked against cwd AND each allowed dir.
323
+
324
+
`load_allowed_dirs` in `hooks/common.sh` reads `allowed_dirs` from all four
325
+
rule files, concatenates, and deduplicates. It is separate from `load_rules`
326
+
to preserve the `{version, allow, deny, ask}` contract. Bootstrap imports
327
+
Claude Code's `additionalAllowedWorkingDirs` from settings and writes them
328
+
to `allowed_dirs` in `passthru.imported.json`.
329
+
330
+
See `docs/rule-format.md` for the schema and `CONTRIBUTING.md` for guidance
331
+
on extending `allowed_dirs` support.
332
+
333
+
## Internal tool auto-allow
334
+
335
+
Agent, Skill, and Glob are always auto-allowed with an explicit `allow`
336
+
decision (not passthrough). This runs before rule loading (step 3b in
337
+
`pre-tool-use.sh`) so it is fast and cannot be affected by broken rule files.
338
+
These tools are logged with source `passthru-internal`.
339
+
340
+
ToolSearch, TaskCreate, and other CC-internal tools remain in the step 7
341
+
passthrough list and emit `{"continue": true}`.
342
+
247
343
## Releases
248
344
249
345
Use the `release-tools:new` skill (`/release-tools:new`) to cut a new release. The skill handles version calculation, the GitHub release, and the description prompt.
@@ -291,3 +387,6 @@ The release flow in one-line form:
291
387
* Changing the overlay UI or keyboard flow: `scripts/overlay-dialog.sh` is the TUI, `scripts/overlay.sh` is the multiplexer dispatcher, and `scripts/overlay-propose-rule.sh` proposes the regex on A/D. Test via `PASSTHRU_OVERLAY_TEST_ANSWER`; see `tests/overlay.bats` for the stub-tmux pattern.
292
388
* Changing ask-rule semantics: the merged document-order logic sits in `hooks/common.sh` (`find_first_match`) and `hooks/handlers/pre-tool-use.sh`. Ask rule parsing + validation is in `validate_rules` + `load_rules`. The verifier's conflict and shadowing checks in `scripts/verify.sh` must also cover `ask[]`.
293
389
* Adding a new overlay multiplexer backend: add detection + launch lines in `scripts/overlay.sh` (search for the tmux / kitty / wezterm branches) and a stub fixture in `tests/fixtures/overlay/`. The shared detector helper lives in `hooks/common.sh` (`detect_overlay_multiplexer`).
390
+
* Adding a new readonly command: add the command to `PASSTHRU_READONLY_COMMANDS` (simple), `PASSTHRU_READONLY_TWO_WORD_COMMANDS` (two-word), or `PASSTHRU_READONLY_CUSTOM_REGEXES` (custom regex) in `hooks/common.sh`. Test via `tests/hook_handler.bats`. See `CONTRIBUTING.md` section "Extending the readonly command list".
391
+
* Changing compound command splitting: the splitter is `split_bash_command` in `hooks/common.sh` (inline perl). The per-segment matching logic is `match_all_segments` in the same file. Test via `tests/command_splitting.bats`.
392
+
* Working with allowed dirs: see `CONTRIBUTING.md` section "Working with `allowed_dirs`". Key functions are `load_allowed_dirs`, `_pm_path_inside_any_allowed`, and `permission_mode_auto_allows` (5th parameter) in `hooks/common.sh`.
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+49Lines changed: 49 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -83,6 +83,55 @@ Non-breaking additions (new optional fields, new optional top-level keys) do not
83
83
2. Cross-file checks (duplicates, conflicts, shadowing) live later in the script and operate on the merged rule set. Add new cross-file checks there.
84
84
3. Add bats tests in `tests/verifier.bats` covering the success and failure case. Fixtures go in `tests/fixtures/`.
85
85
86
+
## Extending the readonly command list
87
+
88
+
The readonly auto-allow list lives in `hooks/common.sh` across three arrays:
89
+
90
+
*`PASSTHRU_READONLY_COMMANDS` - simple commands using the generic safety regex (`^<cmd>(?:\s|$)[^<>()$\x60|{}&;\n\r]*$`). Add commands here when the generic regex is sufficient (no special flags or subcommands to worry about).
91
+
*`PASSTHRU_READONLY_TWO_WORD_COMMANDS` - two-word commands like `docker ps` that use the same generic safety regex with the full two-word prefix.
92
+
*`PASSTHRU_READONLY_CUSTOM_REGEXES` - full PCRE patterns for commands needing custom validation (e.g. `echo` rejects `$`/backticks, `find` rejects `-exec`/`-delete`, `jq` rejects `-f`/`--from-file`).
93
+
94
+
To add a new readonly command:
95
+
96
+
1. Decide which array it belongs in. Most simple commands go in `PASSTHRU_READONLY_COMMANDS`. Only use a custom regex when the generic safety pattern is insufficient.
97
+
2. Add the entry to the appropriate array in `hooks/common.sh`.
98
+
3. Add tests in `tests/hook_handler.bats` covering both the positive case (command auto-allowed) and the negative case (dangerous variant not auto-allowed).
99
+
4. Run the full test suite: `bats tests/*.bats`.
100
+
101
+
The list mirrors Claude Code's `readOnlyValidation.ts`. Check CC source when adding commands to keep the two lists in sync.
102
+
103
+
## Extending the compound command splitter
104
+
105
+
The compound command splitter (`split_bash_command` in `hooks/common.sh`) uses inline perl to tokenize Bash commands. It handles:
The per-segment matching algorithm (`match_all_segments` in `hooks/common.sh`) implements:
112
+
113
+
* Deny: ANY segment matching a deny rule blocks the whole command
114
+
* Allow: ALL segments must match. Different segments may match different rules
115
+
* Ask: ANY segment matching ask (with no deny) triggers ask
116
+
117
+
Tests live in `tests/command_splitting.bats` (splitter unit tests) and `tests/hook_handler.bats` (integration tests for compound matching in the hook).
118
+
119
+
When modifying the splitter:
120
+
121
+
1. Add tests in `tests/command_splitting.bats` first.
122
+
2. The fail-safe behavior (parse errors return original command as one segment) must be preserved.
123
+
3. The perl tokenizer handles all splitting and redirection stripping in a single process for performance.
124
+
125
+
## Working with `allowed_dirs`
126
+
127
+
The `allowed_dirs` field in passthru.json extends the set of trusted directories for path-based auto-allow. When adding or modifying `allowed_dirs` support:
128
+
129
+
*`load_allowed_dirs` in `hooks/common.sh` reads all four rule files and returns a deduplicated JSON array. It is separate from `load_rules` to preserve the `{version, allow, deny, ask}` contract.
130
+
*`_pm_path_inside_any_allowed` checks a path against both cwd and each allowed dir. It is used by `permission_mode_auto_allows` and `readonly_paths_allowed`.
131
+
*`permission_mode_auto_allows` accepts an optional 5th parameter (`allowed_dirs_json`). Callers that do not pass it get the old behavior (cwd only).
132
+
*`validate_rules` tolerates the `allowed_dirs` key and validates entries: must be an array of non-empty strings, rejects path traversal (`/../`).
133
+
* Bootstrap imports `additionalAllowedWorkingDirs` from CC's `settings.json` via `extract_allowed_dirs` and writes them to `allowed_dirs` in `passthru.imported.json`.
134
+
86
135
## Branch policy
87
136
88
137
`main` is protected on GitHub. All changes must go through pull requests. Direct pushes to `main` are blocked.
0 commit comments