Back to skill

Security audit

project-memory

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent project-memory purpose, but unsafe path handling can let project names escape the intended folder and delete, move, or write unintended local files.

Review carefully before installing. The main risk is not the memory concept itself, but the implementation: do not use this version with untrusted or arbitrary project names, and avoid deletion/archive operations until project-name validation, path containment checks, and real backup behavior are added. Also treat saved memories as persistent shared local data that may be visible to other agents.

Vulnerability Patterns
  • 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
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
handler.js:52
Finding
Unsanitized Project Names Allow Filesystem Path Traversal and Recursive Deletion<![CDATA[ ## Vulnerability Details **File Location**: `handler.js:52-96`, `handler.js:175-204`, `handler.js:208-240` **Vulnerability Type**: Path traversal leading to arbitrary directory creation, relocation, and recursive deletion **Risk Level**: High ### Vulnerable Code ```js function projectCreate(name, description = '') { if (!name || name.trim() === '') { return { success: false, error: '项目名称不能为空' }; } const index = readIndex(); if (index.projects[name]) { return { success: false, error: `项目 "${name}" 已存在` }; } const projectDir = path.join(PROJECTS_DIR, name); ensureDir(projectDir); ensureDir(path.join(projectDir, 'memory')); ensureDir(path.join(projectDir, 'context')); const now = new Date().toISOString(); const projectConfig = { name, description, createdAt: now, updatedAt: now, agents: [], tags: [], status: 'active' }; fs.writeFileSync( path.join(projectDir, 'project.json'), JSON.stringify(projectConfig, null, 2) ); fs.writeFileSync( path.join(projectDir, 'memory', 'entries.json'), JSON.stringify({ entries: [] }, null, 2) ); index.projects[name] = { description, createdAt: now, updatedAt: now, status: 'active' }; index.currentProject = name; writeIndex(index); } ``` ```js function projectArchive(name) { const index = readIndex(); if (!index.projects[name]) { return { success: false, error: `项目 "${name}" 不存在` }; } index.projects[name].status = 'archived'; index.projects[name].archivedAt = new Date().toISOString(); if (index.currentProject === name) { index.currentProject = null; } writeIndex(index); ensureDir(ARCHIVED_DIR); const srcDir = path.join(PROJECTS_DIR, name); const destDir = path.join(ARCHIVED_DIR, name); if (fs.existsSync(srcDir)) { fs.renameSync(srcDir, destDir); } } ``` ```js function projectDelete(name, confirm) { if (!confirm) { return { success: false, error: '删 ...[truncated 3013 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict project names to a conservative allowlist, such as letters, digits, spaces, underscores, and hyphens. 2. Explicitly reject: - Absolute paths. - `.` and `..`. - `/` and `\`. - Null bytes and control characters. - Names that normalize to reserved project directories. 3. Resolve and verify every filesystem target before use: ```js function resolveContainedPath(root, name) { if ( typeof name !== 'string' || !/^[\p{L}\p{N} _-]{1,100}$/u.test(name) || name === '.' || name === '..' ) { throw new Error('Invalid project name'); } const resolvedRoot = path.resolve(root); const resolvedTarget = path.resolve(resolvedRoot, name); if ( resolvedTarget === resolvedRoot || !resolvedTarget.startsWith(resolvedRoot + path.sep) ) { throw new Error('Project path escapes the allowed root'); } return resolvedTarget; } ``` 4. Apply containment checks independently to project and archive paths immediately before every create, read, write, rename, and delete operation. 5. Refuse to recursively delete the storage root, its ancestors, or any symbolic-link target. 6. Prefer generated immutable directory identifiers while storing the user-provided project name only as metadata. 7. Add regression tests for absolute paths, nested traversal, mixed separators, repeated traversal, symlinks, and root-directory deletion attempts. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
handler.js:247
Finding
Persisted Current-Project and Entry Metadata Can Escape the Memory Storage Boundary<![CDATA[ ## Vulnerability Details **File Location**: `handler.js:89-96`, `handler.js:247-291`, `handler.js:300-361`, `handler.js:370-399` **Vulnerability Type**: Persistent path traversal in memory read and write operations **Risk Level**: High ### Vulnerable Code ```js index.projects[name] = { description, createdAt: now, updatedAt: now, status: 'active' }; index.currentProject = name; writeIndex(index); ``` ```js function memorySave(title, content, tags = []) { const index = readIndex(); if (!index.currentProject) { return { success: false, error: '请先选择一个项目', hint: '使用 /project use <项目名> 或 /project create <项目名>' }; } const projectDir = path.join(PROJECTS_DIR, index.currentProject); const entriesFile = path.join(projectDir, 'memory', 'entries.json'); ensureDir(path.join(projectDir, 'memory')); let entries = { entries: [] }; if (fs.existsSync(entriesFile)) { entries = JSON.parse(fs.readFileSync(entriesFile, 'utf8')); } const entryId = `mem_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const now = new Date().toISOString(); const entry = { id: entryId, title, tags, createdAt: now, updatedAt: now, agent: process.env.OPENCLAW_AGENT_ID || 'unknown' }; entries.entries.unshift(entry); fs.writeFileSync(entriesFile, JSON.stringify(entries, null, 2)); const contentFile = path.join(projectDir, 'memory', `${entryId}.md`); fs.writeFileSync( contentFile, `# ${title}\n\n${content}\n\n---\n创建时间: ${now}\n标签: ${tags.join(', ')}` ); } ``` ```js function memorySearch(query, limit = 10) { const index = readIndex(); if (!index.currentProject) { return { success: false, error: '请先选择一个项目' }; } const projectDir = path.join(PROJECTS_DIR, index.currentProject); const entriesFile = path.join(projectDir, 'memory', 'entries.json'); if (!fs.existsSync(entriesFile)) { return { success: true, results: [], message: '暂无记忆' }; ...[truncated 3513 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `currentProject` whenever it is read from `_index.json`; do not assume persistent metadata is trustworthy. 2. Resolve the project directory and verify that it is strictly below `PROJECTS_DIR` before every memory operation. 3. Validate entry IDs loaded from storage against the exact generated format: ```js const ENTRY_ID_PATTERN = /^mem_[0-9]+_[a-z0-9]{9}$/; function validateEntryId(id) { if (typeof id !== 'string' || !ENTRY_ID_PATTERN.test(id)) { throw new Error('Invalid memory entry ID'); } } ``` 4. Resolve every content file and verify containment within the expected memory directory: ```js const memoryDir = path.resolve(projectDir, 'memory'); const contentFile = path.resolve(memoryDir, `${entry.id}.md`); if (!contentFile.startsWith(memoryDir + path.sep)) { throw new Error('Memory file escapes the allowed directory'); } ``` 5. Validate the schema of `_index.json` and `entries.json`, including field types, array shapes, ID formats, and reasonable size limits. 6. Reject symbolic links or use filesystem APIs and deployment permissions that prevent symlink-based boundary escapes. 7. Consider storing entries by internally generated opaque IDs in a database or key-value store rather than deriving paths from mutable metadata. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
SKILL.md:151
Finding
Documented Automatic Backup Safety Control Is Not Implemented<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:151-157`, `handler.js:175-235` **Vulnerability Type**: Missing documented safety control for destructive operations **Risk Level**: Low ### Vulnerable Documentation and Code The skill documentation states: ```md ## 注意事项 1. **项目隔离**: 不同项目的记忆完全隔离 2. **权限共享**: 所有智能体默认可访问所有项目 3. **数据安全**: 删除操作需要二次确认 4. **自动备份**: 重要操作前自动备份 ``` However, archive and deletion operations directly move or remove data: ```js function projectArchive(name) { const index = readIndex(); if (!index.projects[name]) { return { success: false, error: `项目 "${name}" 不存在` }; } index.projects[name].status = 'archived'; index.projects[name].archivedAt = new Date().toISOString(); if (index.currentProject === name) { index.currentProject = null; } writeIndex(index); ensureDir(ARCHIVED_DIR); const srcDir = path.join(PROJECTS_DIR, name); const destDir = path.join(ARCHIVED_DIR, name); if (fs.existsSync(srcDir)) { fs.renameSync(srcDir, destDir); } return { success: true, message: `项目 "${name}" 已归档` }; } ``` ```js function projectDelete(name, confirm) { if (!confirm) { return { success: false, error: '删除操作需要确认,请设置 confirm: true' }; } const index = readIndex(); if (!index.projects[name]) { return { success: false, error: `项目 "${name}" 不存在` }; } const projectDir = path.join(PROJECTS_DIR, name); const archivedDir = path.join(ARCHIVED_DIR, name); if (fs.existsSync(projectDir)) { fs.rmSync(projectDir, { recursive: true }); } if (fs.existsSync(archivedDir)) { fs.rmSync(archivedDir, { recursive: true }); } delete index.projects[name]; if (index.currentProject === name) { index.currentProject = null; } writeIndex(index); return { success: true, message: `项目 "${name}" 已永久删除` }; } ``` ### Technical Analysis The documentation promises automatic backup before important operations, but the implementation conta ...[truncated 1263 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement a verified backup before archive and permanent-delete operations. 2. Store backups outside the directory tree that will be deleted. 3. Use unique, immutable backup identifiers and record creation time, source project, size, and integrity hash. 4. Abort the destructive operation if backup creation or verification fails. 5. Return the backup identifier and restoration instructions to the caller. 6. Add a restore command and test backup restoration regularly. 7. If automatic backup is not intended, remove the claim from `SKILL.md` and explicitly state that confirmed deletion is irreversible. 8. Consider a two-stage deletion model in which projects first enter a recoverable quarantine area and are permanently removed only after a retention period. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The markdown file documents `/project delete <名称>` as well as memory save functionality and states that all project data is stored under `~/.openclaw/projects/`, but it does not warn users about deletion consequences, persistence, or shared-access privacy implications. For markdown files, safety-relevant behaviors affecting user data should be accompanied by clear warnings.

Session Persistence

Medium
Category
Rogue Agent
Content
### 项目管理
```
/project list           # 列出所有项目
/project create <名称>  # 创建新项目
/project use <名称>     # 切换到项目
/project info           # 显示当前项目
/project archive <名称> # 归档项目
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill instructs agents to automatically save conversation content in several situations, but it does not require clear user notice or consent before persisting potentially sensitive data. In a cross-agent shared memory system, this increases the chance that private, regulated, or security-relevant information is stored and later retrieved by other agents without the user's informed awareness.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The skill claims 'complete isolation' between projects while also stating that all agents can access all projects by default. This contradiction can mislead users and downstream agents into assuming stronger confidentiality boundaries than actually exist, creating a real risk of cross-project data exposure in a shared memory system.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code persistently writes project metadata and memory content into files under the user's home directory, which affects user data storage and privacy. Although the behavior is part of the feature set, there is no visible confirmation prompt or user-facing disclosure in the code indicating that content will be stored locally.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The display name, description, tool descriptions, and all trigger patterns are presented only in Chinese, which effectively constrains use to a specific language. There is no indication that the skill is region-specific or that users can opt into another language, which conflicts with the policy against forcing a specific language without user choice.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The manifest explicitly describes a cross-agent project memory system with shared access, but it does not present a clear warning that user data may persist and be accessible across agents or sessions. This creates a meaningful confidentiality risk because users may reveal sensitive information assuming it is ephemeral or isolated when it is not.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger set is broad and generic, including common phrases like switching projects, creating projects, and saving/searching memory. In an agent environment, this increases the chance of unintended activation and accidental execution of memory or project-management actions based on normal conversation rather than explicit user intent.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The configuration enables autoSave by default, implying content may be written to persistent storage automatically without a visible consent step. In a memory-management skill, this is especially dangerous because users may unknowingly persist credentials, internal notes, or other sensitive project data to disk.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The README presents all operational instructions in Chinese, which effectively forces a specific language for users without any opt-in or explanation that the skill is intended for a Chinese-speaking audience. The policy requires avoiding language or locale constraints unless the user is given a choice or the limitation is clearly justified.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
All user-facing instructions, commands, and prompts in the skill are presented in Chinese, and the document does not indicate that the language is optional or region-specific. This can constitute a language policy issue when users are not given a choice or informed of the locale constraint.

Missing User Warnings

Low
Confidence
79% confidence
Finding
The archive operation renames and relocates a project directory into an archived area, which changes the user's filesystem state. The code returns a success message after completion, but it does not provide advance disclosure or an in-code warning that the operation will move files on disk.

Context-Inappropriate Capability

Low
Confidence
82% confidence
Finding
With no manifest or stated purpose available, reading process environment is not justified by documented intent in this file. The code accesses OPENCLAW_AGENT_ID to persist agent identity into saved memory entries, which is an extra capability beyond the otherwise local project/memory file management behavior.