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<![CDATA[ ## Vulnerability Details **File Location**: `scripts/memory_health.sh`, lines 93–96 **Vulnerability Type**: OS filename injection into dynamically constructed Python source **Risk Level**: High ### Vulnerable Code ```bash for cache in /tmp/openclaw-session-cache-*.json; do KEYS=$(python3 -c "import json; print(len(json.load(open('$cache'))))" 2>/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
<![CDATA[ ## Remediation Suggestions Never interpolate filenames into executable Python source. Pass the path as a positional argument: ```bash for cache in /tmp/openclaw-session-cache-*.json; do KEYS=$( python3 -c \ 'import json, sys; print(len(json.load(open(sys.argv[1], encoding="utf-8"))))' \ "$cache" 2>/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<![CDATA[ ## Vulnerability Details **File Location**: `scripts/session_cache.py`, lines 18–20 and 43–50 **Vulnerability Type**: Unsafe temporary-file creation and predictable cache identity **Risk Level**: High ### Vulnerable Code ```python SESSION_ID = os.environ.get("OPENCLAW_SESSION_ID", "default") SAFE_ID = re.sub(r"[^a-zA-Z0-9_-]", "", SESSION_ID) or "default" CACHE_FILE = Path(f"/tmp/openclaw-session-cache-{SAFE_ID}.json") ``` ```python def load_cache() -> 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
<![CDATA[ ## Remediation Suggestions 1. Create a private cache directory owned by the current user: ```python cache_dir = Path(os.environ.get("XDG_RUNTIME_DIR", f"/tmp/openclaw-{os.getuid()}")) cache_dir.mkdir(mode=0o700, parents=True, exist_ok=True) os.chmod(cache_dir, 0o700) ``` 2. Derive the filename from a cryptographic hash of the complete session identifier rather than deleting characters: ```python safe_id = hashlib.sha256(SESSION_ID.encode()).hexdigest() ``` 3. Create files with `O_CREAT | O_EXCL | O_NOFOLLOW` where supported and mode `0600`. 4. Verify that existing files are regular files owned by the current user. 5. Write to a securely created temporary file, `fsync()` it, and atomically replace the destination. 6. Add explicit cleanup at session termination; do not rely solely on reboot. 7. Avoid the shared `default` identity or generate a random session identifier when none is provided. ]]>

T02 · Agent Memory Poisoning

Error
Location
scripts/session_state.py:275
Finding
Arbitrary Local File Restore Into Persistent Agent State<![CDATA[ ## Vulnerability Details **File Location**: `scripts/session_state.py`, lines 275–297 **Vulnerability Type**: Missing path containment and memory poisoning **Risk Level**: High ### Vulnerable Code ```python def cmd_restore(latest: bool = False, file: str | None = None): """Restore SESSION-STATE.md from a snapshot.""" snapshot_dir = WORKSPACE / "memory" / "session-snapshots" if not snapshot_dir.exists(): print("No snapshots found.") return if file: snapshot_file = Path(file) if not snapshot_file.exists(): snapshot_file = snapshot_dir / file else: snapshots = sorted(snapshot_dir.glob("session-*.md")) if not snapshots: print("No snapshots found.") return snapshot_file = snapshots[-1] if not snapshot_file.exists(): print(f"Snapshot not found: {snapshot_file}") return # Save current state before overwriting if STATE_FILE.exists(): cmd_snapshot() content = snapshot_file.read_text(encoding="utf-8") write_state(content) print(f"Restored from: {snapshot_file}") ``` ### Technical Analysis When a filename is supplied, the command first interprets it directly as a filesystem path. An existing absolute path or relative path outside the snapshot directory is accepted without containment validation. The selected file is then copied into `SESSION-STATE.md` or a channel-specific persistent state file. Unlike normal state commands, restored content is not passed through `check_sensitive()` or validated against an expected snapshot schema. This creates two related security failures: 1. Any readable UTF-8 file can be imported into agent memory. 2. Attacker-controlled text can be persisted as trusted state and influence future sessions. Symbolic links and non-regular files are not rejected. ### Attack Path 1. The attacker or an untrusted instruction causes the agent to run: ```bash python3 scrip ...[truncated 1077 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Resolve and validate every requested snapshot path: ```python snapshot_root = snapshot_dir.resolve() candidate = (snapshot_dir / file).resolve() if not candidate.is_relative_to(snapshot_root): raise ValueError("Snapshot path is outside the snapshot directory") if not candidate.is_file() or candidate.is_symlink(): raise ValueError("Snapshot must be a regular, non-symlink file") ``` Additional measures: 1. Accept only snapshot basenames matching a strict pattern such as `session-YYYYMMDD-HHMMSS.md`. 2. Never treat an absolute user-supplied path as a valid snapshot. 3. Run restored content through the same sensitive-data checks as normal writes. 4. Validate required headings and reject unexpected sections. 5. Limit snapshot size to prevent memory or disk exhaustion. 6. Open files with no-follow semantics where supported to reduce race and symlink risks. 7. Log restoration events without printing sensitive content. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/classify_memory.py:210
Finding
Arbitrary File Modification Through Unrestricted Classification Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/classify_memory.py`, lines 210–217 and 310–345 **Vulnerability Type**: Missing write-path authorization **Risk Level**: Medium ### Vulnerable Code ```python if not dry_run and applied > 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
<![CDATA[ ## Remediation Suggestions 1. Resolve the configured workspace and candidate path before processing. 2. Permit only: - `WORKSPACE/MEMORY.md` - Regular top-level `.md` files under `WORKSPACE/memory` 3. Reject absolute external paths, traversal, symbolic links, devices, and FIFOs. 4. Separate analysis from modification: unrestricted paths may be permitted only in guaranteed read-only mode. 5. Require explicit confirmation before applying changes to a specifically selected file. 6. Use atomic writes and preserve file permissions. 7. Add tests for absolute paths, `../` traversal, and symlink escape. A suitable containment check is: ```python workspace = WORKSPACE.resolve() candidate = filepath.resolve() allowed_memory_file = candidate == (workspace / "MEMORY.md") allowed_daily_file = ( candidate.parent == (workspace / "memory") and candidate.suffix == ".md" ) if not (allowed_memory_file or allowed_daily_file): raise ValueError("File is outside the authorized memory scope") ``` ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/memory_decay.py:64
Finding
Unrestricted Archive Destination Moves Memory Outside the Workspace<![CDATA[ ## Vulnerability Details **File Location**: `scripts/memory_decay.py`, lines 64–71, 192–195, and 247–248 **Vulnerability Type**: Unrestricted destination path for destructive file moves **Risk Level**: Medium ### Vulnerable Code ```python if age > 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
<![CDATA[ ## Remediation Suggestions 1. Remove `--archive-dir` unless external archive destinations are a required feature. 2. If configurability is necessary, require the resolved destination to remain under: ```text WORKSPACE/memory/archive ``` 3. Reject symbolic links in every destination component. 4. Verify that source files are regular top-level daily-note files matching `YYYY-MM-DD.md`. 5. Refuse to overwrite an existing destination file. 6. Use a staging operation and atomic rename when source and destination share a filesystem. 7. Make dry-run the default and require an explicit confirmation flag for destructive moves. 8. Record a manifest so incorrectly archived files can be restored safely. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/session_cache.py:23
Finding
Inconsistent Sensitive Data Filtering Permits Credential Persistence<![CDATA[ ## Vulnerability Details **File Location**: `scripts/session_cache.py`, lines 23–30 **Vulnerability Type**: Incomplete blacklist-based secret detection **Risk Level**: Medium ### Vulnerable Code ```python # Sensitive patterns — refuse to cache SENSITIVE_PATTERNS = [ re.compile(r"(?:password|passwd|pwd)\s*[:=]\s*\S+", re.IGNORECASE), re.compile(r"(?:api[_-]?key|token|secret|bearer)\s*[:=]\s*\S+", re.IGNORECASE), re.compile(r"clh_[A-Za-z0-9]{30,}", re.IGNORECASE), re.compile(r"sk-[A-Za-z0-9]{20,}", re.IGNORECASE), re.compile(r"ghp_[A-Za-z0-9]{30,}", re.IGNORECASE), ] ``` The Skill documentation makes a broader claim: ```markdown All write commands automatically reject inputs matching: - API keys/tokens - Passwords - Private keys This is a hard block at the script level — the agent cannot bypass it. ``` ### Technical Analysis The cache filter contains no private-key pattern. Other project filters detect only: ```regex -----BEGIN (?:RSA |EC )?PRIVATE KEY----- ``` That expression does not cover common formats such as: ```text -----BEGIN OPENSSH PRIVATE KEY----- ``` The blacklist also does not comprehensively detect AWS access keys, generic high-entropy credentials, modern GitHub token formats, credentials without expected labels, or secrets split across lines. Consequently, the implementation does not enforce the documented statement that every write path provides an unbypassable hard block. Unsupported secrets may be persisted in shared temporary cache files or workspace memory. ### Attack Path 1. A user or agent supplies an unsupported secret format, such as an OpenSSH private key, to the cache: ```bash python3 scripts/session_cache.py set material \ '-----BEGIN OPENSSH PRIVATE KEY-----...' ``` 2. None of the cache patterns match. 3. The value is written to the predictable `/tmp` JSON cache. 4. Another local user, later process, backup, diagnostic script, or agent operation may read the persisted value. ...[truncated 644 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Centralize secret detection in one shared module used by every write path. 2. Add structural coverage for common private-key headers, including: - `OPENSSH PRIVATE KEY` - `PGP PRIVATE KEY BLOCK` - PKCS#8 encrypted private keys 3. Cover current token formats for supported providers and common cloud access-key formats. 4. Apply filtering to both keys and values, not only values. 5. Detect multiline secret blocks before splitting or normalizing input. 6. Consider entropy-based detection as a secondary control, with carefully tuned false-positive handling. 7. Encrypt sensitive local state if legitimate secret storage is ever required; otherwise reject it. 8. Change the documentation to state that pattern matching is best-effort rather than unbypassable. 9. Add tests containing representative supported and unsupported credential formats. ]]>
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.

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.

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.

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.

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.

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.

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.

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.

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.

Lp3

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.

Vague Triggers

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

Session Persistence

Medium
Category
Rogue Agent
Content
> ⚠️ **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
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.

Session Persistence

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

Vague Triggers

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

Missing User Warnings

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

Missing User Warnings

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

Missing User Warnings

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

Intent-Code Divergence

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

Missing User Warnings

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

Intent-Code Divergence

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

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

Medium
Category
Data Flow
Content
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):
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.

Missing User Warnings

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

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

Medium
Category
Data Flow
Content
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:
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.

Missing User Warnings

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

Intent-Code Divergence

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

Static analysis

No suspicious patterns detected.