Back to skill

Security audit

Brain CMS

Security checks for vulnerabilities and agentic risk

Overview

This skill is a coherent local memory system, but it should be reviewed because it can automatically rewrite persistent agent memory from LLM output and installs unpinned dependencies.

Install only if you are comfortable with this skill modifying long-term OpenClaw memory and AGENTS.md. Review generated REM updates before relying on them, avoid storing secrets in daily logs or schemas, and prefer a version that pins dependencies, removes shell=True from the installer, narrows triggers, and adds explicit approval or rollback for persistent memory changes.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T02 · Agent Memory Poisoning

Error
Location
brain_scripts/rem.py:56
Finding

Untrusted Log Content Can Poison Persistent Agent Memory During REM Consolidation

Content
View full analysis
dict: domain_list = ", ".join(f'"{d}"' for d in domains) prompt = f"""You are a memory consolidation system. Read the week's logs and extract NEW factual updates per domain. Output ONLY valid JSON with keys: {{{domain_list}}} Each value: list of short factual strings (max 15 words). Empty list [] if nothing new. Only include genuinely new facts, decisions, or status changes — not general knowledge. LOGS: {weekly_logs[:4000]} JSON:""" ``` The resulting model output is written directly into persistent schema files: ```python updates = extract_updates(weekly_logs, list(schemas.keys())) for domain, new_facts in updates.items(): if not new_facts: continue schema_path = schemas.get(domain) if not schema_path: continue section = f"\n\n## REM Update — {date_str}\n" + "".join(f"- {f}\n" for f in new_facts[:10]) with open(schema_path, "a", encoding="utf-8") as f: f.write(section) ``` ### Technical Analysis The weekly logs are treated as trusted prompt content even though they may contain text originating from users, retrieved documents, external messages, or other untrusted sources. The logs are concatenated directly into an instruction-bearing LLM prompt without a robust trust boundary. The response is only checked for a parseable JSON object. There is no validation that: - Each returned value is a list of plain factual statements. - The statements are supported by the source logs. - The output excludes instructions aimed at future agent sessions. - The values conform to the stated 15-word limit. - A human has approved the proposed persistent changes. Once parsed, up to ten generated entries per recognized domain are ...[truncated 1822 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
install.py:136
Finding

Installer Retrieves and Installs Unpinned Third-Party Dependencies

Content
View full analysis
=0.29.0\nnumpy>=1.24.0\npyarrow>=12.0.0\nrequests>=2.28.0\n") ``` ### Technical Analysis The installer asks `pip` to resolve the latest available versions of four packages from the user's configured package index. No exact versions, cryptographic hashes, lock file, or verified package source is provided. The generated `requirements.txt` uses minimum-version constraints, which still permit arbitrary future versions. It is also written after the initial installation and therefore does not constrain that installation. Python package installation may execute package build hooks or other installation-time code. Consequently, a compromised package release, compromised package index, malicious index configuration, or dependency substitution could result in code execution during Skill setup. ### Attack Path 1. An attacker compromises a permitt ...[truncated 1160 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
install.py:91
Finding

Installer Uses a Shell with Unquoted Path Interpolation

Content
View full analysis
bool: result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if check and result.returncode != 0: print(f" [WARN] {result.stderr[:100]}") return False return True ``` Paths derived from the user's home directory are interpolated without shell quoting: ```python venv_path = brain_dir / ".venv" if not venv_path.exists(): print("📦 Creating Python venv...") run(f"python3 -m venv {venv_path}") pip = venv_path / "bin" / "pip" print("📦 Installing dependencies...") run(f"{pip} install lancedb numpy pyarrow requests --quiet") ``` ### Technical Analysis `WORKSPACE` is constructed from `Path.home()`, and the resulting virtual-environment paths are embedded directly into shell command strings. Because `run()` sets `shell=True`, shell metacharacters, whitespace, command substitutions, and separators in a path are interpreted as shell syntax rather than as a literal argument. A normal home-directory path is not inherently hostile. Exploitation therefore requires influence over the effective home-directory environment or execution under an account whose home path contains shell-significant characters. Nevertheless, using a shell is unnecessary for these commands and expands the installer's privilege surface. The same implementation can also produce reliability failures when paths contain ordinary spaces. ### Attack Path 1. An attacker gains control over the environment or account context used to launch the installer, including the effective home-directory value resolved by `Path.home()`. 2. The resulting workspace or virtual-environment path contains shell syntax such as a command separato ...[truncated 956 chars]
Remediation
View remediation
bool: result = subprocess.run(args, shell=False, capture_output=True, text=True) if check and result.returncode != 0: print(f" [WARN] {result.stderr[:100]}") return False return True run(["python3", "-m", "venv", str(venv_path)]) run([ str(pip), "install", "--require-hashes", "-r", str(locked_requirements_path), "--quiet", ]) ``` Additional hardening should include: 1. Validate that installation destinations remain beneath the expected workspace directory. 2. Resolve paths before use and reject unexpected directory traversal or symbolic-link destinations where relevant. 3. Avoid constructing executable command lines through string interpolation. 4. Check subprocess return codes before continuing or reporting successful installation. 5. Use the virtual environment's Python executable through an explicit argument list for package installation. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (18)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
96% confidence
Finding

This pattern is a true positive because the installer invokes shell commands through a generic string-based helper, enabling tool parameter abuse and shell metacharacter interpretation. In an installation skill that writes into a user's workspace and installs packages, this context is more dangerous because compromise would occur during bootstrap with the user's privileges and could lead to arbitrary command execution or unintended package/tool invocation.

Content

Scanner excerpt · install.py (reported line 100)May include surrounding context.

python
# ─── Install ───────────────────────────────────────────────────────────────────
def run(cmd: str, check=True) -> bool:
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    if check and result.returncode != 0:
        print(f"  [WARN] {result.stderr[:100]}")
        return False

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The trigger list includes very broad terms such as "brain," "memory system," and "vector store," which can cause the skill to activate in contexts far beyond its intended use. Unintended activation is risky here because the skill encourages installation steps and persistent memory workflows, increasing the chance of unnecessary dependency installation, model pulls, or changes to long-term agent state.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
82% confidence
Finding

This skill is explicitly designed to create and maintain persistent memory artifacts, including semantic schemas, anchors, logs, and a vector store, which can retain information across sessions. Persistent session memory is dangerous when not bounded by retention, sensitivity filtering, and user consent because it can accumulate secrets, personal data, or stale instructions that later influence agent behavior unexpectedly.

Content

Scanner excerpt · SKILL.md (reported line 31)May include surrounding context.

md
memory/
├── INDEX.md          ← Hippocampus: topic router + cross-links
├── ANCHORS.md        ← Permanent high-significance event store
└── schemas/          ← Domain-specific semantic schemas (you create these)

memory_brain/
├── index_memory.py   ← Embeds schemas into LanceDB vector store

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The code posts each text chunk from memory markdown files to http://localhost:11434/api/embeddings, which transmits user-managed content over HTTP to another service. Although this is likely part of the skill's purpose, the file provides no explicit warning that local memory content will be sent to an external process/service for embedding.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

If the table exists, the code drops it and then creates a new one, which is a destructive operation affecting stored index data. There is no confirmation prompt or explicit warning in comments or user-facing output that rerunning the script will overwrite the existing vector index.

Content

No source excerpt is available for this finding.

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 · brain_scripts/nrem.py (reported line 52)May include surrounding context.

python
venv_py = BRAIN_DIR / ".venv" / "bin" / "python3"
    python  = str(venv_py) if venv_py.exists() else sys.executable
    try:
        r = subprocess.run([python, str(BRAIN_DIR / "index_memory.py")],
                          capture_output=True, text=True, timeout=120)
        print("[NREM] Vector store re-indexed ✅" if r.returncode == 0
              else f"[NREM] Indexer error: {r.stderr[:100]}")

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% 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 · brain_scripts/index_memory.py (reported line 36)May include surrounding context.

python
EMBED_MODEL = "nomic-embed-text"

def embed(text: str) -> list[float]:
    r = requests.post(OLLAMA_URL, json={"model": EMBED_MODEL, "prompt": text}, timeout=30)
    r.raise_for_status()
    return [float(x) for x in r.json()["embedding"]]

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% 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 · brain_scripts/query_memory.py (reported line 21)May include surrounding context.

python
EMBED_MODEL = "nomic-embed-text"

def embed(text: str) -> list[float]:
    r = requests.post(OLLAMA_URL, json={"model": EMBED_MODEL, "prompt": text}, timeout=30)
    r.raise_for_status()
    return [float(x) for x in r.json()["embedding"]]

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The docstring states the action should confirm before running, but the implementation performs consolidation and file modification immediately. This is dangerous because the skill writes LLM-derived content into memory files and triggers follow-on processing without explicit user consent, increasing the chance of unintended data changes.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
91% confidence
Finding

The code transmits weekly logs to an HTTP service endpoint for model processing. Even though the default target is localhost, the transmitted content may include sensitive memory data, and a local service can still be exposed, misconfigured, proxied, or replaced by another process, making this a real confidentiality boundary crossing.

Content

Scanner excerpt · brain_scripts/rem.py (reported line 41)May include surrounding context.

python
def ollama_generate(prompt: str, max_tokens: int = 600) -> str:
    try:
        r = requests.post(OLLAMA_URL, json={"model": REM_MODEL, "prompt": prompt,
            "stream": False, "options": {"num_predict": max_tokens, "temperature": 0.3}}, timeout=120)
        r.raise_for_status()
        return r.json().get("response", "").strip()

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 · brain_scripts/rem.py (reported line 74)May include surrounding context.

python
venv_py = BRAIN_DIR / ".venv" / "bin" / "python3"
    python  = str(venv_py) if venv_py.exists() else sys.executable
    try:
        r = subprocess.run([python, str(BRAIN_DIR / "index_memory.py")],
                          capture_output=True, text=True, timeout=180)
        print("[REM] Vector store re-indexed ✅" if r.returncode == 0
              else f"[REM] Indexer error: {r.stderr[:100]}")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill appends LLM-generated facts directly into memory files without any validation, review, or confirmation. In context, weekly logs are untrusted input and prompt output can be wrong, manipulated, or prompt-injected, so this can corrupt persistent memory and poison downstream indexing or agent behavior.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
94% confidence
Finding

The helper wraps subprocess.run with shell=True and accepts a free-form command string, which is a classic command-injection sink if any part of the string ever becomes attacker-controlled. In this file the current call sites are internally constructed, but they interpolate filesystem paths into shell commands, so unusual path characters or future code changes could turn this into exploitable shell execution.

Content

Scanner excerpt · install.py (reported line 100)May include surrounding context.

python
# ─── Install ───────────────────────────────────────────────────────────────────
def run(cmd: str, check=True) -> bool:
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    if check and result.returncode != 0:
        print(f"  [WARN] {result.stderr[:100]}")
        return False

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The code writes an index_meta.json file containing indexing metadata, which is a filesystem side effect. The current usage text does not mention that running the script creates or updates this metadata file.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

This code rewrites memory markdown files, appends to ANCHORS.md, and writes sleep_log.json, but the top-level docstring only describes "compression" and runtime. There is no warning in the description that user memory files and logs will be modified, which reduces transparency for a shutdown-triggered skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This code sends the user's query text to an HTTP endpoint via requests.post, which is a network transmission of user-provided data. Although the endpoint is localhost, there is no confirmation prompt or user-facing warning near the operation indicating that query contents will be sent to an external service process for embedding.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The documentation frames operation as purely local and cost-free, but the implementation uses requests.post to call http://localhost:11434/api/generate. While this may still be a local service and not necessarily billable, the docstring's phrasing can mislead about the actual network-style interaction performed by the code.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The skill automatically launches a reindex subprocess after modifying data, without warning or user approval. This compounds the risk of bad writes by immediately propagating potentially poisoned or incorrect memory into the searchable vector store and any downstream agent retrieval flows.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.