fix(tools): symlink-safe exclusive creation for all spill/cache writers

Spill files (terminal overflow, hook context, web_extract full text,
subagent summaries) were written with plain open()/write_text into
predictable directories. A pre-planted symlink at any of those paths
redirected the write onto an arbitrary user-owned file, and raw
pre-redaction terminal/hook spills landed world-readable under the
default umask.

New tools/spill_safety.py helpers create files with
O_CREAT|O_EXCL|O_NOFOLLOW (a link-shaped path fails the write instead of
following it) and overwrite via lstat-checked unlink + exclusive
re-create, so even the redaction rewrite cannot be diverted. Private
tier (0o700 dir / 0o600 file) covers raw terminal and hook spills;
cache/web and cache/delegation keep umask perms because those dirs are
bind-mounted into remote backends that must read them.

Pattern borrowed from DeepSeek Harness dsh-spill-local (MIT):
private root + exclusive owner-only opens for spill artifacts.
This commit is contained in:
Teknium
2026-08-13 10:56:22 -07:00
parent 6def7ce1df
commit 005dfcbfcc
7 changed files with 276 additions and 8 deletions
+8 -1
View File
@@ -506,7 +506,14 @@ def _store_full_text(url: str, content: str) -> Optional[str]:
+ f"\n\n[... stored copy truncated at {MAX_STORED_TEXT_CHARS:,} chars "
f"of {len(content):,}; re-extract a more specific URL for the rest ...]"
)
path.write_text(content, encoding="utf-8")
from tools.spill_safety import write_text_exclusive
# Deterministic filename in a well-known dir: refuse symlinks via
# lstat-unlink + exclusive create. Re-extraction of the same URL
# legitimately overwrites (same slug-digest name). Not private:
# cache/web is bind-mounted into remote backends whose container UID
# must be able to read it, and content is fetched public text.
write_text_exclusive(path, content, private=False, overwrite=True)
return str(path)
except Exception as exc: # noqa: BLE001
logger.debug("Failed to store full web_extract text for %s: %s", url, exc)