{"id":2923,"date":"2024-07-05T10:22:36","date_gmt":"2024-07-05T16:22:36","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=2923"},"modified":"2024-07-05T10:22:37","modified_gmt":"2024-07-05T16:22:37","slug":"script-integrity","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/script-integrity\/","title":{"rendered":"Script Integrity"},"content":{"rendered":"\n<p>There was lots of news recently about the <code>polyfill.io<\/code> project. This was a popular project a few years back, as you could just link it up and it would polyfill whatever web platform features it needed to based on feature testing and UA sniffing. If you loaded the script up from <em>their domain<\/em>, as the homepage suggested you did, you might have been served some <strong>malicious code<\/strong>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Users are being redirected to sports betting websites or adult domains, likely based on their location, the threat intelligence firm said.<\/p>\n\n\n\n<p><a href=\"https:\/\/www.securityweek.com\/polyfill-supply-chain-attack-hits-over-100k-websites\/\">SecurityWeek<\/a><\/p>\n<\/blockquote>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"652\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.39.48%402x.png?resize=1024%2C652&#038;ssl=1\" alt=\"\" class=\"wp-image-2924\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.39.48%402x.png?resize=1024%2C652&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.39.48%402x.png?resize=300%2C191&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.39.48%402x.png?resize=768%2C489&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.39.48%402x.png?resize=1536%2C978&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.39.48%402x.png?resize=2048%2C1305&amp;ssl=1 2048w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">The project is offline now, but as of June 1st, 2024 this is how they suggested hotlinking.<\/figcaption><\/figure>\n\n\n\n<p>When you link to <em>any<\/em> resource on a domain you don&#8217;t control, it&#8217;s a risk. It can be a calculated risk. For example, tons of websites load a script from <code>google.com<\/code> for their Google Analytics. Or load fonts from <code>adobe.com<\/code> for their typography. Those tend to be trusted sources, but that trust is entirely up to you.<\/p>\n\n\n\n<p>It&#8217;s entirely possible that a resource you link to from a third-party disappears or changes. Worst case: changed maliciously, like we&#8217;ve seen here. You can bet your ass Google and Adobe don&#8217;t load any resources, especially JavaScript, from third-party domains they don&#8217;t control. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Protection Against Changes<\/h2>\n\n\n\n<p>There is a web platform feature that can help against a third party changing the code they are providing. It&#8217;s <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Security\/Subresource_Integrity#tools_for_generating_sri_hashes\">the <code>integrity<\/code> attribute<\/a> on <code>&lt;script><\/code> or <code>&lt;link><\/code> elements (which are <code>rel=\"stylesheet\"<\/code>,\u00a0<code>rel=\"preload\"<\/code>, or\u00a0<code>rel=\"modulepreload\"<\/code>). It&#8217;s called &#8220;Subresource Integrity&#8221;, to name it correctly. <\/p>\n\n\n\n<p>So for example&#8230;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\">  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/third-party.com\/script.js\"<\/span><\/span>\n<\/span><\/span><mark class='shcb-loc'><span><span class=\"hljs-tag\">  <span class=\"hljs-attr\">integrity<\/span>=<span class=\"hljs-string\">\"sha384-&#91;hash value here]\"<\/span>&gt;<\/span>\n<\/span><\/mark><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now if <code>script.js<\/code> changes <em>at all, even just a single character,<\/em> the browser will <strong>refuse the execute the script <\/strong>(or stylesheet). Safety! <\/p>\n\n\n\n<p>Some responsible third-parties will offer this directly, which is nice to see.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"648\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.59.33%402x.png?resize=1024%2C648&#038;ssl=1\" alt=\"\" class=\"wp-image-2925\" style=\"width:658px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.59.33%402x.png?resize=1024%2C648&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.59.33%402x.png?resize=300%2C190&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.59.33%402x.png?resize=768%2C486&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.59.33%402x.png?resize=1536%2C973&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-03-at-15.59.33%402x.png?w=1870&amp;ssl=1 1870w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">instant.page versions their script and provides the <code>integrity<\/code> attribute for safety.<\/figcaption><\/figure>\n\n\n\n<p>CDNjs makes this a part of the default code you copy and paste.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"907\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-05-at-09.02.57%402x.png?resize=1024%2C907&#038;ssl=1\" alt=\"\" class=\"wp-image-2940\" style=\"width:668px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-05-at-09.02.57%402x.png?resize=1024%2C907&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-05-at-09.02.57%402x.png?resize=300%2C266&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-05-at-09.02.57%402x.png?resize=768%2C680&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-05-at-09.02.57%402x.png?resize=1536%2C1360&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-05-at-09.02.57%402x.png?w=1942&amp;ssl=1 1942w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>I particularly like how the protection <code>integrity<\/code> provides protects against some possible middleman attacks as well. Say your weird hotel WiFi intercepts requests and changes the response (I&#8217;ve seen it!), this will prevent the tampered-with script from executing. Uh, well, unless they tamper with the HTML and change the attribute value, which is certainly possible as altering HTML is exactly what <a href=\"https:\/\/open-web-advocacy.org\/blog\/in-app-browsers-the-worst-erosion-of-user-choice-you-havent-heard-of\/\">invasive in-app browsers do<\/a>. Still, most security is a do-as-much-as-you-can game and this helps. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">When Not to use Script Integrity<\/h2>\n\n\n\n<p>The above two examples are kinda perfect as the scripts they are linking to are versioned. They are published with that exact version and that version will never change. In this case, it&#8217;s by virtue of strong conventions. When public libraries update, the code at that version is locked in stone. Any changes cause a version update. If the code of a version changes <em>without<\/em> a version change, that would be <em>highly<\/em> suspicious, probably malicious, and a great situation for <code>&lt;script integrity=\"...\"><\/code> to block. Plus, the major places where libraries are published (i.e. npm) literally don&#8217;t allow changes to published versions anyway.<\/p>\n\n\n\n<p>While <code>integrity<\/code> is often an excellent idea, it&#8217;s specifically for versioned resources that don&#8217;t change. You would <em>not<\/em> use it if:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>You&#8217;re linking to a resource that is OK to change<\/li>\n\n\n\n<li>You&#8217;re providing a resource that you intend to change<\/li>\n<\/ol>\n\n\n\n<p>Perhaps you&#8217;re using a script from some analytics service provider. It&#8217;s pretty likely that they <em>don&#8217;t <\/em>use the <code>integrity<\/code> attribute when they give you the script they want you to add. That&#8217;s likely because they want to be able to actively work on this script and what it does without having to tell every single customer they need to update the version of the script otherwise it will stop working. <\/p>\n\n\n\n<p>Ironically, a company being able to update a script on the fly means they could potentially <em>patch<\/em> a security problem. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Would Script Integrity have Stopped the Polyfill.io Problem?<\/h2>\n\n\n\n<p>Maybe! It depends what they did. <a href=\"https:\/\/cside.dev\/blog\/more-than-100k-websites-targeted-in-web-supply-chain-attack\">Some reporting said<\/a> that:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>The malicious code dynamically generates payloads based on HTTP headers, activating only on specific mobile devices, evading detection, avoiding admin users and delaying execution. The code is also obfuscated.<\/p>\n<\/blockquote>\n\n\n\n<p>So: yes. If what was happening is that the initial response\/content of the script was changed from what it was when the integrity value was created, it would have prevented these malicious changes from running. <\/p>\n\n\n\n<p>But they <em>could<\/em> have evaded this kind of stoppage. <\/p>\n\n\n\n<p>They way that polyfill.io worked already was that it loaded <em>additional content<\/em> \u2014 the polyfills themselves \u2014 as needed. That additionally-loaded content could have been changed to be malicious and would not have been subject to subresource integrity. I&#8217;m not trying to make bad guys lives easier here, just sayin&#8217;.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How To Do It Yourself<\/h2>\n\n\n\n<p>You don&#8217;t need a third-party to hand you this attribute to use it. It&#8217;s just a web platform feature, so you can use it if you want to. <\/p>\n\n\n\n<p>Maybe the easier way is to go to the SRI Hash Generator website, pop in the URL of a resource you want to protect, and hit the button to get the code:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"438\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot-2024-07-05-at-11.11.42%E2%80%AFAM.png?resize=1024%2C438&#038;ssl=1\" alt=\"\" class=\"wp-image-2943\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot-2024-07-05-at-11.11.42%E2%80%AFAM.png?resize=1024%2C438&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot-2024-07-05-at-11.11.42%E2%80%AFAM.png?resize=300%2C128&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot-2024-07-05-at-11.11.42%E2%80%AFAM.png?resize=768%2C328&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot-2024-07-05-at-11.11.42%E2%80%AFAM.png?resize=1536%2C657&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot-2024-07-05-at-11.11.42%E2%80%AFAM.png?w=2020&amp;ssl=1 2020w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p><strong>NOTE:<\/strong> I&#8217;ve seen plenty of places recommend this site, but when I used it, it didn&#8217;t seem to work for me. For example, the exact code above:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/assets.codepen.io\/3\/log-something.js\"<\/span> <span class=\"hljs-attr\">integrity<\/span>=<span class=\"hljs-string\">\"sha384-ZTxYWn5UcuOi7Xt1wWg\/QFcsZJEXjJg7tgCnVbx0+ssBvbi1Sw\/hY5GzPGHSD1NW\"<\/span> <span class=\"hljs-attr\">crossorigin<\/span>=<span class=\"hljs-string\">\"anonymous\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That fails for me in Chrome. I had to let Chrome error with a message in the console, which provides the correct hash in the error message, and use that corrected hash for it to work:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/assets.codepen.io\/3\/log-something.js\"<\/span> <span class=\"hljs-attr\">integrity<\/span>=<span class=\"hljs-string\">\"sha384-H7W+IxM2qbwMSJYRqmcgYXq0TRko\/BIFtURUjh2QG0Z8MM9I6t2f4n+2BOCmbjvD\"<\/span> <span class=\"hljs-attr\">crossorigin<\/span>=<span class=\"hljs-string\">\"anonymous\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>So&#8230; your milage may vary. <a href=\"https:\/\/codepen.io\/chriscoyier\/pen\/qBzWMxQ?editors=1000\">Here&#8217;s the proof on all that.<\/a> <\/p>\n\n\n\n<p>It&#8217;s also worth noting that CORS is involved. Without that <code>crossorigin=\"anonymous\"<\/code> on there, I was seeing a CORS error with that code above, even though we <em>do<\/em> serve assets with a <code>Access-Control-Allow-Origin: *<\/code> header. Shrug \u2014 websites are hard.<br><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Polyfill.io recently served malicious code, redirecting users to inappropriate sites. Subresource Integrity (SRI) can help prevent such issues by verifying script integrity.<\/p>\n","protected":false},"author":1,"featured_media":2945,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[3,197,198],"class_list":["post-2923","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-javascript","tag-security","tag-subresource-integrity"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/czNmcy1wcml2YXRlL3Jhd3BpeGVsX2ltYWdlcy93ZWJzaXRlX2NvbnRlbnQvbHIvZnJiYXJiZWRfd2lyZV9mZW5jZV9vbGQtaW1hZ2Uta3liZWI4M2IuanBn.webp?fit=1024%2C683&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2923","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=2923"}],"version-history":[{"count":6,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2923\/revisions"}],"predecessor-version":[{"id":2946,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2923\/revisions\/2946"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/2945"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=2923"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=2923"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=2923"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}