T09 · Insecure Skill Coding Practices
- Location
core/seem_skill.py:1697- Finding
Automatic Deserialization of Untrusted Pickle Files Enables Arbitrary Code Execution
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
SEEM is a real conversation-memory skill, but it needs Review because it stores conversation data by default and auto-loads pickle files that can execute code if tampered with.
Install only if you are comfortable with conversation content being stored on disk and sent to configured LLM and embedding providers. Keep the data directory private, do not share or import persisted .pkl files, avoid storing secrets, and prefer disabling persistence/cache or using a trusted local provider until pickle persistence and data-retention controls are fixed.
core/seem_skill.py:1697Automatic Deserialization of Untrusted Pickle Files Enables Arbitrary Code Execution
core/seem_skill.py:1537Sensitive Conversation Memories Are Persisted in Plaintext with Default Filesystem Permissions
The skill advertises capabilities that imply environment access, file reads, and network use, but it does not declare an explicit tool scope or permissions boundary. This creates a trust and review gap: operators cannot easily tell what the skill may access, and an agent runtime may grant broader capabilities than users expect, increasing the chance of unintended data exposure.
The description presents the skill as a conversation memory system but omits that user conversation content is stored persistently and may be sent to third-party LLM and embedding providers. This is dangerous because users may provide sensitive personal or business data under the assumption it remains local, leading to privacy, compliance, or confidentiality violations.
The documented clear command deletes stored memory data, yet the skill does not warn users about permanence, recovery limitations, or operational consequences. In a memory system, destructive deletion can cause accidental loss of conversation history and derived knowledge, especially if invoked by an agent or user without understanding the impact.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Embedding API Configuration (SiliconFlow / OpenAI compatible)
EMBEDDING_CONFIG = {
"api_key": "", # Set via environment: MM_ENCODER_API_KEY
"base_url": "https://api.siliconflow.cn/v1",
"model": "Qwen/Qwen3-Embedding-8B",
}
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Embedding API Configuration (SiliconFlow / OpenAI compatible)
EMBEDDING_CONFIG = {
"api_key": "", # Set via environment: MM_ENCODER_API_KEY
"base_url": "https://api.siliconflow.cn/v1",
"model": "Qwen/Qwen3-Embedding-8B",
}
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Embedding API Configuration (SiliconFlow / OpenAI compatible)
EMBEDDING_CONFIG = {
"api_key": "", # Set via environment: MM_ENCODER_API_KEY
"base_url": "https://api.siliconflow.cn/v1",
"model": "Qwen/Qwen3-Embedding-8B",
}
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Embedding API Configuration (SiliconFlow / OpenAI compatible)
EMBEDDING_CONFIG = {
"api_key": "", # Set via environment: MM_ENCODER_API_KEY
"base_url": "https://api.siliconflow.cn/v1",
"model": "Qwen/Qwen3-Embedding-8B",
}
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Embedding API Configuration (SiliconFlow / OpenAI compatible)
EMBEDDING_CONFIG = {
"api_key": "", # Set via environment: MM_ENCODER_API_KEY
"base_url": "https://api.siliconflow.cn/v1",
"model": "Qwen/Qwen3-Embedding-8B",
}
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Embedding API Configuration (SiliconFlow / OpenAI compatible)
EMBEDDING_CONFIG = {
"api_key": "", # Set via environment: MM_ENCODER_API_KEY
"base_url": "https://api.siliconflow.cn/v1",
"model": "Qwen/Qwen3-Embedding-8B",
}
This file is a code file, so SQP-3 applies to natural-language strings inside the code. The instruction to use "未知" when the object cannot be determined conflicts with the surrounding policy to use the same language as the input text, effectively forcing Chinese output without user opt-in for non-Chinese inputs.
The skill initializes external LLM and embedding clients and later sends conversation text, timestamps, and image captions to those services for extraction and retrieval enrichment. For a memory system handling multi-turn conversations, this context makes the issue more sensitive because the transmitted data can include long-lived personal or confidential information without an obvious disclosure or consent mechanism in the code.
The skill automatically persists raw conversations, summaries, graphs, facts, and embeddings to disk, which expands the data exposure surface far beyond transient memory behavior users may expect. In a conversation-memory skill, this is especially sensitive because stored content may include personal, confidential, or regulated data and persists across sessions without clear consent boundaries in code.
Using enable_cache as both a cache toggle and a persistence switch is a dangerous semantic mismatch that can mislead integrators into enabling durable storage, auto-loading, and reset-time deletion when they intended only in-memory caching. This increases the chance of accidental data retention, privacy violations, and insecure operational assumptions around where sensitive conversation data lives.
The persistence routine writes raw memories, chunk_store contents, graphs, facts, and embeddings to disk in a predictable local directory without any visible warning, encryption, or access-control enforcement. Given the skill’s purpose as a conversation memory system, the stored artifacts can reveal sensitive user communications and derived knowledge long after the original interaction.
The code deserializes on-disk data with pickle.load(), which can execute attacker-controlled code during loading if the .pkl files are modified or replaced. Because the skill auto-loads persisted state during initialization and the data directory is predictable, any attacker who can influence local files or prior saved artifacts may achieve arbitrary code execution.
memory_embeddings_path = os.path.join(data_dir, "memory_embeddings.pkl")
if os.path.exists(memory_embeddings_path):
with open(memory_embeddings_path, "rb") as f:
self.memory_embeddings = pickle.load(f)
chunk_embeddings_path = os.path.join(data_dir, "chunk_embeddings.pkl")
if os.path.exists(chunk_embeddings_path):
This pickle.load() call introduces the same unsafe deserialization risk for chunk embeddings. On object initialization, loading a crafted pickle can trigger arbitrary code execution before any higher-level checks occur, making the persistence feature an attractive local persistence/RCE vector.
chunk_embeddings_path = os.path.join(data_dir, "chunk_embeddings.pkl")
if os.path.exists(chunk_embeddings_path):
with open(chunk_embeddings_path, "rb") as f:
self.chunk_embeddings = pickle.load(f)
entity_embeddings_path = os.path.join(data_dir, "entity_embeddings.pkl")
if os.path.exists(entity_embeddings_path):
Deserializing entity embeddings via pickle.load() is dangerous because pickle payloads may contain instructions that execute during unpickling. In this skill, persistence is automatic and tied to a feature named enable_cache, which may cause operators to enable unsafe loading without realizing it implies trusted-code deserialization.
entity_embeddings_path = os.path.join(data_dir, "entity_embeddings.pkl")
if os.path.exists(entity_embeddings_path):
with open(entity_embeddings_path, "rb") as f:
self.entity_embeddings = pickle.load(f)
fact_embeddings_path = os.path.join(data_dir, "fact_embeddings.pkl")
if os.path.exists(fact_embeddings_path):
The fact embeddings are also loaded with pickle.load(), creating another arbitrary code execution sink from files in the skill's data directory. Since the skill handles conversation memory and auto-loads state, compromise of this directory can convert data tampering into code execution on the next startup.
fact_embeddings_path = os.path.join(data_dir, "fact_embeddings.pkl")
if os.path.exists(fact_embeddings_path):
with open(fact_embeddings_path, "rb") as f:
self.fact_embeddings = pickle.load(f)
# Load normalized fact set
fact_norm_set_path = os.path.join(data_dir, "fact_norm_set.json")
The manifest describes an episodic memory system with retrieval and recall features, but this utility file also implements generic OpenAI-compatible chat completion and embedding clients that send conversation content to external services. While embeddings are plausibly part of retrieval, a full text-generation client is a broader behavior than the manifest explicitly states and changes the skill from local memory handling into remote model-backed processing.
User-provided prompts and text are transmitted to external LLM and embedding APIs without any visible consent, minimization, or disclosure controls in this code path. Because this is an episodic memory skill, the transmitted content may include sensitive conversation history, making silent exfiltration to third-party services more dangerous than in a generic stateless utility.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
Environment variables (OpenClaw injects these automatically):
- LLM_API_KEY (required)
- LLM_MODEL (default: qwen3.5-plus)
- LLM_BASE_URL (default: https://api.xiaomimimo.com/v1)
- MM_ENCODER_API_KEY (required)
- MM_ENCODER_MODEL (default: Qwen/Qwen3-Embedding-8B)
- MM_ENCODER_BASE_URL (default: https://api.siliconflow.cn/v1/embeddings)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
Environment variables (OpenClaw injects these automatically):
- LLM_API_KEY (required)
- LLM_MODEL (default: qwen3.5-plus)
- LLM_BASE_URL (default: https://api.xiaomimimo.com/v1)
- MM_ENCODER_API_KEY (required)
- MM_ENCODER_MODEL (default: Qwen/Qwen3-Embedding-8B)
- MM_ENCODER_BASE_URL (default: https://api.siliconflow.cn/v1/embeddings)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
Environment variables (OpenClaw injects these automatically):
- LLM_API_KEY (required)
- LLM_MODEL (default: qwen3.5-plus)
- LLM_BASE_URL (default: https://api.xiaomimimo.com/v1)
- MM_ENCODER_API_KEY (required)
- MM_ENCODER_MODEL (default: Qwen/Qwen3-Embedding-8B)
- MM_ENCODER_BASE_URL (default: https://api.siliconflow.cn/v1/embeddings)
The tool is presented as a local memory CLI, but its configuration wires user data into remote LLM and embedding services via externally configurable endpoints and API keys. That expands the trust boundary and creates a real data-exposure risk because stored and recalled conversation content may be sent off-host without strong disclosure or restriction in this wrapper.
User-supplied message text and recall queries are passed into the SEEM skill, which is configured to use remote LLM and embedding endpoints, yet the CLI usage text does not clearly warn that this content may leave the local environment. In a memory tool, users may input sensitive multi-turn conversation data, making silent third-party transmission especially risky.
No suspicious patterns detected.