T09 · Insecure Skill Coding Practices
- Location
scripts/yield_scout.py:22- Finding
Predictable Shared Temporary Cache Enables Symlink Overwrite and Cache Poisoning
- Content
View full analysis
Vulnerability Details
File Location:
scripts/yield_scout.py, lines 22 and 76–88
Vulnerability Type: Unsafe temporary-file handling
Risk Level: MediumVulnerable Code
python CACHE_PATH = os.path.join(tempfile.gettempdir(), "yield_scout_pools.json")python def load_pools(force=False): if not force and os.path.exists(CACHE_PATH): age = time.time() - os.path.getmtime(CACHE_PATH) if age < CACHE_TTL: with open(CACHE_PATH, "r") as f: return json.load(f) print("Fetching pool data from DeFiLlama (this may take a moment)...", file=sys.stderr) data = fetch_json(POOLS_URL, timeout=120) pools = data.get("data", data) if isinstance(data, dict) else data with open(CACHE_PATH, "w") as f: json.dump(pools, f) return poolsTechnical Analysis
The pool cache uses a fixed, predictable filename in the system temporary directory. On multi-user systems, that directory is commonly shared and writable by untrusted local users.
The implementation:
- Does not ensure that the cache is owned by the current user.
- Does not verify that the cache is a regular file.
- Does not prevent symbolic-link traversal.
- Separately checks and opens the path, creating a time-of-check/time-of-use window.
- Writes directly to the destination rather than securely creating a temporary file and atomically replacing it.
- Trusts cached JSON without validating its structure or provenance.
An attacker with local access can pre-create the cache as attacker-controlled JSON. If its modification time falls within the 15-minute TTL,
scanandbreakevenwill trust that content instead of retrieving current DeFiLlama data. This can falsify pool APYs, TVL figures, protocol names, risk ratings, and breakeven recommendations.The attacker can alternatively create the predictable path as a symbolic link. When the cache is refreshed,
open(CACHE_PATH, "w")follows that link and truncates or ov ...[truncated 1881 chars]- Remediation
View remediation
Remediation Suggestions
-
Store the cache in a private per-user cache directory rather than the shared temporary directory. For example, use an appropriate platform-specific user cache directory and ensure that the directory has mode
0700. -
Before trusting an existing cache:
- Use
os.lstat()rather than following links. - Reject symbolic links and non-regular files.
- Verify that the file is owned by the current user.
- Reject files with group or world write permissions.
- Validate the decoded JSON against the expected pool schema.
- Use
-
Create cache files securely with restrictive permissions. On supported systems, use flags equivalent to:
python os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOWSet the file mode to
0600.-
Avoid writing directly to the final cache path. Securely create a temporary file inside the same private directory, write and flush the complete payload, call
os.fsync()where durability matters, and atomically install it withos.replace(). -
Handle malformed or untrusted cache data safely. If validation fails, discard the cache and retrieve fresh data rather than terminating or using partially trusted records.
-
If a shared cache is operationally required, use a dedicated directory with controlled ownership and permissions, unpredictable filenames, and locking to prevent races between concurrent processes.
-
