Back to skill

Security audit

OpenClaw Codebase Intelligence

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real local codebase-analysis skill, but its default cache can execute code from an untrusted repository, so it needs review before installation.

Install only if you understand that it scans local repositories and writes cache/export files. Do not run it on untrusted repositories until pickle caching is replaced with a safe format and symlink boundary checks are added. Clear or avoid any preexisting .codebase-intelligence directory in projects, prefer a trusted cache location outside repositories, and expect generated reports or exports to create or overwrite files when those flags are used.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/indexer.py:553
Finding

Arbitrary Code Execution Through Untrusted Pickle Cache Deserialization

Content
View full analysis

Vulnerability Details

File Location: scripts/indexer.py:147-155, scripts/indexer.py:553-562
Vulnerability Type: Unsafe deserialization of an attacker-controlled cache
Risk Level: High

Vulnerable Code

python
def __init__(self, root_path: str, cache_dir: Optional[str] = None):
    self.root = Path(root_path).resolve()
    self.cache_dir = Path(cache_dir) if cache_dir else self._get_default_cache_dir()
    self.cache_file = self.cache_dir / 'codebase_index.pkl'
    self.ignore_patterns = self.DEFAULT_IGNORE
    
    # Load or create index
    self.index: Optional[CodebaseIndex] = None
python
def _get_default_cache_dir(self) -> Path:
    """Get default cache directory"""
    # Store cache in project's .codebase-intelligence directory
    cache_dir = self.root / '.codebase-intelligence'
    cache_dir.mkdir(exist_ok=True)
    return cache_dir
python
def _load_cache(self) -> Optional[CodebaseIndex]:
    """Load index from cache"""
    try:
        if self.cache_file.exists():
            with open(self.cache_file, 'rb') as f:
                return pickle.load(f)
    except Exception as e:
        print(f"  ⚠️  Could not load cache: {e}")
    return None

Technical Analysis

The index cache is stored by default in .codebase-intelligence/codebase_index.pkl inside the project being analyzed. Consequently, the cache file can be supplied or modified by the author of an untrusted repository.

Python pickle is an executable serialization format. During pickle.load(), specially constructed objects can invoke arbitrary callables through reconstruction methods such as __reduce__. Catching exceptions around pickle.load() does not prevent execution because the malicious callable can run before deserialization returns or raises an exception.

The documented analyze, index, and ask workflows construct an Indexer and call build_index(). That ...[truncated 1559 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove pickle deserialization and use a non-executable format such as JSON.
  2. Validate the deserialized JSON against a strict schema before constructing CodebaseIndex and FileIndex objects.
  3. Store caches outside the analyzed repository, such as under a user-owned platform cache directory keyed by a hash of the canonical project path.
  4. Create cache directories and files with restrictive permissions.
  5. Do not automatically migrate or load legacy pickle files found inside repositories.
  6. If legacy cache migration is necessary, require an explicit trusted-cache migration operation and clearly warn that pickle files must not originate from untrusted projects.
  7. Treat cache parse failures as invalid cache data and rebuild the index from source.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/indexer.py:173
Finding

Out-of-Root File Disclosure Through Symbolic Links

Content
View full analysis

Vulnerability Details

File Location: scripts/indexer.py:173-190, scripts/indexer.py:425-438; similar traversal patterns occur in scripts/analyze.py:220-230, scripts/ask.py:23-43, scripts/deps.py:123-136, and scripts/diagram.py:25-45
Vulnerability Type: Filesystem boundary bypass through followed symbolic links
Risk Level: Medium

Vulnerable Code

python
def _parse_file(self, filepath: Path) -> Optional[FileIndex]:
    """Parse a single file and extract information"""
    try:
        stat = filepath.stat()
        language = self._detect_language(filepath)
        
        if not language:
            return None
        
        content = filepath.read_text(encoding='utf-8', errors='ignore')
        lines = content.count('\n') + 1
        file_hash = hashlib.md5(content.encode('utf-8')).hexdigest()[:16]
        
        # Extract symbols based on language
        functions, classes, imports = self._extract_symbols(content, language, filepath)
        
        return FileIndex(
            path=str(filepath.relative_to(self.root)),
python
# Collect all files
all_files = list(self.root.rglob('*'))
source_files = []

for filepath in all_files:
    if not filepath.is_file():
        continue
    if self._should_ignore(filepath):
        continue
    if self._detect_language(filepath):
        source_files.append(filepath)

The legacy Q&A implementation contains the same relevant pattern:

python
for filepath in self.root.rglob('*'):
    if not filepath.is_file():
        continue
...
content = filepath.read_text(encoding='utf-8', errors='ignore')

Technical Analysis

Path.is_file(), Path.stat(), and Path.read_text() follow symbolic links. The code verifies that the lexical path is beneath the selected project root but does not resolve each candidate and verify that its final target remains beneath that root ...[truncated 1963 chars]

Remediation
View remediation

Remediation Suggestions

  1. Reject symbolic links during traversal by checking filepath.is_symlink() before any metadata or content read.
  2. Resolve every candidate path and verify that the canonical target remains under the canonical project root:
    python
    resolved = filepath.resolve(strict=True)
    if not resolved.is_relative_to(self.root):
        continue
    
  3. Perform the boundary check immediately before opening the file to reduce time-of-check/time-of-use exposure.
  4. Apply the same policy consistently in indexer.py, analyze.py, ask.py, deps.py, and diagram.py.
  5. Consider opening files without following symlinks where the operating system and Python version provide a safe mechanism.
  6. Document whether symlinks are unsupported or require explicit opt-in.
  7. Add tests covering direct file symlinks, broken symlinks, links to external directories, and links changed during analysis.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/diagram.py:149
Finding

Mermaid and Graphviz Syntax Injection Through Unescaped Repository Names

Content
View full analysis

Vulnerability Details

File Location: scripts/diagram.py:149-162, scripts/diagram.py:182-199, scripts/diagram.py:224-238, scripts/diagram.py:256-268
Vulnerability Type: Generated diagram syntax injection
Risk Level: Low

Vulnerable Code

python
for i, module in enumerate(modules):
    node_id = f"M{i}"
    node_ids[module] = node_id
    
    # Get file types for icon
    extensions = self.modules[module]
    icon = self._get_module_icon(extensions)
    
    lines.append(f'    {node_id}["{icon} {module}"]')
python
while queue and len(node_ids) < 30:
    current = queue.pop(0)
    if current in visited:
        continue
    visited.add(current)
    
    node_id = f"N{node_count}"
    node_ids[current] = node_id
    node_count += 1
    
    # Determine node type
    icon = "🚀" if current in entry_points else "📦"
    lines.append(f'    {node_id}["{icon} {current}"]')
python
for i, module in enumerate(modules):
    node_id = f"m{i}"
    node_ids[module] = node_id
    
    # Get color based on language
    extensions = self.modules[module]
    color = self._get_module_color(extensions)
    
    lines.append(f'    {node_id} [label="{module}", fillcolor="{color}", style="filled,rounded"];')
python
for module in modules[:10]:  # Limit per category
    node_id = f"N{node_count}"
    node_ids[module] = node_id
    node_count += 1
    
    style = subgraph_colors.get(cat, subgraph_colors['other'])
    lines.append(f'        {node_id}["{module}"]:::{cat}')

Technical Analysis

Top-level repository directory names are attacker-controlled data. Flow-diagram entry points may also be supplied through command-line arguments. These values are interpolated directly into Mermaid and Graphviz DOT labels without escaping quotation marks, backslashes, line breaks, or grammar-specific delimiters.

A crafted directory ...[truncated 1639 chars]

Remediation
View remediation

Remediation Suggestions

  1. Implement separate escaping functions for Mermaid labels and Graphviz DOT quoted strings.
  2. Escape quotation marks, backslashes, carriage returns, and line feeds before interpolation.
  3. Remove or replace other control characters.
  4. Keep generated node identifiers independent from display labels, as the current implementation already does.
  5. Validate command-line entry points before using them as labels.
  6. Consider serializing labels through a well-maintained Mermaid or Graphviz construction library rather than assembling source with string interpolation.
  7. Add tests using names containing ", \, newlines, brackets, semicolons, and renderer-specific directive syntax.
  8. Render generated diagrams under restrictive renderer settings that disable unsafe links or interactive directives where available.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
83% confidence
Finding

The code substantially aligns with the general theme of codebase analysis: it explores project structure, identifies modules/directories, detects languages, and extracts some dependency information. However, important declared capabilities are missing. There is no caching mechanism anywhere in the script, despite the description explicitly advertising caching. There is also no functionality for answering user questions about the codebase; the code is a batch analyzer/report generator only. Its analysis is fairly heuristic and limited to extension-based language detection, simple import parsing for a few languages, module tree construction, and report generation. Additionally, it can write reports to disk via an output argument, which is not necessarily problematic by itself, but it is an extra operational behavior beyond the stated description. Overall, this is a partial match in domain but a meaningful description-behavior mismatch because key advertised capabilities are absent.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

The code is broadly related to codebase understanding and question answering, so the high-level domain matches. However, key declared capabilities are not actually implemented in this chunk. There is no caching mechanism, no meaningful module/project-structure analysis, and no real dependency analysis; dependency questions return a placeholder message. The implementation is a lightweight local indexer and query tool using regex extraction and keyword scoring, which is materially narrower than the declared description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The code is related to codebase understanding, but it is substantially narrower than the declared purpose. Its actual function is a standalone dependency analyzer that parses imports and generates dependency, reverse-dependency, and graph reports. The description promises broader intelligent codebase analysis, project structure exploration, module identification, question answering, and caching. Of these, only dependency analysis is clearly implemented. Because several prominent declared capabilities are absent and the implemented behavior is specifically a CLI reporting tool rather than a general interactive codebase-understanding skill, this is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The code does perform partial codebase structure and dependency analysis, which overlaps with the description. However, its primary purpose is diagram generation, not generalized 'intelligent codebase analysis and understanding' or answering questions about the codebase. The description also explicitly mentions caching, which is absent. Additionally, the script has undeclared output/file-generation behavior by saving diagrams to disk when requested. Overall, the declared description does not accurately represent the actual behavior and omits the tool's main artifact-generation capability.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/indexer.py (reported line 82)May include surrounding context.

python
DEFAULT_IGNORE = [
        '.git', '.svn', '.hg',
        'node_modules', 'vendor', '__pycache__',
        '.venv', 'venv', 'env', '.env',
        'dist', 'build', 'target', 'out',
        '*.min.js', '*.min.css', '*.map',
        '.DS_Store', 'Thumbs.db',

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill documentation advertises and demonstrates capabilities that imply broad filesystem access, shell execution, and potentially network use, but it declares no explicit tool scope or permissions boundary. In an agent environment, missing scope metadata can cause the skill to be invoked with more authority than users expect, increasing the chance of unsafe file reads/writes or command execution against arbitrary repositories.

Content

No source excerpt is available for this finding.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/analyze.py (reported line 285)May include surrounding context.

python
"# Codebase Analysis Report",
            "",
            f"**Root Directory**: `{self.root}`",
            f"**Generated**: {__import__('datetime').datetime.now().isoformat()}",
            "",
            "## Overview",
            "",

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest describes exploring and understanding a codebase, which implies read-oriented inspection of project files. In this script, providing --output causes the tool to create or overwrite a file with the generated report, adding a write side effect that is not reflected in the stated skill description.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The module docstring says it is an "Enhanced Natural Language Query Script with LLM" and specifically mentions using OpenClaw's kimi_search, but the code never imports or calls any LLM or network-backed service. Instead, answer() classifies questions with keyword matching and uses the local Indexer for search and symbol lookup before formatting canned responses.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The help text for --no-llm says "Use local search only (no LLM)", which implies the default path uses an LLM. In reality, the use_llm parameter passed at L454 is ignored by answer(), and every path uses only local indexing and heuristics.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest describes an analysis/understanding capability, which is primarily read-oriented, but this script also persists generated content to a user-specified path. Producing reports is adjacent to analysis, yet arbitrary filesystem writes go beyond the stated description of exploring structure, identifying modules, and answering questions.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script prepends a sibling c-support/lib directory to sys.path and imports c_parser from there, causing code outside the declared skill scope to be executed implicitly at startup. If that external directory is writable, replaced, or supplied from an untrusted checkout, an attacker can achieve arbitrary code execution simply by triggering the indexer.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The indexer reads repository files and persists a cache containing metadata plus the first 500 characters of each file as content_preview, and also writes a JSON copy for inspection. In a codebase-analysis skill, this expands exposure of potentially sensitive source code, secrets, or proprietary snippets into persistent local artifacts without clear user notice or consent.

Content

No source excerpt is available for this finding.

Insecure deserialization: pickle.load()

Medium
Category
Dangerous Code Execution
Confidence
97% confidence
Finding

The indexer deserializes a cache file with pickle.load(), which can execute arbitrary Python code during loading if the pickle file is attacker-controlled or tampered with. In this skill, the cache path is user-influenced via --cache-dir and defaults to a project-local directory, so opening an untrusted repository or shared workspace can turn cache loading into local code execution.

Content

Scanner excerpt · scripts/indexer.py (reported line 561)May include surrounding context.

python
try:
            if self.cache_file.exists():
                with open(self.cache_file, 'rb') as f:
                    return pickle.load(f)
        except Exception as e:
            print(f"  ⚠️  Could not load cache: {e}")
        return None

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/main.py (reported line 33)May include surrounding context.

python
print(f"\n📊 Codebase Analysis Report")
        print(f"{'='*50}")
        print(f"Root Directory: {stats['root']}")
        print(f"Generated: {__import__('datetime').datetime.now().isoformat()}")
        print(f"\n## Overview")
        print(f"- **Total Files**: {stats['total_files']}")
        print(f"- **Total Lines**: {stats['total_lines']:,}")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/main.py (reported line 109)May include surrounding context.

python
cmd.extend(['--format', args.format])
    if args.output:
        cmd.extend(['--output', args.output])
    subprocess.run(cmd)


def run_ask(args):

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/main.py (reported line 137)May include surrounding context.

python
cmd.extend(['--format', args.format])
    if args.output:
        cmd.extend(['--output', args.output])
    subprocess.run(cmd)


def run_ask(args):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The ask command can send codebase-derived context to an LLM by default without any visible notice, consent prompt, or clear data-handling disclosure. In a codebase-intelligence skill, this is more dangerous because source code, secrets, proprietary logic, or internal paths may be transmitted to an external model provider unintentionally.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The document presents core instructions and feature descriptions partly in Chinese and partly in English, but does not indicate that the user can choose a preferred language. This can violate a language/locale policy when documentation forces a language without explicit opt-in or justification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The documentation states that the skill automatically creates a cache directory and supports exporting indexes, but it does not warn users that local files and directories will be written. In agent-driven workflows, silent writes can surprise users, modify repositories, pollute working trees, or overwrite expected artifacts, especially when run in sensitive project directories.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The integration examples redirect output to files and generate artifacts without warning that files may be created or overwritten. In CI, hooks, or automated agent usage, this can lead to accidental clobbering of repository files or unexpected artifact creation that changes build state or commit contents.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code performs a file write to the path provided by --output, but there is no confirmation prompt or explicit warning that an existing file may be overwritten. For a code file, file writes should have some visible disclosure unless clearly covered elsewhere in the skill description, which is not present in this file.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

This code performs a filesystem write by exporting the generated index to a JSON file, but there is no prior confirmation prompt or warning in the surrounding command description that data from the analyzed codebase will be written to disk. Although the success message appears after the write, the user is not proactively informed before the operation occurs.

Content

No source excerpt is available for this finding.