T09 · Insecure Skill Coding Practices
- Location
scripts/main.py:54- Finding
Unsanitized Gene Identifier Enables Cache Path Traversal
- Content
View full analysis
Vulnerability Details
File Location:
scripts/main.py, lines 54–65 and 83–84
Vulnerability Type: Path traversal through an attacker-controlled cache filename
Risk Level: MediumVulnerable code:
python def get_cache_path(gene: str) -> Path: CACHE_DIR.mkdir(exist_ok=True) return CACHE_DIR / f"{gene}_ensembl.json" def fetch_gene_data(gene: str, species: str) -> Optional[dict]: """Fetch gene structure from Ensembl REST API with caching.""" cache_path = get_cache_path(gene) if cache_path.exists(): with open(cache_path) as f: return json.load(f)The same attacker-controlled path is subsequently used for writing:
python data = resp.json() with open(cache_path, "w") as f: json.dump(data, f)Technical Analysis
The
genevalue originates from the user-controlled--genecommand-line argument.get_cache_path()directly interpolates it into a path beneath.cachewithout validating allowed characters, rejecting path separators, resolving the path, or verifying that the resolved destination remains inside the cache directory.pathlib.Pathdoes not neutralize..components. Consequently, a value containing traversal segments can cause the resulting path to escape.cache. The forced_ensembl.jsonsuffix limits target names but does not prevent access to files outside the intended directory. If the selected path already exists, the application opens it and parses its contents as JSON. If it does not exist and the Ensembl request succeeds, the response is written to that external path.The write operation also follows symbolic links. An attacker able to prepare a symlink at the selected cache path could redirect the write to another file accessible to the process.
Attack Path
- The attacker invokes the script with a crafted gene identifier containing parent-directory components, fo ...[truncated 1587 chars]
- Remediation
View remediation
Remediation Suggestions
-
Validate gene identifiers before filesystem use. Permit only the documented identifier character set, such as ASCII letters, digits, periods, underscores, and hyphens:
python import re GENE_PATTERN = re.compile(r"^[A-Za-z0-9._-]+$") def validate_gene(gene: str) -> str: if not GENE_PATTERN.fullmatch(gene): raise ValueError( "Gene identifiers may contain only letters, digits, periods, " "underscores, and hyphens." ) return gene -
Use a fixed-length derived cache key. Hash both
speciesandgeneinstead of incorporating raw input into a filename:python import hashlib def get_cache_path(gene: str, species: str) -> Path: CACHE_DIR.mkdir(parents=True, exist_ok=True) key = hashlib.sha256(f"{species}\0{gene}".encode("utf-8")).hexdigest() return CACHE_DIR / f"{key}_ensembl.json" -
Enforce directory containment. Resolve the cache root and candidate path, then reject any candidate outside the root:
python cache_root = CACHE_DIR.resolve() candidate = (cache_root / filename).resolve() if candidate.parent != cache_root: raise ValueError("Invalid cache path") -
Harden file writes. Write to a temporary file within the verified cache directory and atomically replace the destination. Where supported, reject symbolic links by using no-follow file-opening semantics.
-
Include species in the cache key. The current implementation keys only on
gene, which can cause cache collisions between species. Although this is primarily an integrity and correctness concern, including both inputs prevents cross-species cache confusion. -
Handle invalid or corrupted cache data safely. Catch
json.JSONDecodeErrorand report or remove the invalid cache entry rather than terminating with an unhandled exception.
-
