Skip to content

[Bug] _lib-read-config.sh has the same unanchored cwd self-location as #1062, and its fallback is unanchored too #1102

Closed

Description

@atlas-apex

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=""; fi

Discarding 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

  1. Decide what the non-apexyard escape hatch should do. The current unanchored git rev-parse fallback 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.
  2. Anchor the fallback accordingly.
  3. 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.
  4. 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

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

  1. added
    bugSomething isn't working
    P2Medium — plan-worthy, not urgent
    on Jul 31, 2026
  2. atlas-apex commented on Jul 31, 2026

    @atlas-apex
    CollaboratorAuthor

    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.

    Site BASH_SOURCE spelling Anchored fallback?
    _lib-read-config.sh:91 ${BASH_SOURCE[0]:-} unanchored git rev-parse (deliberate)
    _lib-extract-push-ref.sh:62 ${BASH_SOURCE[0]} none
    _lib-git-hooks-path.sh:21 ${BASH_SOURCE[0]:-$0} none
    _lib-protected-branches.sh:55 ${BASH_SOURCE[0]:-$0} none

    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, $0 inside a sourced file is the sourcing path as written, so relative-path sourcing degrades them identically — the :-$0 spelling 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.sh first. It supplies the protected-branch regex behind block-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

    1. Enumerate by behaviour, not by spelling — find every lib that self-locates and sources a sibling, regardless of how BASH_SOURCE is written. Record the count so a future ticket cannot undercount again.
    2. Decide the escape hatch for _lib-read-config.sh (unchanged from the original scope — its unanchored fallback is deliberate and needs a decision before a guard helps).
    3. Give the three fallback-less libs an anchored fallback, then the guard. The guard alone would leave them with nowhere safe to land.
    4. Extend test_lib_self_location_cwd_anchor.sh with 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 with BASH_SOURCE empty, and /approve-merge, /approve-design, /approve-architecture and 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 -f guard 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.

  3. added a commit that references this issue on Aug 1, 2026
  4. me2resh commented on Aug 1, 2026

    @me2resh
    Owner

    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.

  5. added a commit that references this issue on Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — plan-worthy, not urgentbugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions