other
- 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
Security audit
Security checks for vulnerabilities and agentic risk
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.
lib/backup.js:18Unencrypted Backup of Credentials, Identity Keys, Tokens, and Private Agent Data to an Arbitrary Git Repository
scripts/cli.js:83Shell Command Injection Through Cron Expressions, Repository URLs, and Access Tokens
scripts/cli.js:51Opt-In Cron Persistence Repeatedly Executes the Backup and Sensitive Upload Path
scripts/cli.js:29Repository Access Token Persisted in Plaintext in Crontab and Git Remote Configuration
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.
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.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
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.
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.
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.
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.
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 };
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.
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
// 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 */ }
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.
// 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 */ }
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.
// Add cron job
const cronLine = `${cronExpr} ${script}`;
execSync(`(crontab -l 2>/dev/null; echo "${cronLine}") | crontab -`, { shell: '/bin/bash' });
console.log(`Cron enabled: ${cronExpr}`);
}
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.
}
// 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}`);
}