T09 · Insecure Skill Coding Practices
- Location
scripts/yotta_chain.py:1279- Finding
Requirements Include Path Traversal Permits Reads Outside the Selected Project
- Content
View full analysis
Vulnerability Details
File Location:
scripts/yotta_chain.py, lines 1279–1308; reachable from lines 1399–1401
Vulnerability Type: Unrestricted path traversal in recursive requirements-file includes
Risk Level: MediumComplete Code Snippet
python def parse_requirements(text, base_dir=None, depth=0, out=None): """Parse requirements.txt content. Returns {"packages": {name: {"spec", "has_hash"}}, "index": url|None, "extra": [url,...]}. Follows -r includes (bounded depth). """ out = out if out is not None else {"packages": {}, "index": None, "extra": []} if depth > 5: return out for raw in (text or "").splitlines(): line = raw.strip() if not line or line.startswith("#"): continue low = line.lower() if low.startswith("--index-url") or low.startswith("-i "): parts = line.split(None, 1) if len(parts) > 1: out["index"] = parts[1].strip().split()[0] continue if low.startswith("--extra-index-url"): parts = line.split(None, 1) if len(parts) > 1: out["extra"].append(parts[1].strip().split()[0]) continue if low.startswith("-r ") or low.startswith("--requirement "): parts = line.split(None, 1) if len(parts) > 1 and base_dir: sub = parts[1].strip().split()[0] parse_requirements(_read_text(Path(base_dir) / sub), Path(base_dir), depth + 1, out) continueThe vulnerable parser is invoked for project-controlled requirements files as follows:
python for rf in sorted(base.glob("requirements*.txt")): touched = True parsed = parse_requirements(_read_text(rf), base_dir=rf.parent)Technical Analysis
The scanner treats
-rand--requirementdirectives as paths relative to the directory containing the initial requirements file. However, it neither rejects absolute paths no ...[truncated 2459 chars]- Remediation
View remediation
Remediation Suggestions
- Resolve the scan root once and pass it explicitly to the recursive parser.
- Reject absolute include paths unless an explicit, separately authorized option permits them.
- Resolve each included path before reading and require it to remain within the scan root:
python root = Path(scan_root).resolve() candidate = (Path(base_dir) / sub).resolve() try: candidate.relative_to(root) except ValueError: raise ValueError("Requirements include escapes the selected project") parse_requirements( _read_text(candidate), base_dir=candidate.parent, scan_root=root, depth=depth + 1, out=out, ) - Use the included file's parent directory for nested relative includes instead of retaining the original base directory.
- Track resolved paths in a visited set to prevent cycles and repeated processing.
- Apply an explicit symlink policy. If symlinks are allowed, containment must be checked after resolution.
- Redact URL user information and other credential-bearing components before registry URLs are emitted in text, JSON, CSV, or CI reports.
- Add tests covering absolute paths,
../traversal, symlink escapes, nested includes, cycles, and valid in-project includes.
