Back to skill

Security audit

Zhuaxia

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real OpenClaw backup/import tool, but it handles sensitive local files and imported archives with enough under-scoped safeguards that users should review it carefully before installing.

Install only if you are comfortable reviewing .claw archives and the generated export contents yourself. Do not rely on the stated automatic credential stripping for shared packages, avoid importing from arbitrary URLs or untrusted senders, and use dry-run plus a separate backup before allowing it to write workspace or skill files.

Vulnerability Patterns
  • 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
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/clawctl.mjs:3238
Finding
Sensitive Files Inside Installed Skills Are Exported Without Filtering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clawctl.mjs:3238-3265` **Vulnerability Type**: Unfiltered sensitive-file export **Risk Level**: High ### Vulnerable Code ```js async function collectAllFiles2(dir, baseDir) { const files = []; let entries; try { entries = await readdir2(dir, { withFileTypes: true }); } catch { return files; } for (const entry of entries) { const fullPath = join3(dir, entry.name); const relPath = relative2(baseDir, fullPath); if (entry.isDirectory()) { files.push(...await collectAllFiles2(fullPath, baseDir)); } else if (entry.isFile()) { const content = await readFile3(fullPath); files.push({ relativePath: relPath, absolutePath: fullPath, content, size: content.length }); } } return files; } async function scanSkills(stateDir) { const skillsDir = join3(stateDir, "skills"); // ... for (const entry of entries) { if (!entry.isDirectory()) continue; const skillDir = join3(skillsDir, entry.name); const files = await collectAllFiles2(skillDir, skillDir); // ... skills.push({ name: entry.name, version, source, files }); } return skills; } ``` ### Technical Analysis The workspace scanner applies mandatory filename exclusions, but `scanSkills()` invokes a separate unrestricted recursive collector. Every regular file under each directory in `~/.openclaw/skills/` is read and subsequently included in the exported package. The skill collector does not apply `MANDATORY_FILE_EXCLUSIONS`, user-defined `.clawignore` rules, secret-pattern detection, or content redaction. Files such as `.env`, credential stores, private keys, certificates, session artifacts, and skill-specific configuration files can therefore be bundled without warning. This directly conflicts with the documentation’s broad assertion that API keys, tokens, and passwords are automatically stripped and that the resulting package is sa ...[truncated 1031 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply the same mandatory and user-defined exclusion rules to skill files as to workspace files. 2. Explicitly reject common secret-bearing files, including `.env`, credentials, authentication profiles, session stores, private keys, certificates, and password databases. 3. Run content-based secret detection over every textual skill file before packaging. 4. Redact detected values or fail the export and require explicit user review. 5. Reject symbolic links and non-regular files during recursive collection. 6. Report which files were excluded and distinguish configuration sanitization from full-package secret scanning. 7. Add tests proving that nested `.env`, `.pem`, credential JSON, and token-bearing text files are not exported. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/clawctl.mjs:3546
Finding
Workspace Files Are Exported Verbatim Without Content Sanitization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clawctl.mjs:3546-3578` **Related Documentation**: `SKILL.md:16, 124, 298-304` **Vulnerability Type**: Incomplete secret sanitization and misleading security assurance **Risk Level**: High ### Vulnerable Code ```js const scan = await scanInstance(source); console.log("Running security scan..."); const sanitized = scan.config ? sanitizeConfig(scan.config) : { sanitized: {}, removedCredentials: [] }; if (sanitized.removedCredentials.length > 0) { console.log(`Removed ${sanitized.removedCredentials.length} sensitive values:`); for (const cred of sanitized.removedCredentials) { console.log(` - ${cred.path} (${cred.provider}:${cred.type})`); } } const workspaceFiles = flags["include-memory"] ? scan.workspaceFiles : scan.workspaceFiles.filter((f2) => f2.relativePath !== "MEMORY.md"); // ... const result = await pack({ ref: parsed, description: flags.description ?? "", sanitizedConfig: sanitized.sanitized, workspaceFiles: workspaceFiles.map((f2) => ({ relativePath: f2.relativePath, content: f2.content, size: f2.size })), removedCredentials: [...sanitized.removedCredentials], plugins: pluginNames, skills: skillInputs, channels, outputDir: stagingDir }); ``` The corresponding documentation states: ```md Sensitive data (API keys, tokens) is automatically stripped. ``` ```md - API keys, tokens, passwords (automatically stripped) ``` ### Technical Analysis The security scan invokes `sanitizeConfig()` only on the parsed `openclaw.json` configuration. Workspace files are selected by filename and forwarded to `pack()` with their original byte content. Although the workspace scanner excludes certain filenames and extensions, it does not inspect the contents of otherwise allowed Markdown, JSON, YAML, text, or source files. Credentials embedded in files such as `IDENTITY.md`, `SOUL.md`, instruction files, notes, or custom configuration documents are the ...[truncated 1345 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Scan all textual workspace files with secret detectors before packaging. 2. Redact each detected secret while preserving non-sensitive content, or abort the export and require explicit approval. 3. Support structured sanitization for JSON, YAML, TOML, and environment files in addition to generic text scanning. 4. Treat binary or unscannable files as unsafe by default and require an explicit opt-in. 5. Expand memory exclusion to normalized paths and documented memory locations rather than checking only `MEMORY.md`. 6. Display a precise report of scanned, redacted, excluded, and unscannable files. 7. Replace unconditional “safe to share” language with a warning that automated secret detection is not exhaustive and that the archive should be reviewed. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/clawctl.mjs:3144
Finding
Configuration Arrays Bypass Recursive Credential Sanitization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clawctl.mjs:3144-3178` **Vulnerability Type**: Incomplete recursive data sanitization **Risk Level**: High ### Vulnerable Code ```js function sanitizeConfig(config) { const removedCredentials = []; function walk(obj, currentPath) { const result = {}; for (const [key, value] of Object.entries(obj)) { const path = [...currentPath, key]; const pathStr = path.join("."); if ( typeof value === "object" && value !== null && !Array.isArray(value) ) { result[key] = walk(value, path); } else if (typeof value === "string") { if (isSecretRef(value)) { result[key] = value; } else if ( SENSITIVE_JSON_PATHS.some((p2) => matchesJsonPath(pathStr, p2)) ) { const provider = path.length >= 2 ? path[path.length - 2] : "unknown"; const type = key; result[key] = `$CLAW_PLACEHOLDER:${provider}:${type}`; removedCredentials.push({ path: pathStr, provider, type }); } else { const detected = detectSensitiveValues(value); if (detected.length > 0) { const provider = detected[0].name; result[key] = `$CLAW_PLACEHOLDER:${provider}:${key}`; removedCredentials.push({ path: pathStr, provider, type: key }); } else { result[key] = value; } } } else { result[key] = value; } } return result; } const sanitized = walk(config, []); return { sanitized, removedCredentials }; } ``` ### Technical Analysis The recursive branch explicitly excludes arrays by checking `!Array.isArray(value)`. An array consequently falls through to the final `else` branch and is copied into the sanitized result by reference without traversing its elements. Any string secret directly inside an array, or ...[truncated 1106 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Implement a type-preserving recursive sanitizer that processes arrays as well as objects: ```js function walk(value, path) { if (Array.isArray(value)) { return value.map((item, index) => walk(item, [...path, String(index)])); } if (value && typeof value === "object") { return Object.fromEntries( Object.entries(value).map(([key, child]) => [ key, walk(child, [...path, key]) ]) ); } if (typeof value === "string") { return sanitizeString(value, path); } return value; } ``` Additionally: 1. Update path matching so numeric array indices can match wildcard components. 2. Detect sensitive key names such as `token`, `password`, `secret`, `apiKey`, and authorization headers at arbitrary nested depths. 3. Add tests for primitive arrays, arrays of objects, nested arrays, and mixed structures. 4. Validate the final serialized output with a second secret-scanning pass before archive creation. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/clawctl.mjs:3036
Finding
Archive Integrity Verification Does Not Cover Undeclared Installed Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clawctl.mjs:3036-3064` **Installation Locations**: `scripts/clawctl.mjs:3670-3707` **Vulnerability Type**: Incomplete archive integrity validation **Risk Level**: High ### Vulnerable Code ```js async function unpack(archivePath, outputDir) { await mkdir2(outputDir, { recursive: true }); await lo({ file: archivePath, cwd: outputDir, gzip: true, preservePaths: false, strict: true }); const manifestRaw = await readFile2( join2(outputDir, "manifest.json"), "utf-8" ); const manifest = JSON.parse(manifestRaw); let integrityValid = true; for (const fileEntry of manifest.files) { try { const content = await readFile2(join2(outputDir, fileEntry.path)); const hash = createHash2("sha256").update(content).digest("hex"); if (hash !== fileEntry.sha256) { integrityValid = false; } } catch { integrityValid = false; } } const files = await collectAllFiles(outputDir, outputDir); return { manifest, files, outputDir, integrityValid }; } ``` The installer then processes the complete extracted file list rather than only verified manifest entries: ```js for (const f2 of result.files) { if (f2.relativePath.startsWith("workspace/")) { const relPath = f2.relativePath.slice("workspace/".length); const content = await readFile4(f2.absolutePath); const dest = resolve(workspaceDir, relPath); await mkdir3(resolve(dest, ".."), { recursive: true }); await writeFile2(dest, content); installed++; } } // ... const skillFiles = result.files.filter( (f2) => f2.relativePath.startsWith(`skills/${skillMeta.name}/`) ); for (const f2 of skillFiles) { const relPath = f2.relativePath.slice( `skills/${skillMeta.name}/`.length ); const dest = resolve(targetSkillDir, relPath); await mkdir3(resolve(dest, ".."), { recursive: true }); const content = await readFile4(f2.absolutePath); await writ ...[truncated 2050 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Normalize every manifest and extracted path before comparison. 2. Build sets of expected and extracted regular-file paths and require exact equality. 3. Reject undeclared files, duplicate normalized paths, directories used where files are expected, and missing declared files. 4. Install files exclusively by iterating validated `manifest.files`, not the unrestricted extraction result. 5. Validate the manifest against a strict schema, including path, hash, size, and allowed top-level directories. 6. Enforce per-file and total extraction size limits to reduce archive resource-exhaustion risks. 7. If package authenticity is required, use a detached digital signature verified against a trusted public key. A digest embedded in the same archive cannot establish authenticity. 8. Ensure the CLI reports “verified” only after all path-set, file-type, size, hash, and signature checks pass. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/clawctl.mjs:3024
Finding
Imported Archive Symbolic Links Can Expose Files Outside the Extraction Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clawctl.mjs:3024-3040` **Installation Location**: `scripts/clawctl.mjs:3670-3681` **Vulnerability Type**: Symbolic-link traversal and unauthorized local file read **Risk Level**: High ### Vulnerable Code ```js async function collectAllFiles(dir, base) { const results = []; const entries = await readdir(dir, { withFileTypes: true }); for (const entry of entries) { const full = join2(dir, entry.name); if (entry.isDirectory()) { results.push(...await collectAllFiles(full, base)); } else { const content = await readFile2(full); results.push({ relativePath: relative(base, full), absolutePath: full, size: content.length }); } } return results; } ``` The collected path is later read and copied into the workspace: ```js for (const f2 of result.files) { if (f2.relativePath.startsWith("workspace/")) { const relPath = f2.relativePath.slice("workspace/".length); const content = await readFile4(f2.absolutePath); const dest = resolve(workspaceDir, relPath); await mkdir3(resolve(dest, ".."), { recursive: true }); await writeFile2(dest, content); installed++; } } ``` ### Technical Analysis `Dirent.isDirectory()` is false for a symbolic link. The link therefore reaches the generic `else` branch, where `readFile2(full)` follows it and reads the target. No `lstat()` check rejects symbolic links, and no `realpath()` containment check ensures that the resolved target remains inside the temporary extraction directory. Because integrity checking also ignores undeclared files, an attacker can include an undeclared symbolic link under `workspace/` without supplying a matching manifest hash. If archive extraction preserves that link, the collector and installer may read a host file outside the extraction root and copy its contents into the OpenClaw workspace. ### Attack Path 1. An attacker creates a `.claw` arc ...[truncated 1312 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use `lstat()` for every extracted entry and reject symbolic links, hard links, devices, sockets, FIFOs, and all other non-regular file types. 2. Resolve each accepted file with `realpath()` and verify that it is contained under the canonical extraction root. 3. Perform containment checks immediately before each read and write to reduce time-of-check/time-of-use exposure. 4. Configure archive extraction to reject link entries where supported, and enforce the rule again after extraction. 5. Install only regular files explicitly listed and successfully verified in the manifest. 6. Use a temporary directory with restrictive permissions and remove it on every success and failure path. 7. Add security tests for absolute symlinks, relative escaping symlinks, hard links, chained links, undeclared links, and links replacing expected manifest files. ]]>
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (15)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
- **User wants to export + share in one step**: After export, suggest easy transfer methods (AirDrop, scp, cloud drive).
- **Multiple .claw files**: If the user says "install all claw packages", iterate through them one by one with preview for each.
- **Rollback chain**: Each rollback creates a safety backup, so the user can always undo a rollback. Explain this when asked.
- **Disk space**: If the user has many backups, suggest cleaning old ones: `rm -rf ~/.openclaw/.zhuaxia-backups/<old-id>`.

## What Gets Exported
Confidence
90% 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
- **User wants to export + share in one step**: After export, suggest easy transfer methods (AirDrop, scp, cloud drive).
- **Multiple .claw files**: If the user says "install all claw packages", iterate through them one by one with preview for each.
- **Rollback chain**: Each rollback creates a safety backup, so the user can always undo a rollback. Explain this when asked.
- **Disk space**: If the user has many backups, suggest cleaning old ones: `rm -rf ~/.openclaw/.zhuaxia-backups/<old-id>`.

## What Gets Exported
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
- **User wants to export + share in one step**: After export, suggest easy transfer methods (AirDrop, scp, cloud drive).
- **Multiple .claw files**: If the user says "install all claw packages", iterate through them one by one with preview for each.
- **Rollback chain**: Each rollback creates a safety backup, so the user can always undo a rollback. Explain this when asked.
- **Disk space**: If the user has many backups, suggest cleaning old ones: `rm -rf ~/.openclaw/.zhuaxia-backups/<old-id>`.

## What Gets Exported
Confidence
90% 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).

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill directs the agent to read files, inspect environment/state, and execute shell commands, but the manifest does not declare any explicit tool scope or allowed-tools boundary. That makes the skill's effective privilege unclear and increases the chance an agent will run sensitive local operations without a clear authorization model.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The trigger list includes broad natural-language phrases that could match ordinary requests and invoke a high-impact skill that reads local state, writes config, imports files, and installs bundled skills. Over-broad activation increases the chance of unintended execution of privileged workflows.

Session Persistence

Medium
Category
Rogue Agent
Content
node {baseDir}/scripts/clawctl.mjs backup [--source <path>] [--label <text>]
```

Create a snapshot of current workspace + config. Stored in `~/.openclaw/.zhuaxia-backups/<id>/`.

### List Backups
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.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The workflow says to run when the user says things like broad export/backup phrases, without strong boundaries that this only applies to OpenClaw instance packaging. In context, that ambiguity is risky because the skill performs filesystem discovery and package operations that should require precise user intent.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The import workflow can trigger on generic 'install' or 'load' requests, which may cause the agent to treat unrelated files or tasks as OpenClaw package installs. Because import modifies workspace, config, and skills, accidental invocation has meaningful integrity impact.

Session Persistence

Medium
Category
Rogue Agent
Content
> New items to merge: [list]
   > Shall I merge the non-sensitive settings?

   If the user agrees, read both JSON files, merge intelligently (keep current credentials, add new non-sensitive settings), and write back to `openclaw.json`.

3. **Credential checklist** — For each `$CLAW_PLACEHOLDER` in the imported config, tell the user exactly what they need to set:
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.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The skill tells the agent to fetch an arbitrary URL with curl and then proceed with import, which extends a local backup/migration workflow into untrusted network retrieval. This can introduce malicious .claw packages, SSRF-style access to internal resources, or accidental retrieval of attacker-controlled content without validation.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill instructs network download of external content without warning the user about network access, provenance, or the risk of importing untrusted packages. In a workflow that later installs package contents and bundled skills, lack of a trust warning materially increases the likelihood of compromise.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest describes portable export/import for backing up, migrating, restoring, and rolling back an OpenClaw setup, but the backup/restore path only copies `workspace/` and `openclaw.json`. It does not preserve or restore other state the exporter scans or excludes elsewhere, such as bundled skill install state unless present in the imported package, so 'rollback' and 'backup my instance' do not actually restore the full setup they imply.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Rollback copies backed-up files over the current workspace and config without deleting files added after the backup or warning about overwrites. This can leave a mixed state where stale or attacker-planted files persist, undermining rollback expectations and potentially preserving malicious content after a supposed recovery.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The load command writes workspace files and bundled skill files from an imported archive directly into the user's target directory without explicit approval. In this skill's context, packages are meant to be shared and imported, so untrusted archives are plausible; that makes silent installation more dangerous because a malicious package can place executable or deceptive content into the user's environment.

Description-Behavior Mismatch

Low
Confidence
89% confidence
Finding
The skill is described primarily as exporting and importing OpenClaw instances as portable packages, but `runLoad` also installs bundled skills into the target `skills` directory as a separate capability. That behavior is related, but materially broader than just restoring an instance backup because it performs package-managed skill deployment into the local installation.

Static analysis

Detected: suspicious.destructive_delete_command

Documentation contains a destructive delete command without an explicit confirmation gate.

Warn
Code
suspicious.destructive_delete_command
Location
SKILL.md:293