Conversation
…ache
Replaced `temp_file = cache_file.with_suffix(".tmp")` and unlinking logic with `tempfile.mkstemp` inside `save_disk_cache`.
This securely generates an unpredictable temporary file name within the targeted directory, using `O_CREAT | O_EXCL` and `0o600` permissions. This closes a Time-Of-Check-To-Time-Of-Use (TOCTOU) vulnerability window that could allow attackers to manipulate symlinks to overwrite arbitrary files or inject malicious payload into the cache. Included robust cleanup via `try...finally` block.
Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
Merging to
|
|
👋 Development Partner is reviewing this PR. Will provide feedback shortly. |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request resolves a critical security vulnerability in the caching mechanism by overhauling how temporary files are created and managed. The changes prevent potential Time-Of-Check-To-Time-Of-Use (TOCTOU) race conditions and symlink attacks, thereby safeguarding against arbitrary file overwrites or cache data manipulation by malicious actors. The update ensures that temporary files are generated securely and cleaned up robustly, significantly enhancing the application's security posture. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request effectively addresses a critical TOCTOU (Time-Of-Check-To-Time-Of-Use) vulnerability in the disk caching mechanism. The previous implementation used a predictable temporary filename and a non-atomic unlink-then-create pattern, which created a race condition exploitable via symlink attacks. The fix correctly replaces this with tempfile.mkstemp to generate a secure, unique temporary file, and wraps the file operations in a try...finally block for robust cleanup. The change is well-implemented and follows security best practices. I have one suggestion to improve the logging in the cleanup logic to make it even more robust.
| except OSError: | ||
| pass |
There was a problem hiding this comment.
The except OSError: pass silently ignores failures to clean up the temporary file. While the main logic is now secure, failing to remove temporary files can lead to disk space exhaustion over time if errors are frequent. It's better to log this failure as a warning so that operators are aware of potential issues and can investigate if they occur repeatedly.
| except OSError: | |
| pass | |
| except OSError as e: | |
| log.warning(f"Failed to remove temporary file {temp_path}: {_sanitize_fn(e)}") |
| # Security: use tempfile.mkstemp to securely create a unique temporary file | ||
| # with O_CREAT | O_EXCL and 0o600 permissions, preventing predictable | ||
| # temporary file vulnerabilities and TOCTOU races. | ||
| fd, temp_file_path_str = tempfile.mkstemp(prefix="blocklists.", suffix=".tmp", dir=str(cache_dir)) |
Check warning
Code scanning / Pylintpython3 (reported by Codacy)
Variable name "fd" doesn't conform to snake_case naming style Warning
| # Security: use tempfile.mkstemp to securely create a unique temporary file | ||
| # with O_CREAT | O_EXCL and 0o600 permissions, preventing predictable | ||
| # temporary file vulnerabilities and TOCTOU races. | ||
| fd, temp_file_path_str = tempfile.mkstemp(prefix="blocklists.", suffix=".tmp", dir=str(cache_dir)) |
Check warning
Code scanning / Pylintpython3 (reported by Codacy)
Line too long (106/100) Warning
| json.dump(_disk_cache, f, indent=2) | ||
|
|
||
| # POSIX guarantees rename is atomic. | ||
| temp_path.replace(cache_file) |
Check warning
Code scanning / Pylintpython3 (reported by Codacy)
Variable name "f" doesn't conform to snake_case naming style Warning
| # Security: use tempfile.mkstemp to securely create a unique temporary file | ||
| # with O_CREAT | O_EXCL and 0o600 permissions, preventing predictable | ||
| # temporary file vulnerabilities and TOCTOU races. | ||
| fd, temp_file_path_str = tempfile.mkstemp(prefix="blocklists.", suffix=".tmp", dir=str(cache_dir)) |
Check warning
Code scanning / Pylint (reported by Codacy)
Line too long (106/100) Warning
| # Security: use tempfile.mkstemp to securely create a unique temporary file | ||
| # with O_CREAT | O_EXCL and 0o600 permissions, preventing predictable | ||
| # temporary file vulnerabilities and TOCTOU races. | ||
| fd, temp_file_path_str = tempfile.mkstemp(prefix="blocklists.", suffix=".tmp", dir=str(cache_dir)) |
Check warning
Code scanning / Pylint (reported by Codacy)
Variable name "fd" doesn't conform to snake_case naming style Warning
| json.dump(_disk_cache, f, indent=2) | ||
|
|
||
| # POSIX guarantees rename is atomic. | ||
| temp_path.replace(cache_file) |
Check warning
Code scanning / Pylint (reported by Codacy)
Variable name "f" doesn't conform to snake_case naming style Warning
| **Vulnerability:** The `_api_client` and `_gh` HTTP clients in `main.py` relied on a broad scalar timeout (`timeout=30`), opening the possibility for attackers to use Slowloris-style DoS attacks or slow network scenarios to exhaust connection pools or processing limits. | ||
| **Learning:** Default or generic long timeouts are not enough; specifying an explicit connection timeout enforces that sockets resolve or fail promptly, separating wait time for establishing a connection from reading data. | ||
| **Prevention:** Use explicit timeouts via `httpx.Timeout(default=read_timeout, connect=connect_timeout)` (e.g., `httpx.Timeout(default=10.0, connect=5.0)`) instead of simple scalar delays, forcing connections to be completed quickly and mitigating resource exhaustion vulnerabilities. | ||
| ## 2024-03-11 - [CRITICAL] Predictable Temporary File Vulnerability in Cache |
Check notice
Code scanning / Remark-lint (reported by Codacy)
Warn when shortcut reference links are used. Note
| **Vulnerability:** The `_api_client` and `_gh` HTTP clients in `main.py` relied on a broad scalar timeout (`timeout=30`), opening the possibility for attackers to use Slowloris-style DoS attacks or slow network scenarios to exhaust connection pools or processing limits. | ||
| **Learning:** Default or generic long timeouts are not enough; specifying an explicit connection timeout enforces that sockets resolve or fail promptly, separating wait time for establishing a connection from reading data. | ||
| **Prevention:** Use explicit timeouts via `httpx.Timeout(default=read_timeout, connect=connect_timeout)` (e.g., `httpx.Timeout(default=10.0, connect=5.0)`) instead of simple scalar delays, forcing connections to be completed quickly and mitigating resource exhaustion vulnerabilities. | ||
| ## 2024-03-11 - [CRITICAL] Predictable Temporary File Vulnerability in Cache |
Check notice
Code scanning / Remark-lint (reported by Codacy)
Warn when references to undefined definitions are found. Note
|
👋 Development Partner is reviewing this PR. Will provide feedback shortly. |
abhimehro
left a comment
There was a problem hiding this comment.
Review Summary
Verdict: Approve for merge
This PR correctly fixes a TOCTOU/symlink attack vulnerability in save_disk_cache() by replacing predictable temp file names with tempfile.mkstemp. The fix is sound:
- Security:
tempfile.mkstempgenerates unpredictable filenames withO_CREAT | O_EXCLand0o600permissions — eliminates the symlink attack vector. - Cleanup: The
try/finallyblock correctly handles both success (wherereplace()moves the temp file, soexists()returns False) and failure (where the temp file is cleaned up). - Atomicity: The atomic
rename/replacepattern is preserved.
Minor note
The except OSError: pass in cleanup was flagged by Gemini code review as a candidate for logging. This is valid but non-blocking — silent cleanup failure in a finally block is a common pattern and won't affect correctness.
Conflict resolution
Resolved a trivial conflict in .jules/sentinel.md caused by both #630 and #632 appending entries to the same file. Both entries are now present in chronological order.
All CI checks passing. Squash-merging.
🤖 Reviewed by automated backlog cleanup agent
There was a problem hiding this comment.
Pull request overview
Addresses a local security vulnerability in cache.save_disk_cache() by replacing a predictable temp filename + unlink/retry pattern with a securely generated unique temp file, reducing TOCTOU and symlink-attack risk when writing the on-disk cache.
Changes:
- Switch temp-file creation in
save_disk_cache()totempfile.mkstemp(...)and wrap write+rename intry...finallycleanup. - Document the vulnerability and prevention guidance in the Sentinel security log.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
cache.py |
Uses tempfile.mkstemp for unique temp cache writes and ensures cleanup after atomic replace. |
.jules/sentinel.md |
Adds an entry documenting the predictable temp-file vulnerability and mitigation guidance. |
You can also share your feedback on Copilot code review. Take the survey.
| ## 2024-03-11 - [CRITICAL] Predictable Temporary File Vulnerability in Cache | ||
| **Vulnerability:** `save_disk_cache` used a predictable file name (`cache_file.with_suffix(".tmp")`) when creating temporary cache files. Despite using `O_EXCL`, the code would unlink existing files and retry if a collision occurred. This opened up a Time-Of-Check-To-Time-Of-Use (TOCTOU) vulnerability window where a malicious user could exploit a symlink attack to overwrite arbitrary system files or inject malicious cache data. |
There was a problem hiding this comment.
The new Sentinel entry labels this issue as [CRITICAL], but the PR title/description classify it as [HIGH]. Please align the severity wording between the PR metadata and the Sentinel log to avoid confusion in future triage/reporting.
| fd, temp_file_path_str = tempfile.mkstemp(prefix="blocklists.", suffix=".tmp", dir=str(cache_dir)) | ||
| temp_path = Path(temp_file_path_str) | ||
|
|
||
| # POSIX guarantees rename is atomic. | ||
| temp_file.replace(cache_file) | ||
| try: | ||
| with os.fdopen(fd, "w", encoding="utf-8") as f: |
There was a problem hiding this comment.
If os.fdopen(fd, ...) raises before the context manager takes ownership, the raw file descriptor fd will leak and the finally cleanup may fail to unlink the temp file on Windows (open files can’t be deleted). Consider ensuring fd is closed in the finally block when it hasn’t been successfully wrapped (e.g., set fd = None after os.fdopen succeeds, and os.close(fd) in finally when still set), and perform cleanup after closing.
| # Security: use tempfile.mkstemp to securely create a unique temporary file | ||
| # with O_CREAT | O_EXCL and 0o600 permissions, preventing predictable | ||
| # temporary file vulnerabilities and TOCTOU races. | ||
| fd, temp_file_path_str = tempfile.mkstemp(prefix="blocklists.", suffix=".tmp", dir=str(cache_dir)) | ||
| temp_path = Path(temp_file_path_str) |
There was a problem hiding this comment.
The existing disk-cache tests appear to assert the absence of a specific temp filename (e.g., blocklists.tmp). With mkstemp(prefix="blocklists.", suffix=".tmp"), that assertion becomes a no-op (the file is never created under that fixed name), so the atomic-write/temp-cleanup behavior may no longer be meaningfully tested. Please update/add a test that checks the cache dir for any leftover blocklists.*.tmp files after save_disk_cache() and (where supported) validates that predictable temp names/symlink pre-creation cannot influence the write.
🚨 Severity: HIGH
💡 Vulnerability:
cache.pyused a predictable temporary file name (blocklists.tmp) with logic that unlinked any existing file before opening it withO_EXCL. This logic was susceptible to a Time-Of-Check-To-Time-Of-Use (TOCTOU) race condition and symlink attacks.🎯 Impact: A malicious actor with local system access could use a symlink attack to overwrite arbitrary files the application user has write access to, or potentially manipulate the loaded cache data by swapping files mid-write.
🔧 Fix: Replaced predictable file logic with Python's secure
tempfile.mkstempusingO_CREAT | O_EXCLand0o600permissions. Wraps writing and renaming inside a robusttry...finallyblock for secure execution and artifact cleanup in case of exceptions.✅ Verification: Run
uv run pytestanduv run ruff check .tests successfully. Tested and reviewed memory to ensure artifacts are properly removed and operations run safely.PR created automatically by Jules for task 7722500068853735371 started by @abhimehro