Back to skill

Security audit

Healthy Backup

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent backup skill, but it can include raw sensitive OpenClaw configuration in default backups despite promising that sensitive config is scrubbed.

Review carefully before installing. Use only after fixing or accepting the raw openclaw.json backup issue, because default migratable/full backups may contain unredacted OpenClaw configuration even though the documentation says it is scrubbed. Keep rclone sync disabled unless you trust the remote, leave crontab and npm collection off unless needed, and rotate any credentials that may already have been stored in affected backups.

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

T09 · Insecure Skill Coding Practices

Error
Location
healthy-backup.sh:58
Finding
Raw OpenClaw Configuration Is Included in Migratable and Full Backups## Vulnerability Details **File Location**: `healthy-backup.sh:58-62, 188-193, 222-230` **Vulnerability Type**: Sensitive data exposure caused by incomplete backup exclusions **Risk Level**: High **Vulnerable Code:** ```bash EX=(--exclude=shared/secrets/ --exclude=credentials/ --exclude='*.key' --exclude='*.pem' --exclude='*.env' --exclude='*.secret' --exclude='.env' --exclude='.git/' --exclude='node_modules/' --exclude='BACKUPS/') ``` ```bash sync_dir() { local s="$1" d="$STAGING/$2" shift 2 mkdir -p "$d" rsync -a "${EX[@]}" "$@" "$s/" "$d/" } stage_config() { mkdir -p "$STAGING/config" jq 'walk(if type=="object" then with_entries( if (.key|test("password|token|secret|key";"i")) then .value="<redacted>" else . end) else . end )' "$OC_CFG" > "$STAGING/config/openclaw.json" ok "openclaw.json (scrubbed)" } ``` ```bash case "$TIER" in migratable) stage_config sync_dir "$OC" openclaw --exclude='logs/' --exclude='media/' --exclude='browser/' ok "~/.openclaw"; stage_deps ;; full) stage_config sync_dir "$OC" openclaw --exclude='logs/' --exclude='media/' --exclude='browser/' ok "~/.openclaw"; stage_deps [ -d "$WORKSPACE" ] && { sync_dir "$WORKSPACE" workspace --exclude='canvas/'; ok "workspace"; } || warn "workspace not found" [ -d "$SKILLS" ] && { sync_dir "$SKILLS" skills --exclude='.venv/'; ok "skills"; } || warn "skills not found" ;; esac ``` ### Technical Analysis `stage_config` correctly creates a scrubbed copy at `config/openclaw.json`. However, the migratable and full tiers then recursively synchronize the entire `~/.openclaw` directory into `openclaw/`. The exclusion list removes credential directories and selected filename patterns, but it does not exclude the root-level `openclaw.json`. Consequently, the archive can contain both: - `config/op ...[truncated 1772 chars]
Remediation
## Remediation Suggestions 1. Explicitly exclude the source configuration when synchronizing `~/.openclaw`: ```bash sync_dir "$OC" openclaw \ --exclude='/openclaw.json' \ --exclude='logs/' \ --exclude='media/' \ --exclude='browser/' ``` 2. Keep only the scrubbed `config/openclaw.json` in the archive. 3. Apply the exclusion as a source-root-relative rule so similarly named nested files are not unintentionally affected. 4. Add automated tests for every backup tier that decrypt the resulting archive and assert: - No `openclaw/openclaw.json` exists. - Exactly one intended scrubbed configuration copy exists. - Known test secrets do not appear anywhere in extracted archive content. 5. Treat backups created by affected versions as potentially containing raw credentials. Rotate sensitive credentials and securely delete or replace affected local and remote archives. 6. Update release checksums and documentation after publishing the corrected script.

T09 · Insecure Skill Coding Practices

Warning
Location
healthy-backup.sh:210
Finding
Incomplete Crontab Redaction Can Leak Credentials into Backups## Vulnerability Details **File Location**: `healthy-backup.sh:210-213` **Vulnerability Type**: Inadequate sanitization of sensitive command data **Risk Level**: Medium **Vulnerable Code:** ```bash echo; echo "## Cron (values redacted)" [ "$C_CRON" = "true" ] && crontab -l 2>/dev/null \ | sed 's/\([A-Za-z_][A-Za-z0-9_]*\)=[^ ]*/\1=<REDACTED>/g' \ || echo "(opt-in off)" ``` ### Technical Analysis The sanitization expression only recognizes text shaped like an unquoted `NAME=value` token and consumes characters until the next space. It is not a general secret detector and cannot safely sanitize arbitrary shell commands stored in a crontab. Sensitive information can remain visible when represented as: - Positional command arguments - HTTP authorization headers - URL query parameters - Quoted values containing spaces - Tokens without a preceding variable assignment - Credentials embedded in URLs - Arguments passed through shell commands or scripts For example, a cron entry such as the following is not adequately redacted: ```cron 0 * * * * curl -H "Authorization: Bearer sensitive-token" https://example.invalid/task ``` The complete command is written to `DEPENDENCIES.md`, then included in the encrypted backup and potentially synchronized to a configured rclone destination. Crontab collection is explicitly opt-in and disabled by default, which reduces likelihood. Nevertheless, the interface and documentation characterize the collected crontab as sanitized, potentially causing users to rely on a protection that is incomplete. ### Attack Path 1. The user's crontab contains an API token, password, authorization header, signed URL, or another secret outside an unquoted `NAME=value` pattern. 2. During setup, the user enables sanitized crontab collection. 3. The backup script executes `crontab -l`. 4. The `sed` expression fails to identify the sensitive argument or only partially redacts it. ...[truncated 736 chars]
Remediation
## Remediation Suggestions 1. Do not archive complete cron command strings. Prefer collecting only non-sensitive metadata such as: - Schedule expressions - Whether entries are enabled - Executable basenames or user-approved labels 2. If commands must be collected, require explicit review and confirmation of the exact generated manifest before staging it. 3. Clearly state that automatic redaction cannot guarantee removal of secrets from arbitrary shell syntax. 4. Consider replacing command text with a stable hash so users can compare configurations without storing the commands. 5. Add tests covering authorization headers, signed URLs, positional tokens, quoted assignments, escaped characters, and shell pipelines. 6. Advise affected users to rotate any credentials stored in collected crontabs and replace previously generated archives.
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (41)

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

High
Category
YARA Match
Content
tegrity, key directories, disk space, encryption readiness, secrets file permissions, cloud remote (if rclone), Ollama models
- Three backup tiers: `minimal` (openclaw.json + secrets manifest), `migratable` (+ full `~/.openclaw` + `DEPENDENCIES.md`), `full` (+ workspace + skills)
- `DEPENDENCIES.md` generated at migratable/full tiers: binary versions, OS info, optional npm globals, ollama models, crontab
- AES256 GPG symmetric encryption
- Retention: keeps last N healthy backups (default 5), prunes older archives automatically
- Optional rclone cloud sync
- Config resolution hierarchy: skill config → env var → auto-detect
- Health report (`HEALTH_REPORT.txt`) embedded in every archive
- Inspired by **simple-backup** (VACInc) and **claw-backup** (vidarbrekke)

---

## [1.3.0] — compaction & verify merge

### Changed
- `verify-backup.sh` eliminated — merged into `healthy-backup.sh` as `--verify [path]` subcommand. Now just 2 scripts total.
- `healthy-backup.sh`: 384 → 259 lines
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Credential Access

High
Category
Privilege Escalation
Content
### Secrets manifest

At all tiers the script reads `openclaw-secrets.env` to extract variable *names* only — values are never written. The resulting `secrets-manifest.txt` lists what environment variables your rig expects, useful for rebuilding on a new machine.

### GPG passphrase
Confidence
71% confidence
Finding
The skill intentionally reads a secrets file and derives a manifest of environment variable names. Although values are not copied, secret names can still disclose integrations, internal systems, auth schemes, or high-value targets, so this is credential-adjacent data exposure rather than direct secret theft.

Credential Access

High
Category
Privilege Escalation
Content
| What | Why | Sensitive? |
|------|-----|-----------|
| `~/.openclaw/openclaw.json` | Load config + stage (scrubbed copy) | Sensitive fields redacted before staging |
| `~/.openclaw/shared/secrets/openclaw-secrets.env` | Extract variable *names* for manifest | Values never written; file never copied |
| `~/.openclaw/credentials/backup.key` | Load encryption password | Read into memory only; file excluded from rsync |
| `~/.openclaw/` (migratable+) | rsync to staging | Secrets paths hard-excluded |
| Workspace + skills dirs (full tier) | rsync to staging | Secrets paths hard-excluded |
Confidence
83% confidence
Finding
The documented runtime behavior reads both the secrets env file and the backup key from disk. Accessing credentials is necessary for a backup tool, but it remains a sensitive capability: compromise of the script or its execution environment would expose encryption material and secret inventory.

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

High
Category
YARA Match
Content
l tier) | rsync to staging | Secrets paths hard-excluded |
| `command -v <bin>` | Check required binaries exist | No |
| `df -m $HOME` | Check available disk space | No |
| `ollama list` (audit + opt-in manifest) | Check models loaded / list for DEPENDENCIES.md | Model names only |
| `npm list -g` (opt-in, default off) | List global packages for DEPENDENCIES.md | Can reveal installed tooling |
| `crontab -l` (opt-in, default off) | List cron jobs for DEPENDENCIES.md | VAR=values redacted before writing |
| `rclone listremotes` (if uploadMode=rclone) | Verify configured remote exists | No |
| `rclone sync` (if uploadMode=rclone) | Upload encrypted archives | Transfers only `*.gpg` files |

No network calls are made by the script itself beyond `rclone` when explicitly configured.

---

| Tier | What's included |
|------|----------------|
| `minimal` | `openclaw.json` + secrets manifest (key names only) |
| `migratable` | Everything in minimal + `~/.openclaw` (secrets excluded) + `DEPENDE
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Credential Access

High
Category
Privilege Escalation
Content
#
# Secrets policy:
#   openclaw.json staged with sensitive field values SCRUBBED (jq walk)
#   shared/secrets/ credentials/ *.key *.pem *.env *.secret never rsynced
#   secrets-manifest: key names only — values never written
#   GPG passphrase: chmod-600 temp file, never on CLI, deleted on EXIT
set -euo pipefail
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#
# Secrets policy:
#   openclaw.json staged with sensitive field values SCRUBBED (jq walk)
#   shared/secrets/ credentials/ *.key *.pem *.env *.secret never rsynced
#   secrets-manifest: key names only — values never written
#   GPG passphrase: chmod-600 temp file, never on CLI, deleted on EXIT
set -euo pipefail
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Chaining Abuse

High
Category
Tool Misuse
Content
# ── Cleanup ───────────────────────────────────────────────────────────────────
STAGING="" ARCHDIR="" PF=""
cleanup() { rm -rf "$STAGING" "$ARCHDIR"; [ -f "$PF" ] && rm -f "$PF"; }
trap cleanup EXIT

# ── Config ────────────────────────────────────────────────────────────────────
Confidence
81% confidence
Finding
The cleanup trap performs 'rm -rf' on variables that are initialized as empty strings and later populated dynamically. Quoting prevents word-splitting, but if either path variable is corrupted, points somewhere unintended, or is attacker-influenced through environment/config interactions or future code changes, the trap can recursively delete arbitrary directories on exit. In a backup utility handling filesystem paths and temp dirs, destructive cleanup logic deserves extra scrutiny.

Credential Access

High
Category
Privilege Escalation
Content
C_NPM=$(cfg  collectNpm     COLLECT_NPM     "false")
C_CRON=$(cfg collectCrontab COLLECT_CRONTAB "false")
C_OLL=$(cfg  collectOllama  COLLECT_OLLAMA  "true")
SECRETS="$OC/shared/secrets/openclaw-secrets.env"

PW="${BACKUP_PASSWORD:-$(cfg password BACKUP_PASSWORD "")}"
KF="$OC/credentials/backup.key"
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
C_NPM=$(cfg  collectNpm     COLLECT_NPM     "false")
C_CRON=$(cfg collectCrontab COLLECT_CRONTAB "false")
C_OLL=$(cfg  collectOllama  COLLECT_OLLAMA  "true")
SECRETS="$OC/shared/secrets/openclaw-secrets.env"

PW="${BACKUP_PASSWORD:-$(cfg password BACKUP_PASSWORD "")}"
KF="$OC/credentials/backup.key"
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
[ -z "$PW" ] && [ -f "$KF" ] && PW=$(tr -d '\n' < "$KF")

EX=(--exclude=shared/secrets/ --exclude=credentials/
    --exclude='*.key' --exclude='*.pem' --exclude='*.env'
    --exclude='*.secret' --exclude='.env'
    --exclude='.git/' --exclude='node_modules/' --exclude='BACKUPS/')
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
[ -z "$PW" ] && [ -f "$KF" ] && PW=$(tr -d '\n' < "$KF")

EX=(--exclude=shared/secrets/ --exclude=credentials/
    --exclude='*.key' --exclude='*.pem' --exclude='*.env'
    --exclude='*.secret' --exclude='.env'
    --exclude='.git/' --exclude='node_modules/' --exclude='BACKUPS/')
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Session Persistence

Medium
Category
Rogue Agent
Content
- Cron default changed from `y` to `n` — explicit opt-in required.

### Changed
- Setup flow restructured into 5 explicit phases: Questions → Write config → Install cron → Dry run → Summary.
- Cron recommended time updated to 03:15 (after Total Recall dream cycle at 03:00).
- Summary always shown regardless of dry-run outcome.
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documents shell execution and installation/cron operations, but it declares no explicit tool scope such as allowed shell usage. This creates a governance gap: an agent or platform cannot enforce least privilege or clearly warn users that the skill requires command execution and filesystem access.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`openclaw.json` is **never copied verbatim**. Before staging, the script uses `jq walk()` to replace the *value* of any field whose name contains `password`, `token`, `secret`, or `key` with `"<redacted>"`. Config structure and all non-sensitive values are preserved. The live file on disk is never modified.

> **Recommendation:** store your backup password in `~/.openclaw/credentials/backup.key` (chmod 600) rather than inline in `openclaw.json`. The health audit will warn if an inline password is detected.

### rsync exclusions — always applied, no config override
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Static analysis

No suspicious patterns detected.