lockdown: treat leading "cd <path>" as no-op for leading-token analysis #5

Closed
opened 2026-05-23 20:53:52 +00:00 by coilysiren · 1 comment
Owner

Originally filed by @coilysiren on 2026-05-14T19:25:41Z - https://github.com/coilysiren/cli-guard/issues/58

Problem

cli-guard's lockdown PreToolUse hook segment-splits a Bash invocation and validates each segment's leading token against the allowlist. Today, cd <path> && coily ... fails because segment 1's leading token is cd, not coily, even though cd is a shell builtin with no execve and the post-cd segment is the only thing that needs gating.

Observed today on a Mac session: harness denied cd /Users/kai/projects/coilysiren/cli-guard with no surfaced workaround. The denial fires during the exact kind of work the wrapper is meant to enable (cross-repo coily invocations bound to a specific commit-scope).

Proposed change

In the segment loop (see lockdown/hook.go segment validation), if a segment matches a bare cd <path> shape with no shell metacharacters and <path> resolves to a real directory, skip it from leading-token analysis and let the next segment speak for the chain. The chdir then happens naturally in bash when Claude runs the approved command. No hook rewrite needed.

Boundary considerations

  • Path arg must pass the existing shell-metachar reject (no $(...), backticks, globs, unescaped spaces).
  • Path must resolve to a real directory at validation time. Reject otherwise.
  • Decide multi-cd: walk all leading cd segments, or cap at one. I lean cap-at-one to keep the rule legible.
  • Post-cd segments still hit the normal allowlist. cd X && rm -rf / stays denied because rm is not allowed, not because cd is.
  • Error path: when a post-cd segment is denied, the message names that segment, not cd.
  • Pairs with the --commit-scope recovery suggestion in the denial message (separate work in coily-discipline, filed in coilysiren/coily).
  • Pairs with the stale kai-coily-discipline pointer cleanup (filed in coilysiren/coilyco-ai).
_Originally filed by @coilysiren on 2026-05-14T19:25:41Z - [https://github.com/coilysiren/cli-guard/issues/58](https://github.com/coilysiren/cli-guard/issues/58)_ ## Problem cli-guard's lockdown PreToolUse hook segment-splits a Bash invocation and validates each segment's leading token against the allowlist. Today, `cd <path> && coily ...` fails because segment 1's leading token is `cd`, not `coily`, even though `cd` is a shell builtin with no execve and the post-`cd` segment is the only thing that needs gating. Observed today on a Mac session: harness denied `cd /Users/kai/projects/coilysiren/cli-guard` with no surfaced workaround. The denial fires during the exact kind of work the wrapper is meant to enable (cross-repo coily invocations bound to a specific commit-scope). ## Proposed change In the segment loop (see `lockdown/hook.go` segment validation), if a segment matches a bare `cd <path>` shape with no shell metacharacters and `<path>` resolves to a real directory, skip it from leading-token analysis and let the next segment speak for the chain. The chdir then happens naturally in bash when Claude runs the approved command. No hook rewrite needed. ## Boundary considerations - Path arg must pass the existing shell-metachar reject (no `$(...)`, backticks, globs, unescaped spaces). - Path must resolve to a real directory at validation time. Reject otherwise. - Decide multi-cd: walk all leading `cd` segments, or cap at one. I lean cap-at-one to keep the rule legible. - Post-cd segments still hit the normal allowlist. `cd X && rm -rf /` stays denied because `rm` is not allowed, not because `cd` is. - Error path: when a post-cd segment is denied, the message names that segment, not `cd`. ## Related - Pairs with the `--commit-scope` recovery suggestion in the denial message (separate work in coily-discipline, filed in coilysiren/coily). - Pairs with the stale `kai-coily-discipline` pointer cleanup (filed in coilysiren/coilyco-ai).
Author
Owner

Closing as obsolete.

This proposed a fix to the lockdown hook's leading-token analysis under the assumption it validated each segment against an allowlist (so a leading cd <path> tripped it). The hook is now a denylist: check_segment in lockdown/hook.go strips whitespace + env VAR=val prefixes, then matches the leading token against deny prefixes and exit 2s only on a match. Anything not denied falls through and passes.

Consequences:

  • cd is not in the deny set (lockdown/defaults.yaml has no cd/chdir entry), so cd /path && coily ... already passes cli-guard's hook today: segment 1 (cd /path) matches no deny prefix and is allowed, segment 2 (coily ...) is the gated one. The "skip cd" code this issue proposed is unnecessary for the denylist model.
  • The denial originally observed (cd /Users/... blocked) came from the Claude Code allowlist permission layer (settings.json), which is upstream of and separate from cli-guard's deny hook. cli-guard's hook can't change that behavior.
  • The live workaround is already encoded in the global agent context: on Linux, invoke binaries bare rather than prefixing with cd (coily ops verbs are host-wide and repo verbs self-discover their declarant, so bare invocation from any cwd is correct).

No code change needed here. If the allowlist-layer friction recurs, that belongs against the harness settings, not this hook.

Closing as obsolete. This proposed a fix to the lockdown hook's leading-token analysis under the assumption it validated each segment against an **allowlist** (so a leading `cd <path>` tripped it). The hook is now a **denylist**: `check_segment` in `lockdown/hook.go` strips whitespace + `env VAR=val` prefixes, then matches the leading token against deny prefixes and `exit 2`s only on a match. Anything not denied falls through and passes. Consequences: - `cd` is not in the deny set (`lockdown/defaults.yaml` has no `cd`/`chdir` entry), so `cd /path && coily ...` already passes cli-guard's hook today: segment 1 (`cd /path`) matches no deny prefix and is allowed, segment 2 (`coily ...`) is the gated one. The "skip cd" code this issue proposed is unnecessary for the denylist model. - The denial originally observed (`cd /Users/...` blocked) came from the Claude Code **allowlist** permission layer (settings.json), which is upstream of and separate from cli-guard's deny hook. cli-guard's hook can't change that behavior. - The live workaround is already encoded in the global agent context: on Linux, invoke binaries bare rather than prefixing with `cd` (coily ops verbs are host-wide and repo verbs self-discover their declarant, so bare invocation from any cwd is correct). No code change needed here. If the allowlist-layer friction recurs, that belongs against the harness settings, not this hook.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/cli-guard#5
No description provided.