Tuesday, 26 August 2014

Private Source Maps

Since the launch of source maps, you have been asking for a way to support source maps without having to make all your code public. Today I'm glad to announce private source map support in Errorception.

Rather than explain how the feature works, I shall take you through the thought process behind private source maps support. It has taken 2 months to fine-tune the experience. I was actively working with a bunch of people in a private beta for this feature. (Thanks to you fine folk. You are awesome!)

A quick primer: Source map files, generated by your minifier, contains a mapping between your original source code and your minfied code. Your source map files are linked to from within your minified code, using a //#sourceMappingUrl=... comment in your minified code, inserted by your minifier. The source map file doesn't contain the original source code itself — just the mappings. Instead, the source map file links to your source code, which a consumer (like Errorception, or your browser's dev tools) will have to fetch separately.

The First Cut

When I had launched source map support two months ago, I had already written the code for private source maps too. The way I intended it to work was that I'd ask you to upload your source map files and your source code to Errorception as part of your deploy script. I wasn't sure how everyone would take to the idea of making an API call from within their deploy script, so I decided to start a private beta and work with a few select people directly to see what their experience would be.

However, everyone disliked the idea of making API calls from their deploy scripts. Every single one of them! Here's the big reasons:

  • It introduces a remote network dependency in the deploy script. This means that the deployment's success or failure depends on a third-party service (Errorception in this case), and on the network. This simply isn't a good idea.
  • If, for some reason, the API call fails, it will cause inconsistency in data at Errorception's end. It's not just that source maps won't work, it's worse than that — source maps will point to the wrong location in your files! That's misleading, and actively inhibits easy debugging. Not good at all.
  • Making API calls isn't really the simplest of things to do, especially when you have to do it from a deploy script. You'd first have to prepare your bundle by zipping up your files (which is likely to be a pretty big zip), then send it across to Errorception using some HTTP client, after you've figured out how to do multipart uploads. To deal with failures, you'd also have to implement some kind of retry mechanism. It doesn't have to be this complicated.
  • As an aside, this is also tremendously wasteful. Unless your errors are all over your code-base, Errorception doesn't need all your files at all. The bulk of the zip you'll have uploaded won't be useful for debugging at all, but there's no way to know which parts are needed beforehand.

Errorception's Crawler

Instead of asking you to upload your source maps and source code to Errorception, we decided that you could instead upload the files to your own web server/CDN. This is much more easy to do, considering that your deploy script already does this with your built code. It also eliminates the external network request to Errorception at deploy-time, which makes your deployment script simpler and far more reliable.

Errorception already has a crawler that crawls your site to get the required JavaScript files, needed both for the code view and for source maps support. This crawler is actually pretty powerful, and has evolved quite a bit within its few months of existence. It has baked into it a couple of really cool ideas to ensure consistency of data. This ensures that Errorception can always show you the correct version of the file that caused the error, even if the file has since changed with newer deployments. It is also resilient to network failures, retrying failed requests intelligently, and retroactively updating existing errors with the file. (Building this has been pretty crazy engineering-wise — heck, I even had to create a versioned file store to manage file versions correctly!)

Let's say you have an error in one of your script files. Errorception's crawler parses your JavaScript file to look for the //#sourceMappingUrl pragma comment. If it finds this comment, it already has everything it needs to crawl your source map files and your source code. This is how public source maps work already.

However, many people would rather not have that //#sourceMappingUrl comment in their code. That's because this comment is the one link to all of your code, and will let anyone with a browser access the original unminified source code.

Private Source Maps

If this //#sourceMappingUrl comment is removed from your minified file, your source maps are now effectively private. This is because no one can know where you've put your files if there isn't a link pointing them to them. HTTP doesn't have any discovery mechanism built in, and a secret path is just as unguessable as a password, since no one else knows the secret. (This assumes that you don't have directory listing turned on.)

So, this is how private source maps work in Errorception: You specify a secret folder name in Errorception's Settings > Source Maps. This secret folder should be as unguessable as you would want a password to be. Then, modify your build/deploy script such that Errorception can find your source map on your web server by constructing a path that incorporates this secret folder. (More about this below.) Once the crawler gets your source map file, it has everything it needs to figure its way about your code.

Examples

Here's how Errorception uses your secret folder to discover your source map file: Let's say an error occurred in your script at http://example.com/script.js, and you've specified your secret folder to be deadbeef, Errorception will look for the source map at http://example.com/deadbeef/script.js.map. That is, it looks inside a secret folder (which is expected to be a sibling of the script file), for a file that has the same name as the script file with a .map appended to it.

To give you another example, if the error was in http://example.com/a/b/c/script.js and you specify your secret folder to be secret, Errorception will look for the source map at http://example.com/a/b/c/secret/script.js.map.

All of this sounds complicated, but it really isn't. In fact, in most cases, it will simply be one or two lines in your deploy script — to strip the //#sourceMappingUrl comment, and to copy your source map files and original source code to the secret folder. Doing stuff like copying and modifying files is exactly what deploy scripts are good at, so it plays to the strengths of the deploy script too.

But this isn't really private at all

Yes, in a sense, this is really only security by obscurity. However, it is security by obscurity in the same way that passwords are security by obscurity. As with good passwords, a good folder name would be just as unguessable. Since it is impossible to discover anything over HTTP if you don't explicitly link to it, unwanted access to your source code should be near-impossible.

That said, I can see how you might be worried that all your files are still public. I'm open to consider even more stringent security, if you like. Feel free to get in touch. However, like I said, you shouldn't have to worry about it in the first place.

Also, Errorception turned three last week. Drink one for Errorception! Cheers!

7 comments:

  1. Hi Rakesh,

    Nice job with private source maps, that's something I've wanted for a long time. I do have one feature request though. Would it be possible to add one more search path for the source maps to path everything relative to the secret folder? Instead of sprinkling my secret directories around on the file system, I'd rather keep all of the source maps in one place.

    So for your example with http://example.com/a/b/c/script.js I'd like to put the map in http://example.com/secret/a/b/c/script.js.map

    What do you think?

    ReplyDelete
    Replies
    1. Hi EWorley,

      That's the one part of the story I left out for brevity, so it's interesting that you brought it up. :)

      My first cut of private source maps with the secret folder used the URL resolution scheme that you mentioned. However, this turned out to be very difficult to integrate with, across all the folks in the beta. It's a very subtle problem, one that I didn't certainly realise at the start. It will be difficult to convey here, but I'll try.

      At it's heart is the problem that your build tool will generally create your built script file and your source map at the same folder level. Once that's done, you will need to cp/mv your source map to a different location. This will need to be done regardless of the URL scheme we choose. However, if you have to move files to a directory at the root of your directory tree, you are now operating outside of the build folder, breaking locality of your scripts directory. This means that your build script will now need to know and operate on paths outside of it's own working directory. That simply doesn't feel right. Because the locality is broken, the complexity of the deploy script increases, as there is now also the secret directory to keep track of. This introduces room for error, which subtly increases frustration when trying to set things up the first time. Considering that most people in the beta were running into this problem, it wasn't turning out to be a good enough experience.

      That said, ideally, the problem you mention should not really be an issue at all. You usually won't have secret folders all over the place. In fact, you would typically have only one secret folder. This is because, regardless of how your source tree looks, your built files will only be very few in number (maybe just one or two files), and there isn't much of an advantage to structuring them hierarchically. You might just dump your built files into one `built` folder. So, your secret folder then only lives at `built/secret`, and you won't have the need to create multiple secret folders.

      All that said, I'm making some assumptions about your setup here, and there's that thing they say about assumption being the mother of all... problems. If your setup doesn't lend itself well to this arrangement, please feel free to mail me (rakeshpai@errorception.com), and I would love to understand and work with your setup and figure out a solution that can work across the board. However, once again, the current URL scheme has been chosen after ironing out lots of subtle "usability" issues in the build/deploy process, so I suggest you to give it a shot.

      Delete
  2. Hi Rakesh,

    Awesome job with source maps and I love that errorception keeps getting better! I'm about to deploy source maps and was thinking about using private source maps once things were working. However, I realized this would probably be an inconvenience for our own engineers since our browsers wouldn't see the source maps either. Obviously this is by design, but now I'm thinking there might be a simpler way.

    If we only allow whitelisted IP addresses to access *.map files (and optionally, the actual sources), we could still view source on errors from our own machines. We could enhance this by adding cookie based whitelisting too. The best part is, this doesn't even require any special implementation on your end or changes in the build process.

    Is there a finite list of IP addresses that errorception will crawl from so we could whitelist those?

    ReplyDelete
    Replies
    1. Hey Jon. Your suggestion totally works! You won't have to change a thing in your build process, and there are no changes required at Errorception's end either.

      The crawler makes requests from 173.255.210.131. I will let you know in case this IP changes, but I don't see that happening in the immediate foreseeable future.

      Delete
    2. Thanks for the info Rakesh! We'll give that a shot soon.

      Delete
  3. hi Rakesh Pai,

    Thanks for such an amazing tool! I'm really happy to use it, but I'm facing an error related to our source maps file that I'd love to fix either in the Errorception panel or in my code.

    I have an hybrid mobile application and I was able to set up Errorception inside the code, unfortunately, it isn't reading the source map file:

    //# sourceMappingURL=../maps/scripts/vendor-{timestamp}.js.map

    But this problem only happens when the issue is throw from within the mobile app, if I use it in a web browser, it works just fine.

    Do you know if there's a way to make the map file available from within the mobile app?

    Thanks in advance!

    ReplyDelete
    Replies
    1. Hi Diego,

      For source maps to work, Errorception should have the column number of the error available. This is a rather recent addition to the window.onerror specs, so older and stagnant browsers might not support reporting column numbers. This is likely the issue you are facing. There's no easy fix to this problem, unfortunately. We'll just have to wait for older browsers to fade away.

      If this isn't the issue you are facing, feel free to reply here, or email me at rakeshpai@errorception.com, and I'll be glad to help.

      Delete