Skip to content

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.

Updated 02 Nov 2026Checked against cli@de9b52a
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 by return; / break;.
  • if/else branches 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 by res.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.

Edit this pageSources: cli/internal/sast/rules/vnx-666.rego, cli/internal/sast/docs/vnx-666.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.