Back to skill

Security audit

Subagent Distiller

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent memory-distillation purpose, but it broadly scans and persists conversation history, including reset/deleted transcripts, with weak privacy controls and some unsafe write paths.

Review this skill carefully before installing. It is not showing clear exfiltration or intentional deception, but it can copy private conversation history, including reset/deleted sessions, into long-lived local files and optional scheduled jobs. Use it only if you are comfortable with that retention model, and avoid enabling cron until you have scoped the session inputs, added redaction/cleanup controls, and fixed the domain finalization path validation.

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

Error
Location
incremental_slice.py:52
Finding
Deleted and Reset Conversation Transcripts Are Collected and Persisted Without Data-Minimization Controls## Vulnerability Details **File Location**: `incremental_slice.py:22`, `incremental_slice.py:52-62`, and `incremental_slice.py:96-120` **Vulnerability Type**: Excessive access to deleted conversation data and plaintext sensitive-data retention **Risk Level**: High ### Vulnerable Code ```python SESSIONS_DIR = Path("/home/aqukin/.openclaw/agents/main/sessions") ``` ```python def get_session_files(): files = [] if SESSIONS_DIR.exists(): for f in SESSIONS_DIR.glob('*.jsonl'): files.append(f) for f in SESSIONS_DIR.glob('*.jsonl.reset.*'): files.append(f) for f in SESSIONS_DIR.glob('*.jsonl.deleted.*'): files.append(f) return sorted(files, key=lambda x: x.stat().st_mtime, reverse=True) ``` ```python def create_slice(session_file, start_line, end_line, content_lines): timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') slice_name = f"slice_{session_file.stem}_{start_line}_{end_line}_{timestamp}.json" slice_path = CHUNKS_DIR / slice_name content = ''.join(content_lines) slice_data = { 'source': str(session_file), 'source_name': session_file.name, 'start_line': start_line, 'end_line': end_line, 'timestamp': timestamp, 'content': content, 'line_count': len(content_lines) } with open(slice_path, 'w', encoding='utf-8') as f: json.dump(slice_data, f, indent=2, ensure_ascii=False) return slice_path ``` ### Technical Analysis Reading active conversation transcripts is consistent with the declared memory-distillation purpose. However, the scanner deliberately includes files marked as reset or deleted. This exceeds the minimum data scope needed to process current conversations and undermines the expected effect of conversation deletion or reset operations. The selected transcript lines are copied verbatim into persistent ...[truncated 2529 chars]
Remediation
## Remediation Suggestions 1. Exclude deleted and reset transcripts by default: ```python def get_session_files(): if not SESSIONS_DIR.exists(): return [] return sorted( SESSIONS_DIR.glob('*.jsonl'), key=lambda path: path.stat().st_mtime, reverse=True, ) ``` 2. Require a clearly documented, explicit opt-in before processing historical reset files. Deleted files should not be processed under normal operation. 3. Add a configurable allowlist of sessions or conversation identifiers rather than scanning the entire main-agent session directory. 4. Redact credentials and sensitive values before writing chunks. At minimum, detect API keys, authorization headers, private keys, access tokens, passwords, cookies, and common connection strings. 5. Avoid retaining full raw transcript content where possible. Store only the minimum extracted fields needed for processing, and delete raw chunks immediately after successful extraction. 6. Create directories and files with owner-only permissions, such as directory mode `0700` and file mode `0600`, independent of the ambient process umask. 7. Implement a documented retention policy and a secure cleanup command that removes chunks, task files, state entries, and derived cards associated with a deleted source conversation. 8. Add installation and uninstall documentation covering removal of the recommended cron entries so processing does not continue after the Skill is no longer wanted.

T02 · Agent Memory Poisoning

Error
Location
realtime_distill.py:42
Finding
Untrusted Conversation Content Can Poison Persistent Agent Memory Through Prompt Injection## Vulnerability Details **File Location**: `realtime_distill.py:42-50` and `realtime_distill.py:196-204`; related persistent writes occur during extraction finalization **Vulnerability Type**: Indirect prompt injection leading to long-term memory poisoning **Risk Level**: High ### Vulnerable Code ```python def get_prompt(slice_data): return f"""You are a professional knowledge extraction engineer. Extract structured knowledge from the following conversation slice. Source: {slice_data['source_name']} Line {slice_data['start_line']}-{slice_data['end_line']} Content: {slice_data['content']} ``` The original prompt continues with extraction instructions, but the transcript is interpolated directly into the same instruction string. ```python prompt = get_prompt(slice_data) task = { 'slice_path': str(slice_path), 'slice_name': slice_path.name, 'prompt': prompt, 'slice_hash': slice_hash } return False, task ``` Extracted topics are later converted into persistent cards: ```python topic_name = topic_data['topic'] if not re.match(r'^[\w\-_]+$', topic_name): continue card_path = TOPICS_DIR / f"{topic_name}.md" if card_path.exists(): new_content = merge_topic(card_path, topic_data) else: new_content = create_new_card(topic_data) with open(card_path, 'w', encoding='utf-8') as f: f.write(new_content) ``` ### Technical Analysis Transcript content is untrusted data: it can contain messages from users, external participants, copied web pages, tool output, or other attacker-influenced sources. The implementation interpolates this data verbatim into the same prompt used to instruct the extraction subagent. Although the outer prompt asks for JSON output, it does not establish a strong trust boundary or explicitly require the model to treat instructions inside the transcript as inert quoted data. A transcript can therefore contain an instruction such as a ...[truncated 2592 chars]
Remediation
## Remediation Suggestions 1. Treat transcript content explicitly as untrusted data. Place it in a separately delimited data section and add an instruction that no commands, policies, or requests inside that section may be followed. 2. Prefer structured role separation where supported: place trusted extraction policy in a system or developer message and transcript content in a distinct user-data message. 3. Add strong boundary markers with unpredictable identifiers and instruct the extractor to analyze only the content between those markers. 4. Validate extraction output with a strict JSON schema: - Restrict `status` to the documented enum. - Restrict `temporal` and `domain` to approved values. - Enforce string and array length limits. - Reject unexpected fields and malformed structures. - Limit the number of topics per slice. 5. Add semantic provenance controls. Every conclusion should include a source excerpt or line reference that can be verified against the original slice before persistence. 6. Require human confirmation before saving security-sensitive instructions, credentials, agent behavior rules, tool-use procedures, or other content capable of affecting future operation. 7. Detect common indirect prompt-injection phrases and quarantine suspicious slices for review rather than automatically processing them. 8. Store extracted knowledge as informational data, not executable instructions. Future agents should be explicitly instructed not to treat memory-card text as higher-priority policy. 9. Prevent propagation by applying the same untrusted-content boundaries and output validation to `domain_consolidate.py`.

T09 · Insecure Skill Coding Practices

Warning
Location
domain_consolidate.py:349
Finding
Unsanitized Domain Argument Allows Writes Outside the Consolidated Memory Directory## Vulnerability Details **File Location**: `domain_consolidate.py:349-355` **Vulnerability Type**: Path traversal and arbitrary writable Markdown file overwrite **Risk Level**: Medium ### Vulnerable Code ```python def finalize_consolidation(domain, result_file): with open(result_file, 'r', encoding='utf-8') as f: content = f.read() output_path = CONSOLIDATED_DIR / f"{domain}.md" with open(output_path, 'w', encoding='utf-8') as f: f.write(content) print(f"✅ Saved: {output_path}") print(f" Size: {len(content)} characters") ``` The command-line entry point passes the user-supplied argument directly to this function: ```python if len(sys.argv) > 1 and sys.argv[1] == '--finalize': if len(sys.argv) >= 4: finalize_consolidation(sys.argv[2], sys.argv[3]) ``` ### Technical Analysis The `domain` parameter is incorporated into a filesystem path without validation or canonical containment checking. `pathlib.Path` does not prevent `..` components from escaping a base directory. For example, a domain value such as `../../target` produces a path equivalent to: ```text /home/aqukin/.openclaw/workspace/memory/domains/../../target.md ``` After path resolution, that location is outside `memory/domains`. The file is opened with mode `w`, so an existing target is truncated and replaced with the contents of the attacker-selected `result_file`. Normal domain discovery derives names from card filenames and is comparatively constrained. However, the `--finalize` command accepts an arbitrary command-line domain and does not verify that it corresponds to a generated task. Exploitation therefore requires local command invocation or a compromised orchestration flow capable of controlling finalization arguments. ### Attack Path 1. An attacker obtains the ability to invoke the Skill's finalization command or influence arguments passed by the orchestration agent. 2. Th ...[truncated 1373 chars]
Remediation
## Remediation Suggestions 1. Restrict domain names to a conservative allowlist: ```python if not re.fullmatch(r'[A-Za-z0-9_-]+', domain): raise ValueError("Invalid domain name") ``` 2. Resolve the destination path and verify containment before opening it: ```python base = CONSOLIDATED_DIR.resolve() output_path = (base / f"{domain}.md").resolve() if output_path.parent != base: raise ValueError("Output path escapes the domain directory") ``` 3. Verify that the domain corresponds to an existing, previously generated consolidation task before accepting the finalization result. 4. Use an atomic write pattern: create a temporary file inside `CONSOLIDATED_DIR`, apply restrictive permissions, flush it, and replace the intended destination only after validation succeeds. 5. Refuse symbolic-link destinations or verify the resolved destination immediately before replacement to reduce symlink-based redirection risks. 6. Apply size limits and content validation to `result_file` before writing it into persistent memory. 7. Avoid exposing raw finalization arguments through untrusted agent output. Pass task identifiers that are resolved through trusted state instead of accepting arbitrary filesystem-related names.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (23)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# 检查 cursor 文件
ls cursors/
# 删除后重新扫描
rm cursors/*.cursor && python3 incremental_slice.py
```

### Q: 提取结果不理想?
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### Q: 如何彻底重置?
```bash
# 清空所有状态
rm -rf chunks/* cursors/* state.json slice_summary.json
python3 incremental_slice.py
```
Confidence
91% confidence
Finding
The documented `rm -rf chunks/* cursors/* state.json slice_summary.json` is a forceful recursive deletion command that removes multiple state locations at once. Although the paths appear intended for the skill workspace, using `rm -rf` in documentation is dangerous because users may execute it from the wrong directory or adapt it unsafely, causing unintended data loss.

Natural-Language Policy Violations

Medium
Confidence
82% confidence
Finding
Natural-language policy requires avoiding forced language or locale constraints unless the skill offers user choice or clearly documents a justified regional limitation. This SKILL.md presents all instructions and operational guidance only in Chinese, with no indication that the language is optional or region-specific.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The documentation instructs users to automatically scan conversation sessions, generate slices, maintain cursors, and persist state, but it does not warn that potentially sensitive conversation content will be processed and stored on disk. In a memory-distillation skill, this materially increases privacy risk because users may unknowingly retain confidential prompts, credentials, or personal data in generated cache/state files.

Session Persistence

Medium
Category
Rogue Agent
Content
### 2. 每日自动运行(推荐)
```bash
# 添加到 crontab
crontab -e

# 凌晨 3:00 增量提取
0 3 * * * cd ~/.openclaw/workspace/skills/subagent-distiller && python3 incremental_slice.py && python3 realtime_distill.py
Confidence
85% confidence
Finding
The skill recommends persistent cron-based automation to repeatedly scan conversation history and generate artifacts, creating ongoing unattended processing of potentially sensitive data. This increases exposure because data collection and retention continue in the background, and users may forget the task is running or fail to review what content is being ingested and stored.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The reset instructions include deletion of chunks, cursors, and state files, but the documentation does not clearly warn that these commands irreversibly remove generated caches and processing state. Even though the paths appear scoped to the skill workspace, users can still lose audit/history data or disrupt processing continuity by running the command without understanding the impact.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This Python file contains natural-language docstrings and console messages entirely in Chinese, starting with the module description and continuing through the operational prompts. Because the skill does not offer any language selection or explain that it is intended only for a Chinese-language environment, it violates the language/locale policy for natural-language content.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This code file contains natural-language interface text that assumes Chinese for descriptions and operational guidance. The policy specifically flags language or locale constraints when the skill forces a specific language without user opt-in.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The top-level documentation says the tool classifies cards into specific domains such as Polymarket, OpenClaw, Research, and System. However, the actual classification path in discover_domains/classify_domain_dynamic derives domains from the filename prefix before the first underscore and never uses DOMAIN_RULES, so behavior materially differs from the documented intent.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The code creates or overwrites JSON task files and consolidated markdown output files in the user workspace. While the script prints status after writing, there is no prior confirmation, cautionary comment/docstring, or explicit warning to the user that files in these locations will be modified.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The finalize flow unconditionally overwrites an existing consolidated domain file with externally supplied result content, which can destroy prior outputs or replace trusted summaries with tampered material. In this skill context, result_file is intended to come from a separate agent workflow, so a malformed or malicious upstream output could silently poison persisted knowledge artifacts.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The module header and all user-facing console messages are written in Chinese, which imposes a fixed language on users. The file does not indicate that Chinese is optional, configurable, or required for a region-specific purpose, so this is a natural-language locale policy concern.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The top-level docstring says the core improvement is that it 'no longer fully scans all jsonl' and 'only reads new lines'. In practice, main() calls count_lines() for each candidate file, and count_lines() iterates through the entire file to compute total lines before reading the new section. That directly contradicts the documented claim of avoiding full scans.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The script intentionally enumerates not only active session files but also '*.jsonl.reset.*' and '*.jsonl.deleted.*', which broadens collection scope to data users may expect to be discarded or archived. In this skill context, the slicer is operating on conversation/session logs, so processing reset/deleted variants increases privacy exposure and retention risk beyond a minimal incremental update function.

Ssd 3

Medium
Confidence
95% confidence
Finding
The slicer writes full newly added session text into persistent chunk files under the skill directory, effectively duplicating potentially sensitive user conversations and metadata into another storage location. In this context, session logs may contain secrets, credentials, personal data, or internal prompts, so creating plain-text derivative copies materially increases data leakage, retention, and unauthorized access risk.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This Python file contains its primary description and operational messages entirely in Chinese, and the runtime prints later in the file follow the same pattern. For a general-purpose skill, forcing one language without user opt-in matches the stated natural-language policy violation criteria.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The module header says '归档 ABANDONED(仅保留避坑价值)', indicating abandoned items are archived. In the implementation, the ABANDONED branch only appends an issue note, and the inline comment at L090 states '不再自动归档,只标记', which directly contradicts the documented behavior.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The manifest description is written entirely in Chinese, which imposes a specific language in user-facing metadata without any opt-in, alternative locale, or documented region-specific justification. This can violate language/locale policy when skills are expected to be discoverable and understandable across a broader user base.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The module description, user-facing prints, and especially the generated prompt instruct the downstream agent entirely in Chinese and require a specific JSON schema with Chinese field content conventions. There is no indication that the user can opt into another language or that the skill is limited to a Chinese-only context, which creates a locale-policy concern.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module documentation says the tool no longer appends chronologically and instead incrementally merges into existing cards rather than appending. However, the implemented merge strategy explicitly inserts new bullet lines into the existing '最新结论' section, and comments describe it as appending new content there, which contradicts the stated behavior at the intent/documentation level.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This script persistently writes state, task lists, and markdown topic cards under the workspace using multiple open(..., 'w') operations. While some progress messages are printed at runtime, the file-modifying behavior is not clearly disclosed up front in a docstring/comment as a user warning, and the script can overwrite existing files such as distill_state.json, extraction_tasks.json, and topic markdown cards.

Intent-Code Divergence

Low
Confidence
92% confidence
Finding
The messaging in the interactive summary and command hint repeatedly states that the script will perform deletion ('执行删除', '将删除'), while the implementation in the execution routine uses shutil.move to relocate files into the archive directory. This is an intent/documentation mismatch that could mislead an operator about the script's real side effects and retention behavior.

Intent-Code Divergence

Low
Confidence
78% confidence
Finding
The code comment and helper name imply proper frontmatter/YAML handling, but yaml_safe_load is only a line-splitting key/value extractor and does not parse real YAML structures. This is an intent/documentation divergence because the comment suggests a capability the implementation does not provide.

Static analysis

No suspicious patterns detected.