T09 · Insecure Skill Coding Practices
- Location
scripts/subaccount.js:14- Finding
OS Command Injection Through Unvalidated NEAR CLI Arguments
- Content
View full analysis
Vulnerability Details
File Location:
scripts/subaccount.js:14-17,scripts/subaccount.js:27-30,scripts/subaccount.js:43-46, andscripts/subaccount.js:68-69
Vulnerability Type: OS command injection throughchild_process.exec
Risk Level: HighVulnerable Code
js async function createSubaccount(subaccountName, masterAccount) { const subaccountId = `${subaccountName}.${masterAccount}`; const cmd = `near create-account ${subaccountId} --masterAccount ${masterAccount} --initialBalance 0.1 ${networkFlag}`; try { await execAsync(cmd);js async function listSubaccounts(accountId) { const cmd = `near view ${accountId} list_subaccounts ${networkFlag}`; try { const { stdout } = await execAsync(cmd);js async function deleteSubaccount(subaccountId, masterAccount) { const cmd = `near delete-account ${subaccountId} --beneficiaryId ${masterAccount} ${networkFlag}`; try { await execAsync(cmd);js for (const subaccountId of subaccounts) { try { const cmd = `near send ${masterAccount} ${subaccountId} ${amount} ${networkFlag}`; await execAsync(cmd);Technical Analysis
The application builds shell command strings by directly interpolating values from command-line arguments, the
NEAR_ACCOUNTenvironment variable, and an attacker-controllable JSON file. These strings are passed to the promisified form ofchild_process.exec.Unlike an API that executes a program with a separate argument array,
execsends the command string through a system shell. Consequently, shell metacharacters and constructs contained in values such assubaccountName,masterAccount,accountId,subaccountId, oramountare interpreted by the shell rather than treated solely as NEAR CLI arguments.The application does not validate these values against the NEAR account-ID syntax, constrain the amount to a positive decimal number, or ...[truncated 1837 chars]
- Remediation
View remediation
Remediation Suggestions
- Replace
child_process.execwithexecFileorspawn, passing every argument as a distinct array element and keeping shell execution disabled.
js const { execFile } = require('child_process'); const { promisify } = require('util'); const execFileAsync = promisify(execFile); await execFileAsync('near', [ 'create-account', subaccountId, '--masterAccount', masterAccount, '--initialBalance', '0.1', '--networkId', 'testnet' ]);-
Apply the same argument-array approach to the
view,delete-account, andsendoperations. Do not construct a command by concatenating or interpolating user-controlled strings. -
Strictly validate every account identifier, including values from JSON and
NEAR_ACCOUNT, against the exact NEAR account-ID rules. Reject whitespace, shell metacharacters, malformed labels, invalid lengths, and unexpected network suffixes. -
Parse distribution amounts as numeric decimal values and enforce explicit minimum and maximum limits. Convert the validated value back to a canonical decimal string before passing it to the NEAR CLI.
-
Validate the entire distribution document before initiating any transfer. Reject unexpected properties, non-string entries, duplicate accounts, oversized arrays, and invalid destination accounts.
-
Do not treat shell escaping as the primary fix. Correct process invocation with
shell: false, separate arguments, and strict domain validation provides stronger protection. -
Add automated tests using inputs containing spaces, semicolons, quotes, command substitution, redirection operators, and newlines. The tests should verify that malicious input is rejected and never interpreted by a shell.
- Replace
