What does the upgrade-insecure-requests policy directive do that a browser's automatic upgrade of mixed content does not?
answer
- same rewrite, different reach
- one is built in, one is declared
- it also covers what would be blocked
- it runs before the check, not instead
- cross-origin top-level navigation stays as written
basics
~20 sAutoupgrade rewrites only upgradeable mixed content - images, audio and video. The upgrade-insecure-requests directive rewrites every insecure request the document makes, blockable ones included, and it runs before the mixed-content check, so the check finds nothing left to block.
solid answer
~50 s`upgrade-insecure-requests` is a Content-Security-Policy directive that sets the document's **insecure requests policy** to *Upgrade*, and it is inherited by nested contexts. Two things separate it from the built-in autoupgrade. First, **scope**: autoupgrade touches only the narrow upgradeable set, while this directive rewrites blockable requests too - scripts, stylesheets, data requests - so a plaintext script becomes an `https` script instead of a failure. Second, **ordering**: the rewrite happens *before* the mixed-content check, so by the time the check runs the URL is already `https` and the check is a no-op. It does not permit mixed content; it removes it. Navigations follow one carve-out: form submissions and nested navigations are upgraded, a **cross-origin top-level navigation is not**. Browsers also send `Upgrade-Insecure-Requests: 1` on navigation requests so a server can redirect - and that response needs `Vary: Upgrade-Insecure-Requests` so a shared cache does not serve the redirect to clients that never asked.
code
http · 13 linesGET /viewer HTTP/1.1
Host: archive.example.org
Upgrade-Insecure-Requests: 1
HTTP/1.1 307 Temporary Redirect
Location: https://archive.example.org/viewer
Vary: Upgrade-Insecure-Requests
---
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy: upgrade-insecure-requestsgo deeper
Recall that this is a Content-Security-Policy directive an author opts into, and that it rewrites plaintext subresource URLs to https rather than permitting them to load.
Explain the two differences from the built-in autoupgrade - it covers blockable requests too, and it runs before the mixed-content check - and name the request header a browser sends alongside it.
Show the operational consequences: the rewrite is a bet that every referenced host serves https, the redirect response needs Vary, and a plaintext script arriving over https is the tell that the policy is live.
Weigh the directive as a bridge rather than a destination: it holds a migration together while stored addresses are corrected, and it does nothing for the consumers of those addresses that are not browsers.
## Three different upgrades, three different moments This material is full of things that rewrite `http` to `https`, and the interview question is really *which one, and when*: 1. **Autoupgrade** - the browser's built-in handling of **upgradeable** mixed content. No opt-in, no directive, and it touches only an image request with an empty initiator, audio, or video. 2. **`upgrade-insecure-requests`** - a **Content-Security-Policy directive** the author ships. It sets the insecure requests policy on the document's settings object to *Upgrade*, and it applies to **every** insecure request the document makes. 3. Scheme rewriting driven by stored per-host enforcement state, which happens before a request is made at all - a different mechanism on a sibling leaf. This leaf's question is the gap between the first two. ## What the directive adds - **Scope.** Autoupgrade rescues a plate and a recording. The directive also rescues the things that would otherwise be *failed*: scripts, stylesheets, frames, data requests. For an archive whose catalogue records hold absolute plaintext addresses, that is the difference between a viewer that half-renders and one that works. - **Ordering.** The upgrade runs **before** the mixed-content check. By the time the check looks at the request, the URL is already `https`, so the check has nothing to act on and is effectively a no-op. It is worth saying this precisely, because the common misreading is that the directive *allows* mixed content. It does the opposite: it removes the insecure request rather than permitting it. - **Inheritance.** The policy is inherited by nested contexts, so a frame the document creates is covered without its own header. ## The navigation carve-out Not every navigation is upgraded, and the exceptions are deliberate: | Request | Upgraded? | |---|---| | any subresource request from the document | yes | | a form submission from the document | yes | | a nested context's navigation | yes | | a **top-level** navigation to a **different** origin | **no** | The browser keeps an *upgrade insecure navigations set* of the hosts whose policy asked for upgrading, and top-level navigation is upgraded only for a host in that set. The reason is that you may declare a policy for your own documents; you may not silently rewrite where a visitor goes when they leave you. A link out to a plaintext page on someone else's host stays as written. ## The request header, and the caching trap A browser applying this policy also attaches **`Upgrade-Insecure-Requests: 1`** to navigation requests. That is a **request** header - note the direction, because the directive with the same words is delivered on a **response** - and it exists so a server can feature-detect: a client that sends it has declared it can handle being redirected to the secure address, so the server may answer with a redirect instead of the plaintext document. That is where the cache bites. Two clients ask for the same plaintext URL; one sent the header and is redirected, the other did not and gets the document. A shared cache keyed on the URL alone will hand one client the other's answer. The deployment advice is therefore to send **`Vary: Upgrade-Insecure-Requests`** on that response so the cache keys on the header too. ## What it does not buy you 1. **It is not a promise that the upgraded URL exists.** The rewrite changes the scheme and drops an explicit default plaintext port; it does not change host, path or query, and it does not check anything first. If `plates.example.org` serves nothing over `https`, the directive turns a plate that used to fail loudly into a plate that still fails - just after a different request. 2. **It is not a substitute for fixing the records.** Every page load still carries the rewrite, and anything that reads those addresses outside a browser - an export job, a feed, a partner integration - gets no upgrade at all, because the directive is a browser-side document policy. 3. **It is not a relaxation.** Nothing about it makes an insecure request permissible; there is no way to opt a subresource out. ## How to tell which one acted On the console an autoupgraded request and a directive-upgraded request both show an `https` URL you never wrote. The tell is the class of request: if a plaintext **script or stylesheet** arrives over `https` instead of failing, the document's policy is doing it, because the built-in autoupgrade would never have touched it. That single observation is the fastest way to confirm a policy is live on the response you are actually looking at.
- Why does a response carrying this directive also need Vary: Upgrade-Insecure-Requests?Because the server's answer depends on a request header. A client sending `Upgrade-Insecure-Requests: 1` may be redirected to the secure address, while a client that did not gets the document itself. A shared cache keyed only on the URL would serve one client the other's response, so the response must declare that it varies on that header.
- Does the directive let a plaintext subresource load when no https copy exists?No. It rewrites the request rather than permitting it, and there is no opt-out for a particular subresource. If the rewritten `https` URL fails, the request fails - a script that used to be blocked as mixed content is now a script that 404s or cannot connect. The directive is a bet that every host you reference serves the same paths over `https`.
- A visitor clicks a link from the viewer to a plaintext page on a partner's host. Is that upgraded?No. A top-level navigation to a different origin is deliberately excluded; only hosts in the upgrade insecure navigations set have their top-level navigations upgraded. Form submissions and nested navigations are upgraded. The reasoning is that a document may declare a policy over its own requests, not silently rewrite where a visitor goes when they leave it.
saying these in an interview costs you the question
- Says the directive allows mixed content to load
- Thinks it only covers images, audio and video like autoupgrade
- Calls Upgrade-Insecure-Requests a response header
- Assumes the rewrite verifies the https URL exists first
- Expects every outbound link to be upgraded, including cross-origin ones
- Ships the redirect without Vary and blames the shared cache