inspector/core
Repository files (latest commit first)
Filename Latest commit message Latest commit date
cliffhall e956d49833 fix(auth): lock the snapshot during hand-off, keep quiet on ERELEASED (#2082)
Copilot review round 7 — two findings, both upheld.

**An in-progress migration snapshot could be adopted by another startup.**
The fast path counts every `*.migrating-*` as work and
`recoverOrphanedSnapshots` adopts them — including one whose owner is still
reading it. It link/unlinks the snapshot back to the live path and re-claims
it under a new name, the owner's hand-off then fails `ENOENT`, and if the
adopting process exits before copying, that healthy session starts with none
of those secrets, recoverable only on some later run.

Fixed the second way the reviewer suggested, not the first. Holding the main
lock across the hand-off also closes it and was tried — it broke "claims the
file atomically, so a later write is not deleted", because it blocks every
ordinary writer for the whole migration and fails their save past the retry
budget. That is a worse regression than the race, since #1950 guarantees a
write completing after the claim survives. The existing test caught it.

So the hand-off takes a lock on the **snapshot** instead, and
`recoverOrphanedSnapshots` skips an orphan whose lock is held. Liveness comes
from the lock rather than from the pid already in the filename: a pid both
outlives its process and recurs — pid 1 on every container start, which
#1950 documents as the reason the name carries a nonce — whereas a lock
expires by itself if its owner dies, so a genuinely abandoned snapshot
becomes adoptable with nothing to clean up. `isFileLockHeld` answers `false`
when it cannot tell, since the alternative to a wrong `false` is never
recovering an abandoned file.

**A compromised lock arrives as `ERELEASED`, and we told the operator to
delete the lock.** `setLockAsCompromised` marks the lock released and drops
its registry entry *before* calling `onCompromised`, so `release()` answers
`ERELEASED` without touching the filesystem — and the directory at that path
is by then the **winner's live lock**. "Remove it by hand" would destroy the
exclusion of a process that did nothing wrong. Handled separately and
silently; `onCompromised` has already reported what happened. A conditional
rather than an early return, since a `return` inside `finally` discards
whatever the body was returning or throwing.

Both new tests verified by mutation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTHVxSu8AUgHRLvo1ntZ8H
Signed-off-by: cliffhall <cliff@futurescale.com>
2026-08-23 11:54:47 -04:00
..
auth fix(auth): lock the snapshot during hand-off, keep quiet on ERELEASED (#2082) 2026-08-23 11:54:47 -04:00
client fix(auth): make the durability guard provenance-aware, and harden the claim 2026-08-22 22:10:07 -04:00
json fix: decline empty enums, drop mislabelling enumNames, treat $ref as opaque 2026-08-15 20:27:19 -04:00
logging feat: port CLI, TUI, and launcher from v1.5 into v2 (#1452) 2026-06-14 20:27:49 -04:00
mcp Merge remote-tracking branch 'origin/v2/main' into v2/feat/1950-file-secret-store 2026-08-23 01:40:01 -04:00
node feat(web): enforce localhost-only binding; expand loopback origin allow-list (#1796) 2026-07-26 22:15:10 -04:00
react fix(auth): stop blaming the passphrase for a corrupt payload, and refresh the stale docs 2026-08-22 23:08:52 -04:00
storage fix(auth): keep oauth-persist browser-safe (move Node file backend out) 2026-07-08 16:20:31 -04:00
tsconfig.json feat: port CLI, TUI, and launcher from v1.5 into v2 (#1452) 2026-06-14 20:27:49 -04:00