A page calls navigator.clipboard.writeText() and registers a service worker. Both work when the site is served from http://localhost:3000, but on http://staging.internal the browser reports navigator.clipboard and navigator.serviceWorker as undefined. What rule is the browser applying, and which origins satisfy it?
answer
- not a bug, a gating rule
- scheme and host decide it
- loopback is the exception
- window.isSecureContext
- ancestors must qualify too
basics
~20 sPowerful browser APIs are gated on secure contexts: HTTPS and WSS origins plus loopback hosts (localhost, 127.0.0.1, ::1). Plain HTTP on staging is not one, so those APIs are absent entirely; window.isSecureContext reports the verdict.
solid answer
~40 sThose APIs are restricted to **secure contexts**. A browsing context is secure when its own origin is potentially trustworthy — an `https:` or `wss:` origin, or a loopback host such as `localhost`, `127.0.0.1` or `::1` — and every ancestor document is too. `http://staging.internal` is plain HTTP on a non-loopback host, so the whole class of gated APIs is simply not attached: `navigator.serviceWorker`, `navigator.clipboard` and `crypto.subtle` are `undefined`, which is why it reads as a missing feature rather than a permission error. Older APIs that predate the rule, like `navigator.geolocation`, still exist but fail at call time. The check is `window.isSecureContext`, also available inside workers. The fix is a real certificate on staging — the loopback exemption exists so local development needs no certificate, and no other private address or invented hostname inherits it.
code
javascript · 12 linesfunction copyToClipboard(text) {
if (!window.isSecureContext) {
console.warn('Insecure origin: clipboard API unavailable on', location.origin);
return Promise.resolve(false);
}
if (!navigator.clipboard?.writeText) {
return Promise.resolve(false);
}
return navigator.clipboard.writeText(text).then(() => true, () => false);
}
copyToClipboard('hello').then((ok) => console.log('copied:', ok));go deeper
Be able to say plainly that modern browser APIs need HTTPS, that localhost is deliberately exempt so you can develop without a certificate, and that the symptom is usually an undefined object rather than an error message.
Explain what makes an origin potentially trustworthy — scheme plus loopback host — and why a LAN IP or a .internal hostname is excluded. Mention window.isSecureContext and why some APIs disappear while older ones fail at call time.
Show you can diagnose this from a bug report: identify it from the TypeError shape, check the ancestor chain for embedded documents, and drive the fix to a certificate on the environment rather than a code workaround or a disabled-security flag.
Own the consequence for the delivery pipeline: every environment where the product is exercised, including preview deploys and device labs, needs trustworthy origins, otherwise whole features go untested until production. Decide whether that is a public CA, an internal CA rolled to test devices, or tunnelling.
## Why the API vanished instead of failing Web platform features that expose the user's device, location, identity, or the ability to intercept a site's own network traffic are restricted to *secure contexts*. A secure context is not quite "a page served over HTTPS": it is a `Window` or a `WorkerGlobalScope` whose own origin is **potentially trustworthy** and whose entire chain of ancestor browsing contexts is potentially trustworthy as well. When the context is not secure, the browser does not attach the gated interface at all. `navigator.serviceWorker`, `navigator.clipboard`, `crypto.subtle` and `navigator.storage` are `undefined`, so the first symptom a developer sees is a language-level error such as `TypeError: Cannot read properties of undefined (reading 'writeText')`. That is a deliberate design: the feature is absent, not denied, so a feature-detection check naturally routes around it. A handful of older APIs predate the rule and keep their shape for compatibility. `navigator.geolocation` still exists on an insecure origin, but `getCurrentPosition()` invokes the error callback with a permission-denied error instead of prompting. So the same underlying rule produces two different-looking symptoms depending on the age of the API. ## What counts as potentially trustworthy The Secure Contexts specification defines the set. In practice: - Any origin whose scheme is `https:` or `wss:`. - Loopback hosts: `localhost` and `*.localhost`, the `127.0.0.0/8` IPv4 range, and `::1`. - `file:` URLs, though browsers differ on which APIs they actually enable there. - Browser-internal schemes a user agent registers as trustworthy, such as `chrome-extension:`. Just as important is what is *not* in the set. A private LAN address such as `http://192.168.1.10` or `http://10.0.0.5` is not trustworthy — it is reachable from other machines on the network, so there is a network attacker to defend against. Neither is an invented hostname like `http://staging.internal` or `http://myapp.local`, even if it only resolves inside a VPN. The browser has no way to know your intranet is friendly. ## Why localhost gets an exemption Traffic to a loopback address never leaves the machine, so no network position exists from which to tamper with it. Demanding a certificate for local development would be pure friction with no security gain, so the loopback exemption exists purely to keep the development loop cheap. The exemption is keyed on the **host**, not on "development mode". The classic trap follows directly: you test on your laptop at `http://localhost:3000`, then open the same dev server from your phone at `http://192.168.1.10:3000` and every gated feature disappears. Nothing about the code changed; the host did. ## The ancestor rule Secure-context status is inherited down the frame tree. An HTTPS document embedded in an iframe whose top-level page was loaded over plain HTTP is **not** a secure context, because an attacker who controls the outer page controls the inner one anyway. So an embedded widget can be served perfectly over HTTPS and still find its gated APIs missing — the embedder is the problem. ## Detecting it in code `window.isSecureContext` is a boolean available on both `Window` and `WorkerGlobalScope`. Use it for a clear diagnostic rather than letting a `TypeError` surface: ```js if (!window.isSecureContext) { console.warn('Insecure origin: clipboard, service workers and crypto.subtle are unavailable.'); } ``` For a specific feature, prefer detecting the feature itself, since secure context is a necessary but not sufficient condition — a browser may still lack the API, or a Permissions Policy may have disabled it: ```js if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); } else { showManualCopyFallback(text); } ``` ## Fixing the staging environment The only real fix is to make the origin trustworthy: issue a certificate for the staging hostname from a public CA or from an internal CA that the test machines already trust, and serve over HTTPS. Common shortcuts that developers reach for instead — a tunnelling service that gives you an HTTPS hostname, or forwarding the remote port to a local loopback port so the browser sees `http://localhost` — work because they change the origin the browser evaluates, not because they change the rule. What does not work is a polyfill. There is no user-land implementation of a service worker or of `crypto.subtle`'s primitives with the same guarantees, and the absence is the browser enforcing a security boundary rather than a gap in its feature set.
- Why does geolocation behave differently from the clipboard API on an insecure origin?`navigator.geolocation` predates the secure-context rule, so removing the object would have broken existing pages that feature-detect it. Browsers kept the interface and moved the rejection to call time: `getCurrentPosition()` invokes the error callback with a permission-denied error. Newer APIs are simply never attached, which makes feature detection the correct check.
- A widget you serve over HTTPS reports isSecureContext as false in one customer's site. What do you tell them?Secure-context status requires the whole ancestor chain to be potentially trustworthy. Their top-level page is being served over plain HTTP, so your HTTPS iframe inherits an insecure context — an attacker on the wire controls the outer page and therefore the inner one. Nothing in the widget can fix it; the embedding page has to move to HTTPS.
- Does being in a secure context mean a gated API will work?No — it is necessary, not sufficient. The browser may not implement the feature at all, a Permissions Policy may have disabled it for the document, the user may have denied the corresponding permission, or the call may additionally require user activation. Always feature-detect the specific entry point and handle failure at call time.
saying these in an interview costs you the question
- Thinks HTTPS only matters for pages handling passwords or payments
- Believes any private or LAN IP counts as localhost
- Proposes polyfilling navigator.clipboard or the service worker
- Assumes an HTTPS iframe is secure regardless of the parent page
- Blames the browser version instead of checking the URL scheme