Back to skill

Security audit

Openclaw Skill Self Improvement

Security checks for vulnerabilities and agentic risk

Overview

This is a legitimate skill-maintenance tool, but it needs review because it broadly reads local Agent transcripts and contains an unsafe heartbeat command runner.

Review the scripts before installing in a sensitive environment. Run only against workspaces and Agent data you are comfortable having locally scanned, and avoid the daily heartbeat until the shell command construction is fixed or you can guarantee the workspace path is trusted and simple. Prefer a test workspace or a least-privileged account.

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

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/skill-health-check.mjs:99
Finding
Unrestricted Reading of Agent Session Transcripts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/skill-health-check.mjs`, lines 8, 99–113, and 138–146 **Vulnerability Type**: Excessive access to potentially sensitive Agent session data **Risk Level**: Medium ### Vulnerable Code ```js const agentsRoot = '/Users/m1/.openclaw/agents'; function collectSessionFiles(dir) { const files = []; if (!fs.existsSync(dir)) return files; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) { files.push(...collectSessionFiles(full)); } else if (full.endsWith('.jsonl')) { files.push(full); } } return files; } const sessionFiles = collectSessionFiles(agentsRoot); for (const file of sessionFiles) { const stat = fs.statSync(file); const lines = fs.readFileSync(file, 'utf8').split('\n').slice(-200); for (const line of lines) { if (!line) continue; for (const skill of skills) maybeUpdateUsage(skill, line, stat.mtimeMs); } } ``` ### Technical Analysis The health-check script recursively enumerates every `.jsonl` file under the hard-coded Agent directory and reads the most recent 200 lines of each file. Agent session transcripts may contain user conversations, tool responses, credentials, private data, filesystem paths, and other operational information. The stated purpose only requires determining whether a skill was recently used. Reading arbitrary transcript contents is broader than necessary and violates least-privilege and data-minimization principles. The directory is neither explicitly supplied by the user nor restricted to a dedicated usage-event store. The current implementation does not transmit transcript contents over the network or include them directly in its generated report. Nevertheless, the sensitive data is loaded into process memory, and the breadth of access increases the consequences of future defects, malicious modifications, logging changes, or runtime ...[truncated 1142 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make transcript analysis explicitly opt-in rather than running it during every health check. 2. Replace the hard-coded Agent directory with a user-supplied, validated path. 3. Require the selected Agent data directory to reside within an approved filesystem boundary. 4. Prefer a dedicated usage-event index containing only skill identifiers and timestamps instead of parsing complete transcripts. 5. If transcript processing remains necessary: - Parse structured JSONL records and inspect only the required routing fields. - Stream records rather than loading complete file contents. - Enforce file-count, file-size, and recursion-depth limits. - Reject symbolic links or resolve real paths and verify that they remain inside the approved directory. - Avoid logging or persisting message contents. 6. Clearly document what session data is accessed, why it is needed, how much is read, and what is retained. 7. Run the analysis with a dedicated low-privilege account that cannot read unrelated Agent or user data. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/daily-health-heartbeat.mjs:13
Finding
Shell Command Injection Through the Workspace Argument<![CDATA[ ## Vulnerability Details **File Location**: `scripts/daily-health-heartbeat.mjs`, lines 5 and 13–19 **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```js const workspaceRoot = process.argv[2] || '/Users/m1/.openclaw/workspace'; async function main() { const { execSync } = await import('node:child_process'); // Run health check execSync(`node ${path.join(workspaceRoot, 'skills/openclaw-self-improvement/scripts/skill-health-check.mjs')} ${workspaceRoot}`, { encoding: 'utf8' }); // Run eval execSync(`node ${path.join(workspaceRoot, 'skills/openclaw-self-improvement/scripts/routing-eval-runner.mjs')} ${workspaceRoot}`, { encoding: 'utf8' }); ``` ### Technical Analysis The script accepts `workspaceRoot` from `process.argv[2]` and interpolates it directly into two command strings passed to `execSync()`. String-form `execSync()` executes the command through a system shell. Because the workspace value is not quoted, escaped, or validated, shell metacharacters contained in the argument can terminate or modify the intended command and introduce additional commands. The value appears twice in each command—once as part of the script path and once as an argument—which creates multiple injection points. Even non-malicious workspace paths containing spaces or shell-significant characters can cause incorrect command parsing. Path normalization alone would not fix this issue because the unsafe shell interpretation is the underlying vulnerability. ### Attack Path 1. An attacker controls, influences, or convinces a user to supply a crafted workspace argument to `daily-health-heartbeat.mjs`. 2. The argument contains shell syntax that changes the structure of the generated command. 3. The script concatenates the untrusted value into the `execSync()` command string. 4. `execSync()` invokes the system shell. 5. The shell interprets the injected syntax and executes attacker-selected commands with the privileges of t ...[truncated 825 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Eliminate shell interpretation by using `execFileSync()` or `spawnSync()` with an explicit argument array: ```js import { execFileSync } from 'node:child_process'; const healthCheckPath = path.join( workspaceRoot, 'skills/openclaw-self-improvement/scripts/skill-health-check.mjs', ); const routingEvalPath = path.join( workspaceRoot, 'skills/openclaw-self-improvement/scripts/routing-eval-runner.mjs', ); execFileSync(process.execPath, [healthCheckPath, workspaceRoot], { encoding: 'utf8', shell: false, }); execFileSync(process.execPath, [routingEvalPath, workspaceRoot], { encoding: 'utf8', shell: false, }); ``` Additional hardening should include: 1. Resolve the workspace with `path.resolve()` and, where appropriate, `fs.realpathSync()`. 2. Verify that the workspace exists and is a directory. 3. Verify that both target scripts are regular files beneath the approved Skill directory. 4. Reject symbolic-link escapes by comparing canonical paths against the canonical workspace boundary. 5. Do not attempt to fix the vulnerability solely through manual shell escaping. 6. Apply execution timeouts and output-size limits to child processes. 7. Use the running Node.js executable from `process.execPath` rather than relying on a shell-resolved `node` command. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Ae1

High
Category
analysis-evasion
Content
node scripts/skill-health-check.mjs /path/to/workspace
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/skill-health-check.mjs /path/to/workspace
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Vague Triggers

Medium
Confidence
94% confidence
Finding
This JSON file is a manifest-like evaluation file, so vague-trigger review applies. The input phrase "do a retro for this week" relies on the broad phrase "do a retro," which is common workplace language and could ambiguously match multiple retrospective or meeting-related skills without additional scope constraints or negative examples in the trigger itself.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The script recursively scans agent session transcript JSONL files under a fixed agents directory and infers skill usage by matching skill names and IDs in transcript lines. Even if intended for health metrics, this creates an unnecessary cross-boundary data collection behavior because transcripts may contain sensitive prompts, outputs, or identifiers unrelated to skill maintenance.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script reads recent lines from all discovered session transcript files without any notice, consent flow, or narrowing to only telemetry fields. Because transcripts can contain sensitive conversation data, this silent processing creates a privacy risk and may leak or repurpose data beyond what a user expects from a skill audit tool.

Missing User Warnings

Low
Confidence
86% confidence
Finding
This markdown file explains that the daily heartbeat writes summaries and enumerates several output files under `.learnings/`, but it does not explicitly warn users that running the skill will create or overwrite files in their workspace. Because markdown files should disclose behaviors that affect user data or system state, this is a missing warning for filesystem changes.

Missing User Warnings

Low
Confidence
91% confidence
Finding
The README describes functionality that scans agent session records for usage signals and writes multiple report artifacts into the target workspace, but it does not clearly warn about these side effects in the quick-start or requirements sections. This can lead users to run the tool against sensitive workspaces without realizing conversational logs may be traversed and metadata-derived reports persisted, creating privacy, compliance, or data handling concerns.

Description-Behavior Mismatch

Low
Confidence
83% confidence
Finding
The script expands its inventory beyond the provided workspace by enumerating globally installed system skills from a hard-coded user-specific path. This exceeds the apparent scope of a workspace health check and can expose metadata about other installed skills, creating unnecessary visibility into environment-wide configuration.

Missing User Warnings

Low
Confidence
88% confidence
Finding
This code creates a directory and writes a persistent report file to .learnings/skill-health-report.json, which modifies the user's workspace state. The script does not provide advance notice beyond the final JSON console output, and there is no inline comment or prompt warning that a file will be created or overwritten.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/daily-health-heartbeat.mjs:16