fix(file_tools): allowlist macOS user-writable temp subtrees under /private/var/#11983
fix(file_tools): allowlist macOS user-writable temp subtrees under /private/var/#11983briandevans wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Updates the sensitive-path write guard in tools/file_tools.py to stop incorrectly blocking macOS user-temp directories that resolve under /private/var/..., and adds targeted regression tests to lock in the new behavior.
Changes:
- Add a sensitive-path allowlist for macOS per-user temp subtrees and integrate it into
_check_sensitive_path. - Add a focused test suite validating the allowlist and ensuring key sensitive paths remain blocked.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
tools/file_tools.py |
Introduces an allowlist and helper to permit macOS temp paths while keeping sensitive prefixes blocked. |
tests/tools/test_file_write_safety.py |
Adds regression coverage for allowlisted macOS temp paths and canaries for still-blocked sensitive locations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| _SENSITIVE_PATH_ALLOWLIST = ( | ||
| # macOS per-user temp directory (what ``tempfile.gettempdir()`` returns | ||
| # after symlink resolution). User-writable by design. | ||
| "/private/var/folders/", | ||
| # ``/tmp`` on macOS is a symlink into ``/private/tmp/``. | ||
| "/private/tmp/", | ||
| ) |
There was a problem hiding this comment.
The allowlist is applied unconditionally across all platforms. That means _check_sensitive_path("/private/var/folders/..." ) becomes allowed on Linux/Windows too (previously anything under /private/var/ was blocked). This conflicts with the PR description claim that Linux behavior is byte-for-byte identical; if the intent is strictly macOS, consider gating the allowlist logic on sys.platform == "darwin" (and adjusting/marking tests accordingly).
| for prefix in _SENSITIVE_PATH_PREFIXES: | ||
| if resolved.startswith(prefix) or normalized.startswith(prefix): | ||
| if _is_allowlisted_sensitive_path(resolved, normalized): | ||
| # Looks sensitive by prefix, but it's a documented | ||
| # user-writable location (e.g. macOS temp) — permit. | ||
| return None |
There was a problem hiding this comment.
In _check_sensitive_path, if os.path.realpath(...) raises, resolved falls back to the raw, unexpanded filepath string. Because the allowlist check later uses resolved.startswith(...), a crafted path could be allowlisted based on the raw string even when the normalized/expanded form would not be. To keep the guard consistent/fail-closed, consider setting resolved = normalized in the exception branch (or otherwise ensure the allowlist only consults expanded+normalized values when realpath fails).
|
Addressed both Copilot review points in 1. Cross-platform gate — the allowlist is now gated on 2.
The AND guard required one more refinement: Regression coverage:
Validation: |
…te/var/ The /private/var/ sensitive-path prefix (added in 311dac1 to close the /etc→/private/etc symlink bypass) is too broad on macOS: it also blocks writes to /private/var/folders/, which is the realpath of the macOS per-user temporary directory (tempfile.mkdtemp(), /tmp). Add _is_macos_user_temp() with a dual-form AND guard: both the realpath- resolved path and the normpath-normalized path must fall under an allowlisted prefix (/private/var/folders/, /var/folders/, /private/tmp/, /tmp/). The AND requirement defeats a symlink-substitution bypass where an attacker creates a symlink from an allowlisted path into a blocked system path — realpath would resolve through to the blocked target and fail the check. Fixes TestWriteInvalidatesDedup tests that use tempfile.mkdtemp() (paths land in /private/var/folders/ on macOS) and were blocked as sensitive. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ace9ff4 to
ae9224a
Compare
|
Closing to keep the queue clean — happy to reopen if this is still useful. |
What does this PR do?
Fixes the file-tool sensitive-path guard so it permits writes to macOS per-user temp directories (`/private/var/folders/…`, `/private/tmp/…`) while continuing to block every genuinely sensitive subtree.
Root cause
Commit 311dac1 added `/private/var/` to `_SENSITIVE_PATH_PREFIXES` to close the `/etc` → `/private/etc` symlink bypass on macOS. The prefix is correct for blocking system-owned paths, but `/private/var/folders/` is the macOS per-user temporary directory (`tempfile.mkdtemp()` returns paths there, and `/tmp` is a symlink into `/private/tmp/`).
Fix
Adds `_is_macos_user_temp(resolved, normalized)` with a dual-form AND guard: both the `realpath`-resolved form and the `normpath`-normalized form of the path must fall under an allowlisted prefix. The AND requirement defeats a symlink-substitution bypass: if an attacker creates a symlink from an allowlisted path into a blocked system path, `realpath` resolves through the symlink to the blocked target and fails the check.
Allowlisted prefixes (macOS only, gated on `sys.platform == "darwin"`):
Tests