I recently shared a notebook with a mailing list, and one of the list members objected to the “security blunder” of pulling in resources from Gist and jsdeliver. This particular person runs NoScript on Firefox, it should be said.
By design, Observable is friendly to pulling resources from other sites. Also by design, I made use of those features. What can I do to make sure my notebooks are trustworthy to visitors like the one I mentioned? It is hard to appeal to the browser’s same-origin policy, when we often use sites that set very permissive CORS headers, seemingly eroding the trust we should be seeking to build.
Observable’s security model is that notebook content runs in a sandboxed, crossorigin iframe. This prevents any malicious content in notebooks from being able to access the surrounding page, so the risk of malicious JavaScript modifying the Observable UI, stealing credentials, or other nefarious activity, is nullified. It’s true that on other websites, loading resources from third-parties is a risky proposal given that jsdelivr or unpkg or another site could be taken over. But in the case of Observable, the impact of that would be minimal.
That said, some folks object to loading data cross-origin, and there are always a few with JavaScript turned entirely off. I expect that using the internet this way must be tough, and as a site that supports dynamic JavaScript-based visualizations, it’s unlikely that we’d be able to sate that desire.
The other thing I would advise here, both for security and more generally for reliability, is version pinning. For example, if you are loading a data file from a Gist, you can pin the exact SHA of the Gist version to guarantee that it won’t change. Similarly when you require a JavaScript library, you can pin the version (either require("d3@5") for the major version or require("d3@5.9.2") for an exact version).
In the future we intend to provide automatic version pinning, similar to a yarn.lock file, for both require and import. That will possibly also include integrity hashes as another layer of protection.
I don’t follow what this has to do with CORS I guess. I mean, everything tom and Mike said makes sense, but doesn’t really have anything to do with CORS, the same sorts of things apply for loading a script tag you don’t host yourself etc. I thought CORS protects you against sites “hijacking” an existing session you have on that other site to change state on that site, i.e. via a POST request over AJAX. I mean, in the case of loading data from a gist, what’s the potential for abuse there? I’m definitely not saying there isn’t one, I’m genuinely curious.
I think you mistakenly replied on this thread, FYI. You might want to reply again on the correct one… though it looks like @bgchen followed the thread here successfully.
Thank you, Mike and Tom, for the good information and advice.
@davertron, I probably should not have put CORS in the title; I confess I have trouble separating the different scenarios in my head. They seem linked because the same-origin policy favors “self-contained” sites, those which clearly aren’t loading resources (which could be scary JavaScript!) from mysterious places. To my NoScript-using friend, such a site seems easier to trust. However, it is so easy to set permissive CORS headers, I’m unclear on how they protect us. Certainly every bit of adware on the web happily allows its use from any domain, as does Google Analytics, jsdeliver, unpkg, gist, etc. I think I need to see a sequence diagram of a CORS-defeated attack scenario!
Tom, that’s great to hear. Yes, I imagine the modern web must be quite unpleasant to someone using NoScript. Evidently NoScript does not distinguish requests coming from inside an iFrame. Even if it did, I suspect that would still fail to combat the FUD involved.
Can the same assurance be provided when notebook cells are embedded in another page? In short, is there still an iFrame in use in that case?
The Same Origin Policy (SOP) is the policy browsers implement to prevent vulnerabilities via Cross Site Scripting (XSS). In other words, the browser would not allow any site to make a request to any other site. It would prevent different origins from interacting with each other through such requests, like AJAX. This policy exists because it is too easy to inject a link to a javascript file that is on a different domain. This is a security risk - you really only want code that comes from the site you are on to execute and not just any code that is out there.
The Cross Origin Resource Sharing (CORS) is one of the few techniques for relaxing the SOP. Because SOP is “on” by default, setting CORS at the server-side will allow a request to be sent to the server via an XMLHttpRequest even if the request was sent from a different domain. This becomes useful if your server was intended to serve requests from other domains (e.g. if you are providing an API).
JSON with Padding is just a way to circumvent same-origin policy, when CORS is not an option. This is risky and a bad practice. Avoid using this.
If you want to bypass that restriction when fetching the contents with fetch API or XMLHttpRequest in javascript, you can use a proxy server so that it sets the header Access-Control-Allow-Origin to *.
If you need to enable CORS on the server in case of localhost, you need to have the following on request header.