Back to skill

Security audit

Cloud Backup [S3, R2, B2, MinIO & more]

Security checks for vulnerabilities and agentic risk

Overview

This backup skill mostly matches its stated purpose, but it contains an under-disclosed plaintext bypass that can publish sensitive OpenClaw backups without encryption.

Review this skill before installing. Its backup and restore flows are mostly explicit and purpose-aligned, but do not rely on its stated guarantee that sensitive backups can never be plaintext until the --force-plaintext path is removed or blocked. Use only bucket-scoped credentials, keep passphrase files owner-only, avoid plaintext OpenClaw config secrets, and treat prune/retention as capable of deleting backup objects.

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:747
Finding
Encryption Policy Bypass Allows Plaintext Secret-Bearing Backups<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cloud-backup.sh:747-750` **Vulnerability Type**: Encryption policy bypass resulting in plaintext sensitive-data storage **Risk Level**: High ### Vulnerable Code ```bash elif [ "$required" = "true" ] && [ "$FORCE_PLAINTEXT" = "true" ] && [ -t 0 ]; then warn "FORCED PLAINTEXT for a secret-material scope (interactive --force-plaintext)" DO_ENCRYPT=false else ``` ### Technical Analysis The script determines that `full` and `settings` backups require encryption when they contain credentials, secret stores, configuration secrets, or other sensitive OpenClaw state. However, when no usable passphrase is available, the `--force-plaintext` option overrides this requirement if standard input is attached to a terminal. Setting `DO_ENCRYPT=false` causes the plaintext archive to continue through the normal backup pipeline. Depending on configuration, it can be published to the local archive directory and uploaded to the configured S3-compatible destination. This behavior contradicts the documented security contract in `SKILL.md`, which states that secret-bearing scopes force encryption and that the script refuses to produce plaintext archives for those scopes. Merely requiring a TTY does not provide meaningful authorization or prevent an operator, automation wrapper with a pseudo-terminal, or misdirected agent invocation from using the bypass. ### Attack Path 1. OpenClaw state contains sensitive material, such as `credentials/`, secret-store files, authentication profiles, or plaintext secrets in `openclaw.json`. 2. No valid GPG passphrase is configured or the configured passphrase cannot be resolved. 3. The script is invoked from a terminal with: ```bash bash scripts/cloud-backup.sh backup full --force-plaintext ``` or: ```bash bash scripts/cloud-backup.sh backup settings --force-plaintext ``` 4. Sensitivity detection marks encryption as required. 5. The override branch sets `DO_E ...[truncated 789 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--force-plaintext` override for all secret-bearing scopes. 2. When `required=true`, fail unconditionally unless a valid encryption passphrase has been resolved. 3. Permit plaintext output only for scopes explicitly designed to be non-sensitive, such as a workspace-only backup that has passed sensitivity checks. 4. If users need a shareable archive, require `config.excludeSecrets=true` and verify that the resulting sensitivity verdict is `refs-only` before allowing encryption to be disabled. 5. Remove `--force-plaintext` from argument parsing and usage text to prevent accidental or undocumented invocation. 6. Add regression tests confirming that `full` and `settings` backups cannot produce or upload `.tar.gz` plaintext artifacts under any combination of TTY state, flags, or missing passphrase conditions. 7. Ensure failed encryption-policy checks clean the staging directory without publishing or uploading any plaintext artifact. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cloud-backup.sh:333
Finding
Group-Readable GPG Passphrase Files Are Accepted<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cloud-backup.sh:333-344` **Vulnerability Type**: Insufficient access-control validation for encryption credentials **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 implementation rejects files with permissions granted to “other” users, but files accessible by the owning group only generate a warning. The script then reads and uses the passphrase normally. Consequently, modes such as `0640`, `0660`, or `0440` are accepted even though group members can read the encryption key. This conflicts with the project’s documented requirement that the passphrase file be mode `0600`. The passphrase protects all archives created with it. Access by one unintended local group member therefore compromises every retained local and remote backup encrypted under that passphrase. The implementation also does not verify that the passphrase file is owned by the effective user. Permission checks alone are insufficient if the file is owned or controlled by another account. ### Attack Path 1. The configured passphrase file has group-readable permissions, such as: ```bash chmod 0640 ~/.openclaw/credentials/cloud-backup.passphrase ``` 2. Another local user belongs to the file’s group. 3. The script warns about the mode but still reads and uses the file. 4. The other group member reads the same passphrase file. 5. The attacker obtains an encrypted archive from the local backup directory, the configured bucket, or an ...[truncated 637 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject any group or other permission bits instead of warning: ```bash if (( perm & 0077 )); then fail "$E_PASSPHRASE" "passphrase file must be accessible only by its owner; fix: chmod 600 $PASSFILE" fi ``` 2. Verify that the file is owned by the effective user before reading it. 3. Fail closed if permissions or ownership cannot be determined. 4. Consider rejecting symbolic links or resolving the canonical path and validating the final file to reduce file-substitution risks. 5. Open the validated file once and read from that descriptor where practical, minimizing the interval between validation and use. 6. Add tests covering modes `0600`, `0400`, `0640`, `0660`, `0604`, and `0666`, and verify that only owner-only modes are accepted. 7. Update status output and documentation so that insecure permissions are reported as a blocking error rather than a warning. ]]>
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
91% confidence
Finding
The skill invokes shell commands (`bash ...`, `aws`, `gpg`) but does not declare an explicit tool scope such as `permissions` or `allowed-tools`. That means an agent/runtime may permit broader command execution than intended or fail to constrain this skill to its minimal required capabilities, increasing the chance of misuse or unsafe invocation in a 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.