How to implement SRI in your build process

Imagine getting a call from a customer who says your website is serving malware. Your heart drops, you start sweating, and then the tweets start pouring in. Something is up. You find out your systems have not been tampered with.

In fact, it was your CDN provider that got hacked, and the scripts you included on your website have become malicious. You tell your customers what happened and they don’t care. You failed to provide a secure product, now trust is lost. If this happened 2 years ago, I would feel bad for you. But if it happened to you today I would sigh and say,“You should have used SRI.”

Subresource Integrity (SRI) is a new-ish web application security standard and W3C spec that helps prevent situations like the one above. Think of SRI as a safety net or rock-climbing rope for website content security. It is simply an attribute named integrity with a SHA-2 hash as the value within a <script> or <link> tag. For example:

<script src="http://code.jquery.com/jquery-2.2.3.min.js" 
    integrity="sha256-a23g1Nt4dtEYOj7bR+vTu7+T8VP13humZFBJNIYoEJo=" 
    crossorigin="anonymous">
</script>

In order for SRI to work, the CDN needs CORS enabled. That (and more) is explained in detail in a previous Hacks post introducing SRI.

Support

File types

The first version of the SRI standard was designed to address the biggest attack vectors caused by subresource hijacking on CDNs prevalent in Cascading Style Sheets (CSS) and JavaScript (JS) files. Other file types such as Flash, image, and video are not currently supported, but may be added in a future revision of the spec.

Browsers

As of April 2016 CanIUse.com reports that ~52% of browsers (mobile + desktop) globally support SRI. The desktop user-agents that support SRI are Firefox (44+), Chrome(47+) & Opera (36+). The following Android-only mobile browsers also support SRI: Chrome (49+), Android Browser (49+), Opera (36+) & Firefox (45+).

Please note, SRI will still not work with browsers such as Firefox for iOS. This is because Apple requires all apps (including browsers & in-app browsing) to use the WebKit web browser engine. It would be nice to see a status update

SRI in your build process

There’s more than one way to get SRI into your build process. A number of tools already have plugins, for example: Grunt, Gulp, Broccoli, Webpack, Ember CLI & Handlebars.

We decided to show a real world example of SRI in a build process. We used a Grunt plugin called grunt-sri to generate the hashes.

How we did it for jQuery:

When looking at including SRI integrity for code.jquery.com, we took a fairly simple approach compared to previous implementations. The code base was already using Grunt, and as such, it was pretty straightforward to include a target using the popular grunt-sri node module. grunt-sri traverses a specified list of files and generates a JSON payload including all the metadata required for implementing SRI in a code base. Once generated, the output file can easily be used as the base datasource when building out an application. Here’s a simple example of implementing a base generator using grunt-sri:

// Gruntfile.js
grunt.loadNpmTasks("grunt-sri");
grunt.initConfig({
    sri: {
        generate: {
            src: [ 'public/**/*.js', 'public/**/*.css' ],
            options: { algorithms: [ 'sha256' ] }
        }
    }
});

Once implemented, running grunt sri:generate will output ./payload.json for requiring in your application, or another Grunt task. The SRI SHA can then be accessed from the payload file from your code as shown in the grunt-sri documentation:

// ES6 from https://github.com/neftaly/grunt-sri#javascript
var payload = require("./payload.json");
var sri = (id) => payload.payload[id];

var element = `<link
    href='/style.css?cache=${ sri("@cssfile1").hashes.sha256 }'
    integrity='${ sri("@cssfile1").integrity }'
    rel='stylesheet'>`;

For additional implementation details see https://github.com/neftaly/grunt-sri or peruse our pull request to code.jquery.com, specifically this diff.

Beyond JavaScript / Node.js

If you prefer to keep it old school with a Makefile, no problem. Assuming you are using a unix-like environment, you can skip using node modules and do something like this:

# Makefile
generate:
    cat FILENAME.js | openssl dgst -sha256 -binary \
        | openssl enc -base64 -A

For examples of generating SRI SHAs in various other platforms, see this Gist.

Conclusion

Subresource Integrity is a very simple way to secure static assets hosted on servers you have no control over. There are several tools that allow you to easily integrate SRI support into your build process(es). Modern website/application developers should not only do their part in implementing SRI, but discuss it with their peers explaining the benefits.

Big thanks to Frederik Braun, Jonathan Kingston, Francois Marier & Havi Hoffman for their help reviewing this article.

About Justin Dorfman

Justin is MaxCDN’s Director of Developer Relations and is responsible for evangelizing the company’s technologies and championing the needs of developers who use the network. He started BootstrapCDN in 2012 and is heavily involved with the FOSS community contributing to Bootstrap, Font Awesome, Grunt, Ionic, jQuery Foundation, Twemoji, Nginx & GNU Bash.

More articles by Justin Dorfman…

About Joshua Mervine

Joshua is an SRE at Heroku with over 20 years of experience Development and Systems Engineering. He took over the lead developer role on BootstrapCDN in 2013 and is heavily involved in open source and the community, both through his own projects and his contributions to others.

More articles by Joshua Mervine…


11 comments

  1. oxdef

    We still have risks if external script attempts to load another one via RequireJS or evaling() some response, don’t we?
    Anyway this is an interesting web application defensive approach.

    April 12th, 2016 at 12:48

    1. Jonathan Kingston

      There is talk of other ways to use SRI, for example with @imports in CSS and module code in JS. How that might be implemented is still a long way off.

      The Fetch API which is the new drop in replacement for XHR has the ability to specify an SRI hash using the integrity attribute in the request: https://fetch.spec.whatwg.org/#concept-request-integrity-metadata

      The Fetch API is now used internally for all new and future requests internally withing the browser, so in newer versions of the SRI spec it would be just plumbing in new syntax to connect to this (in the order of highest security risk etc)

      This is all however future work to be done, however the current specification gives the ability to remove the risk of static assets for developers happier to use it.

      April 12th, 2016 at 14:16

  2. Anders

    Will this cooperate with the cache so a request for “http://code.jquery.com/jquery-2.2.3.min.js” might use a cached file from “https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js” if the hash matches?

    April 12th, 2016 at 13:25

    1. Jonathan Kingston

      Not in the current iteration of the spec, there has been talk about improving caching but it wasn’t put in the first iteration.
      Cross origin the answer is likely not to be possible due to cookies, headers and other issues being cached.

      April 12th, 2016 at 14:08

      1. Anders

        Why would cookies be a problem? A browser is not under any obligation to perform all requests (and isn’t the crossorigin=”anonymous” meant to indicate that e.g. cookies are not important). And as the crystallographic hash is given the browser should be able to assume that, if it has content with a matching hash, it will be the same.

        April 12th, 2016 at 14:19

        1. Jonathan Kingston

          I was referring more to the response cache, the hash is regarding the response body of the request it doesn’t factor in anything else in the response.

          If one jquery.com and cloudflare.com origins used different response headers we might end up serving the wrong response headers if we overly reused cache.

          There is also crossorigin=”use-credentials” mode which can send credentials for for CORS requests to the server.

          April 12th, 2016 at 14:43

          1. Anders

            If the headers can be used to change the meaning of the content (e.g. a files byte content might have different meaning in utf-8 and utf-16) and the hash do not include the headers. Then the hash is already useless as it doesn’t prevent the third party delivering something with a different meaning than intended.

            April 13th, 2016 at 15:25

          2. Jonathan Kingston

            The hash really isn’t useless, it prevents files of a different content from loading on your site.
            The headers is an issue if we wanted to cache the response from one server and give it to a completely different site.
            The headers have limited impact but sharing them across origins without the developer knowing from cache could cause encoding issues for example.

            April 14th, 2016 at 02:49

        2. Frederik

          Using SRI for caching is dangerous and has thus been left out. See the other SRI post comments (https://hacks.mozilla.org/2015/09/subresource-integrity-in-firefox-43/#comment-18850) for more. We’re interested in re-kindling the discussion though.

          April 13th, 2016 at 05:16

          1. Anders

            I don’t really see the threat. I believe the relevant part of the linked text is “Content injection (XSS) may then use a previously stored hash so that it looks like you are hosting the evil JavaScript payload that the browser has previously seen on evil.com.” But if your site is vulnerable to XSS you are already hosed (or using CSP in which case you could just white-list all the known trusted cdns) and otherwise you still have to put the hash of the evil JavaScript on your site.

            April 13th, 2016 at 15:38

          2. Jonathan Kingston

            There are hash collision issues however rare they might be with >sha256 they are theoretically possible. There are timing attacks for the storage for seeing privileged content.

            April 14th, 2016 at 02:55

Comments are closed for this article.