Skip to content

fix(specialist): overwrite is race-prone and non-atomic #753

Description

@PierrunoYT

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    issue-approvedReviewed and approved by the core team; community PRs may implement this issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions