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:
+8
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user