Summary
Specialist file overwrite checks the target with Lstat and then separately opens the same pathname with O_TRUNC. A concurrent replacement can swap in a symlink between those two operations, and independently, disk exhaustion, interruption, or an I/O error after truncation can destroy the previous specialist content or leave a partially-written file.
Affected code
internal/specialist/storage.go:66-97
Details
- TOCTOU symlink race: the existence/type check (
Lstat) and the actual write (an O_TRUNC open of the same pathname) are two separate syscalls. A concurrent process can replace the target with a symlink in the window between them, so the truncating write can land somewhere other than the intended specialist file.
- Non-atomic overwrite: because the write path truncates the destination in place, any failure after truncation begins (disk full, process kill, I/O error) leaves the specialist either empty or holding partial content — destroying the previous good version with no way to recover it.
Suggested fix
Write a mode-0600 sibling temporary file, sync and close it, validate the destination with no-follow semantics, and atomically rename the temp file over the old one so a specialist write either fully succeeds or leaves the original file untouched.
Reference
Item AUD-006 from the 2026-07-18 codebase audit (docs/CODEBASE_AUDIT_2026-07-18.md).
Summary
Specialist file overwrite checks the target with
Lstatand then separately opens the same pathname withO_TRUNC. A concurrent replacement can swap in a symlink between those two operations, and independently, disk exhaustion, interruption, or an I/O error after truncation can destroy the previous specialist content or leave a partially-written file.Affected code
internal/specialist/storage.go:66-97Details
Lstat) and the actual write (anO_TRUNCopen of the same pathname) are two separate syscalls. A concurrent process can replace the target with a symlink in the window between them, so the truncating write can land somewhere other than the intended specialist file.Suggested fix
Write a mode-
0600sibling temporary file, sync and close it, validate the destination with no-follow semantics, and atomically rename the temp file over the old one so a specialist write either fully succeeds or leaves the original file untouched.Reference
Item AUD-006 from the 2026-07-18 codebase audit (
docs/CODEBASE_AUDIT_2026-07-18.md).