js-loaders icon indicating copy to clipboard operation
js-loaders copied to clipboard

Same Origin Policy, <script src=>, and load()

Open jorendorff opened this issue 11 years ago • 11 comments

Per @samth, in the browser, cross-origin <script src=> loads do go through the fetch hook, but they quietly skip the translate and link hooks. (This is to avoid revealing cross-origin text to user code.)

Once a cross-origin script is fetched, when we process its imports, we do call normalize, resolve and fetch as normal.

That isn't implemented. It isn't clear how it could be implemented!

  • We do not know before calling fetch whether a URL is cross-origin or not. Redirects and CORS headers make it impossible to know a priori.
  • System.fetch has to fail if you give it a cross-origin URL.
  • We think loader.load() should have feature parity with <script src=>, which means being able to load scripts across origins.

Taken together, this means certain parts of the Loader need to be privileged—they need to be able to parse code from a different origin, which ordinary user scripts aren't even permitted to see. I think we have to design a back channel or extension point between Loader and the browser.

jorendorff avatar May 30 '13 19:05 jorendorff

I suggested this:

  • When System.fetch fails due to the same-origin policy, but <script src=> would succeed, System.fetch calls the reject callback passing a DOMException with the response data stored in an internal property.
  • The Loader's reject callback checks for that internal property on the exception object. Finding it, we let the load proceed (rather than failing as reject normally does) but we skip the translate and link hooks for the current load.

In the code, this would mean browser-loader.js would have to import, at least, a secret function setCrossOriginFetchPayload(exc, payload) from impl.js.

I think it's OK for this to be ugly, within reason.

jorendorff avatar May 30 '13 19:05 jorendorff

I think this is a little over-complicated. I like the user-visible behavior, but I don't think we should spec the weird internal unobservable reject/exception/private state interaction. Just state that the resulting code goes directly to the compiler, without passing through any more hooks.

samth avatar May 30 '13 21:05 samth

It's all observable.

What I proposed is observably different from, say, stack inspection. It's transparent to lambda:

let originalFetch = System.fetch;
System.fetch = (...args) => originalFetch(...args);

Stack inspection would see a non-privileged caller. It's transparent to using setTimeout to sever the stack:

System.fetch = (...args) => setTimeout(() => originalFetch(...args), 0);

Stack inspection would see only non-privileged code.

Another possible implementation would be for System.fetch to examine the fulfill hook and only pass secret data to the real Loader fulfill hook. But that wouldn't be transparent to lambda:

System.fetch = (addr, fulfill, reject) =>
    originalFetch(addr, (s, a) => fulfill(s, a), exc => reject(exc));

What I proposed is transparent to this kind of callback-wrapping. (And I expect this is something users will actually do.)

I am totally open to alternatives, but this weirdness is smack in the middle of a user hook; a great deal about the interaction will inevitably be observable.

jorendorff avatar May 31 '13 12:05 jorendorff

I agree that stack inspection is the wrong solution. I was thinking of your second solution, but if you think that consequence is not ok, then I'm open to your original solution.

One thing to note is that if some opaque response is added (that's what Navigation Controllers do, btw), then we'll change from calling reject to calling fulfill.

samth avatar May 31 '13 12:05 samth

OK, we're on the same page then.

I wonder if what Navigation Controllers plan on doing here is really OK. Even an opaque response exposes success/failure of requests against cross-domain URLs.

jorendorff avatar May 31 '13 15:05 jorendorff

@dherman says that @wycats may have thoughts on this.

jorendorff avatar Jun 07 '13 21:06 jorendorff

The plan of record is to do what the navcontrollers spec does. Same problem, same solution. I'll add a primitive to the implementation and code that shows what the control flow should be if the fetch hook produces an opaque object representing a cross-origin response.

jorendorff avatar Jun 28 '13 22:06 jorendorff

To clarify, if there were CORS headers the translate and link hooks would still work fine though?

guybedford avatar Jul 01 '13 20:07 guybedford

Yes.

jorendorff avatar Jul 01 '13 20:07 jorendorff

Polymorphism for src gets us this. the "plan of record" from 3 months ago is basically right.

jorendorff avatar Oct 09 '13 19:10 jorendorff

This is still open because, as I said 3 months ago: "I'll add a primitive to the implementation and code that shows what the control flow should be..." The decision is made; the implementation needs to catch up.

jorendorff avatar Oct 09 '13 20:10 jorendorff