mirror of https://github.com/razor-ai/soup.git
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:
parent
c4ac3da695
commit
b4924b12a1
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue