T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/code_search.py:75
- Finding
- Symbolic Link Following Allows Source Disclosure Outside the Indexed Root## Vulnerability Details **File Location**: `scripts/code_search.py:60-77`, with sensitive content serialization at `scripts/code_search.py:145-149` **Vulnerability Type**: Improper symbolic-link handling and missing path-boundary validation **Risk Level**: Medium The directory scanner relies on the lexical path returned by `Path.rglob()` and does not reject symbolic links or verify that the resolved file remains beneath the requested root directory. **Vulnerable code:** ```python def parse_file(self, path: Path) -> list[CodeFragment]: try: source = path.read_text(encoding="utf-8", errors="ignore") tree = ast.parse(source) except (SyntaxError, UnicodeDecodeError): return [] fragments = [] source_lines = source.splitlines() for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): docstring = ast.get_docstring(node) or "" start = node.lineno end = node.end_lineno or start code = "\n".join(source_lines[start - 1:end]) fragments.append(CodeFragment( file_path=str(path), start_line=start, end_line=end, name=node.name, code=code, docstring=docstring, frag_type="method" if isinstance(getattr(node, 'parent', None), ast.ClassDef) else "function" )) elif isinstance(node, ast.ClassDef): docstring = ast.get_docstring(node) or "" start = node.lineno end = node.end_lineno or start code = "\n".join(source_lines[start - 1:end]) fragments.append(CodeFragment( file_path=str(path), start_line=start, end_line=end, name=node.name, code=code, docstring=docstring, frag_type="class" )) return fragments def parse_directory(self, root: Path, extensions: set[str] = None) -> list[CodeFragment]: extensions = extensi ...[truncated 3209 chars]
- Remediation
- ## Remediation Suggestions 1. Resolve the indexing root once and require every candidate's resolved path to remain beneath it. 2. Reject symbolic links explicitly before opening files. 3. Perform validation immediately before opening the file to reduce path-substitution opportunities. 4. Catch filesystem exceptions such as `OSError`, `PermissionError`, and broken-link errors so unsafe entries do not terminate the scan. 5. Consider opening files through a directory file descriptor with platform-supported no-follow protections when indexing untrusted repositories and stronger race resistance is required. 6. Store only the source fields required for search. If complete source is unnecessary, omit `code` and full docstrings from the persisted index. 7. Create saved indexes with restrictive permissions and document that they may contain sensitive source material. Example boundary validation: ```python def parse_directory(self, root: Path, extensions: set[str] = None) -> list[CodeFragment]: extensions = extensions or {".py"} root = root.resolve(strict=True) fragments = [] for candidate in root.rglob("*"): try: if candidate.is_symlink() or not candidate.is_file(): continue resolved = candidate.resolve(strict=True) if not resolved.is_relative_to(root): continue relative_parts = resolved.relative_to(root).parts if resolved.suffix not in extensions: continue if any(part.startswith(".") for part in relative_parts): continue fragments.extend(self.parse_file(resolved)) except (OSError, PermissionError): continue return fragments ``` Where supported, supplement this check with no-follow file-opening semantics to mitigate a time-of-check/time-of-use race between validation and reading.
