Thursday, May 23, 2013

Securely Importing JS Module, Part 2 - Block Element Referencing

..."The summary being - you can't allow anyone else reference to either your element OR your callback function. But HOW??"

That's the question, isn't it: How do you create a script element, add it to the page, make it load an external script, without ANYONE being able to get reference to it?

Think about it this way - how does anyone on the page get reference to anything else? 

Answer: calls, global scope, or DOM. 

Internally scoping blocks the 2nd option. So DOM and function call remain. Your code certainly isn't making any calls, but the page is - all the time - through events. So this list becomes: "Events and DOM reference".  DOM reference can be blocked simply by removing your script element from the DOM - the only way to guarantee no one gets reference to it directly is by removing the element (from the DOM) before the script that attached the element (to the DOM) is finished running. Obviously this is not enough time for the script to actually load - and every browser handles what happens next differently. Grrrrr.

Firefox just runs the script no matter what. Yay Firefox! Or boo?? Maybe I should be concerned that I can do this...?

IE, unsurprisingly, makes my life very difficult. In IE, the element has to be attached to the DOM when the call tree makes it's way back up to the top. This means that I have to give up focus. Fortunately, I still get first dibs on adding event listeners on the element (since I made it), and by adding listeners on the elements mutation events, I can stop propagation of the events, and on modern IE's, stop immediate propagation, and Remove the element from the DOM. Since these events start on my new element, I can stop them before they reach anyone else. Except one, which in IE7,8 still hits the parent element of the script, but by then I've removed the element and there's no reference to it in the event, so "whew! close one". The only "catch" is that I have to keep on repeating this till the browser actually DOES have the script loaded, cached, and runs it in between my attach and my remove. But I CAN do it.

Suppressing the appropriate events on all three major browsers, and for all their (sometimes crappy) versions was a ROYAL PAIN in my rear. But I managed it. With a few caveats.

See my next post for those details...

So.
Since I can block* reference to the element, no one can add listeners, therefore no one can fake/counterfeit my loading of the external resource. But there still has to be a pipeline between the loaded resource and the loader. The loaded JS will run on the page scope, nothing I can do about that, so I have to find a way for it to run, then load it into my own private scope, and remove the traces of it on the global scope, before anyone else can do anything about it. One way would be to pass a "key" from within the private scope, to the loaded JS, which then uses that "key" to set a global object which can only be referenced using that "key". A very helpful fact is that the event listeners on the element are guaranteed to run immediately after the loaded script runs (the onload attribute being first, the eventListeners second, and in order. yes, they actually run in order in my tests).  Given that fact, I don't actually need a private key, I just need an agreed upon reference that will be on the global scope.

At this point we just have two scripts, one running right after the other. Guaranteed. So it becomes a simple thing to pass information from the first to the second, and then remove the traces of the first one. The second script is in a private scope, so BOOM, we have secure loading.

No entity on the page can possibly even know anything just happened.

As I said, see my next post for some details

No comments:

Post a Comment