Back to skill

Security audit

claw-saver

Security checks for vulnerabilities and agentic risk

Overview

This backup skill mostly does what it claims, but it handles very sensitive OpenClaw data and credentials in ways that need careful review before installation.

Install only if you intentionally want a remote Git repository to contain a near-complete copy of your OpenClaw home, including credentials, identity keys, tokens, memory, and knowledge data. Use a private repository, a narrowly scoped/expiring token, and consider encrypting the backup before it enters Git history; do not enable cron unless you accept unattended recurring uploads and token exposure in local crontab/Git config. Treat generated RESTORE.md exclusion claims as unreliable for sensitive-data exposure.

Vulnerability Patterns
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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 (4)

other

Error
Location
lib/backup.js:18
Finding

Unencrypted Backup of Credentials, Identity Keys, Tokens, and Private Agent Data to an Arbitrary Git Repository

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/cli.js:83
Finding

Shell Command Injection Through Cron Expressions, Repository URLs, and Access Tokens

Content
View full analysis
/dev/null; echo "${cronLine}") | crontab -`, { shell: '/bin/bash' }); console.log(`Cron enabled: ${cronExpr}`); } ``` ```js async function setCron(expr) { const { execSync } = await import('child_process'); // Validate cron expression if (!expr || !expr.includes('*')) { console.error('Invalid cron expression. Example: 0 */2 * * *'); process.exit(1); } execSync( `(crontab -l 2>/dev/null | grep -v "backup.*cli.js"; echo "${expr} ${cronScript(expr)}") | crontab -`, { shell: '/bin/bash' } ); console.log(`Cron schedule updated: ${expr}`); } ``` ```js export async function initRepo(repoUrl, token) { const remoteUrl = token ? repoUrl.replace('://', `://${token}@`) : repoUrl; if (!await isGitRepo()) { await execAsync(`git init`, { cwd: DOT }); await execAsync(`git config user.name "OpenClaw Bac ...[truncated 2691 chars]
Remediation
View remediation

T06 · System Persistence

Error
Location
scripts/cli.js:51
Finding

Opt-In Cron Persistence Repeatedly Executes the Backup and Sensitive Upload Path

Content
View full analysis
/dev/null; echo "${cronLine}") | crontab -`, { shell: '/bin/bash' }); console.log(`Cron enabled: ${cronExpr}`); } ``` ```js async function disableCron() { const { execSync } = await import('child_process'); execSync(`crontab -l | grep -v "backup.*cli.js" | crontab -`, { shell: '/bin/bash' }); console.log('Cron disabled.'); } ``` ```js async function setCron(expr) { const { execSync } = await import('child_process'); if (!expr || !expr.includes('*')) { console.error('Invalid cron expression. Example: 0 */2 * * *'); process.exit(1); } execSync( `(crontab -l 2>/dev/null | grep -v "backup.*cli.js"; echo "${expr} ${cronScript(expr)}") | crontab -`, { shell: '/bin/bash' } ); console.log(`Cron schedule updated: ${expr}`); } ``` ### Technical Analysis The scheduled task is part of the skill's declared automatic-backup functionality and is installed only through explicit `enable-cron` or `set-cron` commands. It is therefore not a hidden startup backdoor. Nevertheless, it creates cross-session persistence and repeatedly invokes the broad sensitive-data upload path without a per-run confirmation. The cron entry references a mutable JavaScript file under the OpenClaw skill di ...[truncated 1265 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/cli.js:29
Finding

Repository Access Token Persisted in Plaintext in Crontab and Git Remote Configuration

Content
View full analysis
/dev/null; echo "${cronLine}") | crontab -`, { shell: '/bin/bash' }); ``` ```js export async function initRepo(repoUrl, token) { const remoteUrl = token ? repoUrl.replace('://', `://${token}@`) : repoUrl; // ... try { await execAsync(`git remote add origin ${remoteUrl}`, { cwd: DOT }); } catch { // already exists — ignore } return true; } ``` ```js export async function push(repoUrl, token, retries = 3, retryMs = 60000) { const remoteUrl = token ? repoUrl.replace('://', `://${token}@`) : repoUrl; // Update remote with fresh token try { await execAsync(`git remote set-url origin ${remoteUrl}`, { cwd: DOT }); } catch { /* ignore */ } // ... } ``` ### Technical Analysis The skill embeds the personal access token directly into the cron command as an environment assignment. It also injects the token into the Git remote URL and persists that URL in `~/.openclaw/.git/config`. Redirecting `git push` standard error does not protect these persistent plaintext copies. Any process or user capable of reading the account's ...[truncated 1316 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

This second description/behavior mismatch is particularly dangerous because it indicates the skill propagates a backup token into cron environments and performs non-interactive restore behavior despite claiming otherwise. Misrepresentation around token handling and restore safety can lead to credential exposure, accidental overwrite of local state, and false user assumptions about consent and control.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

This second description/behavior mismatch is particularly dangerous because it indicates the skill propagates a backup token into cron environments and performs non-interactive restore behavior despite claiming otherwise. Misrepresentation around token handling and restore safety can lead to credential exposure, accidental overwrite of local state, and false user assumptions about consent and control.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 21)May include surrounding context.

md
envVars:
      - name: OPENCLAW_BACKUP_GIT_TOKEN
        required: true
        description: Git personal access token with repo scope. Injected via openclaw config.
      - name: OPENCLAW_DIR
        required: false
        description: Override OpenClaw home directory. Defaults to ~/.openclaw.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill explicitly backs up highly sensitive local material, including credentials, identity keys, memory, and configuration, to a remote Git repository, yet it lacks a prominent warning about this transmission risk. In this context, omission of a clear warning materially increases the chance of users exfiltrating secrets, regulated data, or private agent state to third-party infrastructure without informed consent.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The generated .gitignore explicitly preserves highly sensitive material such as credentials/, identity/, kb/, and openclaw.json for backup, but RESTORE.md tells users those items are excluded. This mismatch can cause operators to wrongly assume secrets and private data were never pushed to the remote repository, increasing the chance of undiscovered credential exposure and unsafe sharing of the backup repo.

Content

No source excerpt is available for this finding.

Self-Modification

High
Category
Rogue Agent
Confidence
85% confidence
Finding

Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Content

Scanner excerpt · lib/restore.js (reported line 49)May include surrounding context.

js
console.error('Error: restore requires TTY for interactive confirmation. Use { confirm: true } to bypass.');
      return { cancelled: true, error: 'non-interactive' };
    }
    const warning = `This will overwrite current files with version from ${commit}. Continue? [y/N]`;
    process.stdout.write(warning + ' ');
    const answer = await promptYesNo();
    if (!answer) return { cancelled: true };

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The CLI documentation promises an interactive restore, but the implemented restore path automatically selects the most recent backup and calls restoreBackup(commit, { confirm: false }). Restore is a destructive operation on the user's OpenClaw state, so performing it without explicit confirmation can overwrite current data unexpectedly and violates user expectations in a security-sensitive backup tool.

Content

No source excerpt is available for this finding.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Confidence
75% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · scripts/cli.js (reported line 68)May include surrounding context.

js
onst script = cronScript(cronExpr);

  // Check if cron already exists
  const { execSync } = await import('child_process');
  try {
    execSync(`crontab -l | grep -q "backup.*cli.js"`, { stdio: 'pipe' });
    console.log('Cron already enabled. Use set-cron to change schedule.');
    return;
  } catch { /* not found */ }

  // Add cron job
  const cronLine = `${cronExpr} ${script}`;
  execSync(`(crontab -l 2>/dev/null; echo "${cronLine}") | crontab -`, { shell: '/bin/bash' });
  console.log(`Cron enabled: ${cronExpr}`);
}

/**
 * Disable cron backup
 */
async function disableCron() {
  const { execSync } = await import('child_process');
  execSync(`crontab -l | grep -v "backup.*cli.js" | crontab -`, { shell: '/bin/bash' });
  console.log('Cron disabled.');
}

/**
 * Set cron schedule
 */
async function setCron(expr) {
  const { execSync } = await import('child_process');

  // Validate cron expression
  if (!expr || !expr.includes('*')) {
    console.error('Invalid cron expression. Ex

Missing User Warnings

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The restore command invokes a potentially destructive recovery operation with confirmation disabled at the call site. A user running the documented restore command can unintentionally replace local state with the latest backup, causing data loss or rollback to an attacker-influenced snapshot if the backup source is compromised.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding

The skill requests and relies on a sensitive environment variable containing a Git personal access token, but the manifest does not declare an explicit tool/permission scope. That weakens reviewability and least-privilege controls, making it easier for a backup skill handling sensitive data to gain broader-than-obvious access without clear user visibility.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This markdown skill description explains what the skill does but does not define any explicit trigger phrases, scope limits, or exclusion conditions for activation. Without narrow invocation guidance, an agent may over-associate generic backup-related requests with this skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This code reads a backup Git token from the environment and automatically stages and pushes the full ~/.openclaw contents to a remote repository, including paths the code comments identify as credentials, identity keys, configs, and agent memory. Without a strong user-facing warning or explicit confirmation at the operation point, users may unknowingly exfiltrate secrets and sensitive local state to a third-party Git host.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This file builds shell commands by concatenating untrusted values such as repository URLs, tokens, commit hashes, filenames, and target paths into strings passed to child_process.exec. In a backup skill, this enables command injection if any of those values are attacker-controlled, allowing arbitrary OS command execution under the user's privileges rather than only constrained Git operations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code performs a network operation that sends local backup data to a remote repository and embeds the authentication token into the remote URL. While comments describe token handling for developers, there is no confirmation prompt, user-facing log, or warning to the user that data will be transmitted off-system.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The clone and checkout helpers operate on arbitrary repository URLs, commits, and caller-supplied directories instead of being restricted to the ~/.openclaw backup scope described by the skill. This broadens the capability of the skill into a general-purpose Git client, increasing abuse potential and making command-injection or unsafe file-write issues elsewhere more consequential.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The clone function performs a network operation and may use a token embedded in the repository URL, but the file provides no user-facing notice that remote access with credentials is occurring. Developer comments are present, but they do not disclose the behavior to the user.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The lfsInit function writes a .gitattributes file and then stages it with git add, modifying repository state. Although there are brief code comments, there is no user-facing prompt, log, or disclosure that these changes will be made automatically.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The cron helper reads the backup Git token from configuration and embeds it directly into the generated cron command line. This can expose credentials through crontab contents, local process inspection, backups of user configuration, or accidental disclosure to other tooling, increasing the risk of repository compromise.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
85% 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.

Content

Scanner excerpt · scripts/cli.js (reported line 61)May include surrounding context.

js
// Check if cron already exists
  const { execSync } = await import('child_process');
  try {
    execSync(`crontab -l | grep -q "backup.*cli.js"`, { stdio: 'pipe' });
    console.log('Cron already enabled. Use set-cron to change schedule.');
    return;
  } catch { /* not found */ }

Session Persistence

Medium
Category
Rogue Agent
Confidence
85% 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.

Content

Scanner excerpt · scripts/cli.js (reported line 77)May include surrounding context.

js
// Check if cron already exists
  const { execSync } = await import('child_process');
  try {
    execSync(`crontab -l | grep -q "backup.*cli.js"`, { stdio: 'pipe' });
    console.log('Cron already enabled. Use set-cron to change schedule.');
    return;
  } catch { /* not found */ }

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

This line installs a persistent cron job into the user's crontab, creating automatic recurring execution. In a user-facing backup skill this can be legitimate, but it still establishes persistence and, combined with token embedding and shell command construction, can create lasting unintended execution or credential exposure if misused or invoked unexpectedly.

Content

Scanner excerpt · scripts/cli.js (reported line 68)May include surrounding context.

js
// Add cron job
  const cronLine = `${cronExpr} ${script}`;
  execSync(`(crontab -l 2>/dev/null; echo "${cronLine}") | crontab -`, { shell: '/bin/bash' });
  console.log(`Cron enabled: ${cronExpr}`);
}

Session Persistence

Medium
Category
Rogue Agent
Confidence
86% confidence
Finding

This line rewrites the user's crontab to update persistence for the backup job. In context this is part of a legitimate scheduling feature, but it still modifies persistent execution and does so via shell interpolation of user-supplied schedule data, which raises risk of unintended commands if validation is weak.

Content

Scanner excerpt · scripts/cli.js (reported line 94)May include surrounding context.

js
}

  // Remove old and add new
  execSync(`(crontab -l 2>/dev/null | grep -v "backup.*cli.js"; echo "${expr} ${cronScript(expr)}") | crontab -`, { shell: '/bin/bash' });
  console.log(`Cron schedule updated: ${expr}`);
}