T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/normalize_starcore_family.py:91
- Finding
- Unvalidated Symbols Permit Filesystem Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/normalize_starcore_family.py:91-105, 126-129`; `scripts/bookmark_starcore_family.py:42-49, 66` **Vulnerability Type**: Path traversal through attacker-controlled filename components **Risk Level**: Medium ### Vulnerable Code In `scripts/normalize_starcore_family.py`, symbols obtained from local receipt data are used as dictionary keys and subsequently interpolated into filenames: ```python family_file = state / "starcorex_starcorecoin_clawnch_receipts.json" found: dict[str, dict[str, Any]] = {} for p in pref_files: if p.is_file(): try: j = json.loads(p.read_text(encoding="utf-8")) norm = normalize_starcore(j) if norm.get("symbol"): found[str(norm["symbol"]).upper()] = norm break except (OSError, json.JSONDecodeError): pass if family_file.is_file(): try: j = json.loads(family_file.read_text(encoding="utf-8")) for sym, rec in (j.get("receipts") or {}).items(): found[str(sym).upper()] = normalize_starcore(rec) except (OSError, json.JSONDecodeError): pass ``` The unvalidated key is used directly in the output path: ```python for sym, rec in found.items(): (state / f"{sym}_clawnch_receipt.json").write_text( json.dumps(rec, indent=2, ensure_ascii=False), encoding="utf-8" ) ``` The bookmark generator has a similar issue with command-line symbols: ```python workspace = Path(args.workspace).resolve() symbols = [s.strip().upper() for s in args.symbols.split(",") if s.strip()] recs = load_receipts(workspace, symbols, Path(args.receipts) if args.receipts else None) if not recs: print("No receipts found to bookmark") return 2 out = workspace / args.bookmark_dir out.mkdir(parents=True, exist_ok=True) ``` The symbol is then used directly as a filename: ```python (out / f"{sym}_links.md").write_text("".join(lines[-6:]), encoding="utf-8") ` ...[truncated 2315 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Enforce a strict token-symbol allowlist before using any symbol: ```python import re SYMBOL_RE = re.compile(r"^[A-Z0-9_-]{1,32}$") def validate_symbol(value: str) -> str: symbol = value.strip().upper() if not SYMBOL_RE.fullmatch(symbol): raise ValueError(f"Invalid token symbol: {value!r}") return symbol ``` 2. Apply validation consistently to: - `--symbols` command-line input. - Symbols returned by the Clawnch API. - Keys loaded from receipt and summary JSON files. - Symbols embedded inside individual receipt records. 3. Add a resolved-path containment check immediately before every write: ```python root = state.resolve() destination = (root / f"{symbol}_clawnch_receipt.json").resolve() if root not in destination.parents: raise ValueError("Output path escapes the state directory") ``` 4. Apply the same containment check to bookmark output paths and reject absolute `--bookmark-dir` values if writes are required to remain under the workspace. 5. Reject conflicting symbol values between a receipt object's key and its internal `symbol` field. 6. Add regression tests using values such as `../TARGET`, `A/B`, `A\B`, absolute paths, empty strings, and excessively long symbols. ]]>
