VNX-666: HTTP response written after it was already sent
Reports an Express, Koa-style or Fastify handler that sends a response without returning, so the next statement (or the first one after the if-block, or after a braceless one-line `if`) writes to the same response again. Node.js and TypeScript.
| Field | Value |
|---|---|
| Rule ID | VNX-666 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | JavaScript / TypeScript |
| CWE | CWE-666 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:L/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L |
| Tags | operation-on-wrong-resource cwe-666 express response |
| Source | internal/sast/rules/vnx-666.rego |
Overview
VNX-666 maps to CWE-666: Operation on Resource in Wrong Phase of Lifetime. An HTTP response has a lifetime: headers, then body, then finished. A handler that finishes it (res.send, res.json, res.end, res.redirect, res.render, res.sendFile, res.sendStatus, res.download, or the same at the end of a .status(...) / .code(...) chain) and keeps going writes to a response that is already over.
Why This Matters
The usual cause is a missing return after an error reply. The second write throws ERR_HTTP_HEADERS_SENT (or is silently dropped), and everything between the two writes still runs: the success path executes for a request that was just refused, which is how “404 Not Found” handlers end up leaking the record anyway or performing the update they rejected.
What Gets Flagged
if (!user) {
res.status(404).json({ error: 'not found' }); // FLAGGED: no return
}
res.json(user);
try {
report = await buildReport(month);
} catch (err) {
res.status(500).json({ error: err.message }); // FLAGGED: no return
}
res.json(report);
if (!order) res.status(404).send('missing'); // FLAGGED
res.json(order);
reply.code(404).send({ error: 'gone' }); // FLAGGED (Fastify), when followed by reply.send(item)
The second write may be a few statements later at the same level, and may itself be return res.json(...). The finding is on the first, unreturned send.
What Is Not Flagged
return res.status(404).json(...), or a send followed byreturn;/break;.if/elsebranches that each send once, a send followed by} else,});(a callback) or},(an object of handlers).- A later write inside a nested callback or block (it may run on another path).
res.write(...)in a loop followed byres.end().- Sends in comments or string literals. Python frameworks are not covered.
Remediation
Return as soon as the response is sent:
if (!user) {
return res.status(404).json({ error: 'not found' });
}
res.json(user);
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-666
// nosec vnx-666
Or record an ignore rule: vulnetix ignore add --rule vnx-666 --reason "...". See Suppressing findings.