T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/import-csv.js:60
- Finding
- Email Verification Fails Open on Hunter API Errors<![CDATA[ ## Vulnerability Details **File Location**: `scripts/import-csv.js:60-69` **Vulnerability Type**: Fail-open validation caused by improper HTTP error handling **Risk Level**: Medium ### Complete Code Snippet ```js async function verifyEmail(email) { if (!email || !email.includes("@")) return false; const res = await request({ hostname: "api.hunter.io", path: `/v2/email-verifier?email=${encodeURIComponent(email)}&api_key=${config.hunter.apiKey}`, method: "GET", }); if (res.status !== 200) return true; const status = res.body?.data?.status; return ["valid", "accept_all"].includes(status); } ``` ### Technical Analysis The verification function treats every non-200 HTTP response from Hunter as successful verification: ```js if (res.status !== 200) return true; ``` Consequently, authentication failures, authorization errors, rate limiting, malformed requests, upstream server failures, and service outages all cause the submitted email address to be classified as verified. Network-level errors emitted by `https.request` are rejected separately, but completed HTTP responses with error status codes fail open. This violates the expected security property of an email-verification control: when verification cannot be performed, the result should be unknown or rejected rather than accepted. The resulting lead is added to `verifiedLeads` and later transmitted to Instantly. ### Attack Path 1. An attacker or operational condition causes Hunter to return a non-200 response. Examples include exhausting the account's quota, using an invalid API key, triggering rate limiting, or encountering an upstream service failure. 2. `verifyEmail()` receives the error response. 3. The condition `res.status !== 200` evaluates to true. 4. The function returns `true`, incorrectly marking the email as verified. 5. The calling loop adds the lead and its associated name, company, and personalization data to `verifiedLeads`. 6. `uploadLead()` sends the un ...[truncated 898 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Fail closed whenever verification cannot be completed: ```js async function verifyEmail(email) { if (!email || !email.includes("@")) { return { verified: false, reason: "invalid_format" }; } const res = await request({ hostname: "api.hunter.io", path: `/v2/email-verifier?email=${encodeURIComponent(email)}&api_key=${config.hunter.apiKey}`, method: "GET", }); if (res.status !== 200) { throw new Error(`Hunter verification failed with HTTP ${res.status}`); } const status = res.body?.data?.status; return { verified: ["valid", "accept_all"].includes(status), reason: status || "unknown", }; } ``` Additional hardening should include: 1. Distinguish definitive invalid results from temporary verification failures. 2. Implement bounded retries with exponential backoff for HTTP 429 and transient 5xx responses. 3. Stop or pause the import if verification is unavailable rather than silently accepting leads. 4. Report authentication and quota failures clearly to the operator. 5. Record leads with unknown verification state separately and prevent their upload by default. 6. Add automated tests covering 200, 401, 403, 429, and 5xx responses. ]]>
