Back to skill

Security audit

DeepRecall

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but its file-scope protections are weaker than documented and can send more memory or workspace-derived data to an LLM provider than a user may expect.

Install only if you are comfortable with OpenClaw memory and selected workspace file contents being sent to your configured LLM provider. Avoid using it in workspaces that contain secrets, private logs, or symlinks to sensitive files, and do not rely on identity scope as a strict privacy boundary until the scope enforcement and symlink validation issues are fixed.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
deep_recall.py:410
Finding

Requested recall scope is not enforced when selecting and reading files

Content
View full analysis

Vulnerability Details

File Location: deep_recall.py:410-447; related index construction in memory_indexer.py:100-143
Vulnerability Type: Scope authorization bypass and excessive disclosure of workspace memory
Risk Level: High

Vulnerable Code

python
# 2. Scan memory files
ws = Path(workspace) if workspace else _find_workspace()
scanner = MemoryScanner(workspace=ws)
scanner.scan(scope=scope)

if not scanner.files:
    return "[DeepRecall] No memory files found in workspace."

# 3. Build memory index
memory_index = build_memory_index(workspace=ws)

# 4. Manager: pick the relevant files
try:
    selected_files = _manager_call(query, memory_index, max_files, provider)
except Exception as exc:
    return f"[DeepRecall] Manager call failed: {exc}"

if not selected_files:
    return "[DeepRecall] No relevant memory files identified for this query."

# 5. Workers: extract quotes in parallel
worker_results: list[dict] = []
with ThreadPoolExecutor(max_workers=min(len(selected_files), 4)) as pool:
    futures = {}
    for fpath in selected_files:
        content = _read_file(fpath, ws)
        if content is None:
            continue
        fut = pool.submit(_worker_call, query, fpath, content, provider)
        futures[fut] = fpath

The independently constructed index includes all Markdown memory files:

python
memory_dir = workspace / "memory"
memory_md = workspace / "MEMORY.md"

# Collect all daily logs
daily_logs = {}
if memory_dir.exists():
    for f in sorted(memory_dir.glob("*.md")):
        # Extract date from filename
        date_match = re.match(r"(\d{4}-\d{2}-\d{2})", f.name)
        if date_match:
            date_str = date_match.group(1)
            content = f.read_text(errors="replace")
            topics = extract_topics(content, f.name)
            daily_logs[date_str] = {
                "path": f"memory/{f.name}",
        
...[truncated 3065 chars]
Remediation
View remediation

Remediation Suggestions

  1. Treat the canonical paths in scanner.files as an authorization allowlist:
    python
    allowed_paths = {
        item.path.resolve()
        for item in scanner.files
    }
    
    for fpath in selected_files:
        resolved = _safe_path(fpath, ws)
        if resolved is None or resolved not in allowed_paths:
            logger.warning("Rejected out-of-scope manager selection: %r", fpath)
            continue
    
  2. Build the manager index directly from the already-scoped scanner result rather than rescanning the workspace.
  3. Change build_memory_index() to accept an explicit collection of authorized files or a validated scope.
  4. Apply scope validation after manager output because model responses are untrusted, even if the prompt instructs the model to honor a scope.
  5. Add regression tests proving that identity cannot index or read files under memory/, and that memory cannot select arbitrary project files.
  6. Ensure documentation accurately describes both metadata and full-content boundaries for every scope.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
memory_indexer.py:105
Finding

Workspace scanning and indexing follow symlinks to files outside the workspace

Content
View full analysis

Vulnerability Details

File Location: memory_indexer.py:105-137; related scanner reads in memory_scanner.py:68-76 and memory_scanner.py:154-176
Vulnerability Type: Symlink traversal causing unauthorized local file reads and remote metadata disclosure
Risk Level: Medium

Vulnerable Code

python
# Collect all daily logs
daily_logs = {}
if memory_dir.exists():
    for f in sorted(memory_dir.glob("*.md")):
        # Extract date from filename
        date_match = re.match(r"(\d{4}-\d{2}-\d{2})", f.name)
        if date_match:
            date_str = date_match.group(1)
            content = f.read_text(errors="replace")
            topics = extract_topics(content, f.name)
            daily_logs[date_str] = {
                "path": f"memory/{f.name}",
                "size": len(content),
                "topics": topics,
            }

# Non-dated memory files (e.g. LONG_TERM.md, heartbeat-state.json)
other_memory = {}
if memory_dir.exists():
    for f in sorted(memory_dir.glob("*.md")):
        date_match = re.match(r"(\d{4}-\d{2}-\d{2})", f.name)
        if not date_match:  # Not a daily log
            content = f.read_text(errors="replace")
            topics = extract_topics(content, f.name)
            other_memory[f.name] = {
                "path": f"memory/{f.name}",
                "size": len(content),
                "topics": topics,
            }

The scanner similarly reads paths without first enforcing canonical containment:

python
class MemoryFile:
    """Represents a single discovered memory file."""
    
    def __init__(self, path: Path, workspace: Path):
        self.path = path
        rel = path.relative_to(workspace)
        self.rel_path = str(rel)
        self.content = path.read_text(errors="replace")
        self.size = len(self.content)
        self.headers = extract_headers(self.content)
        self.key_term
...[truncated 2514 chars]
Remediation
View remediation

Remediation Suggestions

  1. Resolve every candidate before reading and verify canonical containment:
    python
    workspace_root = workspace.resolve()
    candidate = f.resolve(strict=True)
    
    if not candidate.is_relative_to(workspace_root):
        logger.warning("Skipping path outside workspace: %s", f)
        continue
    
  2. Reject symbolic links outright if they are not required:
    python
    if f.is_symlink():
        continue
    
  3. Centralize all file access through the existing _safe_path()-style validation instead of maintaining separate unchecked read paths.
  4. Apply canonical validation in MemoryFile.__init__(), MemoryScanner.scan(), build_memory_index(), and update_memory_index().
  5. Perform validation immediately before opening the file to reduce time-of-check/time-of-use risk.
  6. Add tests covering symlinks in the workspace root and memory/, including links to external files and links that are replaced between discovery and reading.
  7. Update the privacy guarantee only after all scanner and indexer paths enforce the same workspace boundary.
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
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (31)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill states it performs memory recall over LLM APIs, but it also documents access to local OpenClaw config and credential paths, including cached tokens, and supports provider/credential bridging across many services. That is dangerous because a skill with access to credential stores and broad network capability materially increases the risk of secret exposure, unauthorized provider use, and unintended transmission of sensitive workspace data.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The skill states it performs memory recall over LLM APIs, but it also documents access to local OpenClaw config and credential paths, including cached tokens, and supports provider/credential bridging across many services. That is dangerous because a skill with access to credential stores and broad network capability materially increases the risk of secret exposure, unauthorized provider use, and unintended transmission of sensitive workspace data.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The skill states it performs memory recall over LLM APIs, but it also documents access to local OpenClaw config and credential paths, including cached tokens, and supports provider/credential bridging across many services. That is dangerous because a skill with access to credential stores and broad network capability materially increases the risk of secret exposure, unauthorized provider use, and unintended transmission of sensitive workspace data.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

The function pulls API keys directly from environment variables, granting the skill access to ambient secrets present in the host process. In a plugin/agent setting, ambient credential access is dangerous because it broadens privilege boundaries and can enable unintended use of provider accounts if the skill is invoked in an untrusted workflow.

Content

Scanner excerpt · provider_bridge.py (reported line 143)May include surrounding context.

python
def _get_api_key_from_env(provider: str) -> Optional[str]:
    """Try to get API key from environment variables."""
    env_key = PROVIDER_ENV_KEYS.get(provider)
    if env_key:
        return os.environ.get(env_key)

Credential Access

High
Category
Privilege Escalation
Confidence
86% confidence
Finding

The function reads provider API keys from configuration structures, potentially including locally stored secrets outside the immediate task scope. In this skill context, that increases sensitivity because the module can aggregate credentials from multiple storage locations and use them for outbound requests.

Content

Scanner excerpt · provider_bridge.py (reported line 151)May include surrounding context.

python
def _get_api_key_from_config(config: dict, provider: str) -> Optional[str]:
    """Try to get API key from OpenClaw config (env section or provider config)."""
    env_section = config.get("env", {})
    env_key = PROVIDER_ENV_KEYS.get(provider)
    if env_key and env_key in env_section:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill advertises significant capabilities: reading workspace files, resolving local credentials/configuration, making outbound LLM HTTP requests, and offering a CLI, yet it does not declare any explicit tool scope or permissions. That mismatch is dangerous because users or hosting platforms may not realize the skill can access sensitive local memory files and transmit selected contents to third-party providers.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
89% confidence
Finding

The HTTP POST path enables transmission of prompts and memory contents to any configured OpenAI-compatible endpoint, which can include third-party services. In the context of a persistent-memory skill, this increases the sensitivity of the transmitted data and can expose private memories, project details, or identifiers to external infrastructure without strong trust validation or endpoint restrictions.

Content

Scanner excerpt · deep_recall.py (reported line 97)May include surrounding context.

python
return resp.json()
    else:
        import requests
        resp = requests.post(url, headers=headers, json=json_body, timeout=timeout)
        resp.raise_for_status()
        return resp.json()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill reads memory file contents and sends them to an external LLM provider through _worker_call and _synthesis_call via _chat, but there is no explicit user-facing consent or warning at the point of transmission. Because these memory files may contain sensitive personal or project data, this creates a confidentiality risk whenever the configured provider is remote or untrusted.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The code reads all memory markdown files and writes a consolidated MEMORY_INDEX.md containing headers, people, projects, keywords, and summary lines extracted from those files. This creates a new, easier-to-consume artifact that can amplify exposure of sensitive memory contents and personal data, especially in an agent system where downstream components may read the index automatically without any user warning or consent checkpoint.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
78% confidence
Finding

The function persists derived session and memory metadata to MEMORY_INDEX.md in the workspace, creating a durable artifact from potentially sensitive agent memory. In the context of a persistent recall skill, this increases retention and discoverability of prior context, which can broaden unintended disclosure if the workspace is shared, synced, inspected by other tools, or left uncleared.

Content

Scanner excerpt · memory_indexer.py (reported line 266)May include surrounding context.

python
def update_memory_index(workspace: Optional[Path] = None) -> Path:
    """Build and write MEMORY_INDEX.md to the workspace."""
    workspace = workspace or Path(
        os.environ.get("OPENCLAW_WORKSPACE",
                       os.path.expanduser("~/.openclaw/workspace"))

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The scanner is designed to aggregate workspace and memory file contents into manifest/context outputs, but there is no built-in warning, consent checkpoint, or scope disclosure before collection. In a persistent-agent skill whose purpose is memory recall, that makes accidental data exfiltration more dangerous because users may assume only narrow memory files are involved while broader workspace content can be swept in.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The documentation promises long-term memory is 'searched, not loaded,' but the implementation constructs MemoryFile objects that immediately read full file contents and get_context() includes LONG_TERM.md verbatim. This mismatch can cause significantly more data to be ingested or transmitted than operators expect, increasing the chance of oversharing sensitive historical memory.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

In project/all modes, the scanner recursively reads arbitrary readable files from the workspace and later concatenates their full contents into a single context string. In an agent setting, that can expose unrelated secrets, proprietary source, credentials, or personal data to downstream LLM calls beyond what a user may expect from a 'memory' feature.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This module explicitly reads API tokens from local configuration, credential files, and environment variables, which expands the skill's effective privileges beyond simple 'memory recall' logic. In an agent skill context, broad credential access is security-sensitive because any downstream bug or prompt-controlled behavior could cause those secrets to be used or relayed to external services.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 43)May include surrounding context.

python
# Provider → OpenAI-compatible base URL mapping
PROVIDER_BASE_URLS = {
    "anthropic": "https://api.anthropic.com/v1",
    "openai": "https://api.openai.com/v1",
    "github-copilot": "https://api.individual.githubcopilot.com",
    "openrouter": "https://openrouter.ai/api/v1",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 44)May include surrounding context.

python
# Provider → OpenAI-compatible base URL mapping
PROVIDER_BASE_URLS = {
    "anthropic": "https://api.anthropic.com/v1",
    "openai": "https://api.openai.com/v1",
    "github-copilot": "https://api.individual.githubcopilot.com",
    "openrouter": "https://openrouter.ai/api/v1",
    "google": "https://generativelanguage.googleapis.com/v1beta/openai",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 49)May include surrounding context.

python
"openrouter": "https://openrouter.ai/api/v1",
    "google": "https://generativelanguage.googleapis.com/v1beta/openai",
    "ollama": "http://localhost:11434/v1",
    "minimax": "https://api.minimax.chat/v1",
    "deepseek": "https://api.deepseek.com/v1",
    "zhipu": "https://open.bigmodel.cn/api/paas/v4",
    "moonshot": "https://api.moonshot.cn/v1",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 50)May include surrounding context.

python
"google": "https://generativelanguage.googleapis.com/v1beta/openai",
    "ollama": "http://localhost:11434/v1",
    "minimax": "https://api.minimax.chat/v1",
    "deepseek": "https://api.deepseek.com/v1",
    "zhipu": "https://open.bigmodel.cn/api/paas/v4",
    "moonshot": "https://api.moonshot.cn/v1",
    "qwen": "https://dashscope.aliyuncs.com/compatible-mode/v1",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 52)May include surrounding context.

python
"minimax": "https://api.minimax.chat/v1",
    "deepseek": "https://api.deepseek.com/v1",
    "zhipu": "https://open.bigmodel.cn/api/paas/v4",
    "moonshot": "https://api.moonshot.cn/v1",
    "qwen": "https://dashscope.aliyuncs.com/compatible-mode/v1",
    "mistral": "https://api.mistral.ai/v1",
    "together": "https://api.together.xyz/v1",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 54)May include surrounding context.

python
"zhipu": "https://open.bigmodel.cn/api/paas/v4",
    "moonshot": "https://api.moonshot.cn/v1",
    "qwen": "https://dashscope.aliyuncs.com/compatible-mode/v1",
    "mistral": "https://api.mistral.ai/v1",
    "together": "https://api.together.xyz/v1",
    "groq": "https://api.groq.com/openai/v1",
    "fireworks": "https://api.fireworks.ai/inference/v1",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 55)May include surrounding context.

python
"moonshot": "https://api.moonshot.cn/v1",
    "qwen": "https://dashscope.aliyuncs.com/compatible-mode/v1",
    "mistral": "https://api.mistral.ai/v1",
    "together": "https://api.together.xyz/v1",
    "groq": "https://api.groq.com/openai/v1",
    "fireworks": "https://api.fireworks.ai/inference/v1",
    "cohere": "https://api.cohere.com/v1",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 56)May include surrounding context.

python
"qwen": "https://dashscope.aliyuncs.com/compatible-mode/v1",
    "mistral": "https://api.mistral.ai/v1",
    "together": "https://api.together.xyz/v1",
    "groq": "https://api.groq.com/openai/v1",
    "fireworks": "https://api.fireworks.ai/inference/v1",
    "cohere": "https://api.cohere.com/v1",
    "perplexity": "https://api.perplexity.ai",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 57)May include surrounding context.

python
"mistral": "https://api.mistral.ai/v1",
    "together": "https://api.together.xyz/v1",
    "groq": "https://api.groq.com/openai/v1",
    "fireworks": "https://api.fireworks.ai/inference/v1",
    "cohere": "https://api.cohere.com/v1",
    "perplexity": "https://api.perplexity.ai",
    "sambanova": "https://api.sambanova.ai/v1",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 58)May include surrounding context.

python
"together": "https://api.together.xyz/v1",
    "groq": "https://api.groq.com/openai/v1",
    "fireworks": "https://api.fireworks.ai/inference/v1",
    "cohere": "https://api.cohere.com/v1",
    "perplexity": "https://api.perplexity.ai",
    "sambanova": "https://api.sambanova.ai/v1",
    "cerebras": "https://api.cerebras.ai/v1",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · provider_bridge.py (reported line 60)May include surrounding context.

python
"fireworks": "https://api.fireworks.ai/inference/v1",
    "cohere": "https://api.cohere.com/v1",
    "perplexity": "https://api.perplexity.ai",
    "sambanova": "https://api.sambanova.ai/v1",
    "cerebras": "https://api.cerebras.ai/v1",
    "xai": "https://api.x.ai/v1",
}

Static analysis

No suspicious patterns detected.