T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/jlm-coffee.py:18
- Finding
- Predictable Shared Temporary Cache Permits Cache Poisoning and Symlink-Based File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/jlm-coffee.py`, lines 18-19 and 91-113 **Vulnerability Type**: Insecure temporary file handling and symlink following **Risk Level**: Medium ### Vulnerable Code ```python CACHE_DIR = os.path.join(tempfile.gettempdir(), "jlm-coffee") CACHE_FILE = os.path.join(CACHE_DIR, "shops.json") ``` ```python def _read_cache(): """Return cached shops list or None if stale/missing.""" if _force_fresh: return None try: if not os.path.exists(CACHE_FILE): return None age = time.time() - os.path.getmtime(CACHE_FILE) if age > CACHE_TTL: return None with open(CACHE_FILE, "r", encoding="utf-8") as f: return json.load(f) except (json.JSONDecodeError, OSError): return None def _write_cache(shops): """Write shops list to cache file.""" try: os.makedirs(CACHE_DIR, exist_ok=True) with open(CACHE_FILE, "w", encoding="utf-8") as f: json.dump(shops, f, ensure_ascii=False) except OSError: pass # cache is best-effort ``` ### Technical Analysis The application stores cached data at the fixed, predictable path `/tmp/jlm-coffee/shops.json` on typical Linux systems. The system temporary directory is commonly shared among local users. Neither the cache directory nor the cache file is validated for ownership, file type, or symbolic-link status. The calls to `os.path.getmtime()` and `open()` follow symbolic links. The directory is also created without explicitly enforcing private permissions. This creates two related risks: 1. **Cache poisoning:** A local attacker can create the predictable cache file before the victim runs the application. If its modification time is sufficiently recent and its contents are valid JSON, `_read_cache()` trusts the attacker-controlled data. 2. **Symlink-based overwrite:** An attacker can place a symbolic link at the cache path. When `_write_cache()` ...[truncated 2342 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store the cache in a per-user cache directory, such as `$XDG_CACHE_HOME/jlm-coffee`, rather than a globally shared temporary location. 2. Create the cache directory with mode `0700` and verify that it is owned by the effective user. 3. Reject existing symbolic links and unexpected file types using `os.lstat()` or file-descriptor-based checks. 4. Open files with `O_NOFOLLOW` where supported so the operating system rejects symbolic links. 5. Write data to a securely created temporary file in the verified cache directory, set mode `0600`, flush and synchronize it, and atomically replace the cache with `os.replace()`. 6. Validate the owner, permissions, and regular-file status of an existing cache before reading it. 7. Do not silently reuse an attacker-controlled directory. If security checks fail, skip caching or terminate with a clear error. A hardened implementation should use a user-private directory and atomic replacement rather than writing directly to the final predictable path. ]]>
