Back to skill

Security audit

Cloud Backup

Security checks for vulnerabilities and agentic risk

Overview

This backup skill is mostly coherent and user-directed, but its implementation can produce plaintext sensitive backups despite claiming forced encryption.

Review before installing. Use only a least-privilege bucket key, keep access keys and passphrases out of openclaw.json, keep the passphrase file owner-only, and do not use --force-plaintext or config.encrypt=false for full/settings backups unless the encryption policy is fixed. Treat prune, restore, and scheduling as high-impact actions and require dry-run output before approving them.

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
scripts/cloud-backup.sh:741
Finding
Mandatory Encryption Can Be Bypassed for Secret-Bearing Backups<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cloud-backup.sh:741-755` and `scripts/cloud-backup.sh:1443-1451` **Vulnerability Type**: Plaintext exposure of sensitive backup data **Risk Level**: High ### Vulnerable Code ```bash decide_encryption() { # $1 mode; sets DO_ENCRYPT + enforces the secrets policy local required=false case "$1" in full) [ "$VERDICT" = "secret-material" ] && required=true ;; settings) required=true ;; # 100% secret material by construction workspace) ;; esac DO_ENCRYPT="$ENCRYPT" [ "$required" = "true" ] && DO_ENCRYPT=true if [ "$1" = "workspace" ] && [ "$NO_ENCRYPT" = "true" ] && [ "$required" != "true" ]; then DO_ENCRYPT=false fi if [ "$DO_ENCRYPT" = "true" ]; then if ! resolve_passphrase "$([ "$DRY_RUN" = "true" ] && echo soft)"; then if [ "$DRY_RUN" = "true" ]; then warn "${PASS_ERROR:-no passphrase configured} — a real run would FAIL (exit $E_PASSPHRASE)" elif [ "$required" = "true" ] && [ "$FORCE_PLAINTEXT" = "true" ] && [ -t 0 ]; then warn "FORCED PLAINTEXT for a secret-material scope (interactive --force-plaintext)" DO_ENCRYPT=false ``` The bypass is exposed through argument parsing: ```bash DRY_RUN=false; EVERYTHING=false; NO_UPLOAD=false; NO_ENCRYPT=false FORCE_PLAINTEXT=false; DEEP=false; ASSUME_YES=false; FORCE=false; IN_PLACE=false ... --force-plaintext) FORCE_PLAINTEXT=true ;; ``` ### Technical Analysis The Skill documentation states that sensitive `full` and `settings` backups force encryption and that plaintext output must be rejected. The implementation contradicts that policy in two ways: 1. An interactive invocation with `--force-plaintext` explicitly changes `DO_ENCRYPT` to `false` after the script has determined that the archive contains secret material. 2. For `full` mode, encryption is considered mandatory only when the heuristic verdict is exactly `secret-material`. If `config.encrypt=false` and the heuristic returns ...[truncated 2328 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--force-plaintext` option and all `FORCE_PLAINTEXT` handling. 2. Make encryption unconditional for `full` and `settings` modes: ```bash case "$1" in full|settings) required=true ;; workspace) required=false ;; esac ``` 3. Reject `config.encrypt=false` for `full` and `settings`, regardless of the heuristic verdict. 4. Permit plaintext output only for an explicitly requested `workspace --no-encrypt` operation. 5. Treat the sensitivity verdict as additional diagnostic information rather than the only enforcement boundary. 6. Add regression tests proving that: - `backup full` cannot create plaintext output; - `backup settings` cannot create plaintext output; - no command-line flag can bypass mandatory encryption; - `config.encrypt=false` is rejected for sensitive modes; - only `workspace --no-encrypt` can intentionally produce plaintext. 7. Remove `--force-plaintext` from usage text and document that sensitive-mode encryption has no override. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cloud-backup.sh:335
Finding
Group-Accessible GPG Passphrase Files Are Accepted<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cloud-backup.sh:335-344` **Vulnerability Type**: Weak credential-file permission enforcement **Risk Level**: Medium ### Vulnerable Code ```bash mode="$(stat -c %a "$PASSFILE" 2>/dev/null || stat -f %Lp "$PASSFILE" 2>/dev/null || echo "")" if [ -n "$mode" ]; then perm=$(( 8#$mode )) if (( perm & 0007 )); then PASS_ERROR="passphrase file is world-readable — refusing. Fix: chmod 600 $PASSFILE" [ "${1:-}" = "soft" ] && return 1 fail "$E_PASSPHRASE" "$PASS_ERROR" elif (( perm & 0070 )); then warn "passphrase file $PASSFILE has mode $mode; expected 600" fi fi PASSPHRASE="$(cat "$PASSFILE")" ``` ### Technical Analysis The script rejects permission bits granted to “other” users but only warns when the group has access. Execution continues and the passphrase is read. Consequently, files with modes such as `0640`, `0660`, or `0670` are accepted even though members of the file’s group may be able to read or alter the encryption key. This contradicts the project’s repeated requirement that the passphrase file be mode `600`. The weakness is particularly significant on multi-user systems, shared administrative hosts, or environments where the file’s group contains service accounts or other operators. A group-readable passphrase defeats backup confidentiality for any group member who can also obtain an encrypted archive. The current check also does not verify that the passphrase file is owned by the effective user or reject symbolic links. Those omissions can further weaken confidence in the credential source. ### Attack Path 1. A passphrase file is created or changed to a group-readable mode such as: ```bash chmod 640 ~/.openclaw/credentials/cloud-backup.passphrase ``` 2. The file’s group includes another local account. 3. The backup script emits a warning but proceeds to use the passphrase. 4. The other group member reads the passphrase file. 5. That account obtains an en ...[truncated 1082 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject all group and other permission bits instead of warning: ```bash if (( perm & 0077 )); then PASS_ERROR="passphrase file permissions are too broad — refusing. Fix: chmod 600 $PASSFILE" [ "${1:-}" = "soft" ] && return 1 fail "$E_PASSPHRASE" "$PASS_ERROR" fi ``` 2. Verify that the passphrase file is owned by the effective user. 3. Reject symbolic links, or open the file using a mechanism that prevents symlink following and checks the opened file descriptor. 4. Require a regular file and reject devices, sockets, FIFOs, and other special files. 5. Consider accepting stricter modes such as `0400` or `0600`, while rejecting any group/other access. 6. Add tests for modes `0400`, `0600`, `0640`, `0660`, `0604`, and `0666`, verifying that only owner-only modes are accepted. 7. Update error messages and documentation to state that owner-only access is enforced rather than merely recommended. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (40)

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
It does not protect against host compromise — a shell on the host already
reads `~/.openclaw` directly. Consequences:

- Host-side mode-600 secret files (`~/.aws/credentials`, the passphrase
  file) are acceptable storage.
- Secrets inside `openclaw.json` are not: **this skill archives that file**,
  so a credential stored there is replicated into every backup it protects —
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
|---|---|---|
| **SQP-1** — activation description broad enough to trigger on generic "backup"/"restore" phrases, on a skill that archives, uploads, mutates config, and schedules | `SKILL.md:3` — "Use when the user says 'backup' …" | Description names OpenClaw state + S3 cloud and requires explicit intent; SKILL.md adds a "When to use — and when not to" section, per-action confirmation gates (config writes, first upload, credential storage, restore, prune, scheduling), and an unattended-runs policy restricting cron payloads to `backup`/`prune` |
| **SQP-2 (cron)** — daily cron job created by default without user opt-in | `SKILL.md:61-75` — "This step should be executed by default unless user asked not to do it" | Scheduling is strictly opt-in: offered once, after the first successful manual backup, with the exact `openclaw cron add` command and full payload shown; never created by default; never re-offered after a decline; the `schedule` subcommand only prints |
| **SQP-2 (credentials)** — provider docs instructed storing long-lived access keys in plaintext OpenClaw config without warnings | `references/providers/aws-s3.md:59-61`, `backblaze-b2.md:28-29`, `digitalocean-spaces.md:27-28`, `cloudflare-r2.md:28-29`, `minio.md:37-38`, `other.md:27-28`, `scripts/cloud-backup.sh:43-47`, and `SKILL.md:33-35` even instructed the agent to write the GPG passphrase into config | All six provider docs lead with least-privilege bucket-scoped keys stored in AWS named profiles (run by the user, outside the chat); the passphrase lives in a chmod-600 file or an OpenClaw SecretRef (`apiKey` + `primaryEnv`); every doc carries an identical "Credential safety" warning block including the amplifier; plaintext config keys still resolve (lowest priority) but emit loud DEPRECATED warnings on every run and are removed in v3 |

Beyond the findings, v2 also fixes two security defects the scanners did not
see:
Confidence
80% confidence
Finding
Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Chaining Abuse

High
Category
Tool Misuse
Content
# --- staging / lock / sweep ------------------------------------------------------
STAGING=""
cleanup_staging() { [ -n "$STAGING" ] && rm -rf "$STAGING"; }

sweep_stale_staging() {
  local d pid
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
size_remote="$(jq -r '.ContentLength // 0' <<<"$head")"
    sha_remote="$(jq -r '.Metadata.sha256 // empty' <<<"$head")"
    if [ "$size_remote" != "$size_local" ] || { [ -n "$sha_remote" ] && [ "$sha_remote" != "$sha" ]; }; then
      s3 rm "s3://$BUCKET/$remote_key" >/dev/null 2>&1 || true
      fail "$E_REMOTE_VERIFY" "remote object mismatch (size $size_remote vs $size_local)"
    fi
  fi
Confidence
95% 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
local n=$(( ${#rk[@]} - KEEP )) i
    info "Remote retention: pruning $n $1 archive(s) (keep $KEEP)"
    for ((i = 0; i < n; i++)); do
      s3 rm "s3://$BUCKET/${rk[$i]}" >/dev/null
      s3 rm "s3://$BUCKET/${rk[$i]}.sha256" >/dev/null 2>&1 || true
    done
  fi
Confidence
95% 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
info "Remote retention: pruning $n $1 archive(s) (keep $KEEP)"
    for ((i = 0; i < n; i++)); do
      s3 rm "s3://$BUCKET/${rk[$i]}" >/dev/null
      s3 rm "s3://$BUCKET/${rk[$i]}.sha256" >/dev/null 2>&1 || true
    done
  fi
Confidence
95% 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
for key in "${rk[@]}"; do
      ts="$(arc_ts "$key")"; [ -n "$ts" ] || continue
      if [ "$ts" -lt "$cutoff" ]; then
        s3 rm "s3://$BUCKET/$key" >/dev/null
        s3 rm "s3://$BUCKET/$key.sha256" >/dev/null 2>&1 || true
      fi
    done
Confidence
95% 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
ts="$(arc_ts "$key")"; [ -n "$ts" ] || continue
      if [ "$ts" -lt "$cutoff" ]; then
        s3 rm "s3://$BUCKET/$key" >/dev/null
        s3 rm "s3://$BUCKET/$key.sha256" >/dev/null 2>&1 || true
      fi
    done
  fi
Confidence
95% 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
95% confidence
Finding
The skill invokes shell commands (`bash ...`, `openclaw`, `aws`, `gpg`) but does not declare any explicit tool scope such as `permissions` or `allowed-tools`. That creates an authorization gap where an agent/runtime may grant broader execution than intended, making accidental or unauthorized command execution easier in a highly sensitive backup/restore workflow.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
| Secret | Home | How |
|---|---|---|
| S3 key pair | AWS named profile (`~/.aws/credentials`) | `aws configure --profile openclaw-backup && chmod 600 ~/.aws/credentials`, then set `config.profile` |
| GPG passphrase | passphrase file | `umask 077 && openssl rand -base64 32 > ~/.openclaw/credentials/cloud-backup.passphrase`, then set `config.passphraseFile` |

## Resolution order (what the script actually does)
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
| Secret | Home | How |
|---|---|---|
| S3 key pair | AWS named profile (`~/.aws/credentials`) | `aws configure --profile openclaw-backup && chmod 600 ~/.aws/credentials`, then set `config.profile` |
| GPG passphrase | passphrase file | `umask 077 && openssl rand -base64 32 > ~/.openclaw/credentials/cloud-backup.passphrase`, then set `config.passphraseFile` |

## Resolution order (what the script actually does)
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
| Secret | Home | How |
|---|---|---|
| S3 key pair | AWS named profile (`~/.aws/credentials`) | `aws configure --profile openclaw-backup && chmod 600 ~/.aws/credentials`, then set `config.profile` |
| GPG passphrase | passphrase file | `umask 077 && openssl rand -base64 32 > ~/.openclaw/credentials/cloud-backup.passphrase`, then set `config.passphraseFile` |

## Resolution order (what the script actually does)
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
3. **OpenClaw secret ref** — `config.passphraseRef` (below). Works from any
   shell; aborts with exit 14 if configured but unresolvable.
4. `config.passphraseFile` — path to a mode-600 file. **Recommended simple
   default.** The script refuses world-readable files and warns on group
   access. The passphrase is passed to gpg over a file descriptor — never on
   a command line (v1 leaked it into `ps`/`/proc/*/cmdline`).
5. DEPRECATED: `skills.entries.cloud-backup.env.GPG_PASSPHRASE` plaintext in
Confidence
70% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Static analysis

No suspicious patterns detected.