Repository navigation
Description
Bug Scenario
Given _lib-read-config.sh self-locates the same way every other trust-chain lib does:
lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-}")" 2>/dev/null && pwd)"
if [ -f "$lib_dir/_lib-ops-root.sh" ]; then
. "$lib_dir/_lib-ops-root.sh"When it is sourced under a non-bash shell, where BASH_SOURCE is unset — so dirname "" → "." and lib_dir becomes the caller's cwd
Then if that cwd happens to contain a file named _lib-ops-root.sh, the -f guard is satisfied and the lib is sourced unanchored from whatever repo the cwd is inside
Expected the same anchoring every sibling lib now gets — resolution should only accept a genuine apexyard fork.
Why this is a separate ticket rather than part of #1100
#1100 fixes exactly this shape at nine sites across eight libs, using:
if [ -z "${BASH_SOURCE[0]:-}" ]; then _lib_dir=""; fiDiscarding the cwd-derived path forces resolution through each lib's anchored git-root fallback (#1033/#1061), which only accepts a real fork.
That guard is not a drop-in here. _lib-read-config.sh's fallback is a deliberately unanchored git rev-parse — its own comment says so, describing it as "legacy behaviour for non-apexyard environments" that keeps the hooks usable in bare clones and CI sandboxes that don't ship the full framework. So clearing lib_dir would route to a fallback that is itself unanchored, moving the problem rather than fixing it.
The anchor has to be added to the fallback first, then the guard. That is a behaviour change to the deliberate non-apexyard escape hatch, and it deserves its own diff, its own reasoning about what should happen in a bare clone, and its own review.
Verified independently: the shape is present at _lib-read-config.sh:91, and _lib-read-config.sh appears zero times in #1100's diff.
Severity
medium — same class as #1062, same trust-chain exposure, but narrower in practice: it requires a non-bash shell and a cwd that happens to contain a file named _lib-ops-root.sh. Not reachable by accident in ordinary use; reachable deliberately, and reachable by an operator working inside an unrelated checkout.
Rated medium rather than low because this lib is sourced by a large fraction of the hook surface — including gates — so an unanchored load here has unusually wide reach.
Suggested approach
- Decide what the non-apexyard escape hatch should do. The current unanchored
git rev-parsefallback exists on purpose; the question is whether "usable in a bare clone" and "never resolves from an unrelated repo" can both hold, and which wins if not. - Anchor the fallback accordingly.
- Only then apply fix(hooks): anchor the cwd-derived branch of trust-chain self-location (#1062) #1100's
BASH_SOURCE-empty guard, so the cwd-derived path routes somewhere safe. - Extend
test_lib_self_location_cwd_anchor.sh(added by fix(hooks): anchor the cwd-derived branch of trust-chain self-location (#1062) #1100) with the equivalent impostor case for this lib. That file already has the zsh harness; it is close to copy-paste.
The test must be discriminating by construction — the impostor sibling must be reachable via cwd, so the pre-fix code sources it and the post-fix code does not. A test that passes both before and after proves nothing; that trap has caught several reviewers in this repo.
Related
- [Bug] Self-location anchor guards only the fallback branch — a cwd inside a non-fork .claude/hooks/ still wins #1062 / fix(hooks): anchor the cwd-derived branch of trust-chain self-location (#1062) #1100 — the same defect at nine other sites, and the guard pattern to follow
- [Bug] Trust-chain libs can load from the cwd's repo via the git rev-parse fallback (follow-up to #1028) #1033 / fix(#1033): anchor the git-derived self-location fallback to an apexyard fork #1061 — anchored the git-derived fallback branch; [Bug] Self-location anchor guards only the fallback branch — a cwd inside a non-fork .claude/hooks/ still wins #1062 was the residue of that, and this is the residue of [Bug] Self-location anchor guards only the fallback branch — a cwd inside a non-fork .claude/hooks/ still wins #1062
- AgDR-0104 — trust-chain controls vs backstops
Glossary
| Term | Definition |
|---|---|
| self-location | A shell lib working out its own directory so it can source siblings from beside itself. Fragile because BASH_SOURCE is a bash feature and does not exist in other shells. |
| anchored resolution | Only accepting a directory that proves it is a genuine apexyard fork (via .apexyard-fork, or the onboarding.yaml + apexyard.projects.yaml pair) rather than trusting whatever the path resolves to. |
| impostor sibling | A file with the same name as a real lib, sitting in an unrelated directory, which an unanchored load would source instead of the genuine one. |
Activity
atlas-apex commentedon Jul 31, 2026
Scope correction: this is FOUR sites, not one
The security review on #1100 found three more, and I verified each independently. This ticket as filed would have been closed with three live sites still open in the same class.
BASH_SOURCEspelling_lib-read-config.sh:91${BASH_SOURCE[0]:-}git rev-parse(deliberate)_lib-extract-push-ref.sh:62${BASH_SOURCE[0]}_lib-git-hooks-path.sh:21${BASH_SOURCE[0]:-$0}_lib-protected-branches.sh:55${BASH_SOURCE[0]:-$0}Verified:
grep -cE 'apexyard-fork|onboarding\.yaml'returns 0 for all three of the newly-found libs. They have a bare-f-then-source with nothing to fall through to — strictly weaker than the nine sites #1100 patched, which at least had an anchored fallback waiting.Why they were missed
Three spellings of the same idiom. #1100 patched
${BASH_SOURCE[0]:-}; these use bare${BASH_SOURCE[0]}and${BASH_SOURCE[0]:-$0}. A grep for the patched form finds none of them. Under zsh,$0inside a sourced file is the sourcing path as written, so relative-path sourcing degrades them identically — the:-$0spelling looks like a fix and isn't one.That is this repo's recurring defect in its purest form: one thing expressed several ways, so a search for the known form misses the variants.
Priority
_lib-protected-branches.shfirst. It supplies the protected-branch regex behindblock-main-push.sh. It also shipped today, in #1090 — so this class was extended while it was being fixed, which is exactly the argument for a sweep rather than another per-site patch.Revised approach
BASH_SOURCEis written. Record the count so a future ticket cannot undercount again._lib-read-config.sh(unchanged from the original scope — its unanchored fallback is deliberate and needs a decision before a guard helps).test_lib_self_location_cwd_anchor.shwith an impostor case per lib, discriminating by construction — the impostor must be reachable via cwd so the pre-fix code sources it and the post-fix code does not.One correction to the severity framing, also from the review
#1100's scope note says these branches are gate-unreachable because hooks launch via
bash -c. That is true for settings.json-wired hooks but not the whole picture: the Claude Code Bash tool runs zsh withBASH_SOURCEempty, and/approve-merge,/approve-design,/approve-architectureand the review-posting flow all source_lib-tracker.sh— marker-writing paths that do reach the degraded branch.What actually bounds the severity is narrower: the cwd branch only wins when the caller's cwd directly contains a same-named sibling. Skills source by absolute path while cwd is a repo root or workspace clone, so the
-fguard fails and the anchored fallback runs.Same conclusion — misresolution surface, not access control — but resting on the real precondition. Worth recording because the wrong reason would mislead whoever evaluates the next zsh-sourced caller.
fix(#1102): centralize trust-chain lib self-location behind one ancho…
me2resh commentedon Aug 1, 2026
Fixed in PR #1125 (merged to dev, commit a345010). All four unanchored self-location sites (_lib-read-config.sh, _lib-extract-push-ref.sh, _lib-git-hooks-path.sh, _lib-protected-branches.sh) now route through one shared resolve_anchored_lib_dir() in _lib-ops-root.sh, replacing the four drifting BASH_SOURCE spellings with a single fail-closed idiom. AgDR-0118 records the decision, including the escape-hatch call: _lib-read-config.sh's git rev-parse config fallback is left UNANCHORED on purpose (it is git-derived not BASH_SOURCE-derived, so unreachable via the cwd-substitution bug; anchoring it would degrade every managed project's own protected-branch config to the framework default). Rex + Hakim both reviewed at HEAD and independently verified the reversal is necessary and safe; full hook suite green. NOTE: the review surfaced a residual out-of-scope site (maintain-docs-index.sh:37) plus a few top-level hooks still on the old idiom — tracked as a separate drift-cleanup follow-up per the AgDR's own thesis.
refactor(#1126): route remaining hook self-location through resolve_a…