Mixed Content
An HTTPS page that pulls a script, stylesheet, frame or image over plain http:// hands every hop on the network a chance to rewrite it. isitsecure reads the HTML and first-party JavaScript it already captured, matches four resource-loading tags against a literal http:// URL, and grades a swapped script higher than a swapped picture. It sends no requests of its own.
What it reads and what it reports
The scanner works entirely from the snapshot that URL ingestion produced: the rendered HTML plus the concatenated first-party JavaScript. Four regexes look for a resource-loading tag whose src or href starts with http://. Each match becomes a finding, graded by what an attacker could do with the swapped bytes.
1URL ingestion already rendered the page with Playwright and kept its HTML and every first-party JavaScript file. The scanner joins those into one string. If the scanned URL is not https:// it returns before running a single regex: an HTTP page has no secure context to break.
2The first regex finds a <script> whose src starts with http://. A script is active content: whoever controls those bytes controls the page. Active → MEDIUM.
3<link href="http://…"> and <iframe src="http://…"> are treated the same way. A stylesheet can restyle a login form and a frame can show one; both are active and both are MEDIUM.
4<img src="http://…"> is passive: a swapped picture cannot run code, but the request leaks the page the user is on and the image can be replaced. Passive → LOW.
5The same tag and URL pair is reported once, and each tag type stops after ten matches. Every finding carries the page URL, the resource URL and the tag, with confidence 0.9. The https:// script was never a match: the pattern needs the literal text http://.
This scanner has no page under docs/scanners/. Everything here is taken from isitsecure/engine/scanners/mixed_content_scanner.py and the README scanner-table row ("http:// resources loaded on an HTTPS page"). The regexes, severities, the HTTPS gate and the per-tag cap of 10 are read directly from that file.
Why a script and an image get different grades
The page itself is protected by TLS, but a sub-resource fetched over http:// travels in the clear and without any proof of who sent it. Anyone on the path can read it and rewrite the reply. What that buys the attacker depends on the tag.
1The user opens https://app.example. The document arrives over TLS, so the browser shows a lock and the app's cookies and tokens live in that origin.
2The page contains <script src="http://cdn.example/app.js">. The browser fetches it over plain HTTP: no encryption, no certificate, nothing that says which server answered.
3A network attacker sitting on the path reads the reply and replaces it with app.js plus a payload. The bytes arrive looking exactly like a normal response.
4The altered script executes inside the HTTPS origin, with the page's DOM, storage and session. That is why script, stylesheet and frame tags are graded MEDIUM: the attacker gets code execution in the page.
5For <img src="http://img.example/logo.png"> the same hop can only swap the picture and see which page requested it. No code runs, so the tag is graded LOW.
Every tag the scanner flags is a place where the page's own TLS guarantee is quietly undone. The finding text says it plainly: active mixed content "can be intercepted and modified by a network attacker to execute arbitrary code in the page context", and passive mixed content "leaks the request and can be swapped". The fix in both cases is the same word: HTTPS.
What stays quiet
Three guards keep the finding list honest. The page must be HTTPS, the URL in the tag must literally begin with http://, and the same tag-and-URL pair is counted once. The rows below are four pages carrying the same kind of tag.
1Four pages, each loading the same script tag in a slightly different way. Only one of them is a problem, and the scanner has to tell them apart from text alone.
2The page at http://app.example is itself plain HTTP. There is no secure context to downgrade, so the scanner returns an empty list before running any regex.
3An https:// resource on an HTTPS page is exactly what should happen. The pattern only matches when the captured value starts with http://, so this line never fires.
4A protocol-relative //cdn.example/a.js inherits the page's scheme and loads over HTTPS. It has no http:// prefix, so it is quiet too.
5An HTTPS page loading http://cdn.example/a.js is the real case. It is reported as Active mixed content (script[src]), MEDIUM, once, even if the tag appears several times in the HTML and in the bundles.
Vulnerable vs fixed markup
The finding's own advice is one line: load it over HTTPS. The GOOD tab shows the same page with the resource moved to a TLS origin.
<!-- served from https://app.example -->
<head>
<script src="http://cdn.example/app.js"></script> <!-- active: MEDIUM -->
<link rel="stylesheet" href="http://cdn.example/site.css"> <!-- active: MEDIUM -->
</head>
<body>
<iframe src="http://widget.example/"></iframe> <!-- active: MEDIUM -->
<img src="http://img.example/logo.png" alt=""> <!-- passive: LOW -->
</body><!-- served from https://app.example -->
<head>
<script src="https://cdn.example/app.js"></script>
<link rel="stylesheet" href="https://cdn.example/site.css">
</head>
<body>
<iframe src="https://widget.example/"></iframe>
<img src="https://img.example/logo.png" alt="">
</body>
// Every resource host must serve TLS. A protocol-relative "//cdn.example/…"
// also resolves to https on an HTTPS page and is not flagged.Reference: what the code does
| Item | Value | Where it comes from |
|---|---|---|
| Input | snapshot HTML + first-party JS, joined with a newline | html_content, first_party_js_content |
| Precondition | scanned URL scheme is https; otherwise no findings | urlparse(snapshot.url).scheme |
| Active tags | script[src], link[href], iframe[src] → MEDIUM | _ACTIVE_PATTERNS |
| Passive tags | img[src] → LOW | _PASSIVE_PATTERNS |
| Match rule | attribute value starts with the literal http://, case-insensitive tag names | the four regexes |
| Dedup / cap | one finding per tag + URL; at most 10 per tag type | seen, _MAX_FINDINGS_PER_KIND |
| Finding | category MIXED_CONTENT, source DAST_URL, confidence 0.9, method GET, endpoint = page URL | _build_finding |
| Network | none: the scanner never fetches the resource | module docstring |
| Scan depth | runs at both quick and deep | factory.py DAST list |
It does not fetch the resource, so it cannot tell whether the http:// host would have redirected to HTTPS. It does not read CSS url() values, <video>, <audio>, <object> or fetch() calls, and it does not evaluate a Content-Security-Policy: upgrade-insecure-requests header that would have fixed the problem at the browser.