Back to skill

Security audit

Openclaw Skill Session Memory

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it persistently stores searchable copies of conversation logs with weak scoping and incomplete privacy protections.

Install only if you are comfortable with OpenClaw conversations being copied into local searchable Markdown files. Avoid using it on shared or multi-user agents unless recording is tied to an explicit session ID and memory files have clear permissions, retention, deletion, and opt-in controls.

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

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
record.py:83
Finding
Global latest-session selection can record an unrelated conversation<![CDATA[ ## Vulnerability Details **File Location**: `record.py:15`, `record.py:83-106` **Related Location**: `skill.py:18`, `skill.py:141-164`, `skill.py:209-224` **Vulnerability Type**: `T05: Unauthorized Access and Privilege Escalation` **Risk Level**: Medium ### Vulnerable Code ```python SESSIONS_DIR = Path.home() / ".openclaw" / "agents" / "main" / "sessions" ``` ```python def get_latest_session_file(): """获取最新的会话文件""" # 查找今天的会话文件 today_str = datetime.now().strftime('%Y-%m-%d') patterns = [ SESSIONS_DIR / f"*{today_str}*.jsonl", SESSIONS_DIR / f"*.jsonl.reset.*", SESSIONS_DIR / f"*.jsonl", ] latest_file = None latest_mtime = 0 for pattern in patterns: for filepath in glob.glob(str(pattern)): try: mtime = os.path.getmtime(filepath) if mtime > latest_mtime: latest_mtime = mtime latest_file = filepath except: pass return latest_file ``` ### Technical Analysis The declared purpose is to record the current conversation after a session ends. Instead of receiving an authenticated current-session identifier, the implementation enumerates all JSONL files in the main agent's session directory and selects whichever file has the newest modification time. File modification time is not an authorization or session-identity boundary. In an environment with concurrent sessions, different channels, multiple users, or reset files, the newest file may belong to a different conversation. The fallback patterns also include every `*.jsonl` file and every `*.jsonl.reset.*` file, broadening access beyond the minimum scope necessary to record the invoking session. The same selection logic is duplicated in `skill.py`. This issue does not grant additional operating-system privileges, but it breaks conversation-level isolation by allowing the recorder to read and duplicate a session o ...[truncated 1551 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require the OpenClaw runtime to pass an explicit current-session ID or exact session-file path to the recorder. 2. Resolve the supplied path with `Path.resolve()` and verify that it remains beneath the expected session directory. 3. Validate that the session identifier belongs to the invoking agent, user, and channel. 4. Reject reset files and files that do not match the expected session format. 5. Do not use modification time to determine which session the caller is authorized to record. 6. If an explicit session identity is unavailable, fail closed rather than recording the globally newest session. 7. Add tests covering concurrent sessions, reset files, symlinks, multiple channels, and rapid modification-time changes. 8. Consolidate the duplicated implementation in `record.py` and `skill.py` so that security fixes cannot diverge. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
record.py:17
Finding
Conversation content is duplicated into plaintext files with incomplete sensitive-data redaction<![CDATA[ ## Vulnerability Details **File Location**: `record.py:17-29`, `record.py:36-76`, `record.py:108-156` **Related Location**: `skill.py:20-32`, `skill.py:39-55`, `skill.py:166-205`; `search.py:16-37` **Vulnerability Type**: `T09: Insecure Skill Coding Practices` **Risk Level**: Medium ### Vulnerable Code ```python REDACTION_RULES = [ (r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', '[EMAIL]'), (r'1[3-9]\d{9}', '[PHONE]'), (r'(api[_-]?key|token|secret|password|access[_-]?key)[=:]\s*["\']?[\w-]{20,}["\']?', '[REDACTED]', re.IGNORECASE), (r'sk-[a-zA-Z0-9]{20,}', '[API_KEY]'), (r'ghp_[a-zA-Z0-9]{36,}', '[GITHUB_TOKEN]'), (r'\d{17}[\dXx]', '[ID_CARD]'), (r'\d{16,19}', '[CARD]'), (r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', '[IP]'), (r'Bearer\s+[a-zA-Z0-9_-]{20,}', '[BEARER_TOKEN]'), (r'["\']?[\w-]{30,}["\']?', '[TOKEN]'), # 通用长字符串 ] ``` ```python def redact_text(text): """对文本进行脱敏处理""" for rule in REDACTION_RULES: pattern = rule[0] replacement = rule[1] flags = rule[2] if len(rule) > 2 else 0 text = re.sub(pattern, replacement, text, flags=flags) return text ``` ```python content_lines.append("") content_lines.append(f"*共 {len(messages)} 条消息*") with open(filepath, 'w', encoding='utf-8') as f: f.write('\n'.join(content_lines)) print(f"📝 已保存 {len(messages)} 条对话到: {filepath}") ``` ### Technical Analysis The recorder copies conversation text into persistent Markdown files and relies on a finite set of regular expressions to remove sensitive information. These patterns do not provide comprehensive secret detection. Examples of formats that may not be removed include: - PEM or OpenSSH private keys. - Session cookies and authentication headers other than the supported Bearer format. - JWTs containing periods and other punctuation. - Provider-specific API keys that do not begin with `sk-` or `ghp_`. - Passwords shorter than 20 characters. - C ...[truncated 2309 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store structured summaries or explicitly selected facts instead of raw conversation text by default. 2. Obtain informed user consent before creating persistent conversation memory. 3. Clearly document that regex-based redaction is best-effort and cannot guarantee removal of all sensitive information. 4. Expand secret detection using maintained, structured detectors for private keys, JWTs, cookies, authorization headers, connection strings, cloud credentials, and provider-specific token formats. 5. Detect multiline secrets before truncating or formatting messages. 6. Create files atomically with owner-only permissions such as mode `0600`, and verify that parent directories are not group- or world-readable. 7. Consider encrypting memory files using a key managed separately from the workspace. 8. Provide configurable retention periods, secure deletion, per-session opt-out, and a command to purge stored memory. 9. Avoid writing empty or replacement files when no authorized session is available. 10. Add tests containing representative secret formats and verify that both recording and search output remain sanitized. 11. Centralize redaction logic so `record.py`, `search.py`, and `skill.py` do not maintain inconsistent rule sets. ]]>

T02 · Agent Memory Poisoning

Warning
Location
record.py:45
Finding
Attacker-controlled conversation text can be persisted as untrusted long-term memory<![CDATA[ ## Vulnerability Details **File Location**: `record.py:45-75`, `record.py:125-151` **Related Location**: `skill.py:109-139`, `skill.py:181-202`; `search.py:67-89` **Vulnerability Type**: `T02: Agent Memory Poisoning` **Risk Level**: Medium ### Vulnerable Code ```python if data.get('type') == 'message': msg = data.get('message', {}) role = msg.get('role', '') content_list = msg.get('content', []) for content in content_list: if content.get('type') == 'text': text = content.get('text', '') # 移除message_id引用 text = re.sub(r'\[message_id:[^\]]+\]', '', text) text = text.strip() if text and len(text) > 2: # 获取时间戳 ts = msg.get('timestamp', 0) if ts: dt = datetime.fromtimestamp(ts/1000) time_str = dt.strftime('%H:%M') else: time_str = '' messages.append({ 'time': time_str, 'role': role, 'text': redact_text(text) }) ``` ```python for msg in messages: time_str = msg.get('time', '') role = msg.get('role', '') text = msg.get('text', '') if time_str and time_str != current_time: # 新的时间段 if session_count > 0: content_lines.append("") ...[truncated 3551 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat all recorded dialogue as untrusted historical data rather than executable instructions. 2. Store messages in a structured format with explicit session ID, sender role, channel, timestamp, and trust level for every message. 3. Preserve the role on each individual message instead of relying on minute-based Markdown headings. 4. When returning search results, wrap them in explicit delimiters and state that instructions inside the recalled text must not be followed. 5. Escape or encode Markdown control syntax before persistence and display. 6. Separate factual memory extraction from raw transcript storage. Only persist narrowly scoped facts that pass a policy-controlled validation step. 7. Prevent memory content from overriding system, developer, or current-user instructions in downstream prompts. 8. Require confirmation before using recalled content to perform sensitive actions or invoke tools. 9. Add adversarial tests for stored prompt injection, role confusion, same-minute messages, and instructions hidden in surrounding search context. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (12)

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README advertises automatic recording and persistent storage of conversation logs, but it does not clearly warn users that conversations will be retained on disk by default or explain the privacy consequences of that behavior. For a memory skill that captures dialogue content, lack of explicit consent and retention notice can lead to unintended storage of sensitive information even if some redaction is attempted.

Natural-Language Policy Violations

Medium
Confidence
82% confidence
Finding
The entire skill documentation is written only in Chinese, and no language choice or opt-in is offered. Under the stated policy, forcing a specific language without user opt-in can be a natural-language policy violation unless clearly justified as region-specific.

Ssd 3

Medium
Confidence
97% confidence
Finding
The skill explicitly implements automatic retention of full conversation content and a searchable recall mechanism, which creates a durable repository of user-provided natural-language data. In context, this is more dangerous because the skill is designed to persist and index conversations by default, while the claimed redaction relies on regex rules that can miss secrets, personal data, or sensitive business information.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill says conversations are automatically saved at session end, but it does not clearly warn users that their messages will be written to persistent local files. This creates a privacy and consent problem because users may disclose sensitive information without realizing it will be retained beyond the live session, and the described redaction is pattern-based and incomplete.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The tool copies conversation data from session logs into a separate markdown file under a memory directory, creating an additional persistent store of potentially sensitive user and assistant content. Although some redaction is attempted, it is regex-based and incomplete, so secrets, personal data, or proprietary prompts may still be retained locally without explicit user notice or consent.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This Python file contains its top-level description entirely in Chinese, and the CLI usage and status messages are also presented only in Chinese. That creates a language-policy issue because the skill forces a specific language experience without giving the user an option to select a locale or language.

Natural-Language Policy Violations

Medium
Confidence
87% confidence
Finding
The top-level natural-language description is written in Chinese and presents the skill behavior only in that language, including user-facing help and status messages throughout the file. There is no indication that the user can choose language or that the skill is intentionally limited to a Chinese-only locale, which violates the language/locale policy criteria.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill persists conversation content to disk under the user's home directory without presenting an explicit consent or privacy warning at the point of recording. Even though some redaction is attempted, the regex-based filtering is incomplete and may miss sensitive data, causing private prompts, secrets, or regulated information to be stored locally where other processes or users might access it.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The module docstring presents the skill's purpose exclusively in Chinese, which can impose a language choice on users without offering an alternative or documenting that the skill is intentionally locale-specific. The policy requires language/locale constraints to be opt-in or clearly justified.

Description-Behavior Mismatch

Low
Confidence
87% confidence
Finding
The module docstring describes extracting dialogue from session logs and saving as markdown, but it does not indicate that the tool writes derived conversation records into a persistent workspace memory location under ~/.openclaw/workspace/memory/conversations. This is a broader behavior than simple extraction because it creates and overwrites files in another repository of user data.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The usage text explicitly advertises 'search.py --record # 记录当前会话', implying the tool can store the current conversation. However, the argument handling in main only supports '--list' or keyword search, and there is no code path that records or writes conversation content.

Intent-Code Divergence

Low
Confidence
87% confidence
Finding
The top-level documentation describes automatic conversation recording, which implies background or implicit capture behavior. In the actual implementation, recording happens only in the explicit CLI path via `record()` when the user runs `skill.py record`, so the documentation overstates how the skill operates.

Static analysis

No suspicious patterns detected.