Open lidel opened 4 years ago
Blocking cookies is a nuclear option that may break some deployments behind reverse proxies that pass all headers. We would need NoWebSecurity
flag (false
by default) in case someone really wants to disable things like Clear-Site-Data
in contexts that provide no origin isolation.
Something to be aware of: executionContexts
and wildcard directives are not recognized by Chromium (bug-898503)
Interesting fact: w3c spec suggests a discrepancy between clearing cookies and storage: https://www.w3.org/TR/2017/WD-clear-site-data-20171130/#grammardef-storage https://www.w3.org/TR/2017/WD-clear-site-data-20171130/#grammardef-cookies
According to the spec storage
is supposed to be purged for origin, while cookies
cleanup is listed as origin + all origins based on subdomains of the current one.
I tested behavior in Chromium 76 and Firefox 74 to see if it negatively impacts subdomain gateways in go-ipfs (https://github.com/ipfs/go-ipfs/pull/6096). This does not seem to be the case: subdomain cookies are not purged if Clear-Site-Data
is present in localhost/ipfs/$cid
301 response.
Sidenote: we could reuse this on locked-down subdomain namespace https://github.com/ipfs/go-ipfs/issues/7318 (think cors.dweb.link/ipfs/
) that is optimized for loading data/subresources and not website roots:
*
)This could be greatly deduplicated and simplified, ideally, we would introduce everything (hardening path gateways; hardening/removing CORS from subdomains; support for long CIDs; support for CORS without compromising any local storage) in a single PR/release.
I would like to suggest taking the restrictions on Permissions-Policy
(which was called Features-Policy
when this issue was opened — @lidel mentions it at the top) further (spec, mdn). When you load a random web page, a number of features are available generally gated by some kind of permission or interaction. That is often okay (or at least it's understood to be a managed risk) but it is open to phishing or other forms of social engineering.
I think that in a pathed context, we can restrict this further and make those powerful capabilities unavailable. The header would look like this:
Permissions-Policy: accelerometer=(),ambient-light-sensor=(),attribution-reporting=(),autoplay=(),battery=(),
bluetooth=(),browsing-topics=(),camera=(),ch-device-memory=(),ch-downlink=(),
ch-dpr=(),ch-ect=(),ch-lang=(),ch-prefers-color-scheme=(),ch-rtt=(),ch-save-data=(),
ch-ua=(),ch-ua-arch=(),ch-ua-bitness=(),ch-ua-full=(),ch-ua-full-version=(),
ch-ua-full-version-list=(),ch-ua-mobile=(),ch-ua-model=(),ch-ua-platform=(),
ch-ua-platform-version=(),ch-ua-reduced=(),ch-ua-wow64=(),ch-viewport-height=(),
ch-viewport-width=(),ch-width=(),clipboard-read=(),clipboard-write=(),conversion-measurement=(),
cross-origin-isolated=(),direct-sockets=(),display-capture=(),document-domain=(),
encrypted-media=(),execution-while-not-rendered=(),execution-while-out-of-viewport=(),
federated-credentials=(),focus-without-user-activation=(),fullscreen=(),gamepad=(),
geolocation=(),gyroscope=(),hid=(),idle-detection=(),interest-cohort=(),
join-ad-interest-group=(),keyboard-map=(),local-fonts=(),magnetometer=(),microphone=(),
midi=(),navigation-override=(),otp-credentials=(),payment=(),picture-in-picture=(),
publickey-credentials-get=(),run-ad-auction=(),screen-wake-lock=(),serial=(),shared-autofill=(),
shared-storage=(),speaker-selection=(),storage-access-api=(),sync-script=(),sync-xhr=(),
trust-token-redemption=(),unload=(),usb=(),vertical-scroll=(),wake-lock=(),web-share=(),
window-placement=(),xr-spatial-tracking=()
So, errr, yeah, your eyes might be bleeding right now and I'm sorry about that. Also, that list grows all the time. It's not great. There are issues about making a blanket "remove anything that might be dangerous" mode but so far they haven't been accepted.
We could decide to keep some things allowed (like camera or sync XHR) if we're worried about breaking legit use cases. But at least this list (which is the most comprehensive I could find — I'm looking into whether there's a reliable way to get an up-to-date list) makes things pretty tight and safe, on top of CSP and clearing the data.
Pretty hardcore, but for sure will do the trick of forcing people to move to subdomain gateways.
I propose we do a test run: set Permissions-Policy
, Clear-Site-Data
and a strict Content-Security-Policy
on ipfs.io
first, in Nginx, and see if there are any screams from the distance.
If the sky does not fall, me and @hacdias can apply this to all path requests in go-libipfs/gateway library.
In addition to Permissions-Policy
, I think the following would be good:
Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval' data: ; form-action 'self'; connect-src 'self' data: ; manifest-src 'none' ; object-src 'none' ; sandbox allow-forms allow-modals allow-scripts allow-top-navigation-by-user-activation
Clear-Site-Data: "cookies", "storage"
X-Content-Type-Options: nosniff
X-Frame-Options: sameorigin
Some quick notes:
Clear-Site-Data
and agree with your original proposal.blob:
which means no file uploads (even locally). One thing that we could add (dynamically) would be the URL up to the CID inclusive (eg. https://ipfs.io/ipfs/someCID/
) instead of 'self'
for default/connect/form.navigate-to
, I think linking to other sites should be ok (but I can change my mind). worker-src
because I don't think that there's much left that a worker could do, but it's easy to add.allow-same-origin
in sandbox
, that's pretty strict and might break things. What this does is that it puts the document in a weird origin of its own, all alone, and every request from that becomes cross-origin, which means that doing things that would require CORS when talking to another server now require CORS when talking to the same server, which is pretty neat. One issue is that it blocks font loading (because fonts require CORS) — maybe that's a problem? Alternatively, we could keep this strong and add Access-Control-Allow-Origin: this.site
on requests that match fonts. I haven't found a way to have this sandboxing but not for fonts.nosniff
, threw it in for completeness.X-Frame-Options
in case anyone is trying to inject this in an iframe elsewhere.I did some very superficial testing on static content, and it works. If you want to play with it locally, run this with a path to a dir to serve from:
#!/usr/bin/env node
import process from 'process';
import express from 'express';
const app = express();
app.use((req, res, next) => {
res.set({
'Permissions-Policy': 'accelerometer=(),ambient-light-sensor=(),attribution-reporting=(),autoplay=(),battery=(),bluetooth=(),browsing-topics=(),camera=(),ch-device-memory=(),ch-downlink=(),ch-dpr=(),ch-ect=(),ch-lang=(),ch-prefers-color-scheme=(),ch-rtt=(),ch-save-data=(),ch-ua=(),ch-ua-arch=(),ch-ua-bitness=(),ch-ua-full=(),ch-ua-full-version=(),ch-ua-full-version-list=(),ch-ua-mobile=(),ch-ua-model=(),ch-ua-platform=(),ch-ua-platform-version=(),ch-ua-reduced=(),ch-ua-wow64=(),ch-viewport-height=(),ch-viewport-width=(),ch-width=(),clipboard-read=(),clipboard-write=(),conversion-measurement=(),cross-origin-isolated=(),direct-sockets=(),display-capture=(),document-domain=(),encrypted-media=(),execution-while-not-rendered=(),execution-while-out-of-viewport=(),federated-credentials=(),focus-without-user-activation=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),hid=(),idle-detection=(),interest-cohort=(),join-ad-interest-group=(),keyboard-map=(),local-fonts=(),magnetometer=(),microphone=(),midi=(),navigation-override=(),otp-credentials=(),payment=(),picture-in-picture=(),publickey-credentials-get=(),run-ad-auction=(),screen-wake-lock=(),serial=(),shared-autofill=(),shared-storage=(),speaker-selection=(),storage-access-api=(),sync-script=(),sync-xhr=(),trust-token-redemption=(),unload=(),usb=(),vertical-scroll=(),wake-lock=(),web-share=(),window-placement=(),xr-spatial-tracking=()',
'Content-Security-Policy': `default-src 'self' 'unsafe-inline' 'unsafe-eval' data: ; form-action 'self'; connect-src 'self' data: ; manifest-src 'none' ; object-src 'none' ; sandbox allow-forms allow-modals allow-scripts allow-top-navigation-by-user-activation`,
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'sameorigin',
});
next();
});
app.use(express.static(process.argv[2]));
app.listen(8888, () => console.warn(`Listening on http://localhost:8888`));
Motivation
Websites loaded via path gateway are able to access cookies and storage of the entire domain. While we are moving to subdomain gateways (#89), requests made to path gateways will continue to lack origin isolation between content roots. Some will be redirected to subdomain ones, but we should look into other means of improving the situation.
TL;DR
Headers to investigate
Clear-Site-Data header
We could leverage
Clear-Site-Data
header and send a hint to user agent to clear any preexisting cookies and storage. This is a "nuclear option", but could incentivize users to switch to subdomain gateways when access Web APIs relying on Origin is required.Note: this requires native subdomain support (https://github.com/ipfs/go-ipfs/issues/6498) to land first.
To purge cookies and storage without reloading any contexts, below header would be returned with every response from
/ipfs/{cid}
and/ipns/{foo}
paths:Content-Security-Policy
Disabling JS and various security features.
Ref. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
Highlights:
sandbox
directive may be the most elegant way, it would apply the same logic as<iframe>
sandbox for entire page.Prior art:
content-security-policy: default-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: https://*.w3s.link https://*.nftstorage.link https://*.dweb.link https://ipfs.io/ipfs/ https://*.githubusercontent.com https://tableland.network https://*.tableland.network ; form-action 'self'; navigate-to 'self'; connect-src 'self' blob: data: https://*.w3s.link https://*.nftstorage.link https://*.dweb.link https://ipfs.io/ipfs/ https://*.githubusercontent.com https://tableland.network https://*.tableland.network ; report-to csp-endpoint ; report-uri https://csp-report-to.web3.storage
reporting-endpoints: csp-endpoint="https://csp-report-to.web3.storage"
Feature-Policy
Another way of disabling various APIs and behaviors
Ref. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy
dweb.link
is on https://publicsuffix.org/, but other gateways may not be)TODO
Gateway.HTTPHeaders
in go-ipfs config may be enough for initial tests