T09 · Insecure Skill Coding Practices
- Location
scripts/nameservice.js:98- Finding
Shell Command Injection Through Registration Arguments
- Content
View full analysis
Vulnerability Details
File Location:
scripts/nameservice.js:98-105
Vulnerability Type: OS command injection through unsafe shell interpolation
Risk Level: HighVulnerable Code
javascript async function registerName(name, accountId) { const fullName = `${name}.near`; const cmd = isTestnet ? `near call ${NAMING_CONTRACT} register '{"account_id":"${fullName}"}' --accountId ${accountId} --networkId testnet` : `near call ${NAMING_CONTRACT} register '{"account_id":"${fullName}"}' --accountId ${accountId}`; try { await execAsync(cmd);Technical Analysis
The
nameandaccountIdvalues originate fromprocess.argv[3],process.argv[4], or theNEAR_ACCOUNTenvironment variable. They are inserted directly into a command string without validation or shell escaping.The command is passed to
child_process.exec(), via its promisifiedexecAsyncwrapper. Unlike argument-based process execution,exec()launches the command through a system shell. Shell metacharacters contained in either user-controlled value can therefore terminate or alter the intendednearcommand and introduce additional commands.accountIdis especially directly exploitable because it is interpolated as an unquoted shell argument. Thenamevalue can also become exploitable by breaking out of the single-quoted JSON argument. This is a coding vulnerability rather than evidence that the package intentionally executes a malicious payload.Attack Path
- An attacker supplies or convinces a user or Agent to use a crafted account ID, either as the fourth CLI argument or through
NEAR_ACCOUNT. - For example, a harmless proof-of-concept account argument could contain
victim.near; touch /tmp/near-name-pwned; #. - The
registerName()function concatenates this value into the shell command. execAsync()invokes the shell, which treats the semicolon as a command separator and the hash cha ...[truncated 1106 chars]
- An attacker supplies or convinces a user or Agent to use a crafted account ID, either as the fourth CLI argument or through
- Remediation
View remediation
Remediation Suggestions
- Replace
exec()withexecFile()orspawn()and pass every command component as a separate argument. Do not enable shell execution.
javascript const { execFile } = require('child_process'); const { promisify } = require('util'); const execFileAsync = promisify(execFile); async function registerName(name, accountId) { validateName(name); validateAccountId(accountId); const fullName = `${name}.near`; const args = [ 'call', NAMING_CONTRACT, 'register', JSON.stringify({ account_id: fullName }), '--accountId', accountId, ]; if (isTestnet) { args.push('--networkId', 'testnet'); } await execFileAsync('near', args, { shell: false, timeout: 120000, }); }-
Validate
nameagainst the exact NEAR naming rules. Reject whitespace, quotes, shell metacharacters, path characters, control characters, and values already containing an unexpected suffix. -
Validate
accountIdagainst the documented NEAR account-ID grammar and impose a reasonable maximum length. Validation should use an allowlist rather than attempting to blacklist individual shell characters. -
Treat environment variables as untrusted input and apply the same validation to
NEAR_ACCOUNTas to command-line arguments. -
Add regression tests containing semicolons, quotes, command substitutions, newlines, and option-like values to confirm they are rejected or passed only as literal process arguments.
-
Consider adding
--where supported by the NEAR CLI to prevent values beginning with a hyphen from being interpreted as additional options.
- Replace
