Back to skill

Security audit

Restic Home Backup (Safe Apply Mode)

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent restic backup helper, but its root-level setup script has unsafe configuration handling that could lead to command execution as root.

Review carefully before installing. Use only plan mode until the bootstrap script validates and safely quotes all inputs, avoids sourcing config as shell code, creates password files with restrictive permissions from the start, and adds least-privilege systemd settings.

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/bootstrap_restic_home.sh:98
Finding
Root Command Injection Through Shell-Sourced Configuration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/bootstrap_restic_home.sh:27-31, 98-103, 109, 116, 123, 215-216` **Vulnerability Type**: Shell command injection through an unsafe generated configuration file **Risk Level**: High ### Vulnerable Code ```bash --repo) REPO="$2"; shift 2 ;; --password-file) PASS_FILE="$2"; shift 2 ;; --timezone) TIMEZONE="$2"; shift 2 ;; ``` ```bash cat >/etc/restic-home.env <<EOF RESTIC_REPOSITORY=${REPO} RESTIC_PASSWORD_FILE=${PASS_FILE} BACKUP_SOURCE=${HOME_DIR} EXCLUDES_FILE=/etc/restic-home/excludes.txt EOF chmod 600 /etc/restic-home.env ``` The generated scripts later interpret this file as shell code: ```bash #!/usr/bin/env bash set -euo pipefail source /etc/restic-home.env exec /usr/bin/restic backup "$BACKUP_SOURCE" --exclude-file "$EXCLUDES_FILE" ``` The repository initialization path also sources it directly: ```bash if [[ "$INIT_REPO" == "yes" ]]; then source /etc/restic-home.env if ! /usr/bin/restic snapshots >/dev/null 2>&1; then /usr/bin/restic init fi fi ``` ### Technical Analysis The values accepted through `--repo`, `--password-file`, and indirectly `--user` are inserted into `/etc/restic-home.env` without escaping or validation. Although systemd supports an `EnvironmentFile` format, the generated backup, prune, and check scripts do not parse the file strictly as systemd environment data. They execute: ```bash source /etc/restic-home.env ``` Consequently, the file is interpreted as shell code. An attacker-controlled argument containing a newline can append a new shell statement to the generated file. Shell metacharacters placed in a separate injected line will execute when the file is sourced. The bootstrap script is intended to be run with elevated privileges because it writes to `/etc`, `/usr/local/bin`, and `/etc/systemd/system`. The generated systemd services do not specify a less-privileged `User=`, so they run as root by default. This converts configuration injection into ...[truncated 1673 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not execute configuration files with `source`. Treat configuration as data rather than shell code. 2. Store values in a parser-safe format and load them with a parser that does not evaluate shell expressions. 3. Alternatively, pass fixed, validated values directly to the generated scripts using safely quoted arguments. 4. Reject carriage returns, newlines, NUL-equivalent input, and other control characters in every command-line value. 5. Validate `--user` against the system account database rather than constructing `/home/${USER_NAME}` directly. 6. Validate the repository according to an explicit allowlist of supported restic repository formats and transport schemes. 7. Require `--password-file` to be an absolute path under an approved directory, such as `/etc/restic-home/`. 8. If a systemd `EnvironmentFile` remains necessary, generate it using correct systemd escaping and ensure no shell script sources it. 9. Add explicit least-privilege service settings where supported. At minimum, consider: - `User=` and `Group=` appropriate for the backup source - `NoNewPrivileges=true` - `PrivateTmp=true` - `ProtectSystem=strict` - Narrow `ReadWritePaths=` entries for locations that genuinely require writes 10. Add tests using values containing newlines, quotes, semicolons, command substitutions, and shell redirection to verify that no supplied value can become executable syntax. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/bootstrap_restic_home.sh:79
Finding
Password Generation Fallback Can Leave Repository Credentials Exposed<![CDATA[ ## Vulnerability Details **File Location**: `scripts/bootstrap_restic_home.sh:79-85` **Vulnerability Type**: Insecure secret-file creation and error handling **Risk Level**: Medium ### Vulnerable Code ```bash if command -v openssl >/dev/null 2>&1; then openssl rand -base64 48 > "$PASS_FILE" else tr -dc 'A-Za-z0-9!@#$%^&*()-_=+[]{}:,.?' </dev/urandom | head -c 64 > "$PASS_FILE" echo >> "$PASS_FILE" fi chmod 600 "$PASS_FILE" ``` ### Technical Analysis The script globally enables both `set -e` and `set -o pipefail`. In the fallback password-generation pipeline, `head -c 64` exits after receiving the required number of bytes. The upstream `tr` process may then receive `SIGPIPE` when it attempts to write additional output. With `pipefail` enabled, the pipeline can return a nonzero status because of the terminated `tr` process. With `set -e` enabled, the script can exit immediately before reaching: ```bash chmod 600 "$PASS_FILE" ``` The output redirection creates the password file before the pipeline starts. Its initial permissions therefore depend on the invoking process's umask. No restrictive `umask` is set before the secret is created. Under a common umask such as `0022`, the file may initially be created with permissions that allow other local users to read it. The risk is greater because `--password-file` is configurable and can identify a location outside the default `/etc/restic-home/` directory. ### Attack Path 1. The bootstrap script runs on a system where `openssl` is unavailable. 2. The script enters the `tr | head` fallback path. 3. Redirection creates the password file using permissions derived from the current umask. 4. `head` exits after reading 64 bytes. 5. `tr` receives `SIGPIPE`, causing the pipeline to return a nonzero status under `pipefail`. 6. `set -e` terminates the script before `chmod 600` executes. 7. A local user reads the partially or fully generated restic password if the file was created with permissive mode b ...[truncated 781 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Set a restrictive umask before creating any secret: ```bash umask 077 ``` 2. Create the password file atomically with mode `0600`, rather than creating it permissively and tightening permissions afterward. 3. Use a temporary file in the destination directory, set its mode before writing sensitive content, and atomically rename it into place after successful generation. 4. Replace the SIGPIPE-sensitive fallback pipeline with an implementation whose expected termination behavior is explicitly handled. 5. Verify that password generation completed successfully and produced the required length before installing the final file. 6. Reject symbolic links and non-regular existing targets before writing or changing permissions. 7. Restrict custom password paths to an approved root-owned directory and require an absolute canonical path. 8. Ensure parent directories are root-owned and inaccessible to untrusted users. 9. On any generation failure, securely remove the incomplete temporary file and return a clear error. 10. Add a regression test that runs the fallback path under `set -euo pipefail` and confirms that the final file always has mode `0600`. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (14)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill clearly contemplates shell-driven system changes, credential file creation, and systemd unit installation, but it declares no explicit tool scope or permissions boundary. That omission makes it easier for an agent runtime to overgrant execution capability or for reviewers to miss that the skill can modify sensitive paths like /etc and /usr/local/bin.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- Never print secrets or tokens in chat/log output.
  - Never delete snapshots/repositories without explicit user confirmation.
  - Never weaken permissions on credential files (`chmod 600` minimum).
  - Never claim backup success without checking command exit status and snapshot listing.
  - Never apply system changes implicitly: require explicit `--apply` (or explicit user confirmation) before writing to `/etc`, `/usr/local/bin`, or `/etc/systemd/system`.

## Workflow
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The publication workflow introduces unrelated packaging and registry actions into a backup-operation skill, expanding its operational scope beyond what a user would reasonably expect. This can cause an agent to perform supply-chain or external publication steps in contexts where the user only asked for local backup configuration, increasing the chance of unintended outbound actions or data exposure.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
### Wrong password
Symptoms: `wrong password or no key found`
- Confirm `/etc/restic-home.env` points to correct `RESTIC_PASSWORD_FILE`.
- Confirm password file permissions (`chmod 600`).

### Repo unreachable
Symptoms: timeout / connection refused
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
### Wrong password
Symptoms: `wrong password or no key found`
- Confirm `/etc/restic-home.env` points to correct `RESTIC_PASSWORD_FILE`.
- Confirm password file permissions (`chmod 600`).

### Repo unreachable
Symptoms: timeout / connection refused
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
### Wrong password
Symptoms: `wrong password or no key found`
- Confirm `/etc/restic-home.env` points to correct `RESTIC_PASSWORD_FILE`.
- Confirm password file permissions (`chmod 600`).

### Repo unreachable
Symptoms: timeout / connection refused
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
### Wrong password
Symptoms: `wrong password or no key found`
- Confirm `/etc/restic-home.env` points to correct `RESTIC_PASSWORD_FILE`.
- Confirm password file permissions (`chmod 600`).

### Repo unreachable
Symptoms: timeout / connection refused
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
### Wrong password
Symptoms: `wrong password or no key found`
- Confirm `/etc/restic-home.env` points to correct `RESTIC_PASSWORD_FILE`.
- Confirm password file permissions (`chmod 600`).

### Repo unreachable
Symptoms: timeout / connection refused
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
#   bash bootstrap_restic_home.sh --user pi --repo /mnt/backup/restic-home
#
# Apply changes:
#   sudo bash bootstrap_restic_home.sh --user pi --repo /mnt/backup/restic-home --apply
#
# Optional:
#   --password-file /etc/restic-home/password
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
#   bash bootstrap_restic_home.sh --user pi --repo /mnt/backup/restic-home
#
# Apply changes:
#   sudo bash bootstrap_restic_home.sh --user pi --repo /mnt/backup/restic-home --apply
#
# Optional:
#   --password-file /etc/restic-home/password
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
source /etc/restic-home.env
exec /usr/bin/restic backup "$BACKUP_SOURCE" --exclude-file "$EXCLUDES_FILE"
EOF
chmod 755 /usr/local/bin/restic-home-backup.sh

cat >/usr/local/bin/restic-home-prune.sh <<'EOF'
#!/usr/bin/env bash
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
source /etc/restic-home.env
exec /usr/bin/restic backup "$BACKUP_SOURCE" --exclude-file "$EXCLUDES_FILE"
EOF
chmod 755 /usr/local/bin/restic-home-backup.sh

cat >/usr/local/bin/restic-home-prune.sh <<'EOF'
#!/usr/bin/env bash
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
source /etc/restic-home.env
exec /usr/bin/restic backup "$BACKUP_SOURCE" --exclude-file "$EXCLUDES_FILE"
EOF
chmod 755 /usr/local/bin/restic-home-backup.sh

cat >/usr/local/bin/restic-home-prune.sh <<'EOF'
#!/usr/bin/env bash
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
systemctl daemon-reload

if [[ "$ENABLE_TIMERS" == "yes" ]]; then
  systemctl enable --now restic-home-backup.timer restic-home-prune.timer restic-home-check.timer
fi

if [[ "$INIT_REPO" == "yes" ]]; then
Confidence
80% 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.

Static analysis

No suspicious patterns detected.