skip to content

What does the upgrade-insecure-requests policy directive do that a browser's automatic upgrade of mixed content does not?

level: seniorimportance: should knowfreq 44%

answer

  1. same rewrite, different reach
  2. one is built in, one is declared
  3. it also covers what would be blocked
  4. it runs before the check, not instead
  5. cross-origin top-level navigation stays as written

basics

~20 s

Autoupgrade 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 lines
http
GET /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-requests

go deeper

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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