An HTTPS page loading HTTP assets breaks differently for you than for visitors
A mixed content error in your AI built app happens when a page served over HTTPS asks the browser to load a resource over plain HTTP. The page itself is encrypted; one or more of its scripts, stylesheets, images, fonts, or fetch calls point at an http:// URL. The browser then has to decide what to do with that downgraded request, and what it decides depends on whether the asset is active or passive.
Active content (scripts, stylesheets, iframes, XHR/fetch, WebSocket) is blocked outright. A modern browser will refuse to load an http:// script on an https:// page, full stop. Passive content (images, audio, video) is usually allowed but flagged, or silently upgraded to HTTPS in newer browsers if the host supports it. So the failure is not uniform: a hardcoded HTTP stylesheet can leave your launched page unstyled, while a hardcoded HTTP image just throws a console warning.
The part that catches people is that this often does not show up in development. On localhost over plain HTTP, every http:// asset loads fine because there is no protocol mismatch. The page only breaks once it is deployed behind HTTPS and a real visitor opens it.
- Observable: open the deployed page, and the browser console reports "Mixed Content: The page was loaded over HTTPS but requested an insecure resource." That message names the exact blocked or upgraded URL.
- Observable: an active asset that got blocked produces a visible break (missing styles, a dead widget, a failed API call), not just a warning.
- Possible, not proven: the HTTP request that did go out (a passive image, or a request before the upgrade) traveled unencrypted and could be read or tampered with on the network path. That is the integrity and confidentiality concern behind the browser's behavior, classed under the CWE cleartext-transmission family. The console warning confirms the downgrade; it does not prove anyone intercepted it.
Why AI-built apps hit this before launch
The pattern is almost always a hardcoded protocol or a leftover dev endpoint baked in at build time. AI coding tools generate working code against whatever environment they are pointed at, and that environment is usually local and unencrypted.
A generated config or component will happily contain an absolute http://localhost:3000 API base, an http:// link to an image host, or a third-party script tag copied from documentation that still uses http://. Each one works on the machine where it was written. None of them is re-checked when the app is deployed to an HTTPS domain, because the deploy step does not rewrite URLs inside your source.
A second, sneakier source: an analytics, payment, or font snippet pasted from an older integration guide. If the embed URL is HTTP and the provider does not auto-upgrade, that single line can block a script your checkout or auth flow depends on.
- The generated default is an absolute URL with an explicit http:// scheme, instead of a protocol-relative or HTTPS URL, or a relative path that inherits the page's protocol.
- The skipped loop is the cross-environment review: nobody diffs "what URLs does this page request in production" against "what it requested on localhost."
- Fast deploy means the HTTPS domain exists and the link gets shared before anyone loads the page in the exact conditions a visitor will see. A preview that you only ever opened over HTTP never exercised the mismatch.
Check your own production page from a clean session
This is safe to run against your own app and takes a few minutes. The goal is to see the page exactly as a visitor's browser sees it.
Open the deployed HTTPS URL in a fresh browser profile or incognito window, then open DevTools before the page finishes loading.
The distinction to hold onto: a blocked active asset is a functional break you can usually see on the page; an allowed-but-flagged passive asset may render fine while still triggering the warning. Both are worth fixing, but the first is the launch blocker.
- Console tab: look for any line beginning "Mixed Content." Each one names the insecure URL and says whether it was blocked or upgraded. Zero such lines on a hard reload is the clean result.
- Network tab: sort or filter by scheme and scan for any request with an http:// (not https://) URL, or check the address-bar lock icon, which most browsers downgrade or annotate when a page pulled insecure content. A page where every request is HTTPS is the safe result; one stray HTTP request is the suspicious result.
- Search the served source: in DevTools open the Sources or page source and search for http:// across your built JS and CSS. Hits inside your own bundle (not inside third-party code you cannot change) are the candidates to fix. Searching the built output, not just your repo, matters because the build can inline values from env vars.
Run this against the production domain, not a localhost build and not an http:// preview. The whole point is that the bug only appears under HTTPS, so a check that runs over HTTP will report clean while the live page is broken.
Fix the URLs, and know what the fix does not cover
There are two layers: fix the asset references, then add a backstop header.
First, the references. Change every http:// URL you control to https://, or better, drop the scheme and host where you can. A relative path like /assets/logo.png inherits the page's protocol automatically and never mismatches. For third-party assets, confirm the provider serves the resource over HTTPS and use that URL. For API bases and similar values, move them to an environment variable that holds the HTTPS production URL, so the deployed build never carries a localhost or http:// default.
Second, the backstop. The upgrade-insecure-requests directive, set either as a Content-Security-Policy header or a meta tag, tells the browser to rewrite http:// requests to https:// before sending them. This rescues passive references and is a reasonable safety net.
For CSP details around payments and analytics embeds, the companion note on CSP for apps that embed Stripe, analytics, and auth covers which directives those third parties need; for getting the HTTPS layer itself right on a custom domain, see the note on custom-domain TLS and HSTS.
- Prefer fixing the source URLs. The header is a backstop, not a substitute for clean references.
- upgrade-insecure-requests only helps when the asset is actually available over HTTPS. If the host has no HTTPS endpoint, the upgraded request fails too. It cannot conjure a secure version of a host that does not offer one.
- It does not retroactively secure an asset that already loaded, and it does not fix active content that a stricter browser blocked before any upgrade logic ran. Where the asset genuinely has no HTTPS option, the real fix is to self-host it or switch providers, which is a code change, not a header.
Where VibeCodeGuard fits, and what it does not prove
VibeCodeGuard's Launch Check scans the production page from the public surface for mixed content signals: it loads the HTTPS URL and reports insecure resource references it can observe, and it reads whether the upgrade-insecure-requests directive is present. Passive mixed content that the page declares is the kind of signal it surfaces, with severity and fix direction, so a stray HTTP asset shows up in your scan rather than in a visitor's broken page.
What it sees is the page's declared and observable references. It is reading the public surface, not executing your full app the way a logged-in user with every feature toggled would. An HTTP asset that only loads behind authentication, inside a flow the scanner does not reach, or via a script that injects URLs at runtime, may not appear in a public-surface pass.
Next step: open your production URL in a clean incognito window, hard-reload with the console open, and fix any "Mixed Content" line before you share the link. Run a focused Launch Check against the public URL so a downgraded asset surfaces in your scan and not on a visitor's first load.
A clean mixed content result does not prove every request your app ever makes is HTTPS, and it does not prove the encrypted requests are correctly authorized or that the assets themselves are trustworthy. It confirms what the public page declares over the wire. Treat a flagged HTTP reference as a launch blocker for active content, and pair the scan with a manual DevTools pass on the real authenticated flows.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.