T09 · Insecure Skill Coding Practices
- Location
index.js:272- Finding
Remote API Response Enables Shell Command Injection
- Content
View full analysis
Vulnerability Details
File Location:
index.js:272-291, 430-440
Vulnerability Type: Shell command injection through an untrusted signup session identifier
Risk Level: HighVulnerable Code
The session data is accepted directly from the remote API:
js async function createSignupSession(params) { const payload = { jsonrpc: '2.0', id: 1, method: 'createSignupSession', params: { walletAddress: params.walletAddress, name: params.name, bio: params.bio, specialization: params.specialization, serviceTitle: params.serviceTitle, serviceDescription: params.serviceDescription, price: typeof params.price === 'string' ? parseFloat(params.price) : params.price, currency: params.currency } }; const response = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await response.json(); if (data.error) throw new Error(data.error.message); return data.result; }The remotely supplied
session.sessionIdis then embedded in a shell command:js if (paymentMethod === 'browser') { const paymentUrl = `${BASE_URL}/signup/${session.sessionId}`; console.log(`\n🌐 Opening payment page in browser...\n`); console.log(` URL: ${paymentUrl}\n`); console.log(' Please complete the payment in your browser.\n'); // Try to open in browser (if available) const { exec } = require('child_process'); exec(`open "${paymentUrl}" || xdg-open "${paymentUrl}" || start "${paymentUrl}"`, () => {}); }Technical Analysis
createSignupSession()trusts the JSON-RPC server'sdata.resultobject without validating the format ofsessionId. The value flows intopaymentUrl, which is interpolated into a command executed bychild_process.exec().exec()invokes a system shell. Quotatio ...[truncated 1945 chars]- Remediation
View remediation
Remediation Suggestions
- Do not use
child_process.exec()to open the URL. Usespawn()orexecFile()with shell processing disabled and pass the URL as a separate argument. - Select the executable by platform rather than constructing an
open || xdg-open || startshell expression. - Validate
session.sessionIdbefore use with a strict allowlist matching the server's documented identifier format, such as^[A-Za-z0-9_-]+$, with an explicit maximum length. - Construct the URL with the
URLAPI and encode the identifier as a path component. - Reject malformed API responses by validating the complete response schema, including the expected types and presence of
sessionId. - Restrict
A2A_API_URLto HTTPS and, if custom marketplace instances are not required, allowlist the official host. - Consider a maintained cross-platform browser-opening library that does not expose input to a shell.
Example hardened approach:
js const { spawn } = require('child_process'); if (!/^[A-Za-z0-9_-]{1,128}$/.test(session.sessionId)) { throw new Error('Invalid signup session identifier'); } const paymentUrl = new URL( `/signup/${encodeURIComponent(session.sessionId)}`, BASE_URL ).toString(); if (process.platform === 'darwin') { spawn('open', [paymentUrl], { shell: false, detached: true }); } else if (process.platform === 'win32') { spawn('cmd.exe', ['/d', '/s', '/c', 'start', '', paymentUrl], { shell: false, detached: true }); } else { spawn('xdg-open', [paymentUrl], { shell: false, detached: true }); }- Do not use
