T09 · Insecure Skill Coding Practices
- Location
agent.js:186- Finding
Local Command Injection Through Unsanitized SSH Key Paths and VM Names
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This appears to be a real Proxmox VM helper, but it handles passwords and generated shell commands unsafely enough that users should review it carefully before installing.
Install only if you are prepared to inspect every generated command before running it on a Proxmox host. Do not reuse passwords with this skill, avoid password SSH where possible, and treat pve-env.md, VM names, key paths, URLs, disk names, and package names as sensitive inputs that could alter generated shell commands. Prefer a revised version that validates inputs, shell-quotes generated commands, avoids printing passwords, and uses safer filesystem/process APIs for local key generation.
agent.js:186Local Command Injection Through Unsanitized SSH Key Paths and VM Names
agent.js:442Command and Configuration Injection in Generated Root-Level Proxmox Commands
agent.js:158Unrestricted User-Controlled URL Fetching Enables SSRF
The instruction to always display generated or user-provided passwords in the final output causes direct credential disclosure into chat logs, terminal scrollback, screenshots, and any downstream observability systems. This is especially dangerous because it normalizes revealing secrets even when the password was user-supplied and may be reused elsewhere.
The security section explicitly mandates embedding the VM password in command output, which guarantees sensitive credential exposure in persistent logs and transcripts. Because the skill also sets ssh_pwauth: true by default, the disclosed secret may immediately enable network login if the VM is reachable.
The skill uses 'env' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.
The skill uses 'network' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.
The generated cleanup command embeds the untrusted vmName directly into a shell rm command without quoting or sanitization. If a user enters shell metacharacters or path traversal sequences in the VM name, the generated command could delete unintended files when executed on the Proxmox host.
lines.push(`# Cleanup: list cloud-init YAML files`);
lines.push(`echo "=== Current cloud-init YAML files ===" && ls -la /var/lib/vz/template/cloud-init/*.yaml 2>/dev/null || echo "(none)"`);
lines.push(`# Review the list above. Delete confirmed files one by one:`);
lines.push(`rm -v /var/lib/vz/template/cloud-init/${vmName}-user-data.yaml`);
lines.push(`# Or remove all stale files (older than 30 days):`);
lines.push(`# ls /var/lib/vz/template/cloud-init/*.yaml`);
lines.push(`# rm -v /var/lib/vz/template/cloud-init/<FILE.yaml> # repeat for each file`);
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).
lines.push(`rm -v /var/lib/vz/template/cloud-init/${vmName}-user-data.yaml`);
lines.push(`# Or remove all stale files (older than 30 days):`);
lines.push(`# ls /var/lib/vz/template/cloud-init/*.yaml`);
lines.push(`# rm -v /var/lib/vz/template/cloud-init/<FILE.yaml> # repeat for each file`);
const output = lines.join('\n');
The skill frames itself as a command-generation-only workflow and explicitly says it must not execute Proxmox commands, but later requires local execution of a generated shell script via exec. Even if the script is intended only for parameter validation, this expands the trust boundary and creates an unnecessary code-execution path from skill-controlled content on the analyst's machine.
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.
18. **VMID auto-detection** — Ask the user to run `pvesh get /cluster/nextid` on the Proxmox node and paste back the result. Use that VMID in all generated commands. Never hardcode a VMID — always get the next ID from the cluster. Add a `# Replace VMID=... if already taken` comment in the output.
19. Build cloud-init user-data YAML (packages, proxy, data disk formatting)
20. **Pre-flight validation** (see Command Pre-flight Validation below) — generate commands internally, validate, fix errors, then present
21. Generate and display the final verified commands in two sections: **Setup commands** (create VM through `qm start`) and **Post-boot cleanup** (delete the snippets YAML — only run after the VM is verified up).
22. Optional: save commands to file
22. Show SSH key path and chmod reminder
The pre-flight section instructs the agent to write and run a local shell script, which is broader authority than needed for a skill whose main function is generating Proxmox commands as text. Any local execution pathway increases the chance of command injection, unintended environment access, or harmful side effects if parameters are mishandled.
The skill requires creating and executing a local pre-flight shell script but does not clearly warn the user that local commands will be run on their machine. This undermines informed consent and can surprise users with filesystem or process activity outside the stated scope of a command-generation helper.
Validation results for node, storage, and bridge are cached on disk under the user's home directory without notice or access control hardening. While the cached data is not highly sensitive, it can disclose infrastructure details and persists longer than users may expect.
The skill generates SSH private keys and writes them to disk automatically, including a fallback that writes placeholder private key material, without explicit user consent or a security warning. Secret material stored in predictable locations can be exposed through weak filesystem permissions, backups, or accidental sharing.
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
await writeFile(priv, `-----BEGIN PRIVATE KEY-----\nPlaceholder for ${uniqueId}\n-----END PRIVATE KEY-----`);
await writeFile(pub, `${keyType} AAAAC3NzaC1lZDI1NTE5AAAAI... placeholder - ${uniqueId}`);
}
await execCmd(`chmod 600 ${priv} ${pub}`);
return { privateKey: priv, publicKey: pub };
}
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
await writeFile(priv, `-----BEGIN PRIVATE KEY-----\nPlaceholder for ${uniqueId}\n-----END PRIVATE KEY-----`);
await writeFile(pub, `${keyType} AAAAC3NzaC1lZDI1NTE5AAAAI... placeholder - ${uniqueId}`);
}
await execCmd(`chmod 600 ${priv} ${pub}`);
return { privateKey: priv, publicKey: pub };
}
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
await writeFile(priv, `-----BEGIN PRIVATE KEY-----\nPlaceholder for ${uniqueId}\n-----END PRIVATE KEY-----`);
await writeFile(pub, `${keyType} AAAAC3NzaC1lZDI1NTE5AAAAI... placeholder - ${uniqueId}`);
}
await execCmd(`chmod 600 ${priv} ${pub}`);
return { privateKey: priv, publicKey: pub };
}
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
await writeFile(priv, `-----BEGIN PRIVATE KEY-----\nPlaceholder for ${uniqueId}\n-----END PRIVATE KEY-----`);
await writeFile(pub, `${keyType} AAAAC3NzaC1lZDI1NTE5AAAAI... placeholder - ${uniqueId}`);
}
await execCmd(`chmod 600 ${priv} ${pub}`);
return { privateKey: priv, publicKey: pub };
}
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
await writeFile(priv, `-----BEGIN PRIVATE KEY-----\nPlaceholder for ${uniqueId}\n-----END PRIVATE KEY-----`);
await writeFile(pub, `${keyType} AAAAC3NzaC1lZDI1NTE5AAAAI... placeholder - ${uniqueId}`);
}
await execCmd(`chmod 600 ${priv} ${pub}`);
return { privateKey: priv, publicKey: pub };
}
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
await writeFile(priv, `-----BEGIN PRIVATE KEY-----\nPlaceholder for ${uniqueId}\n-----END PRIVATE KEY-----`);
await writeFile(pub, `${keyType} AAAAC3NzaC1lZDI1NTE5AAAAI... placeholder - ${uniqueId}`);
}
await execCmd(`chmod 600 ${priv} ${pub}`);
return { privateKey: priv, publicKey: pub };
}
The examples use mkfs.ext4 and append entries to /etc/fstab, which can erase existing data on the target disks and make persistent system changes. The surrounding markdown describes the behavior but does not clearly warn users about the destructive nature of formatting or the need to verify device mappings first.
This markdown file documents qm destroy <VMID>, which deletes a VM, but it does not explicitly warn that the action is destructive and may permanently remove VM data. Under the markdown-file criteria for missing user warnings, destructive operations that can affect user data should be accompanied by a warning.
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.
# Run commands on first boot
runcmd:
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
- echo "Cloud-init complete at $(date)" >> /var/log/cloud-init-complete.log
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.
# Run commands on first boot
runcmd:
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
- echo "Cloud-init complete at $(date)" >> /var/log/cloud-init-complete.log
The runcmd template includes mkfs.ext4 /dev/sdb and mkfs.ext4 /dev/sdc, which are destructive operations executed automatically on first boot. The markdown provides mapping notes, but it lacks an explicit warning that a wrong device selection can permanently erase data and affect system integrity.
Subtle instructions detected that may alter agent decision-making or introduce hidden biases.
## Best Practices
- Always use SSH keys instead of passwords
- Generate unique SSH key per VM
- Store keys in a configurable directory (default: ~/.ssh/pve-builder/)
- Set private key permissions to chmod 600
Declared permissions with no matching code capability may indicate removed functionality or pre-staging for future abuse.