T09 · Insecure Skill Coding Practices
- Location
- scripts/generate_image.py:35
- Finding
- Predictable Shared Temporary Cache Permits Cache Poisoning and Symlink Attacks<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_image.py:35-38, 166-191` **Vulnerability Type**: Predictable temporary file with insufficient ownership and symlink protections **Risk Level**: Medium ### Vulnerable Code ```python _MODEL_LIST_CACHE = {"rows": None, "expires_at": 0.0} _MODEL_LIST_TTL = 300 # 5 minutes import tempfile as _tempfile _MODEL_LIST_DISK_CACHE = os.path.join(_tempfile.gettempdir(), "deepsop_model_list.json") ``` ```python def _load_disk_cache(): """Load the disk cache file if present and still fresh; return rows or None.""" import time try: if not os.path.exists(_MODEL_LIST_DISK_CACHE): return None with open(_MODEL_LIST_DISK_CACHE, "r", encoding="utf-8") as f: blob = json.load(f) if not isinstance(blob, dict) or "rows" not in blob: return None if blob.get("expires_at", 0) < time.time(): return None return blob["rows"] except Exception: return None def _save_disk_cache(rows, expires_at): try: with open(_MODEL_LIST_DISK_CACHE, "w", encoding="utf-8") as f: json.dump({"rows": rows, "expires_at": expires_at}, f, ensure_ascii=False) except Exception: pass # best-effort ``` ### Technical Analysis The model list cache uses the fixed filename `deepsop_model_list.json` in the system-wide temporary directory. The code reads and writes this path using ordinary `open()` calls without: - Verifying that the file is a regular file. - Verifying that it is owned by the current user. - Rejecting symbolic links. - Creating it with exclusive and restrictive permissions. - Using an atomic write-and-replace operation. - Separating cache files by user identity. On multi-user systems, another local user may be able to create or modify the predictable path before the Skill runs. A forged cache can influence model availability decisions for up to the cache lifetime. More seriously, if t ...[truncated 1721 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store the cache in a private, per-user cache directory such as: - `$XDG_CACHE_HOME/ai-image-generator/` - `~/.cache/ai-image-generator/` - A platform-specific directory returned by a trusted cache-directory library. 2. Create the directory with permissions restricted to the current user, such as mode `0700` on POSIX systems. 3. Before reading an existing cache: - Use `os.lstat()` rather than following links. - Reject symbolic links and non-regular files. - Verify that the file owner matches the current user. - Reject files with unsafe group or world permissions. 4. Create new cache files with restrictive mode `0600`. 5. On supported POSIX platforms, open files using `os.open()` with `O_NOFOLLOW`, `O_CREAT`, and appropriate exclusive-creation controls. 6. Write updates to a securely created temporary file in the same private directory, flush and optionally `fsync()` it, then use `os.replace()` for atomic replacement. 7. Treat cached rows as untrusted input and validate their types, permitted source categories, method identifiers, and `hiddenState` values. 8. If cross-process caching is not operationally necessary, remove the disk cache and retain only the in-process cache. ]]>
