Back to skill

Security audit

Semantic Code Search

Security checks for vulnerabilities and agentic risk

Overview

This code-search skill is mostly purpose-aligned, but it under-discloses that indexing can persist raw source snippets locally and can follow symlinks outside the intended project.

Review before installing or using on private or untrusted repositories. If used, run it only on trusted directories, watch for symlinks, and treat .code_index.json as sensitive because it may contain source code and docstrings. Do not commit or share that index unless you have checked its contents.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

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.
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The skill description overstates its semantic capabilities and omits that it may persist indexed code fragments and source snippets to '.code_index.json'. This mismatch is dangerous because users may trust it as a read-oriented search utility while it actually exports repository contents to disk, which can leak proprietary or sensitive code and create an unexpected data retention surface.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises capabilities that imply reading and potentially writing repository data, but it does not declare any explicit tool scope or permissions boundaries. In an agent environment, missing scope declarations can cause overbroad tool access, making it easier for the skill to read sensitive files or persist data unexpectedly without clear user or platform controls.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The manifest describes a semantic code search engine covering AST parsing, embedding generation, similarity search, and intent-based queries for codebases generally. However, the implementation advertises support for .js and .ts files via the default extensions set, yet parse_directory routes all matching files through a Python AST parser, causing non-Python files to fail parsing and be silently skipped. This is a meaningful mismatch between stated capability and actual behavior.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The save path writes an index containing raw source code, file paths, and docstrings to a local JSON file without any warning, consent prompt, or data-minimization controls. In a code-search skill, this increases the chance of unintentionally persisting sensitive proprietary code or secrets into an easily copied artifact, especially if run in shared workspaces, repos, or CI environments.

Description-Behavior Mismatch

Low
Confidence
83% confidence
Finding
The manifest explicitly says the skill covers embedding generation and semantic, intent-based code retrieval. In code, the 'SimpleVectorizer' is a TF-IDF-like bag-of-words model with token splitting and normalization, which does not provide true semantic embeddings or intent understanding beyond lexical overlap. This overstates the actual semantic capability of the skill.

Static analysis

No suspicious patterns detected.