Priority: 🟢 Nice-to-have (Windows-only, LOW)
Audit source: T2 Backend Expert (backend-architect)
Related file: plugins/preview-forge/hooks/escalation-ledger.py:107-114
Problem
The Windows branch of escalation-ledger.py's _lockfile() context manager:
- Yields the lock as a no-op (concurrent-write race window exposed)
- Does not apply O_NOFOLLOW (symlink redirect possible)
Evidence
# plugins/preview-forge/hooks/escalation-ledger.py:107-114
@contextmanager
def _lockfile(lock_path):
if not _HAVE_FCNTL:
# Windows fallback: no lock, no O_NOFOLLOW
yield
return
# POSIX path: O_NOFOLLOW + flock
lock_fd = os.open(...)
...
The docstring justifies it as "rarely concurrent" — acknowledging the POSIX concurrency case while staying silent about Windows.
Attack scenario (Windows-only)
- In a shared-home environment, an attacker pre-creates
~/.preview-forge/escalation-history.lock as a reparse point/junction
- The ledger uses
os.replace to atomically replace history.json
- The reparse point redirects to the attacker's file → ledger contents land in the attacker's file (or the attacker's file becomes the ledger location via redirect)
Implications
- POSIX (macOS/Linux): no impact (fcntl + O_NOFOLLOW)
- Windows: TOCTOU + symlink defense both absent
- Frequency: shared-user scenarios — uncommon, but unsafe by design
Because there is no T-12 Windows CI (Issue I-4), this behavior has 0 runtime verification.
Acceptance Criteria
Option A: a Windows-specific lock mechanism
Option B: reparse-point validation
Option C: explicit limitation documentation (minimum)
Cross-references
Notes
No impact for POSIX users. Worth preparing for marketplace Windows users. Lightest fix is Option C (documentation).
Priority: 🟢 Nice-to-have (Windows-only, LOW)
Audit source: T2 Backend Expert (backend-architect)
Related file:
plugins/preview-forge/hooks/escalation-ledger.py:107-114Problem
The Windows branch of
escalation-ledger.py's_lockfile()context manager:Evidence
The docstring justifies it as "rarely concurrent" — acknowledging the POSIX concurrency case while staying silent about Windows.
Attack scenario (Windows-only)
~/.preview-forge/escalation-history.lockas a reparse point/junctionos.replaceto atomically replace history.jsonImplications
Because there is no T-12 Windows CI (Issue I-4), this behavior has 0 runtime verification.
Acceptance Criteria
Option A: a Windows-specific lock mechanism
msvcrt.locking()(Python stdlib, provides file locking on Windows):Option B: reparse-point validation
GetFileAttributesWand inspect theFILE_ATTRIBUTE_REPARSE_POINTbit — abort if setpywin32would be required, that conflicts with LESSON 0.4 (zero-dep) — call directly via ctypes:Option C: explicit limitation documentation (minimum)
Cross-references
Notes
No impact for POSIX users. Worth preparing for marketplace Windows users. Lightest fix is Option C (documentation).