T09 · Insecure Skill Coding Practices
- Location
scripts/utils.py:16- Finding
Predictable Shared Cache Permits Symlink Following and Path Traversal
- Content
View full analysis
Vulnerability Details
File Location:
scripts/utils.py, lines 16-22, 44-55, and 122
Vulnerability Type: Unsafe temporary-file and path construction
Risk Level: HighVulnerable Code
python # Cache directory CACHE_DIR = Path("/tmp/stock_analysis_cache") CACHE_DIR.mkdir(exist_ok=True) def cache_path(ticker: str, data_type: str, suffix: str = "json") -> Path: """Generate cache file path.""" return CACHE_DIR / f"{ticker}_{data_type}.{suffix}"python cache_file = cache_path(ticker, f"kline_{days}") if is_cache_valid(cache_file): try: with open(cache_file, 'r') as f: return json.load(f) except: pass # Cache read failed, fetch fresh url = f"https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?_var=kline_dayqfq&param={ticker},day,,,{days},qfq" try: resp = requests.get(url, timeout=10)python with open(cache_file, 'w') as f: json.dump(result, f, ensure_ascii=False, indent=2)Technical Analysis
The cache uses a fixed, predictable directory under the globally shared
/tmphierarchy. The directory is created withexist_ok=True, but the code does not verify:- Whether an existing path is a real directory rather than a symlink.
- Whether the directory is owned by the current process user.
- Whether its permissions prohibit modification by other local users.
- Whether an individual cache entry is a regular file rather than a symbolic link.
The ticker is also directly incorporated into the cache path without format validation. Path separators and
..components are therefore not rejected, allowing the resolved cache path to escape the intended directory in execution contexts where an attacker controls the ticker.Ordinary
open(..., 'w')follows symbolic links and truncates the target. Consequently, a maliciously prepared cache entry can redirect a successful cache write to another file writable by the Skill process. Cache reads also follow symbolic lin ...[truncated 1932 chars]- Remediation
View remediation
Remediation Suggestions
- Strictly validate ticker values before they reach either the URL or filesystem:
python TICKER_PATTERN = re.compile(r"^(sh|sz)\d{6}$") def validate_ticker(ticker: str) -> str: if not TICKER_PATTERN.fullmatch(ticker): raise ValueError("Invalid ticker format") return ticker-
Validate
daysas an integer within the supported range, such as 1 through 320. -
Use a per-user private cache directory rather than a predictable shared directory. Create it with mode
0700and verify ownership. -
Resolve every generated path and verify that it remains under the resolved cache root:
python candidate = (CACHE_DIR / filename).resolve() candidate.relative_to(CACHE_DIR.resolve())-
Reject cache paths that are symbolic links or non-regular files.
-
Open files using operating-system flags that prevent symlink following, such as
O_NOFOLLOWwhere supported. -
Write to a securely created temporary file in the same directory, flush and synchronize it, and then atomically replace the destination.
-
Do not silently accept an attacker-created cache directory. Verify that the directory is owned by the expected user and is not group- or world-writable.
