Original Answer
I finally found the answer, in this RFC about CORS-RFC1918 from a Chrome-team member. To sum it up, Chrome has implemented CORS-RFC1918, which prevents public network resources from requesting private-network resources - unless the public-network resource is secure (HTTPS) and the private-network resource provides appropriate (yet-undefined) CORS headers.
There's also a Chrome flag you can change to disable the new behavior for now:
chrome://flags/#block-insecure-private-network-requests
Disabling that flag does mean you're re-opening the security hole that Chrome's new behavior is meant to close.
Update 2021: A few months after I posted this question, the flag I referenced in my original answer was removed, and instead of disabling a security feature I was forced to solve the problem more satisfactorily by serving assets over HTTPS.
Update 2022: Chrome 98 is out, and it introduces support for Preflight requests. According to the announcement, failed requests are supposed to produce a warning and have no other effect, but in my case they are full errors that break my development sites. So I had to add middleware to teach webpack-dev-server
how to serve preflight requests.
Private Network Access (formerly CORS-RFC1918) is a specification that forbids requests from less private network resources to more private network resources. Like HTTP to HTTPS, or a remote host to localhost.
The ultimate solution was to add a self-signed certificate and middleware which enabled requests from my remote dev server to my localhost webpack-dev-server
for assets.
Generate certificates
cd path/to/.ssl
npx mkcert create-cert
Configure webpack-dev-server
to use certificates and serve preflight requests
module.exports = {
//...
devServer: {
https: {
key: readFileSync("./.ssl/cert.key"),
cert: readFileSync("./.ssl/cert.crt"),
cacert: readFileSync("./.ssl/ca.crt"),
},
allowedHosts: ".example.dev", // should match host in origin below
setupMiddlewares(middlewares, devServer) {
// Serve OPTIONS requests
devServer.app.options('*', (req, res) => {
// Only serve if request has expected origin header
if (/^https:\/\/example\.dev$/.test(req.headers.origin)) {
res.set({
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Private-Network": "true",
// Using * results in error if request includes credentials
"Access-Control-Allow-Origin": req.headers.origin,
})
res.sendStatus(200)
}
}
return middlewares
}
}
}
Trust certificates
- Right click
ca.crt
in Windows Explorer and select Install Certificate
- Select Current User.
- Choose Place all certificates in the following store, then Browse..., and select Trusted Root Certification Authorities.
- Finish.
Firefox-specific instructions
Firefox doesn't respect your authoritah! by default. Configure it to do so with these steps:
- Type
about:config
into the address bar
- Search for
security.enterprise_roots.enabled
- Toggle the setting to
true
bootstrap.js
hosted on cloudflare. My understanding is that it should block resources loaded from "more private" endpoints and I hardly see how couldflate could be considered more private in this regard. Could it be considering the proxy address rather than the DNS resolution for the target? – Ciccia