Development record
The home-scoped cache attestation (GHSA-4q78-ccfv-f374) binds exactly two
The home-scoped cache attestation (GHSA-4q78-ccfv-f374) binds exactly two
things: a data_dir and a binary_sha256. It carries no release tag and no
freshness bound, and it is rewritten only when a bootstrap run provisions a
root with manifest trust established. So the record keeps vouching for the
hash it was written with, indefinitely, after the release it names has been
superseded.
The mechanism
- A session provisions the cache online.
hooks/bootstrap.sh§9b writesdata_dir=<D>,binary_sha256=<H(v1)>,cache_trust=manifest. - Releases move on.
v2ships,v1is withdrawn — a bad build, a fixed vulnerability, whatever the reason a release stops being the one to run. - Whoever can write into
<D>restores thev1artefact and itsbinary-metathere. Both are genuine bytes from a genuine release, so nothing about them is forged. abcd ahoy installfinds the attestation naming<D>andH(v1), finds the co-located record carryingH(v1), hashes the artefact toH(v1)— three agreements — and promotesv1to the owned PATH copy with its provenance recorded.
No step is a forgery. The attestation was written by the bootstrap after a real manifest check, and every hash is a real release's. The record simply has no way to say which release it attested or when, so it cannot distinguish "the release this machine authenticated" from "a release this machine authenticated once".
The bound
This is a DOWNGRADE to a previously attested genuine release, never a promotion of attacker-chosen bytes: the three-way agreement still holds the property the advisory's fix established. An attacker who cannot produce an artefact hashing to an attested value gains nothing here. What they can do, given write access to the attested directory, is pin the machine to an older release that this machine really did authenticate at some point in its past — and hold it there, because the attestation is rewritten only by a bootstrap run that reaches the manifest, and a machine that never goes online again never rewrites it.
The write access the step-3 restore needs is the same access the accepted same-uid residual already grants (iss-2609012039107700). The distinct fact here is not the write but the DURATION: the record survives the release it describes, so the window is not "until the next session" but "forever".
What a fix would have to add
Some freshness term the record does not carry today. Candidates, none chosen here:
- a
release_tagin the attestation, compared against the tag the cache'sbinary-metarecords, so an artefact from another release fails the binding even when its hash was once attested; - an
attested_atfreshness bound (the field is written but never read), past which the promotion declines and asks for a re-authenticating session; - rewriting the attestation on every manifest-reaching run rather than only on a provision, so a machine that goes online re-anchors to the current release.
Not fixed in the cut this was found in: the advisory's own property holds, the finding is a strictly narrower residual, and each candidate above changes when the record moves — which is a decision about the record's contract, not a patch.
Remedy grounds (2026-09-29)
- The threat is a downgrade (rollback) or freeze to a previously attested release; TUF names exactly these two and answers them with a monotonic version check (spec section 5.4.3) and an expiry on the metadata (sections 1.5.2, 5.4): https://theupdateframework.github.io/specification/latest/ (consulted 2026-09-29). Each ruling option maps to one of those two defences, so the remedy states each and picks none (ruling W is unanswered).
- Rejected: adopting a TUF client library, which is a new dependency needing the person's sign-off and far heavier than one field and one comparison.
Frontmatter
| id | iss-2609151847117977 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-2609151847117977-a-stale-cache-attestation-permits-a.md
open on GitHub ↗ · commit history ↗
mentions3
- iss-2609012039107700GHSA-gx3m-3224-qqcv (CWE-426, advisory severity medium): the hook shims' PATH fallback executes an unverified `abcd` as the guard and rules loader. In `hooks/hooks.json`, UserPromptSubmit, PreToolUse and PreCompact carry `if [ -z "$g" ] && command -v abcd; then g=abcd; fi` and SessionEnd `exec abcd hook session-end` when the plugin-root binary is missing; PreToolUse's fence passes exit 0/1/2 through, so a foreign binary exiting 0 approves every shell command. SessionStart has no PATH rung and fails closed. Reproduced at v0.7.0 with the shipped PreToolUse command, a failing bootstrap and a hijack directory first on PATH: the planted binary received `guard hook` plus the tool input and its exit 0 was accepted as armed. The rung was introduced by iss-254 (20968bdf, v0.5.1), is pinned as desired behaviour by `internal/surface/cli/hooks_selfprovision_test.go:TestBinaryHooksFallBackToAPathBinary`, and is a documented contract (docs/how-to/install.md: "the plugin root first, then an abcd on PATH … the install one-liner restores it"; brief 04-surfaces/01-ahoy.md section 9; brief 05-internals/03-configuration.md). The record contradicts itself: the 2026-08-01 decisions [dcn-2608010000000005](../../decisions/dcn-2608010000000005-itd-105-spc-21-implementation-the-plugin.md) and [dcn-2608010000000007](../../decisions/dcn-2608010000000007-itd-105-spc-21-hook-ordering-assumption.md) (itd-105 / spc-21) state "Rejected: a PATH fallback in the hook commands (spc-21 forbids it)" and "A PATH fallback is forbidden by the intent's Decisions and is NOT added"; iss-254 later added it with no DECISIONS entry reversing that.
- iss-2610120601505029The install one-liner the site serves (/install.sh, built from site-src/install.sh.tmpl) installs a release without recording the newest-release mark that the bootstrap and abcd update now keep (the fix for iss-2609151847117977 on branch fix/sec-pub-b2-261011), so a machine installed only that way has no mark until the bootstrap or update next runs, and a stale cached release is not refused in that window. Found by lane B2 on 2026-10-12.
- iss-2610120656379101The bootstrap ignores a newest-release mark that fails its ownership or mode check (find ... ! -perm -0020) silently, so a machine whose mark is unreadable looks the same as one with no mark (fix for iss-2609151847117977). The attestation behaves the same way.