This is the record of an abcd-managed repository

abcdBetav0.16.2 · 2026-11-06

Development record

GHSA-xq36-hcgf-9wrj (CWE-693): transcript staging is unlocked and content-blind. history.Stage dedups on the session id alone, so a second SessionEnd for one session carrying different bytes is a no-op, and the drain then stores the stale copy and deletes the only copy of the newer transcript; and Stage takes no lock, so two concurrent SessionEnds for one session id both pass the listing check, write two .raw files, and Drain stores two records claiming the same session. Evidence: internal/core/history/staging.go Stage (the session-id-only loop and its no-lock doc comment), stagedFilename's nanosecond stamp, Drain's unlocked list-capture-remove; TestStageIsIdempotentPerSession pins the defect. The fix must establish: the stage handshake is exclusive (fsutil.WithFileLock on staging/.lock around list, content compare and write), identical bytes are a no-op, different bytes replace the staged copy at the existing path (last-writer-wins, the fresher end-of-session bytes being the ones worth keeping) with a Replaced signal the session-end hook reports instead of no-op, and Drain removes a staged file only if it still holds the bytes it captured, leaving a mid-drain replacement for the next pass.

issueresolvedminoriss-2609012035100572

GHSA-xq36-hcgf-9wrj (CWE-693): transcript staging is unlocked and content-blind. history.Stage dedups on the session id alone, so a second SessionEnd for one session carrying different bytes is a no-op, and the drain then stores the stale copy and deletes the only copy of the newer transcript; and Stage takes no lock, so two concurrent SessionEnds for one session id both pass the listing check, write two .raw files, and Drain stores two records claiming the same session. Evidence: internal/core/history/staging.go Stage (the session-id-only loop and its no-lock doc comment), stagedFilename's nanosecond stamp, Drain's unlocked list-capture-remove; TestStageIsIdempotentPerSession pins the defect. The fix must establish: the stage handshake is exclusive (fsutil.WithFileLock on staging/.lock around list, content compare and write), identical bytes are a no-op, different bytes replace the staged copy at the existing path (last-writer-wins, the fresher end-of-session bytes being the ones worth keeping) with a Replaced signal the session-end hook reports instead of no-op, and Drain removes a staged file only if it still holds the bytes it captured, leaving a mid-drain replacement for the next pass.

Grounds

  • pursued: last-writer-wins under a per-repo flock is the smallest shape that keeps the SessionEnd hook to one write and makes the idempotency guarantee hold concurrently; keep-both would drain two records for one session, the duplicate the advisory names, and holding the lock across Capture would reintroduce the redaction stall staging exists to avoid. Shown wrong if a re-fired SessionEnd carrying fewer bytes ever turns out to be the transcript worth keeping.

Frontmatter

idiss-2609012035100572
typeissue
lifecycleresolved
severityminor