Development record
The guard's script reader resolves a different file than bash runs in three ways: after `cd` it keeps the logical path (so `cd -P link && bash ../s.sh` judges the file beside the link while bash runs the one beside its target), it ignores `CDPATH`, and `source` of a bare name ignores a `PATH` prefix on the same command. Each lets a planted script carrying a bundled blocker run with exit 0.
The guard's script reader resolves a different file than bash runs in three ways: after cd it keeps the logical path (so cd -P link && bash ../s.sh judges the file beside the link while bash runs the one beside its target), it ignores CDPATH, and source of a bare name ignores a PATH prefix on the same command. Each lets a planted script carrying a bundled blocker run with exit 0.
Private security advisory GHSA-xfjm-pv25-v4j4 (draft, severity high). Full text, evidence and reproduction: .abcd/.work.local/scratch/security-drain-2026-10-11/advisories/ in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/.
Evidence (main 3d171421a): cdTarget stores filepath.Clean of the argument and never reads CDPATH; resolve joins that logical directory to a relative script; searchPath uses only the shell state's stored PATH, skips relative entries and falls back to the working directory (all internal/core/guard/script.go). Reproduced at that commit with abcd guard check --command over planted trees: cd -P <T>/link && bash ../s.sh, cd <T>/empty && CDPATH=<T>/cdp cd sub && bash s.sh, PATH=<T>/relp source p.sh and PATH=relp source p.sh are all allow (exit 0); the control export PATH=<T>/relp; source p.sh blocks (exit 1).
Frontmatter
| id | iss-2610111600031244 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | major |
.abcd/work/issues/resolved/iss-2610111600031244-guard-script-lookup-differs-from-bash.md
open on GitHub ↗ · commit history ↗
mentions4
- iss-2610111730513071The guard does not track zsh's `path` array, which zsh ties to PATH: a zsh line that assigns `path=(<dir>)` and then runs or sources a bare script name searches <dir>, while the guard's script reader judges the file its own PATH search finds. Left open by lane G on 2026-10-11 while fixing the script-lookup gap (iss-2610111600031244), because in bash `path` is an ordinary variable name and tracking it there would misjudge bash lines.
- iss-2610111730551327zsh's `source` (and `.`) of a bare name looks in the current directory before PATH, while bash and the guard's script reader search PATH first, so on a zsh line the guard can judge a different file than zsh runs when both a working-directory file and a PATH file share the name. Left open by lane G on 2026-10-11 while fixing the script-lookup gap (iss-2610111600031244).
- iss-2610111730597708After `unset CDPATH` on the line, the guard falls back to the hook environment's CDPATH, so a `cd name` that bash would resolve without CDPATH is still blocked: a false block for a person who exports CDPATH, the one group the new CDPATH block reaches. Left open by lane G on 2026-10-11 (iss-2610111600031244).
- iss-2610111749083184With CDPATH exported in the hook's environment, the new CDPATH block (iss-2610111600031244) blocks every bare `cd name`, on the line and inside every script the guard reads (`bash build.sh` whose build.sh runs `cd sub` blocks as script-unread), including where only one directory can match; and the message never says the CDPATH came from the environment, so a person whose line never mentions CDPATH is told to edit a third-party script's cd. Found by the Fable review of lane G, 2026-10-11 (cdSearchesCDPATH: if !set { return rc.files.cdpath != "" }).