fix(v0.44.0): write_trigger lstat unresolved path so symlink at trigger is detected

trigger_path() returns realpath-resolved path, which FOLLOWS symlinks.
write_trigger then lstat'd the resolved path, which is the symlink's
TARGET (a regular file), so S_ISLNK was always False — the symlink
rejection silently no-op'd. Linux CI caught this:
test_write_trigger_rejects_pre_existing_symlink failed with "DID NOT
RAISE OSError".

Fix: lstat the unresolved os.path.join(output_dir, TRIGGER_FILENAME)
before opening. If it's a symlink, raise OSError without following.

Windows: test still skipped (no POSIX symlink permissions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alpamys 2026-05-10 19:28:02 +05:00
parent c4ac3da695
commit b4924b12a1
1 changed files with 7 additions and 4 deletions

View File

@ -79,15 +79,18 @@ def write_trigger(output_dir: str, *, contents: Optional[str] = None) -> str:
parent = os.path.dirname(path)
if parent and not os.path.isdir(parent):
os.makedirs(parent, exist_ok=True)
# TOCTOU defence: refuse to write through a pre-existing symlink at the
# trigger path (matches v0.33.0 #22 / v0.43.0 Part C policy).
# TOCTOU defence: lstat the UNRESOLVED join path (not the realpath'd one,
# which would follow the symlink and miss it). Matches v0.33.0 #22 /
# v0.43.0 Part C policy.
unresolved = os.path.join(output_dir, TRIGGER_FILENAME)
try:
link_stat = os.lstat(path)
link_stat = os.lstat(unresolved)
except FileNotFoundError:
link_stat = None
if link_stat is not None and stat.S_ISLNK(link_stat.st_mode):
raise OSError(
f"refusing to write through symlink at {os.path.basename(path)}"
f"refusing to write through symlink at "
f"{os.path.basename(unresolved)}"
)
with open(path, "w", encoding="utf-8") as fh:
fh.write(body)