Development record
the history transcript store's read path is unguarded — listRecords and Read use raw os.ReadFile, so a planted FIFO wedges history list and history capture while the store flock is held, and a symlinked record is followed
issueresolvedminoriss-383
the history transcript store's read path is unguarded — listRecords and Read use raw os.ReadFile, so a planted FIFO wedges history list and history capture while the store flock is held, and a symlinked record is followed
Evidence
internal/core/history/store.go:264(listRecords) andinternal/core/history/history.go:232(Read) use rawos.ReadFileover~/.abcd/history/<rootSHA>/transcripts/*.md— noO_NOFOLLOW, noO_NONBLOCK, no regular-file check, no size cap — while the write path is fully hardened (ownedDirsReal,repoLockwithO_NOFOLLOWatstore.go:83-84,WriteFileAtomic, and the iss-347ReadGuardedhook read).- Reproduced: a FIFO
hang.mdin the transcripts dir wedgesabcd history list(exit 124 under timeout) and — worse —history capture, which callslistRecordsathistory.go:117while holding the store flock taken athistory.go:102, the same lock-held-hang shape that made iss-202 critical. A symlinked*.mdis followed, though disclosure does not reproduce (unparseable files are skipped byparseRecord), so availability is the confirmed hazard. - Refuter verdict: CONFIRMED (minor, security/hardening) — the same-identity HOME-store severity band matches resolved iss-347's adjudication; the missing git-committable delivery vector (unlike iss-202's
pii.json) is what caps it at minor. Remedy: route both reads throughfsutil.ReadGuardedwith a size cap, keepinglistRecords' per-file skip tolerance.
Frontmatter
| id | iss-383 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-383-the-history-transcript-store-s-read-path.md
open on GitHub ↗ · commit history ↗
mentions3
- dcn-26082000000000082026-08-20 — Bug-hunt round 3 (hunt B), branch bughunt-b/round-3. Baseline
- iss-202scanner.New reads the per-repo .abcd/config/pii.json override with a bare os.ReadFile — no O_NOFOLLOW, no size cap, no regular-file check — unlike every sibling trust-boundary config reader in this codebase (guard.Load, rules.Load, banlist private store) which goes through fsutil.ReadGuarded; a FIFO planted at that path hangs the read forever and a symlink to a device file (git-committable, mode 120000, no local write access needed) grows the read unbounded toward OOM; reachable automatically via the SessionEnd hook's history capture, which also holds an unbounded history.repoLock flock across the hang, permanently wedging transcript capture for that repo; this is the same bug class already fixed once for ahoy.Detect under iss-97. Independently verified: FIFO hangs past 3s deadline (control: no-config-file case returns in 0.01s), /dev/zero symlink accumulates ~137 MiB/s, causal fix test (swap to fsutil.ReadGuarded) confirmed both cases resolve
- iss-347readTranscript opens the hook-supplied transcript path without O_NOFOLLOW or an over-cap probe, the one guarded read left off fsutil.ReadGuarded, so a symlinked transcript_path is followed and an over-cap file is stored silently truncated