T09 · Insecure Skill Coding Practices
- Location
scripts/ssh-run.sh:124- Finding
Local Command Injection Through Unsafe Evaluation of SSH Configuration Values
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This SSH skill has a coherent purpose, but it needs review because its private-key and sudo-password handling can expose credentials or enable command injection.
Install only if you are comfortable reviewing and hardening the scripts first. Prefer the base key-only edition, avoid password and sudo modes unless necessary, do not export private keys into environment variables, pin dependencies, and treat vault-backed key storage and sudo execution as high-risk until the argument-exposure and command-injection issues are fixed.
scripts/ssh-run.sh:124Local Command Injection Through Unsafe Evaluation of SSH Configuration Values
scripts/ssh-run-native.sh:378OpenSSH Option Injection Through an Unvalidated Target Argument
scripts/ssh-keys.sh:93Private SSH Key Disclosure Through Vault Resolver Process Arguments
scripts/ssh-run-native.sh:351Sudo Password Exposure and Remote Shell Injection Through Password Interpolation
scripts/ssh-client.py:27Unpinned Paramiko Installation and User-Writable Import-Path Precedence
references/server-to-server-rsync.md:103Deprecated Workflow Still Stages a Private SSH Key on a Remote Host
The example export SSH_KEY="$(cat ~/.ssh/id_rsa)" loads raw private key material into an environment variable, greatly expanding exposure beyond the file itself. Environment variables are commonly inherited by child processes and may be captured by debugging tools, crash reports, agent telemetry, or CI logs, making this a serious credential-handling flaw.
# No vault — use env vars or key files:
export SSH_KEY="$(cat ~/.ssh/id_rsa)"
ssh-run.sh --host my-server --user ubuntu --key ~/.ssh/id_rsa -- 'uptime'
# Or with password (requires SSH_EXECUTOR_ALLOW_DANGEROUS=1):
Referenced artifact was not completely inspected
| `terminal` | Bash scripts (`ssh-run.sh`, `ssh-keys.sh`, `ssh-run-native.sh`) | SSH command execution |
Referenced artifact was not completely inspected
| `terminal` | Bash scripts (`ssh-run.sh`, `ssh-keys.sh`, `ssh-run-native.sh`) | SSH command execution |
The documented pattern chains echo into sudo -S, normalizing shell-pipeline secret injection for privileged execution. Chaining itself is not the core issue, but in this context it compounds credential exposure risk and can make review/auditing of privileged command construction harder.
**Security:**
- Password resolved in RAM, never on disk
- Sent via `echo '<pass>' | sudo -S -p ''` — password never appears on the command line (`ps`)
- `-p ''` suppresses sudo password prompt (avoids stderr pollution)
- Original command is base64-encoded to avoid quote escaping issues
- `--sudo` automatically enables `--confirm-dangerous` (no extra flag needed)
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
The skill is split into two editions, built from the same source via `build.sh`:
| Edition | Package | Auth | Sudo | Lines |
|---------|---------|------|------|-------|
| **Base** | `ssh-executor-<ver>.zip` | Key only | No | 270 |
| **Full** | `ssh-executor-full-<ver>.zip` | Key + password | Yes | 481 |
Potential security issue detected. Manual review is recommended.
Compare the process network namespace with the host's:
host_ns=$(readlink /proc/1/ns/net)
container_ns=$(readlink /proc/<PID>/ns/net)
if [ "$host_ns" = "$container_ns" ]; then
echo "network_mode: host"
Potential security issue detected. Manual review is recommended.
Diagnosis performed:
1. `cat /proc/<PID>/cmdline` → confirmed mysqld with MariaDB args
2. `readlink /proc/<PID>/ns/net == readlink /proc/1/ns/net` → network_mode host
3. `ss -tlnp` → **zero** port 3306 on any IP
4. `cat /etc/mysql/mysql.conf.d/mysqld.cnf` → bind-address = 127.0.0.1 on host
(but this is the host config, not the container's)
Potential security issue detected. Manual review is recommended.
| Problem | Cause | Solution |
|----------|-------|---------|
| `ls /proc/PID/fd/` empty | PID runs as different UID (e.g. 999 = mysql) | Try `sudo ls` or use other techniques |
| `nsenter` access denied | No CAP_SYS_ADMIN permission | Do not use nsenter without sudo |
| `ip neigh` shows FAILED | Container with network_mode host (no dedicated IP) | Container shares host IP; look for port on host |
| Bridge linkdown but IP configured | Docker network without containers (created but unused) | Check which bridge has REACHABLE/STALE traffic |
| `docker logs` unavailable | No docker CLI access | Only option: `journalctl` or process logs in datadir |
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
- deletes, rotates, or truncates data (`truncate`, `dd`, logrotate actions)
- changes containers, databases, firewalls, or network state (`docker rm|down|kill`, `kubectl delete`, `iptables`, `ufw`, `firewall-cmd`, `ip link set`, `ip addr add|del`, `nmcli`)
- writes to disk or pipes output to a file (`>`, `>>`, `| tee`, `dd`)
- executes code on the remote host that was not explicitly reviewed (`curl | bash`, `wget -O- | sh`, `eval`, `source`)
**When in doubt, treat the command as dangerous and ask for confirmation.**
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
- deletes, rotates, or truncates data (`truncate`, `dd`, logrotate actions)
- changes containers, databases, firewalls, or network state (`docker rm|down|kill`, `kubectl delete`, `iptables`, `ufw`, `firewall-cmd`, `ip link set`, `ip addr add|del`, `nmcli`)
- writes to disk or pipes output to a file (`>`, `>>`, `| tee`, `dd`)
- executes code on the remote host that was not explicitly reviewed (`curl | bash`, `wget -O- | sh`, `eval`, `source`)
**When in doubt, treat the command as dangerous and ask for confirmation.**
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
| 2 | Credential Mismanagement | 99% | Header forbids passwords but docs instruct storage | Header clarified: "passwords for automation may be stored in Vaultwarden with explicit approval" |
| 3 | Key Exfiltration | 99% | Rsync workflow copies private key to remote server | server-to-server-rsync.md deprecated; ForwardAgent preferred |
| 4 | Host-Key Bypass | 99% | AutoAddPolicy in Python, accept-new in native | Default RejectPolicy (Python), StrictHostKeyChecking=yes (native), accept-new removed |
| 5 | Destructive Commands | 94% | sudo find ... -delete without confirmation | Warning banner in remote-backup-cleanup.md |
| 6 | Undeclared MCP | 92% | Shell, file, env-var access undeclared | MCP Permissions Declaration section added |
## Findings Fixed in v2.2.1 (Medium Severity)
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
| 2 | Credential Mismanagement | 99% | Header forbids passwords but docs instruct storage | Header clarified: "passwords for automation may be stored in Vaultwarden with explicit approval" |
| 3 | Key Exfiltration | 99% | Rsync workflow copies private key to remote server | server-to-server-rsync.md deprecated; ForwardAgent preferred |
| 4 | Host-Key Bypass | 99% | AutoAddPolicy in Python, accept-new in native | Default RejectPolicy (Python), StrictHostKeyChecking=yes (native), accept-new removed |
| 5 | Destructive Commands | 94% | sudo find ... -delete without confirmation | Warning banner in remote-backup-cleanup.md |
| 6 | Undeclared MCP | 92% | Shell, file, env-var access undeclared | MCP Permissions Declaration section added |
## Findings Fixed in v2.2.1 (Medium Severity)
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.
|---|----------|-----------|---------|-----|
| 7 | Temp Key Cleanup | 93-95% | trap EXIT only, kill -9 bypasses | trap EXIT INT TERM HUP + shred -u |
| 8 | restore-to-file Path | 92% | Writes to any caller-specified path | Path validation: rejects /etc, /boot, /sys, /proc, /dev, /run; warns non-tmp |
| 9 | Missing User Warning | — | No warning on --host-key-checking no | Explicit stderr MITM warning |
| 10 | Pass-through Bug | — | --host-key-checking ignored by Python backend | ssh-run.sh now captures and passes to ssh-client.py |
| 11 | SSL/TLS | — | check_hostname=False without caveat | "Only safe on trusted local networks" |
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.
| 8 | restore-to-file Path | 92% | Writes to any caller-specified path | Path validation: rejects /etc, /boot, /sys, /proc, /dev, /run; warns non-tmp |
| 9 | Missing User Warning | — | No warning on --host-key-checking no | Explicit stderr MITM warning |
| 10 | Pass-through Bug | — | --host-key-checking ignored by Python backend | ssh-run.sh now captures and passes to ssh-client.py |
| 11 | SSL/TLS | — | check_hostname=False without caveat | "Only safe on trusted local networks" |
## Confirmed Safe (No Action Needed)
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).
ssh -p <PORT> user@server-b 'touch /path/dest/.test_write && rm /path/dest/.test_write' 2>&1
sudo mkdir -p /path/dest/ (some servers have NOPASSWD)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).
/tmp/staging/ user@server-b:/path/dest/
rm -rf /tmp/staging
⚠️ **Downside:** all traffic passes through the client twice.
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).
/tmp/staging/ user@server-b:/path/dest/
rm -rf /tmp/staging
⚠️ **Downside:** all traffic passes through the client twice.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## 1. UserKnownHostsFile /dev/null Nullifies StrictHostKeyChecking
**Symptom:** `Host key verification failed` (exit 255) even though the host key is in `~/.ssh/known_hosts`.
**Root cause:** `~/.ssh/config` has `UserKnownHostsFile /dev/null` — SSH effectively has no trust store. When our skill sets `StrictHostKeyChecking=yes` (hardening v2.2.0), strict checking fails because there's nothing to check against.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## 1. UserKnownHostsFile /dev/null Nullifies StrictHostKeyChecking
**Symptom:** `Host key verification failed` (exit 255) even though the host key is in `~/.ssh/known_hosts`.
**Root cause:** `~/.ssh/config` has `UserKnownHostsFile /dev/null` — SSH effectively has no trust store. When our skill sets `StrictHostKeyChecking=yes` (hardening v2.2.0), strict checking fails because there's nothing to check against.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## 1. UserKnownHostsFile /dev/null Nullifies StrictHostKeyChecking
**Symptom:** `Host key verification failed` (exit 255) even though the host key is in `~/.ssh/known_hosts`.
**Root cause:** `~/.ssh/config` has `UserKnownHostsFile /dev/null` — SSH effectively has no trust store. When our skill sets `StrictHostKeyChecking=yes` (hardening v2.2.0), strict checking fails because there's nothing to check against.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## 1. UserKnownHostsFile /dev/null Nullifies StrictHostKeyChecking
**Symptom:** `Host key verification failed` (exit 255) even though the host key is in `~/.ssh/known_hosts`.
**Root cause:** `~/.ssh/config` has `UserKnownHostsFile /dev/null` — SSH effectively has no trust store. When our skill sets `StrictHostKeyChecking=yes` (hardening v2.2.0), strict checking fails because there's nothing to check against.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## 1. UserKnownHostsFile /dev/null Nullifies StrictHostKeyChecking
**Symptom:** `Host key verification failed` (exit 255) even though the host key is in `~/.ssh/known_hosts`.
**Root cause:** `~/.ssh/config` has `UserKnownHostsFile /dev/null` — SSH effectively has no trust store. When our skill sets `StrictHostKeyChecking=yes` (hardening v2.2.0), strict checking fails because there's nothing to check against.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## 1. UserKnownHostsFile /dev/null Nullifies StrictHostKeyChecking
**Symptom:** `Host key verification failed` (exit 255) even though the host key is in `~/.ssh/known_hosts`.
**Root cause:** `~/.ssh/config` has `UserKnownHostsFile /dev/null` — SSH effectively has no trust store. When our skill sets `StrictHostKeyChecking=yes` (hardening v2.2.0), strict checking fails because there's nothing to check against.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## 1. UserKnownHostsFile /dev/null Nullifies StrictHostKeyChecking
**Symptom:** `Host key verification failed` (exit 255) even though the host key is in `~/.ssh/known_hosts`.
**Root cause:** `~/.ssh/config` has `UserKnownHostsFile /dev/null` — SSH effectively has no trust store. When our skill sets `StrictHostKeyChecking=yes` (hardening v2.2.0), strict checking fails because there's nothing to check against.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Fix applied: In ssh-run-native.sh, when HOST_KEY_CHECKING=yes, the script now forces:
-o UserKnownHostsFile=${HOME}/.ssh/known_hosts
This overrides the /dev/null from the SSH config and points to the real known_hosts file. A touch ensures the file exists (SSH refuses nonexistent UserKnownHostsFile paths).
No suspicious patterns detected.