T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/dsp-cli.py:419
- Finding
- Unvalidated UID Paths Allow Arbitrary Directory Deletion and Filesystem Access## Vulnerability Details **File Location**: `scripts/dsp-cli.py`, lines 161–175 and 419–455 **Vulnerability Type**: Path traversal and absolute-path injection **Risk Level**: High ### Vulnerable Code Path construction and entity validation at lines 161–175: ```python class Store: def __init__(self, root: Path): self.root = root.resolve() self.base = self.root / DSP_DIR # ── guards ── def ensure_init(self) -> None: if not self.base.is_dir(): _fail(f"directory {self.base} not found — run 'init' first") def entity_exists(self, uid: str) -> bool: return (self.base / uid).is_dir() def require_entity(self, uid: str) -> None: if not self.entity_exists(uid): _fail(f"entity {uid} does not exist") ``` Recursive deletion at lines 419–455: ```python def remove_entity(self, uid: str) -> None: self.s.ensure_init() self.s.require_entity(uid) all_uids = self.s.all_uids() for other in all_uids: if other == uid: continue imports = self.s.read_imports(other) had = any(u == uid or v == uid for u, v in imports) if had: new_lines = [ _format_import_line(u, v) for u, v in imports if u != uid and v != uid ] _write_lines(self.s.imports_path(other), new_lines) for imp_uid, imp_via in self.s.read_imports(uid): if imp_via: _safe_unlink(self.s.exports_dir(imp_via) / imp_uid / uid) else: _safe_unlink(self.s.exports_dir(imp_uid) / uid) for other in all_uids: if other == uid: continue shared = self.s.read_shared(other) if uid in shared: _remove_line_value(self. ...[truncated 3001 chars]
- Remediation
- ## Remediation Suggestions 1. Validate every UID before using it in a path: ```python UID_RE = re.compile(r"^(?:obj|func)-[0-9a-f]{8}$") def validate_uid(uid: str) -> str: if not UID_RE.fullmatch(uid): _fail(f"invalid entity UID: {uid}") return uid ``` 2. Apply UID validation consistently to all entity-related parameters, including: - `uid` - `owner` - `exporter` - `shared_uid` - `importer` - `imported` - Parsed `via` values - TOC root identifiers 3. Centralize safe path construction and verify containment after resolution: ```python def entity_path(self, uid: str) -> Path: validate_uid(uid) candidate = (self.base / uid).resolve() try: candidate.relative_to(self.base.resolve()) except ValueError: _fail("entity path escapes the .dsp directory") return candidate ``` 4. Replace direct expressions such as `self.base / uid` with the centralized safe-path function. 5. Before recursive deletion, perform a second containment check and reject: - Absolute user-supplied path components - `.` and `..` traversal components - Directory separators inside identifiers - Symlinks or resolved paths outside `.dsp` - Attempts to delete `.dsp` itself or the project root 6. Validate identifiers read from existing `imports`, `shared`, TOC, and export-index files. Treat graph files as potentially untrusted project data rather than assuming their contents are safe. 7. Add regression tests covering absolute paths, traversal strings, malformed UIDs, symlink escapes, and valid entity deletion.
