From 448e057f62e18480b3b7f4b7f961bf8429586fca Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:04:50 +0000 Subject: [PATCH] Security: Fix TOCTOU race condition in cache file creation Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com> --- main.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/main.py b/main.py index 7e75861..3cff265 100644 --- a/main.py +++ b/main.py @@ -803,12 +803,13 @@ def save_disk_cache() -> None: # Write atomically: write to temp file, then rename # This prevents corrupted cache if process is killed mid-write temp_file = cache_file.with_suffix(".tmp") - with open(temp_file, "w", encoding="utf-8") as f: - json.dump(_disk_cache, f, indent=2) - # Set file permissions to user-only (rw-------) - if platform.system() != "Windows": - temp_file.chmod(0o600) + # Security: Create file with 0600 permissions atomically to prevent race condition (TOCTOU) + # os.open() allows setting mode at creation time, unlike open() which uses umask first. + # mode=0o600 ensures only the owner can read/write. + fd = os.open(temp_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) + with os.fdopen(fd, "w", encoding="utf-8") as f: + json.dump(_disk_cache, f, indent=2) # Atomic rename (POSIX guarantees atomicity) temp_file.replace(cache_file) @@ -1131,19 +1132,22 @@ def validate_folder_data(data: Dict[str, Any], url: str) -> bool: ) return False if "rules" in rg: - if not isinstance (rg["rules"], list): - log. error ( - f"Invalid data from {sanitize_for_log(url)} : rule_groups[fil].rules must be a list." + if not isinstance(rg["rules"], list): + log.error( + f"Invalid data from {sanitize_for_log(url)}: rule_groups[{i}].rules must be a list." ) return False -# Ensure each rule within the group is an object (dict), -# because later code treats each rule as a mapping (e.g., rule.get(...)). -for j, rule in enumerate (rgi"rules"1): -if not isinstance (rule, dict): - log. error ( - f"Invalid data from {sanitize_for_log(u rl)}: rule_groups[fiłl.rules[kił] must be an object." - ) - return False + # Ensure each rule within the group is an object (dict), + # because later code treats each rule as a mapping (e.g., rule.get(...)). + for j, rule in enumerate(rg["rules"]): + if not isinstance(rule, dict): + log.error( + f"Invalid data from {sanitize_for_log(url)}: rule_groups[{i}].rules[{j}] must be an object." + ) + return False + + return True + # Lock to protect updates to _api_stats in multi-threaded contexts. # Without this, concurrent increments can lose updates because `+=` is not atomic.