# Two Node.js bugs you should know about > Two Node.js vulnerabilities can replay SQLite writes or crash workers through oversized DNS responses. See the affected paths, fixed releases, and mitigations. Author: Cantina Published: August 11, 2026 Topics: Node.js, CVE-2026-58041, CVE-2026-58042, node:sqlite, DNS, vulnerability disclosure Canonical URL: https://www.cantina.security/blog/nodejs-cve-2026-58041-cve-2026-58042 Two bugs in Node.js crossed different trust boundaries. One lets a stale SQLite iterator run a later write again. The other aborts a Node.js worker when DNS returns more than 256 A records. The Node.js project fixed both issues in the July 29, 2026 security release. The release tracks them as CVE-2026-58041 and CVE-2026-58042. The code looks separate at the JavaScript layer. Underneath, both paths rely on native state or assumptions that outlive the call that created them. These findings extend [Cantina's earlier Node.js research into three trust-boundary vulnerabilities](https://www.cantina.security/blog/nodejs-tls-dns-trust-boundary-cves) across DNS resolution, SNI context selection, and TLS hostname verification. ## **Finding 1: `node:sqlite` iterator replay can re-execute writes** **Medium** `DatabaseSync#createTagStore()` / `StatementSyncIterator` • [CVE-2026-58041](https://nvd.nist.gov/vuln/detail/CVE-2026-58041) • [Node.js advisory](https://nodejs.org/en/blog/vulnerability/july-2026-security-releases) `SQLTagStore` caches prepared statements by SQL query text. That improves performance, but calls that use the same query also share the native statement behind the cache. The failure starts when one call creates an iterator and another call resets the cached statement. `SQLTagStore` calls `sqlite3_reset()` directly. That path does not update the generation counter that invalidates direct `StatementSync` iterators. The old iterator remains valid after the second caller binds new values. A later `iterator.next()` can step the newly bound statement. If the statement writes to the database, the stale iterator runs that write again. The iterator also returns `{ done: true }` without setting its internal `done_` flag. A later call can start the statement again. The replay can therefore continue instead of stopping after one extra execution. ### **Vulnerable application setup** The following service uses one tag store for an arm endpoint, a payment endpoint, and a fire endpoint: ```javascript const { DatabaseSync } = require('node:sqlite'); const db = new DatabaseSync(':memory:'); const sql = db.createTagStore(); let armedIter = null; function arm(from, to, amount) { armedIter = sql.iterate` INSERT INTO transfers(from_user, to_user, amount) VALUES (${from}, ${to}, ${amount}) RETURNING id `; } function pay(from, to, amount) { return sql.get` INSERT INTO transfers(from_user, to_user, amount) VALUES (${from}, ${to}, ${amount}) RETURNING id `; } function fire(count) { const results = []; for (let i = 0; i < count; i++) { results.push(armedIter.next()); } return results; } ``` The attacker calls `arm()` and leaves the iterator untouched. A victim then calls `pay()` with the same tagged query. The tag store resets and rebinds the shared prepared statement with the victim's values. The attacker calls `fire()`. The stale iterator advances the statement that now contains the later bound values. ### **Observed result** The end-to-end test uses a transfer trigger that debits the source account and credits the destination account: ```text Initial balances: victim: 1000 attacker: 0 After the victim makes one transfer: victim: 900 attacker: 100 Stale iterator results: { done: false, value: { id: 2 } } { done: true, value: null } { done: false, value: { id: 3 } } Final balances: victim: 700 attacker: 300 ``` The application must expose a long-lived iterator or equivalent state across requests for this attack. The Node.js bug does not create that endpoint by itself. The replay uses the later caller's bound parameters. The destination is therefore not always the attacker's account. The bug can replay a victim-bound write without authorization. That can mean duplicate transfers, repeated token issuance, inventory changes, quota consumption, or repeated permission grants. ### **Why the cache boundary matters** The direct `StatementSync` path uses a reset-generation counter. When another operation resets the statement, the iterator detects the new generation and throws `ERR_INVALID_STATE`. `SQLTagStore` bypasses that protection. It reuses the prepared statement but does not tell existing iterators that the statement changed. The API returns an iterator that looks independent, while the native implementation keeps it attached to shared cached state. ## **Finding 2: `dns.resolveAny()` can abort the process** **Medium** `dns.resolveAny()` / c-ares response parsing • [CVE-2026-58042](https://nvd.nist.gov/vuln/detail/CVE-2026-58042) • [Node.js advisory](https://nodejs.org/en/blog/vulnerability/july-2026-security-releases) Node.js implements `dns.resolveAny()` through c-ares. The parser allocates a fixed 256-entry `ares_addrttl[]` buffer for A-record TTL values. c-ares honors the caller's limit for the TTL buffer. It can still expose more addresses through `hostent->h_addr_list`. Node.js counts all addresses and compares that count with the number of TTL entries. With 257 A records, the values differ: ```text naddrttls = 256 a_count = 257 ``` The parser requires the values to match: ```cpp CHECK_EQ(static_cast(naddrttls), a_count); ``` The failed check calls the Node.js assertion handler. The process terminates with `SIGABRT`. A JavaScript `catch` handler does not get a chance to handle the failure. ### **Vulnerable application setup** The application only needs to pass an attacker-controlled hostname to `resolveAny()`: ```javascript const { Resolver } = require('node:dns').promises; const resolver = new Resolver(); resolver.setServers(['127.0.0.1:5399']); resolver.resolveAny('crash.attacker.example') .then(records => console.log(records.length)) .catch(error => console.error(error)); ``` The test DNS server returns 257 A records for the query. It can answer over UDP and TCP, so the test does not depend on the transport selected by the resolver. ### **Observed result** The child Node.js process prints the failing assertion and exits with `SIGABRT`: ```text Node v24.15.0 DNS server returning 257 A records Assertion failed: (static_cast(naddrttls)) == (a_count) exit: null / signal: SIGABRT ``` An attacker can repeat the trigger when a service resolves attacker-controlled names. Each trigger terminates the worker that performs the lookup. A supervisor can restart the worker, but repeated requests can still disrupt the service. ## **The shared lesson: native state is part of the security boundary** These vulnerabilities do not depend on SQL injection or memory corruption. The SQLite issue crosses a lifetime boundary. JavaScript holds an iterator while native code reuses the statement behind it. The DNS issue crosses a data-shape boundary. c-ares returns more addresses than the fixed TTL buffer can describe, while Node.js treats the two counts as equal. In both cases, the public API hides the state that matters for security. `SQLTagStore` hides a shared prepared statement behind a template tag. `resolveAny()` hides native DNS parsing behind a JavaScript promise. The failure appears when native state no longer matches the JavaScript assumption. An application can reach either path through a normal database write or DNS lookup when the required input and call sequence exist. ## **Who needs to review their applications** Start with applications that use `DatabaseSync#createTagStore()` for write queries. Review code that retains an iterator beyond the current function or request. Review code that allows one user or request to create an iterator and another request to use it. Also review services that pass user-controlled or partner-controlled hostnames to `dns.resolveAny()`. Pay special attention to webhook processors, URL preview services, tenant integrations, and network inventory tools. The key questions are: - Can an attacker supply the hostname passed to `resolveAny()`? - Can a service keep a `SQLTagStore` iterator alive across requests? - Can two request paths use the same cached SQL query? - Does a replayed write trigger a balance change, token issue, quota change, or permission change? - Does the service restart workers after a native abort? ## **What to do now** Upgrade Node.js to a fixed release: - Node.js 22.23.2 - Node.js 24.18.1 - Node.js 26.5.1 For CVE-2026-58041, do not expose long-lived `SQLTagStore` iterators across request or user boundaries. Use the fixed release before relying on application-level controls. For CVE-2026-58042, do not pass attacker-controlled names to `resolveAny()` until the runtime includes the fix. If the service must resolve external names, apply a resolver policy that limits the records and isolates resolver failures from the main worker. After the upgrade, test both paths again. A version check alone does not confirm that the application no longer reaches the affected call sequence. The July 29, 2026 Node.js security release fixes both issues. Upgrade first, then review the application paths that keep these native boundaries reachable. ## **Close the security loop on your environment** These vulnerabilities lived at the boundary between JavaScript and native code, where the public API hid the state that created the risk. Apex, our autonomous OffSec agent, investigates like an attacker, tracing subtle logic flaws and chained exploits through code and running systems. It proves what is exploitable, generates the fix, and verifies the remediation. [See how Apex works](https://www.cantina.security/apex). **[Book a demo](https://www.cantina.security/get-a-demo) and see what Apex can find in your attack surface.** ## **References** - [Node.js Wednesday, July 29, 2026 Security Releases](https://nodejs.org/en/blog/vulnerability/july-2026-security-releases) - [CVE-2026-58041](https://nvd.nist.gov/vuln/detail/CVE-2026-58041) - [CVE-2026-58042](https://nvd.nist.gov/vuln/detail/CVE-2026-58042) - [Node.js ](https://nodejs.org/api/sqlite.html)`[node:sqlite](https://nodejs.org/api/sqlite.html)`[ documentation](https://nodejs.org/api/sqlite.html) - [Node.js DNS documentation](https://nodejs.org/api/dns.html)