Overview

Medium
Severity
5.4
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Interstitials
DescriptionImproper input validation in Interstitials
ComponentInterstitials
Bug ClassInput Validation
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker513702096
Fix commitd7a95acf2a5a (chromium/src) +19/-2
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L
CISA KEVNot listed
CreditedGoogle
Reported2026-05-16 (per vendor advisory)
Fix landed2026-07-29 (public git)
Disclosed2026-09-08
Patch gap41 days — fix public in source before the advisory

Background

Insecure form warning
Chrome’s InsecureFormNavigationThrottle interposes on form submissions and shows an interstitial when a secure (HTTPS) page posts a form to an insecure (HTTP) action endpoint.
`IsInsecureFormActionOnSecureSource`
The helper in insecure_form_util.cc that decides whether a submission qualifies as an insecure-form case by testing that the source origin is HTTPS and the action URL is not secure.
Opaque origin
A url::Origin with no tuple of its own (empty scheme/host/port), produced for sandboxed iframes, data: documents, and similar contexts; it may still carry a precursor tuple recording the origin it was derived from.
`GetTupleOrPrecursorTupleIfOpaque`
A url::Origin accessor that returns the origin’s own tuple when it is a normal origin, or the precursor tuple (e.g. the deriving https://… origin) when the origin is opaque.

Root Cause Analysis

IsInsecureFormActionOnSecureSource gated the entire insecure-form check on base::EqualsCaseInsensitiveASCII(source_origin.scheme(), url::kHttpsScheme). For an opaque origin, source_origin.scheme() returns an empty string rather than "https", so the negated comparison was true and the function took the early path and returned false — meaning an opaque-origin form (such as a sandboxed iframe derived from an HTTPS page) submitting to an http:// action was never classified as an insecure form. The violated invariant is that a document whose effective security context is HTTPS must have its cleartext form submissions throttled regardless of whether its origin is expressed as a tuple or as an opaque origin.

The fix replaces source_origin.scheme() with source_origin.GetTupleOrPrecursorTupleIfOpaque().scheme(), so opaque origins are judged by the scheme of their precursor tuple; an opaque origin derived from https:// now correctly compares equal to url::kHttpsScheme and its insecure action is throttled. This tightens the check: the added unit tests assert EXPECT_TRUE for an opaque HTTPS-derived source with an HTTP action, while still returning false for HTTPS actions, HTTP-derived opaque sources, and a bare url::Origin().

Key insight
The scheme test read the opaque origin’s own (empty) scheme instead of its precursor tuple’s scheme, so opaque contexts silently fell through the HTTPS gate and bypassed the throttle; consulting GetTupleOrPrecursorTupleIfOpaque() restores the intended HTTPS classification for those origins.

Attack Path

  1. Host an opaque embedded form An attacker or malicious content on an HTTPS page places the form inside an opaque-origin context (e.g. a sandboxed iframe derived from the HTTPS origin) whose url::Origin reports an empty scheme.
  2. Point the action at HTTP The form’s action targets an http:// (cleartext) endpoint, creating the mixed-content submission the throttle is meant to warn about.
  3. Fail the HTTPS scheme test IsInsecureFormActionOnSecureSource compares the opaque origin’s empty scheme() to url::kHttpsScheme, the comparison is unequal, and the function returns false.
  4. Bypass the throttle Because the submission is not classified as an insecure form, InsecureFormNavigationThrottle presents no interstitial and the form data is sent over cleartext without user warning.
  5. Interception of submitted data (inferred) A network attacker positioned on the path can read or tamper with the cleartext submission that would otherwise have been gated behind the warning.

Impact Assessment

An attacker gains a bypass of the insecure-form interstitial, allowing an HTTPS-origin (but opaque-context) page to submit form data over cleartext HTTP with no warning shown to the user, in the browser’s navigation-throttle path. The precondition is content able to host the form in an opaque origin (such as a sandboxed iframe) whose precursor is HTTPS with an HTTP action; exploitation of the resulting cleartext to intercept or modify submitted data requires a network man-in-the-middle position (inferred). The advisory classifies this as improper input validation in Interstitials and does not report exploitation in the wild.

Files Changed

  • components/security_interstitials/core/insecure_form_util_unittest.cc
  • components/security_interstitials/core/insecure_form_util.cc

Audit Directions

  • Opaque-origin scheme checks
    Search for security decisions that call Origin::scheme() (or compare against url::kHttpsScheme/kHttpScheme) directly; opaque origins report an empty scheme, so these should usually use GetTupleOrPrecursorTupleIfOpaque() when the effective security context matters.
  • Sandboxed/derived contexts in mixed-content and throttle logic
    Review other navigation throttles and mixed-content guards for assumptions that a secure document always presents a tuple origin, which sandboxed iframes and data:/blob: documents violate.
  • Precursor-tuple omissions
    Audit origin-based allow/deny helpers where an opaque origin should inherit its precursor’s security properties but is instead treated as unknown, causing either an over-permissive fall-through (as here) or an over-restrictive block.
From d7a95acf2a5af92b6d94bff01a6897c01df4dbb1 Mon Sep 17 00:00:00 2001
From: Ari Chivukula <arichiv@chromium.org>
Date: Tue, 28 Jul 2026 18:08:17 -0700
Subject: [PATCH] Resolve InsecureFormNavigationThrottle bypass

Ensure proper throttling of opaque embedded forms.

Fixed: 513702096
Change-Id: I6f0298581b49a29ce8743419044fcfbd5ade4a98
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8164181
Auto-Submit: Ari Chivukula <arichiv@chromium.org>
Commit-Queue: Carlos IL <carlosil@chromium.org>
Reviewed-by: Carlos IL <carlosil@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1669917}
---
diff --git a/components/security_interstitials/core/insecure_form_util.cc b/components/security_interstitials/core/insecure_form_util.cc
index a7fe5564..4ae57fe 100644
--- a/components/security_interstitials/core/insecure_form_util.cc
+++ b/components/security_interstitials/core/insecure_form_util.cc
@@ -43,8 +43,10 @@
 
 bool IsInsecureFormActionOnSecureSource(const url::Origin& source_origin,
                                         const GURL& action_url) {
-  if (!base::EqualsCaseInsensitiveASCII(source_origin.scheme(),
-                                        url::kHttpsScheme)) {
+  // For opaque origins the precursos tuple must be examined.
+  if (!base::EqualsCaseInsensitiveASCII(
+          source_origin.GetTupleOrPrecursorTupleIfOpaque().scheme(),
+          url::kHttpsScheme)) {
 #if BUILDFLAG(IS_IOS)
     // On iOS, tests can't use an HTTPS server that serves a valid HTTPS
     // response. Check if the URL is treated as secure for testing purposes.
diff --git a/components/security_interstitials/core/insecure_form_util_unittest.cc b/components/security_interstitials/core/insecure_form_util_unittest.cc
index f62a178..3223b18 100644
--- a/components/security_interstitials/core/insecure_form_util_unittest.cc
+++ b/components/security_interstitials/core/insecure_form_util_unittest.cc
@@ -54,4 +54,19 @@
       url::Origin::Create(GURL("http://127.0.0.1:123")),
       GURL("http://example.com")));
 #endif
+
+  // Opaque https source with insecure action still counts.
+  EXPECT_TRUE(IsInsecureFormActionOnSecureSource(
+      url::Origin::Create(GURL("https://example.com")).DeriveNewOpaqueOrigin(),
+      GURL("http://example.com")));
+
+  // Other combinations do not.
+  EXPECT_FALSE(IsInsecureFormActionOnSecureSource(
+      url::Origin::Create(GURL("https://example.com")).DeriveNewOpaqueOrigin(),
+      GURL("https://example.com")));
+  EXPECT_FALSE(IsInsecureFormActionOnSecureSource(
+      url::Origin::Create(GURL("http://example.com")).DeriveNewOpaqueOrigin(),
+      GURL("http://example.com")));
+  EXPECT_FALSE(IsInsecureFormActionOnSecureSource(url::Origin(),
+                                                  GURL("http://example.com")));
 }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/security_interstitials/core/insecure_form_util_unittest.cc b/components/security_interstitials/core/insecure_form_util_unittest.cc
index f62a178..3223b18 100644
--- a/components/security_interstitials/core/insecure_form_util_unittest.cc
+++ b/components/security_interstitials/core/insecure_form_util_unittest.cc
@@ -54,4 +54,19 @@
       url::Origin::Create(GURL("http://127.0.0.1:123")),
       GURL("http://example.com")));
 #endif
+
+  // Opaque https source with insecure action still counts.
+  EXPECT_TRUE(IsInsecureFormActionOnSecureSource(
+      url::Origin::Create(GURL("https://example.com")).DeriveNewOpaqueOrigin(),
+      GURL("http://example.com")));
+
+  // Other combinations do not.
+  EXPECT_FALSE(IsInsecureFormActionOnSecureSource(
+      url::Origin::Create(GURL("https://example.com")).DeriveNewOpaqueOrigin(),
+      GURL("https://example.com")));
+  EXPECT_FALSE(IsInsecureFormActionOnSecureSource(
+      url::Origin::Create(GURL("http://example.com")).DeriveNewOpaqueOrigin(),
+      GURL("http://example.com")));
+  EXPECT_FALSE(IsInsecureFormActionOnSecureSource(url::Origin(),
+                                                  GURL("http://example.com")));
 }
Loading diff…

Original Bug Report

reported by vm...@google.com

InsecureFormNavigationThrottle bypass via opaque initiator origin

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A logic error in IsInsecureFormActionOnSecureSource fails to correctly identify secure origins when they are opaque, such as in sandboxed contexts. This allows an HTTPS page to potentially submit form data to an insecure HTTP endpoint without triggering the expected security interstitial warning.

Affected files:

  • components/security_interstitials/core/insecure_form_util.cc
  • components/security_interstitials/content/insecure_form_navigation_throttle.cc

Estimated timestamp from git blame: 2020-10-06

Summary

Chrome’s InsecureFormNavigationThrottle is designed to protect users by displaying an interstitial warning when a secure (HTTPS) page attempts to submit a form to an insecure (HTTP) URL. This protection can potentially be bypassed if the initiating document has an opaque origin (e.g., due to a Content-Security-Policy: sandbox header or being inside a sandboxed <iframe>).

Technical Details

The issue resides in how the browser-side utility function IsInsecureFormActionOnSecureSource evaluates whether the initiator is a secure source. It checks the scheme of the origin directly using source_origin.scheme():

// components/security_interstitials/core/insecure_form_util.cc:44
bool IsInsecureFormActionOnSecureSource(const url::Origin& source_origin,
                                        const GURL& action_url) {
  if (!base::EqualsCaseInsensitiveASCII(source_origin.scheme(),
                                        url::kHttpsScheme)) {
    // On non-iOS platforms, returns false if scheme is not https
    return false;
  }
  return IsInsecureFormAction(action_url);
}

For an opaque origin, url::Origin::scheme() is defined to return an empty string (url/origin.h:226-228), even if the origin has an HTTPS precursor. Consequently, the check source_origin.scheme() == url::kHttpsScheme fails, the function returns false, and the InsecureFormNavigationThrottle permits the navigation to proceed without showing the warning interstitial (components/security_interstitials/content/insecure_form_navigation_throttle.cc:132-136).

Impact

In its default configuration, Chrome does not degrade the ‘SECURE’ lock icon for mixed forms, relying instead on the submission-time interstitial to warn users. Because this interstitial is bypassed for opaque origins, a malicious HTTPS site could host a sandboxed page that appears secure but transmits sensitive user data (e.g., passwords or payment information) over unencrypted HTTP. Passive network observers could then intercept this data. Other protections, such as HttpsUpgradesInterceptor, do not apply to POST requests, and browser-side mixed content checks typically exempt main-frame navigations.

Potential Reproduction Steps

  1. Host a page over HTTPS with the following header: Content-Security-Policy: sandbox allow-forms allow-scripts.
  2. Include a form targeting an HTTP endpoint: <form method='POST' action='http://example.com/collect'><input type='password' name='p' value='secret'></form>.
  3. Trigger the form submission (e.g., via user interaction or script).
  4. Observe that the navigation proceeds directly over HTTP without a security interstitial, while the omnibox continues to display the secure lock icon.

Note: These steps are based on static code analysis; our tooling does not currently have the capability to run code to confirm the behavior.

Suggested Fix

The check in IsInsecureFormActionOnSecureSource should consider the precursor origin for opaque origins. This can be achieved by using GetTupleOrPrecursorTupleIfOpaque() instead of scheme(), which would align the browser-side logic with Blink’s MixedContentChecker:

if (!base::EqualsCaseInsensitiveASCII(source_origin.GetTupleOrPrecursorTupleIfOpaque().scheme(),
                                      url::kHttpsScheme)) {
  return false;
}

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker