Back to skill

Security audit

Smart Memory Plus

Security checks for vulnerabilities and agentic risk

Overview

This is a real local memory skill, but it needs review because it stores agent memory persistently and several scripts can write, move, restore, or expose files outside the intended scope.

Install only if you are comfortable with a memory skill that persists conversation-derived facts locally and mutates memory files. Review or patch the /tmp cache handling, health-report filename handling, restore path containment, classify --file writes, and archive destination controls before using it on shared machines or sensitive workspaces.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/memory_health.sh:93
Finding

Python Code Injection Through Crafted Session Cache Filenames

Content
View full analysis
/dev/null || echo 0) echo " $(basename "$cache"): $KEYS entries" done ``` ### Technical Analysis The cache filename comes from a glob over a shared, attacker-writable `/tmp` directory. It is interpolated directly into a Python program passed through `python3 -c`. Although the shell variable is surrounded by shell double quotes, its contents become part of a single-quoted Python string. Unix filenames may contain single quotes, semicolons, parentheses, and hash characters. A crafted filename can therefore terminate the Python string, close the surrounding function calls, insert additional Python statements, and comment out the remaining source. This is code injection rather than ordinary shell injection: the shell safely expands the variable, but the resulting value is interpreted as Python source. ### Attack Path 1. A local attacker creates a valid JSON file that will satisfy the initial `open()` operation: ```bash printf '{}' > /tmp/openclaw-session-cache-x ``` 2. The attacker creates a matching filename whose basename contains Python syntax, conceptually: ```text /tmp/openclaw-session-cache-x'))));__import__('os').system('id');#.json ``` 3. The agent or user runs: ```bash bash scripts/memory_health.sh ``` 4. The glob includes the crafted filename. 5. The filename is inserted into the `python3 -c` program. 6. The injected Python expression invokes `os.system()` with the privileges of the process running the health script. The payload can be changed from `id` to commands that read, modify, or delete files accessible to the agent account. ### Impact Assessment ...[truncated 503 chars]
Remediation
View remediation
/dev/null || echo 0 ) printf ' %s: %s entries\n' "$(basename -- "$cache")" "$KEYS" done ``` Additional hardening should include: 1. Store caches in a private, user-owned directory with mode `0700` rather than shared `/tmp`. 2. Reject symbolic links and non-regular files before reading. 3. Avoid enumerating cache files belonging to other sessions or users. 4. Use a Python health-check implementation so paths are passed as data throughout. 5. Add regression tests using filenames containing quotes, semicolons, newlines, and parentheses. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/session_cache.py:18
Finding

Predictable Shared Temporary Cache Allows Symlink Overwrite and Cross-Session Disclosure

Content
View full analysis
dict: if CACHE_FILE.exists(): try: return json.loads(CACHE_FILE.read_text(encoding="utf-8")) except (json.JSONDecodeError, OSError): return {} return {} def save_cache(data: dict): CACHE_FILE.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") ``` ### Technical Analysis The cache is stored under a predictable filename in a globally writable temporary directory. The implementation does not: - Create the file exclusively - Reject symbolic links - Verify file ownership - Use atomic replacement - Explicitly set mode `0600` - Use an unpredictable session identifier - Implement the documented session-end cleanup `Path.write_text()` follows symbolic links. An attacker who can predict the session identifier can pre-create the cache path as a symbolic link to another file writable by the victim. A subsequent `set`, `remove`, or `clear` operation will overwrite the symlink target. The identifier is also normalized by deleting unsupported characters. Distinct identifiers may consequently collapse to the same value. For example, identifiers that differ only by removed punctuation can share one cache file. The default identifier is always `default`, making collisions especially likely. ### Attack Path #### Symlink overwrite 1. The attacker predicts that the victim will use the default cache path: ```text /tmp/openclaw-session-cache-default.json ``` 2. The attacker creates that path as a symbolic link to a file writabl ...[truncated 1050 chars]
Remediation
View remediation

T02 · Agent Memory Poisoning

Error
Location
scripts/session_state.py:275
Finding

Arbitrary Local File Restore Into Persistent Agent State

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/classify_memory.py:210
Finding

Arbitrary File Modification Through Unrestricted Classification Path

Content
View full analysis
0: filepath.write_text("\n".join(lines), encoding="utf-8") ``` ```python parser.add_argument("--file", type=Path, help="Specific file to analyze") parser.add_argument("--dry-run", action="store_true", help="Show suggestions without applying") parser.add_argument("--apply", action="store_true", help="Apply suggested tags") ``` ```python # Determine files to process if args.file: files = [args.file] else: memory_dir = WORKSPACE / "memory" files = [WORKSPACE / "MEMORY.md"] if memory_dir.exists(): files.extend(f for f in memory_dir.glob("*.md") if f.name != "archive") all_suggestions = [] for filepath in files: if not filepath.exists(): continue suggestions = analyze_file(filepath) suggestions = [s for s in suggestions if s["confidence"] >= args.min_confidence] all_suggestions.extend(suggestions) if suggestions: if args.apply: count = apply_tags(filepath, suggestions, dry_run=False) ``` ### Technical Analysis The `--file` parameter accepts an arbitrary `Path`. When combined with `--apply`, the file is read, classified, and rewritten without checking whether it belongs to the configured workspace. The implementation contradicts the Skill’s declared restriction that writes remain inside the workspace and occur only against designated memory files. Symbolic links are also followed by `read_text()` and `write_text()`. Only files containing classifiable list entries will be changed, which limits general exploitability, but attacker-selected Markdown, configuration, or documentation files can still be modified. ### Attack Path 1. The attacker identifies a writable external file containing Markdown-style list items. 2. The attacker causes the ...[truncated 915 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/memory_decay.py:64
Finding

Unrestricted Archive Destination Moves Memory Outside the Workspace

Content
View full analysis
max_age_days: archive_subdir = ARCHIVE_DIR / file_date.strftime("%Y-%m") if not dry_run: archive_subdir.mkdir(parents=True, exist_ok=True) shutil.move(str(f), str(archive_subdir / f.name)) ``` ```python def run_decay(max_age_days: int, archive_dir: Path, dry_run: bool) -> None: """Main decay runner.""" global ARCHIVE_DIR ARCHIVE_DIR = archive_dir ``` ```python parser.add_argument("--archive-dir", type=Path, default=ARCHIVE_DIR, help="Archive directory (default: memory/archive)") ``` ### Technical Analysis The command-line archive destination is accepted without verifying that it remains under `WORKSPACE/memory/archive`. The decay operation then moves stale daily memory files into that location. Because `shutil.move()` removes the original source after a successful move, this is a destructive relocation rather than a harmless copy. The destination may be: - Outside the configured workspace - A shared or insecure directory - A path reached through a symbolic link - A location where a file with the same name already exists This behavior exceeds the minimum privileges needed to archive workspace memory and conflicts with the documented workspace-only write restriction. ### Attack Path 1. The attacker chooses a destination outside the workspace. 2. The agent is induced to run: ```bash python3 scripts/memory_decay.py \ --max-age-days 0 \ --archive-dir /attacker/selected/location ``` 3. The script identifies dated memory files older than the threshold. 4. It creates destination subdirectories where permitted. 5. It moves matching files out of the workspace. 6. Memory data may become exposed at the destination, and the workspa ...[truncated 493 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/session_cache.py:23
Finding

Inconsistent Sensitive Data Filtering Permits Credential Persistence

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (26)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The supplied code does implement part of the declared description: memory type classification, tagging of MEMORY.md-style entries, duplicate checking, and simple memory search. However, the description significantly overstates the functionality. There is no evidence in this code of a write-ahead log protocol, temporal decay, session caching, context compression, digest generation, automatic context injection, or task-state tracking. The actual resource access is limited to local markdown files in the workspace, which is consistent with memory-file management, but the primary purpose is materially narrower than the declared 'complete memory system.' Therefore this is a description/behavior mismatch due to substantial missing declared capabilities.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The description significantly overstates the scope. The supplied code chunk implements only the context-compression portion of the declared system: parsing conversation text, extracting decisions/facts/pending items/blockers, sanitizing secrets/URLs/paths, saving compacts to disk, listing them, showing the latest one, and computing simple stats. It does not implement the broader 'complete memory system' features named in the description, such as WAL protocol, temporal decay, session cache, MEMORY.md management, or general memory search/tracking facilities. The code’s filesystem access is limited and consistent with local memory storage, so the main issue is a materially narrower actual purpose than declared.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description presents a full-featured memory subsystem, but the supplied code only implements one small part: extracting candidate memories from text and appending them to a daily markdown file. While type classification is partially present, the majority of the advertised capabilities are absent, including WAL, decay, cache, compression, search, task tracking, and auto-injection. This is a material description-to-behavior mismatch because the primary purpose in practice is limited memory extraction, not a complete memory system.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The declared description substantially overstates the scope of this code chunk. The actual script only performs three related maintenance tasks on markdown memory files: archiving old daily files, checking MEMORY.md for untyped lines, and promoting repeated [LESSON] entries into MEMORY.md. While this partially aligns with the 'temporal decay' and 'clean up memories' aspects of the description, it does not implement most of the claimed system features such as context compression, digests, session cache, WAL protocol, memory search, or task tracking. The primary purpose of the code chunk is therefore much narrower than the declared purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The description presents a broad, full-featured memory subsystem with compression, digesting, auto-injection, temporal decay, and task-state support. This code chunk does something narrower and different: it builds a knowledge graph from markdown memory files and stores/queryies it in SQLite. While there is partial overlap with 'searching memories' and possibly 'extracting insights,' the primary behavior is graph indexing and relation traversal, not comprehensive memory management or context compression. The code also introduces a specific undeclared storage/indexing capability—a SQLite graph database and CLI query interface—that is materially different from the declared feature set.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description describes a broad, feature-rich memory subsystem with active memory management, classification, decay, compression, auto-injection, and retrieval behaviors. The supplied code chunk is much narrower: it only audits the state of memory-related files and prints a health report with suggestions. While it is related to the memory domain, its primary purpose is diagnostic reporting, not operating a complete memory system. It also relies on external helper scripts (memory_decay.py, classify_memory.py) and python3, which conflicts with the 'zero external dependencies' claim. Therefore the description materially overstates and misrepresents what this code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description presents a broad, full-featured memory system with management, compression, and agent-context capabilities. The supplied code implements only one narrow subset: local indexing and BM25-based search over markdown memory files, plus incremental index maintenance and status reporting. It reads memory files and writes index metadata under memory/.index, but it does not implement the major advertised features such as WAL, classification, decay, cache, compaction, digesting, auto-injection, or explicit memory update/cleanup workflows. While 'searching memories' is one valid sub-capability mentioned in the description, the description materially overstates the primary purpose and implemented functionality of this specific code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description presents a broad, full-featured memory subsystem, but the supplied code chunk implements only one narrow component: a temporary session cache. While 'session cache' is mentioned in the description, the overall declared purpose substantially overstates what this code actually does. The code does not manage MEMORY.md, does not extract insights from conversations, does not compress long sessions, and does not implement advanced memory features like WAL, classification, decay, or auto-injection. Its actual behavior is limited to local JSON-backed key-value caching in /tmp with simple CRUD operations and sensitive-pattern filtering.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The description claims a broad, complete memory system with advanced capabilities such as context compression, conversation digests, auto-injection, memory search, type classification, and temporal decay, plus management of MEMORY.md. The supplied code only implements a narrower session-state manager for markdown files representing hot working memory. It supports CRUD-style updates to predefined sections, snapshot/restore, basic session isolation, and sensitive-input rejection. WAL is only mentioned in comments/docstrings; there is no substantive write-ahead logging mechanism beyond direct file writes. This is a material description-behavior mismatch because the actual code is a limited session-state utility rather than the comprehensive memory/compression system described.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill exposes capabilities involving environment access and file read/write behavior but does not declare any explicit tool scope such as permissions or allowed-tools. In an agent ecosystem, missing scope declarations weakens policy enforcement and can let the skill operate with broader authority than reviewers or orchestrators expect.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill advertises broad natural-language triggers such as 'remember this', 'compact', 'what do you know about X', and 'clean up memories', which can cause the agent to invoke persistence or retrieval behavior in situations the user did not intend. In a memory-oriented skill, overbroad activation increases the chance of storing sensitive data, exposing prior context, or modifying persistent state based on ambiguous conversation text.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
87% confidence
Finding

The skill is explicitly designed around persistent storage across sessions using shared files like SESSION-STATE.md, MEMORY.md, and memory/. Persistent memory creates confidentiality and integrity risk because future sessions may read, reuse, or expose previously stored sensitive or stale information, especially when multiple skills or sessions share the same storage locations.

Content

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

md
> ⚠️ **Conflict Warning**: This skill replaces both `smart-memory` and `context-compactor`.
> Do NOT install alongside either of those skills — they share the same files
> (`SESSION-STATE.md`, `memory/`, `MEMORY.md`) and will cause write conflicts.

## Requirements

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

This section mandates writing memory data through scripts and references persistent state files and a /tmp session cache, confirming durable cross-turn and cross-session storage. Even with claimed sanitization, persistent agent memory can retain secrets, personal data, or prompt artifacts and later surface them to unrelated tasks or users if isolation and lifecycle controls are weak.

Content

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

md
| `MEMORY.md` | `memory_decay.py --promote-only` | Direct overwrite |
| `/tmp/openclaw-session-*.json` | `session_cache.py` | Direct write |

**Critical**: Never use the agent's file-write tool directly on memory files. Always pipe through scripts — they enforce sanitization, deduplication, and append-only behavior.

The agent MUST NOT write to:
- Any directory outside the workspace

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The trigger phrases include broad natural-language commands such as "clean up memories" and "archive old stuff," which can plausibly appear in normal conversation and cause the skill to initiate file-management behavior without a clearly scoped confirmation step. In a memory-management skill that moves or compresses stored data, unintended activation can lead to accidental archival, loss of visibility, or mutation of memory state the user did not mean to change.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The rules define automatic archival actions that move and compress stored memory files based on age, but the file does not state that the agent must warn the user or obtain consent before modifying persistent data. In the context of a memory skill, this is risky because automatic file mutation can silently alter availability, organization, or fidelity of stored information, especially when triggered by routine maintenance conditions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code silently prunes stored session compact files once more than 30 exist by unlinking the oldest files. Although retention may be intentional, the user-facing interface and nearby output do not clearly warn that invoking a write can permanently delete prior saved compacts.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script automatically persists extracted conversation content into a workspace memory file without any explicit notice, consent, or confirmation at write time. Although it attempts to filter some obvious secrets, the extraction logic can still store sensitive personal or business information that does not match the regex, creating privacy and data-retention risk in a memory-management skill whose purpose is long-term storage.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The query command advertises read-only SQL but enforces this only with a naive startswith("SELECT") check before passing attacker-controlled SQL directly to sqlite3.execute(). In SQLite, this can still permit dangerous side effects through crafted statements or read-based abuse such as schema exfiltration, invocation of expensive functions, or use of SQLite features that go beyond the intended safe query surface; in a memory skill, that exposes potentially sensitive local knowledge stored in the graph database.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The build operation deletes all existing rows from the relations and entities tables before rebuilding the index, which is a destructive write operation. Although the script prints status after completion, there is no confirmation prompt or pre-execution warning at the point of deletion that existing graph data will be cleared.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The incremental rebuild rewrites old_doc_meta['id'] = doc_id and then immediately uses that rewritten value to fetch old_bm25.doc_tokens[old_doc_meta['id']]. This can attach tokens from the wrong old document to unchanged metadata, silently corrupting the index so searches return inaccurate or misleading results. In a memory system, corrupted retrieval can surface incorrect prior context, which can mislead downstream agent decisions and leak unrelated memory content into results.

Content

No source excerpt is available for this finding.

Tainted flow: 'CACHE_FILE' from os.environ.get (line 21, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/session_cache.py (reported line 50)May include surrounding context.

python
def save_cache(data: dict):
    CACHE_FILE.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")


def cmd_set(key: str, value: str):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The clear command unlinks the current state file and immediately reinitializes it, which destroys prior session-state content. Although the command name hints at the behavior, this code path has no confirmation prompt or stronger user-facing warning before performing the deletion.

Content

No source excerpt is available for this finding.

Tainted flow: 'content' from pathlib.Path.read_text (line 239, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/session_state.py (reported line 259)May include surrounding context.

python
ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
    snapshot_file = snapshot_dir / f"session-{ts}.md"
    content = read_state()
    snapshot_file.write_text(content, encoding="utf-8")
    # Keep only last 20 snapshots
    snapshots = sorted(snapshot_dir.glob("session-*.md"))
    while len(snapshots) > 20:

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This code writes changes back to the target markdown file when --apply is used, modifying user data on disk. Although the script name and --apply flag imply mutation, there is no confirmation prompt and no user-facing notice immediately before the write describing that file contents will be changed.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The docstring states this path performs 'sanitize + path check + save', and the comment says it sanitizes the raw text for missed patterns. In reality, sanitized_text = sanitize(text) is never used to influence content or persisted output, so only the extracted fields are sanitized and the documented extra whole-text sanitation does not happen.

Content

No source excerpt is available for this finding.