Development record
spc-35 BLOCK (security review): the cache promotion 're-verify against recorded binary_sha256' is a corruption check, not a tamper check. The cached artefact and its binary-meta (holding the expected hash) both live in ${CLAUDE_PLUGIN_DATA}/cache, equally same-UID-writable. An attacker writing BOTH — payload plus a meta recording its hash and the current release_tag — passes the promotion gate and gets an unverified binary installed at the guard path and onto PATH, with the success notice asserting 'checksum-verified'. Demonstrated end-to-end vs live v0.6.1. This is exactly adr-46's central claim: on main a poisoned plugin-root binary healed at the next update (harness re-clones a fresh root -> bootstrap re-downloads+verifies from GitHub); here the poisoned cache is preferred over the network indefinitely and survives every update. adr-46, invariant 12, and the notice assert a protection the code does not provide. Fix: in cache mode (which already resolves the latest tag over the network) GET that release's checksums.txt and verify cached_sha against the PUBLISHED digest before use_cache=yes — restores tamper-evidence online, offline stays corruption-only (document honestly). Plus the missing test: TestBootstrapCorruptCacheRefusesLoudly rewrites only the artefact, never the meta, so it never exercises the case that succeeds.
spc-35 BLOCK (security review): the cache promotion 're-verify against recorded binary_sha256' is a corruption check, not a tamper check. The cached artefact and its binary-meta (holding the expected hash) both live in ${CLAUDE_PLUGIN_DATA}/cache, equally same-UID-writable. An attacker writing BOTH — payload plus a meta recording its hash and the current release_tag — passes the promotion gate and gets an unverified binary installed at the guard path and onto PATH, with the success notice asserting 'checksum-verified'. Demonstrated end-to-end vs live v0.6.1. This is exactly adr-46's central claim: on main a poisoned plugin-root binary healed at the next update (harness re-clones a fresh root -> bootstrap re-downloads+verifies from GitHub); here the poisoned cache is preferred over the network indefinitely and survives every update. adr-46, invariant 12, and the notice assert a protection the code does not provide. Fix: in cache mode (which already resolves the latest tag over the network) GET that release's checksums.txt and verify cached_sha against the PUBLISHED digest before use_cache=yes — restores tamper-evidence online, offline stays corruption-only (document honestly). Plus the missing test: TestBootstrapCorruptCacheRefusesLoudly rewrites only the artefact, never the meta, so it never exercises the case that succeeds.
Frontmatter
| id | iss-2608210934566228 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | critical |
.abcd/work/issues/resolved/iss-2608210934566228-spc35-cache-reverify-is-corruption-not.md
open on GitHub ↗ · commit history ↗
mentions3
- adr-46Persisting the hook binary never weakens the verification posture — every promotion re-verifies, and SessionEnd performs no network work
- iss-2609012039102770GHSA-4q78-ccfv-f374 (CWE-345, advisory severity low): the owned PATH-copy verification is self-referential under an env-supplied CLAUDE_PLUGIN_DATA. `internal/core/ahoy/data_dir.go:pluginDataDir` is `os.Getenv("CLAUDE_PLUGIN_DATA")` and nothing else; `owned_copy.go:cacheRecordedSHA` reads `binary_sha256` from a `binary-meta` beside the artefact under that same directory; `apply.go:installOwnedEntry` hashes the artefact, compares it to that record, writes the bytes 0755 as the owned PATH entry and records provenance in `~/.abcd/path-entry`. The bootstrap's manifest authentication (`hooks/bootstrap.sh`, cache_trust=manifest, iss-2608210934566228) covers only the cache-to-plugin-root promotion; the cache-to-PATH promotion re-verifies against the co-located record only, which an attacker who writes both satisfies trivially — adr-46 decision 3 states exactly this principle for the bootstrap. Reproduced at v0.7.0: with CLAUDE_PLUGIN_DATA and ABCD_PLUGIN_ROOT pointed at attacker-chosen directories, `ahoy install --yes --adopt` installed a one-byte file as `~/.local/bin/abcd`, recorded its hash and the fake plugin root in path-entry, and `classifyBinTarget` then vouched for it as an owned copy.
- spc-35hook-binary-to-persistent-data-dir