T09 · Insecure Skill Coding Practices
- Location
scripts/monte_carlo_propagate.py:104- Finding
Path Traversal Through Node-Derived Output Filenames
- Content
View full analysis
None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(obj, indent=2, sort_keys=True), encoding="utf-8") ``` ### Technical Analysis Each user-supplied node identifier is interpolated directly into two output filenames: - `ensembles/_ensemble.json` - `entropy/_entropy.json` There is no validation that the identifier is a simple SWMM node name and no check that the resolved destination remains under `ensemble_dir` or `entropy_dir`. Python's `pathlib` preserves and resolves path separators and `..` components during filesystem access. Consequently, a value containing traversal sequences can cause the generated JSON files to be written outside their intended directories. For example, a node identifier shaped like `../../target` produces paths equivalent to: ```text /ensembles/../../target_ensemble.json /entropy/../../target_entropy.json ``` Exploitation requires `build_ensemble_payload()` to successfully extract data for the supplied identifier befor ...[truncated 1999 chars]- Remediation
View remediation
str: if not node or not SAFE_NODE_RE.fullmatch(node): raise ValueError(f"Unsafe SWMM node identifier: {node!r}") if node in {".", ".."}: raise ValueError(f"Unsafe SWMM node identifier: {node!r}") return node ``` Explicitly reject `/`, `\`, absolute paths, null characters, and traversal components. 2. **Enforce destination-path confinement** Validate the resolved destination immediately before every write: ```python def confined_output_path(base: Path, filename: str) -> Path: base_resolved = base.resolve() destination = (base_resolved / filename).resolve() try: destination.relative_to(base_resolved) except ValueError as exc: raise ValueError("Output path escapes its designated directory") from exc return destination ``` Then construct output paths as follows: ```python safe_node = validate_node_name(node) ensemble_path = confined_output_path( ensemble_dir, f"{safe_node}_ensemble.json", ) entropy_path = confined_output_path( entropy_dir, f"{safe_node}_entropy.json", ) ``` 3. **Separate display identifiers from filesystem names** Prefer generating filenames from an internal index or a safe digest rather than directly from external identifiers: ```python filename_id = hashlib.sha256(node.encode("utf-8")).hexdigest()[:16] ``` Preserve the original node identifier only inside the JSON payload. 4. **Avoid unintended overwrites** If replacing existing output is not required, use exclusive creation or check for an existing destination before writing. Atomic writes through a ...[truncated 426 chars]
