T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/send-campaign.js:104
- Finding
- Explicit zero send limit defaults to 25 outgoing emails<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send-campaign.js`, lines 26 and 104 **Vulnerability Type**: Improper numeric input validation and unsafe fallback behavior **Risk Level**: Medium ### Vulnerable Code ```js if (process.argv[i] === '--limit') args.limit = parseInt(process.argv[++i]); ``` ```js const limit = Math.min(args.limit || RATE_LIMIT, RATE_LIMIT); ``` ### Technical Analysis The command-line value supplied through `--limit` is parsed without validating that it is a finite, non-negative integer. The resulting value is then evaluated using the logical OR operator: ```js args.limit || RATE_LIMIT ``` A legitimate value of `0` is falsy in JavaScript, so `--limit 0` is replaced with the default `RATE_LIMIT` value of 25. This reverses the likely operator intent: instead of sending no messages, the program can send up to 25 messages. Other malformed values are also handled inconsistently. For example, `parseInt()` can produce `NaN` or accept partially numeric strings without rejecting the complete input. ### Attack Path 1. An operator or automation system invokes the campaign sender with a real lead list, template, Resend API key, and `--limit 0`, intending to disable delivery: ```bash node scripts/send-campaign.js \ --list leads.csv \ --template intro.txt \ --from "hello@example.com" \ --limit 0 ``` 2. `parseInt("0")` assigns the numeric value `0` to `args.limit`. 3. The expression `args.limit || RATE_LIMIT` evaluates to `RATE_LIMIT` because zero is falsy. 4. The effective limit becomes 25. 5. Unless `--dry-run` is also present, the script sends real email through the Resend API. ### Impact Assessment No additional operating-system privileges are obtained. The impact is confined to the campaign sender and its authorized Resend account, but it can cause up to 25 unintended emails per execution. Consequences can include unauthorized outreach, consumption of API quota, sender-domain reputation damag ...[truncated 63 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Distinguish an omitted limit from an explicitly supplied zero and validate the entire input: ```js if (process.argv[i] === '--limit') { const rawLimit = process.argv[++i]; if (!/^\d+$/.test(rawLimit)) { throw new Error('--limit must be a non-negative integer'); } args.limit = Number(rawLimit); } ``` Then apply the default only when the argument is absent: ```js const requestedLimit = args.limit === undefined ? RATE_LIMIT : args.limit; if (!Number.isSafeInteger(requestedLimit) || requestedLimit < 0 || requestedLimit > RATE_LIMIT) { throw new Error(`--limit must be between 0 and ${RATE_LIMIT}`); } const limit = requestedLimit; ``` Additional hardening should include automated tests for omitted, zero, negative, nonnumeric, fractional, and excessively large values. A zero limit should exit without contacting the Resend API. ]]>
