T09 · Insecure Skill Coding Practices
- Location
scripts/chan_signal.py:62- Finding
Path Traversal and Unsafe Shared Temporary Cache Handling
- Content
View full analysis
Vulnerability Details
File Location:
scripts/chan_signal.py, lines 62-63 and 90-105, with the unsafe write at lines 172-177
Vulnerability Type: Path traversal, unsafe temporary-file handling, and potential symbolic-link abuse
Risk Level: MediumVulnerable Code
python def _normalize_code(code: str) -> str: return str(code).strip().zfill(6)python code = _normalize_code(code) full_code, market = _code_to_market(code) # Try cache cache_path = os.path.join(CACHE_DIR, str(category), f"{code}.csv") disk_cached = pd.DataFrame() if os.path.isfile(cache_path): try: disk_cached = pd.read_csv(cache_path, encoding="utf-8-sig") if "date" in disk_cached.columns: disk_cached["date"] = pd.to_datetime(disk_cached["date"], errors="coerce") disk_cached = disk_cached.dropna(subset=["date"]) for c in ("open", "close", "high", "low", "vol", "amount"): if c in disk_cached.columns: disk_cached[c] = pd.to_numeric(disk_cached[c], errors="coerce").fillna(0) disk_cached = disk_cached.sort_values("date").reset_index(drop=True) if len(disk_cached) >= count: return disk_cached.iloc[-count:].copy() except Exception: passpython # Save cache try: os.makedirs(os.path.dirname(cache_path), exist_ok=True) df.to_csv(cache_path, index=False, encoding="utf-8-sig") except Exception: passTechnical Analysis
The stock code is documented as a six-digit numeric value, but
_normalize_code()only strips whitespace and left-pads short values. It does not verify that the value contains exactly six digits or reject absolute paths, directory separators, or..traversal components.The normalized value is interpolated directly into a file name and passed to
os.path.join(). A traversal sequence can escape the intended cache directory. If the resulting component is absolute, Python's path joining behavior can discard the ...[truncated 2389 chars]- Remediation
View remediation
Remediation Suggestions
- Enforce the documented stock-code format before performing any network or filesystem operation:
python import re def _normalize_code(code: str) -> str: normalized = str(code).strip() if not re.fullmatch(r"\d{6}", normalized): raise ValueError("Stock code must contain exactly six digits") return normalized-
Validate
category,count, andrecent_barsagainst explicit allowlists and safe numeric ranges. -
Construct and canonicalize the cache path, then verify containment beneath the canonical cache root:
python cache_root = os.path.realpath(CACHE_DIR) cache_path = os.path.realpath( os.path.join(cache_root, str(category), f"{code}.csv") ) if os.path.commonpath([cache_root, cache_path]) != cache_root: raise ValueError("Invalid cache path")-
Create the cache root as a per-user private directory with restrictive permissions, such as mode
0700, rather than relying on a broadly shared predictable temporary path. -
Reject symbolic links for cache files and parent directories. Where supported, use descriptor-based APIs with
O_NOFOLLOWand exclusive creation semantics. -
Write to a safely created temporary file in the same directory, flush and synchronize it, and atomically replace the destination only after successful serialization.
-
Verify cache file ownership and permissions before reading it. Treat cache content as untrusted and enforce schema, row-count, date-range, numeric-range, and maximum-file-size limits.
-
Log cache validation and persistence failures instead of silently suppressing every exception.
