T09 · Insecure Skill Coding Practices
- Location
scripts/wallet.mjs:103- Finding
OAuth Device Code Shell Command Injection
- Content
View full analysis
Vulnerability Details
File Location:
scripts/wallet.mjs, lines 103–119
Vulnerability Type: OS command injection through untrusted OAuth response data
Risk Level: HighVulnerable Code:
js // Try to copy code to clipboard let copied = false; try { const { execSync } = await import("node:child_process"); const platform = process.platform; if (platform === "darwin") { execSync(`echo -n ${JSON.stringify(device.userCode)} | pbcopy`); copied = true; } else if (platform === "linux") { execSync(`echo -n ${JSON.stringify(device.userCode)} | xclip -selection clipboard 2>/dev/null || echo -n ${JSON.stringify(device.userCode)} | xsel --clipboard 2>/dev/null`); copied = true; } else if (platform === "win32") { execSync(`echo|set /p=${JSON.stringify(device.userCode)} | clip`); copied = true; } } catch { // Clipboard not available }Technical Analysis
device.userCodeoriginates from the JSON response returned by the configured OAuth authorization endpoint. The value is interpolated directly into a command passed toexecSync(), which invokes a system shell.JSON.stringify()serializes JavaScript data but does not provide shell escaping. In particular, command substitutions such as$(command)remain active inside double-quoted strings on Unix-like systems. Shell metacharacter behavior on Windows likewise differs from JSON escaping and cannot be safely neutralized this way.Consequently, a malicious or compromised OAuth server can return a crafted device code that causes arbitrary local commands to execute when the Skill attempts to copy the code to the clipboard.
Attack Path
- An attacker compromises the OAuth service or causes the process to use an attacker-controlled API endpoint through
SPONGE_API_URL. - The user or Agent runs
node scripts/wallet.mjs login. - The malicious endpoint returns a crafted
userCode, such as ...[truncated 778 chars]
- An attacker compromises the OAuth service or causes the process to use an attacker-controlled API endpoint through
- Remediation
View remediation
Remediation Suggestions
- Eliminate shell interpretation. Use
spawnSync()orexecFileSync()withshell: falseand send the device code through the child process's standard input. - Prefer a maintained clipboard library that accepts a string directly without constructing shell commands.
- Validate
device.userCodeagainst the OAuth provider's documented format, including a strict character allowlist and maximum length. - Treat validation as defense in depth rather than a substitute for removing shell evaluation.
- Keep clipboard copying optional and report failure without falling back to dynamically constructed shell commands.
Example hardening approach:
js import { spawnSync } from "node:child_process"; const code = String(device.userCode); if (!/^[A-Z0-9-]{4,32}$/.test(code)) { throw new Error("OAuth server returned an invalid device code."); } if (process.platform === "darwin") { spawnSync("pbcopy", [], { input: code, encoding: "utf8", shell: false, stdio: ["pipe", "ignore", "ignore"], }); }- Eliminate shell interpretation. Use
