T09 · Insecure Skill Coding Practices
- Location
scripts/late_brake/io/cache.py:16- Finding
Symlink-Following Telemetry Cache Write Allows Arbitrary Writable-File Corruption
- Content
View full analysis
Vulnerability Details
File Location:
scripts/late_brake/io/cache.py:16-40
Vulnerability Type: Predictable file write without symbolic-link protection
Risk Level: MediumVulnerable Code
python def cache_file_path(source_file: str) -> str: """Generate cache file path.""" source_file_norm = os.path.normpath(source_file) dirname = os.path.dirname(source_file_norm) basename = os.path.basename(source_file_norm) cache_basename = f".{basename}.lb.json" cache_path = os.path.join(dirname, cache_basename) cache_path = os.path.normpath(cache_path) # After normalization, verify cache is still within the normalized source directory # This prevents traversal even if original source_file contained ../ tricks source_dir_norm = os.path.normpath(dirname) + os.sep if not cache_path.startswith(source_dir_norm): raise ValueError(f"Cache path generation failed: {source_file}") return cache_path def save_cached_laps(source_file: str, laps: List[Lap], track_id: str) -> None: """Save parsed and split lap data to the cache.""" cache_path = cache_file_path(source_file) data = { "source_file": source_file, "track_id": track_id, "laps": [lap.model_dump() for lap in laps] } with open(cache_path, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False)Technical Analysis
The cache filename is predictable: a source named
session.vboproduces.session.vbo.lb.jsonin the same directory. The lexical path-prefix check prevents ordinary../traversal, but it does not inspect the destination withlstat, reject symbolic links, or open the file withO_NOFOLLOW.Python's
open(path, "w")follows an existing symbolic link and truncates its target. Consequently, an attacker who can create the predictable cache path can redirect the generated JSON write to another ...[truncated 1494 chars]- Remediation
View remediation
Remediation Suggestions
- Reject symbolic-link destinations using
os.lstatbefore writing. - Open newly created files with
os.openand flags such asO_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, where supported. - Write to a securely created temporary file in the same trusted directory, flush and synchronize it, and publish it with
os.replace. - Apply restrictive cache permissions, such as
0o600. - Validate each relevant path component and reject symlinked parent directories when processing files from attacker-writable locations.
- Consider placing caches in a private application-owned directory instead of beside untrusted telemetry files. Derive cache keys from a canonical source path and metadata.
- Validate cached
source_file,track_id, and lap structure before use. Consider binding the cache to the source using a content hash rather than modification time alone.
- Reject symbolic-link destinations using
