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## 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
## 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## 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
## 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.

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.

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.

Credential Access

High
Category
Privilege Escalation
Content
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)
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.

Credential Access

High
Category
Privilege Escalation
Content
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:
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.

Lp3

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.

External Transmission

Medium
Category
Data Exfiltration
Content
return resp.json()
    else:
        import requests
        resp = requests.post(url, headers=headers, json=json_body, timeout=timeout)
        resp.raise_for_status()
        return resp.json()
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.

Missing User Warnings

Medium
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.

Missing User Warnings

Medium
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.

Session Persistence

Medium
Category
Rogue Agent
Content
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"))
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.

Missing User Warnings

Medium
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.

Intent-Code Divergence

Medium
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.

Description-Behavior Mismatch

Medium
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.

Context-Inappropriate Capability

Medium
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.

External Transmission

Medium
Category
Data Exfiltration
Content
# 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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
# 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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"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",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"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",
}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.