Jan 16, 2026·5 min read·38 visits
Deno versions prior to 2.6.0 contain a critical logic error in the `node:crypto` compatibility layer. Calling `.final()` on a Cipher instance fails to nullify the internal Rust handle. This allows the object to be reused for subsequent encryption operations with the same state, leading to IV reuse and potential plaintext recovery. Update to Deno 2.6.0 immediately.
In Deno's quest for Node.js compatibility, a critical flaw in the `node:crypto` polyfill allowed cryptographic handles to survive past their intended lifespan. By failing to invalidate the internal state after `.final()`, Deno < 2.6.0 permitted 'infinite encryptions'—allowing attackers to reuse key streams and IVs, completely shattering confidentiality guarantees.
Deno is the cool kid on the block—secure by default, Rust-based, and generally intolerant of the sins of the past. But in the modern web ecosystem, you can't survive without speaking the language of the ancients: Node.js. To support the massive npm registry, Deno implements a compatibility layer for Node's built-in modules, including the notoriously complex node:crypto.
Here is the problem: node:crypto is a wrapper around OpenSSL, and Deno is backed by BoringSSL (via Rust). Bridging these two worlds requires a delicate dance of state machines, memory pointers, and garbage collection. When you create a Cipher object, you are spinning up a sensitive cryptographic operation that relies on a specific sequence of events: Init, Update, Final.
CVE-2026-22863 is what happens when that sequence loses its termination signal. It’s like firing a gun, but instead of the slide locking back, the magazine magically refills itself. Deno implemented the encryption logic but forgot to enforce the 'End of Life' for the cipher object, leaving a powerful cryptographic weapon lying around in memory, ready to be fired again by anyone who knew it wasn't actually dead.
In a proper cryptographic implementation, the lifecycle of a Cipher object is finite. You feed it data, you call .final() to handle padding and flush the last block, and then the object should essentially self-destruct. The internal pointers to the cryptographic context should be nullified to prevent reuse. This isn't just best practice; it's a requirement for modes like AES-GCM or AES-CTR, where reusing a state means reusing a nonce.
In affected versions of Deno, the node:crypto implementation failed to transition the underlying Rust-backed CipherBase into a terminal state. Specifically, the JavaScript side maintained a reference to Symbol(kHandle)—the bridge to the raw BoringSSL context—even after the user explicitly signaled that they were done.
Technical analysis suggests this was exacerbated by poor error handling in the compatibility layer. If the native layer returned a specific status code or if a utility like getSystemErrorMessage was missing during the cleanup path, the code responsible for nullifying the handle would be skipped entirely. The result? A zombie object. It looks dead, the user thinks it's dead, but the internal engine is still idling, waiting for more input on the same key and IV.
Let's look at the smoking gun. A simple inspection of the Cipheriv object after finalization reveals the persistence of the internal state. In a patched environment, the handle should be gone. In a vulnerable one, it persists.
The Vulnerable State (Deno < 2.6.0):
import crypto from "node:crypto";
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
// We tell the cipher we are done.
cipher.final();
// But looking inside, the ghost remains:
console.log(cipher);
/*
Cipheriv {
_readableState: ReadableState { ... },
_writableState: WritableState { ... },
[Symbol(kHandle)]: CipherBase {} // <--- CRITICAL: The handle is still alive!
}
*/The Patched State (Deno 2.6.0+):
In the fixed version, the developers enforced a hard cleanup. The state machine explicitly disentangles the JavaScript object from the Rust resource table.
/*
Cipheriv {
_decoder: null,
[Symbol(kHandle)]: null // <--- SAFE: The handle is nuked.
}
*/The fix involved standardizing the 'host object branding' and ensuring that the internal close() method is unconditionally called and the handle set to null immediately upon finalization, regardless of the underlying stream state.
Why is a zombie handle so dangerous? It allows for state reuse. If an attacker can invoke .final() multiple times, or continue writing to the stream after finalization, they can force the cipher to encrypt new data using the initial state (Key + IV).
Consider an application using AES-CTR (Counter Mode). Security in CTR mode relies entirely on the uniqueness of the Key+Counter pair. If you reuse the state, you generate the same keystream (the pseudo-random bits XORed with the plaintext).
Attack Scenario:
Cipher.update(secret); Cipher.final();Cipher.update(known_plaintext) on the same object.Even in CBC mode, this allows for 'Infinite Encryptions', enabling an attacker to perform padding oracle attacks or probe the internal state with zero overhead, bypassing the need to re-initialize connections or handshake parameters.
The remediation logic introduced in Deno v2.6.0 is straightforward but vital: when a cipher is done, it must die completely. The patch explicitly nullifies [Symbol(kHandle)] and clears the _readableState and _writableState buffers.
If you are running Deno in production, check your version immediately:
deno --versionIf the output is anything less than 2.6.0, you are vulnerable. Upgrading is the only viable path. While you could theoretically monkey-patch the Cipher.prototype.final method to manually delete the handle, that is a band-aid on a bullet wound. Trust the vendor patch that fixes the Rust-side resource table management.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Deno Denoland | < 2.6.0 | 2.6.0 |
| Attribute | Detail |
|---|---|
| CWE | CWE-325 (Missing Cryptographic Step) |
| CVSS v4.0 | 9.2 (Critical) |
| Attack Vector | Network |
| Impact | Confidentiality Loss / Keystream Reuse |
| Exploit Status | PoC Available |
| EPSS Score | 0.00017 (Low Probability) |
The product does not perform a required step in a cryptographic algorithm, resulting in a weakening of the encryption.
A critical stored Cross-Site Scripting (XSS) vulnerability was identified in Froxlor server administration software panel before version 2.3.8. Authenticated customers with DNS editor privileges can inject malicious JavaScript into DNS TXT records. Because the application processes these values via a raw formatting callback without context-aware HTML entity encoding, the payload executes in the security context of administrative users who view the affected domain's DNS zones.
An authenticated administrator with privileges to manage admin accounts (such as change_serversettings) can execute arbitrary SQL commands via a second-order SQL injection vulnerability. The flaw resides in Froxlor's administrative API endpoints, specifically during the handling of IP address mapping parameters which are stored as serialized arrays and later interpolated without sanitization into active database queries. This vulnerability allows high-privileged administrative attackers to compromise the database. By injecting a payload into administrative profile metadata, an attacker can extract sensitive credentials, manipulate backend settings, or potentially disrupt database integrity. The vulnerability affects all versions of Froxlor prior to 2.3.8.
CVE-2026-54543 is a DNS Resource Record (RR) Injection vulnerability in Froxlor, an open-source server administration control panel. Prior to version 2.3.8, the DomainZones.add API command failed to perform strict sanitization and validation on the user-controlled record (label) and type parameters before serializing them into BIND-compatible zone files. An authenticated customer with DNS zone management permissions can inject control characters, breaking out of the original record context to define unauthorized resource records within managed zones.
CVE-2026-42533 is a critical security vulnerability discovered in NGINX Open Source, NGINX Plus, NGINX Ingress Controller, and related products, referred to as the 'Two-Pass Capture-Clobbering' bug. The flaw is situated within NGINX's internal evaluation engine when handling complex variables, exposing a heap-based buffer overflow and information leak when a configuration chains regular expression-based map directives with numbered capture groups. An unauthenticated remote attacker can exploit this weakness by transmitting crafted HTTP requests to trigger remote code execution or defeat ASLR.
Froxlor prior to version 2.3.8 contains a high-severity architectural flaw where the standalone lib/ajax.php entry point bypasses the centralized request validation in lib/init.php. Unauthenticated remote attackers can leverage Cross-Site Request Forgery (CSRF) to induce authenticated administrators to submit forged requests that modify API key whitelists and expiration dates, potentially yielding persistent, out-of-band administrative control.
An insecure data retrieval flaw in the Froxlor server administration panel API allows authenticated remote attackers to retrieve unredacted bcrypt password hashes and Base32-encoded Time-Based One-Time Password (TOTP) seeds. Affected endpoints include several 'get' and 'listing' handlers for customers, administrators, and FTP accounts. Utilizing these leaked parameters, attackers can crack the password hashes offline and concurrently generate valid second-factor authentication codes to completely bypass access controls.