Back to skill

Security audit

Pve Builder

Security checks for vulnerabilities and agentic risk

Overview

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.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
agent.js:186
Finding
Local Command Injection Through Unsanitized SSH Key Paths and VM Names<![CDATA[ ## Vulnerability Details **File Location**: `agent.js:186-199`, with attacker-controlled values accepted at `agent.js:377-382` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```js async function ensureDir(d) { try { await execCmd(`mkdir -p ${d}`); } catch (e) {} } async function genSshKey(vmName, keyDir, keyType = 'ed25519') { const base = path.join(keyDir, vmName); const priv = base, pub = base + '.pub'; try { await execCmd(`ssh-keygen -t ${keyType} -f ${base} -N ''`); } catch (e) { const uniqueId = `${vmName}-${Date.now()}`; 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 affected values are obtained from configuration or interactive input: ```js let sshKeysDir = env['Key Path'] || path.join(process.env.HOME, '.ssh', 'pve-builder'); const customDir = await ask(`17. SSH keys directory (default: ${sshKeysDir}): `); if (customDir && customDir.trim() !== '') sshKeysDir = customDir.trim(); await ensureDir(sshKeysDir); console.log(`Generating SSH key for ${vmName} in ${sshKeysDir}...`); const keys = await genSshKey(vmName, sshKeysDir, env['Key Type'] || 'ed25519'); ``` ### Technical Analysis The SSH key directory, VM name, and configured key type are interpolated directly into shell command strings. `execCmd()` ultimately invokes `child_process.exec()` in its local fallback, which executes its argument through a shell. Neither shell escaping nor strict input validation is applied. Consequently, shell metacharacters, command substitutions, redirections, whitespace, and option-like values can change the meaning of the resulting commands. Path construction with `path.join()` does not make a value safe for shell interpretation. The SSH ...[truncated 1867 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace shell-based directory creation with the Node.js filesystem API: ```js await fs.promises.mkdir(sshKeysDir, { recursive: true, mode: 0o700 }); ``` 2. Replace `child_process.exec()` with `spawn()` or `execFile()` and pass every argument separately: ```js const { execFile } = require('child_process'); execFile('ssh-keygen', [ '-t', validatedKeyType, '-f', base, '-N', '' ], callback); ``` 3. Use `fs.chmod()` for permissions rather than executing `chmod` through a shell. 4. Restrict VM names to an explicit Proxmox-compatible pattern, such as letters, digits, periods, underscores, and hyphens, with a conservative maximum length. 5. Permit only an explicit set of supported key types, such as `ed25519`, rather than accepting arbitrary configuration content. 6. Resolve and normalize the key directory, reject control characters, and optionally constrain it to an approved base directory. 7. Remove the placeholder-key fallback. If key generation fails, abort and report the error without claiming that a valid key pair was created. 8. Set the key directory to mode `0700`, the private key to `0600`, and use an appropriate public-key mode such as `0644`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
agent.js:442
Finding
Command and Configuration Injection in Generated Root-Level Proxmox Commands<![CDATA[ ## Vulnerability Details **File Location**: `agent.js:442-526` **Vulnerability Type**: Generated shell command injection and unsafe configuration serialization **Risk Level**: High ### Vulnerable Code ```js const lines = []; lines.push(`# SSH to the Proxmox node first`); lines.push(`ssh root@${node}`); lines.push(''); lines.push(`# Create cloud-init user-data`); lines.push(`mkdir -p /var/lib/vz/template/cloud-init`); lines.push(`cat <<'EOF' > ${userDataPath}`); lines.push(userData.trim()); lines.push(`EOF`); lines.push(''); lines.push(`# Create VM`); lines.push(`qm create ${vmid} \\`); lines.push(` --name ${vmName} \\`); lines.push(` --memory ${ramMb} \\`); lines.push(` --cores ${cpu} \\`); lines.push(` --sockets ${sockets} \\`); lines.push(` --cpu ${cpuType} \\`); lines.push(` --machine ${machineType} \\`); lines.push(` --bios ${biosType} \\`); lines.push(` --ostype ${osType} \\`); lines.push(` --scsihw ${scsiCtrl} \\`); lines.push(` --onboot ${onboot}`); lines.push(''); lines.push(`# Import cloud image as OS disk`); lines.push(`qm importdisk ${vmid} ${imagePath} ${storageForCmd} --format raw`); lines.push(`qm set ${vmid} --scsi0 ${storageForCmd}:vm-${vmid}-disk-0,discard=on,ssd=1`); lines.push(`qm resize ${vmid} --scsi0 ${osDisk}`); lines.push(''); lines.push(`# Network`); lines.push(`qm set ${vmid} --net0 ${netIface},bridge=${selBridge}${vlan ? `,tag=${vlan}` : ''}`); lines.push(''); lines.push(`# Cloud-init`); lines.push(`qm set ${vmid} --ide0 ${storageForCmd}:cloudinit`); if (useDhcp) { lines.push(`qm set ${vmid} --ipconfig0 ip=dhcp`); } else { const ipStr = `ip=${staticIp.address}`; const gwStr = staticIp.gw ? `,gw=${staticIp.gw}` : ''; lines.push(`qm set ${vmid} --ipconfig0 ${ipStr}${gwStr}`); const dnsList = staticIp.dns.split(',').filter(Boolean); lines.push(`qm set ${vmid} --nameserver ${dnsList.join(' ')}`); } lines.push(`qm set ${vmid} --ciuser ${sshUser}`); lines.push(`qm set ${vmid} --sshkeys "${pubKeyContent}"`); ...[truncated 3910 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define strict validation rules for every parameter before generating output: - VMID: digits only and within the valid Proxmox range. - CPU, sockets, RAM, disk sizes, and VLAN: numeric validation with explicit ranges. - Node, storage, bridge, VM name, username, and interface types: conservative allowlists. - IP addresses and CIDR values: parse with a dedicated IP-address library. - Image and output paths: reject control characters and normalize paths. 2. Shell-quote every generated argument with a well-tested POSIX shell-escaping routine. Do not rely on visual formatting or simple quotation marks. 3. Reject carriage returns, line feeds, null bytes, command substitutions, and other control characters in all values intended to occupy a single shell argument. 4. Generate cloud-init with a maintained YAML serializer instead of string concatenation. Ensure scalar values are encoded according to YAML rules. 5. Validate package names against the distribution's expected package-name syntax. Do not allow package fields to introduce additional YAML entries. 6. Treat `pve-env.md` as untrusted input even when it is local. Validate all parsed values before use and recommend mode `0600`. 7. Present a structured parameter summary and a command-by-command review before emitting the final root script. 8. Correct the implementation to match the declared safe workflow: - Use `/var/lib/vz/snippets/`. - Use a Proxmox storage reference such as `user=local:snippets/<file>`. - Keep cleanup in a separate post-boot section. - Do not remove the cloud-init file before successful first boot. 9. Add automated tests containing spaces, quotes, semicolons, newlines, command substitutions, leading dashes, and malformed network values to verify that generated output cannot change command structure. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
agent.js:158
Finding
Unrestricted User-Controlled URL Fetching Enables SSRF<![CDATA[ ## Vulnerability Details **File Location**: `agent.js:158-162` and `agent.js:261-268` **Vulnerability Type**: Server-side request forgery **Risk Level**: Medium ### Vulnerable Code ```js async function webFetch(url, opts = {}) { if (typeof openclaw !== 'undefined' && openclaw.web_fetch) return await openclaw.web_fetch({ url, ...opts }); console.log(`[WEB_FETCH] ${url}`); return { content: '' }; } ``` The URL is accepted and fetched as follows: ```js } else if (swInput.startsWith('http')) { infoUrl = swInput.trim(); const derived = infoUrl.split('/').pop().replace(/\.[^.]+$/, '').replace(/[-_]/g, ' '); console.log(`Fetching URL to extract specs (derived: "${derived}")...`); try { const fetched = await webFetch(infoUrl, { maxChars: 20000, extractMode: 'text' }); const txt = fetched.content || ''; ``` ### Technical Analysis Any input whose string begins with `http` is passed to the `openclaw.web_fetch` capability. The code does not parse the URL with a strict URL parser or validate its scheme, hostname, resolved address, port, redirect destination, or network range. An attacker can therefore direct the fetch capability toward loopback services, private network addresses, link-local endpoints, or cloud metadata services if those destinations are reachable from the fetch execution environment. DNS rebinding and redirects may also bypass hostname-only controls unless validation is repeated after resolution and at every redirect. The request is intended to retrieve public software documentation, so unrestricted access to internal destinations exceeds the minimum network access required for the declared functionality. No code was found that automatically transmits fetched internal content to an attacker. However, the response is processed and some derived values are printed, and the request itself can be used to probe reachable services. ### Attack Path 1. An attacker supplies or recommends an HTTP or HTTPS URL as the software ...[truncated 1265 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse URLs with the standard `URL` API and permit only the `https:` scheme unless plain HTTP is explicitly necessary. 2. Resolve the hostname before the request and reject all loopback, private, carrier-grade NAT, link-local, multicast, reserved, and unspecified IPv4 and IPv6 ranges. 3. Explicitly block cloud metadata destinations, including link-local metadata addresses and provider-specific metadata hostnames. 4. Restrict ports to an approved set, normally TCP 443 and optionally TCP 80. 5. Disable redirects where possible. If redirects are required, repeat scheme, hostname, port, DNS, and address-range validation for every redirect target. 6. Protect against DNS rebinding by connecting only to the validated resolved address while preserving the intended TLS server name, or use a network-layer egress policy. 7. Apply response-size and timeout limits in addition to the existing `maxChars` processing limit. 8. Require explicit confirmation before fetching a hostname outside an approved public-domain policy, and display the normalized destination to the user. 9. Prefer search-provider results or a maintained allowlist of official software documentation domains for requirements lookup. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (28)

Ssd 3

High
Confidence
99% confidence
Finding
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.

Ssd 3

High
Confidence
99% confidence
Finding
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.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding
The skill uses 'env' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding
The skill uses 'network' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
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`);
Confidence
95% confidence
Finding
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.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
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');
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).

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
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
Confidence
60% 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.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
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 };
}
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
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 };
}
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
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 };
}
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
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 };
}
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
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 };
}
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
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 };
}
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
# 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
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.

Session Persistence

Medium
Category
Rogue Agent
Content
# 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
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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
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.

Behavior Manipulation

Medium
Category
Prompt Injection
Content
## 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
Confidence
70% confidence
Finding
Subtle instructions detected that may alter agent decision-making or introduce hidden biases.

Lp4

Low
Category
MCP Least Privilege
Confidence
65% confidence
Finding
Declared permissions with no matching code capability may indicate removed functionality or pre-staging for future abuse.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
agent.js:146