Skip to content

history: clear() can be raced into re-creating commit-log.jsonl with pre-wipe input #313

Description

@send

Found during the PR2 (#295/#288) /code-review max sweep. Pre-existing — not introduced by that PR.

Problem

LexUserHistory::apply_records appends its commit-log lines after releasing the wal mutex:

}                          // wal guard dropped here
for line in &log_lines {
    self.append_commit_log(line);
}

clear_impl serialises everything else behind compact_gate + the wal mutex, but it has no exclusion against a concurrent append_commit_log. It nulls the cached handle, releases the commit-log mutex, then unlinks the file — and CommitLog::append re-creates it with .create(true).append(true).

Interleaving

  1. Thread A commits a conversion, finishes the wal critical section, is descheduled before its commit-log append.
  2. The user clicks 履歴を全消去. clear_impl writes the empty checkpoint, truncates the WAL, drops the log handle, remove_file("commit-log.jsonl") succeeds, returns Ok.
  3. Thread A runs append_commit_log, sees file == None, reopens the path, and writes {"t":…,"reading":…,"surface":…,"rank":…}.

After a wipe the user was told succeeded, a JSONL file containing their raw input exists on disk. Unlike the WAL, the commit log has no startup scrub backstop — recovery never touches it (the comment in clear_impl says so explicitly).

Why this matters

CLAUDE.md 設計哲学: 「学習データはユーザーの資産: 破損・削除・復元をサイレントにしない」. A privacy wipe that silently leaves input strings behind is the same class as #295, on the diagnostic file instead of the history.

Reachability

Needs a commit in flight when clear runs — a narrow window (the gap between releasing the wal mutex and the append), but reachable with a background AsyncWorker commit landing as the user clicks the button.

Fix options

  • Move the commit-log append inside the wal critical section. Correct and simple, but adds a file write to the key-processing thread's critical section, which the design deliberately kept out (SPEC: 「commit-log append(従来通り、wal ロック外、耐久化なし)」).
  • Give CommitLog a clear-generation: apply_records captures it under the wal lock alongside the lines, clear_impl bumps it, and append drops lines carrying a stale generation. Keeps the append off the critical section; costs one counter.

The second looks right, but it is a mechanism and belongs in its own change rather than bolted onto the durability channel.

Severity: medium (privacy). Files: engine/src/api/resources.rs (apply_records, clear_impl, CommitLog::append).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions