Last updated: May 29, 2025

<span aria-hidden="true" id="verify-cdn-integrity-with-sha384sum-in-browsers"></span>

# Verify CDN integrity with sha384sum in browsers

![Kevin van Zonneveld](/assets/images/teammates/avatar-kvz-4.jpg?dpl=dpl_5eQDcFwLGbzg3PfkSEW2a2j2x5pY)

**Kevin van Zonneveld**

Co-founder · Amsterdam, The Netherlands · Show bio

[](https://x.com/kvz)[](https://github.com/kvz)

Ensuring the integrity of resources served from a Content Delivery Network (CDN) is paramount for protecting your users and your reputation. Subresource Integrity (SRI) lets browsers verify that a fetched file matches an expected cryptographic hash—effectively blocking compromised assets. In this DevTip, we will look at why `sha384sum` is the sweet spot for SRI hashes, how to generate and embed those hashes, and how to automate the whole workflow from the command line to your CI/CD pipeline.

<span aria-hidden="true" id="understand-subresource-integrity-sri"></span>

## Understand subresource integrity (sri)

An SRI‐enabled browser walks through the following steps when it encounters the `integrity`attribute:

1. Download the resource (JavaScript, CSS, font, …).
2. Compute its hash on the fly.
3. Compare the result with the hash hard-coded in the HTML.
4. Abort execution if the values differ.

The result is simple yet powerful—if someone tampers with a third-party script, the browser refuses to run it.

<span aria-hidden="true" id="why-choose-sha-384-for-sri"></span>

## Why choose sha-384 for sri?

SHA-256 is the minimum algorithm the spec allows, and SHA-512 offers the strongest theoretical security. SHA-384 hits the sweet spot: stronger than SHA-256 and slightly faster to compute than SHA-512 on most CPUs. Every mainstream browser supports it, so we will use `sha384-…` throughout this article.

<span aria-hidden="true" id="generate-a-hash-on-the-command-line"></span>

## Generate a hash on the command line

`sha384sum` prints a **hex** digest, but SRI needs **base64**. Use `openssl` (readily available on macOS and Linux) to get the right output:

```bash
cat your-file.js | openssl dgst -sha384 -binary | openssl base64 -A

```

To create the full SRI value in one go, prefix it with `sha384-`:

```bash
echo "sha384-$(cat your-file.js | openssl dgst -sha384 -binary | openssl base64 -A)"

```

**Tip**

the cross-platform [srihash-cli⁠](https://github.com/filipedeschamps/srihash-cli) npm package achieves the same without shell gymnastics.

<span aria-hidden="true" id="embed-sri-in-your-html-or-jsx"></span>

## Embed sri in your HTML or jsx

```html
<!-- Single hash example -->
<script
  src="https://example.com/js/library.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"
></script>

<!-- Multiple algorithms for legacy browsers -->
<link
  rel="stylesheet"
  href="https://example.com/css/styles.css"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC sha512-/OeNzF1PFA/xbzX92DyMRPz3opJ4nVDJ+EYhXpqwOFpq1DMLulFbN5OPUXmHkXmNovSH7BwpUQX/xFG0QZgTcA=="
  crossorigin="anonymous"
/>

```

The `crossorigin="anonymous"` attribute is required when the file comes from a different origin— otherwise the browser skips verification.

<span aria-hidden="true" id="generate-hashes-in-the-browser-with-the-web-crypto-api"></span>

## Generate hashes in the browser with the Web Crypto API

When you need to validate or calculate hashes at runtime (think browser extensions, security test pages, or dashboards), call `crypto.subtle.digest`:

```javascript
async function generateSRIHash(url) {
  const response = await fetch(url)
  const buffer = await response.arrayBuffer()
  const hashBuffer = await crypto.subtle.digest('SHA-384', buffer)
  // Convert the ArrayBuffer to a base64 string
  const hashArray = Array.from(new Uint8Array(hashBuffer))
  const binaryString = String.fromCharCode.apply(null, hashArray)
  const base64Hash = btoa(binaryString)
  return `sha384-${base64Hash}`
}

// Demo usage
generateSRIHash('https://cdn.example.com/library.min.js').then(console.log)

```

`crypto.subtle` is available in every current evergreen browser, but only on secure origins (`https://` or `http://localhost` during development).

<span aria-hidden="true" id="browser-support"></span>

## Browser support

Subresource Integrity is supported in:

* Chrome 45+
* Firefox 43+
* Safari 11.1+
* Edge 17+

All of the above also ship the Web Crypto API. Older browsers will simply ignore the `integrity`attribute and load the file as usual.

**Note**

The Web Crypto API used for hash generation requires a secure context (HTTPS).

<span aria-hidden="true" id="automate-sri-inside-your-cicd-pipeline"></span>

## Automate sri inside your ci/cd pipeline

A build step ensures that hashes stay in sync with your bundles. Below is a minimal GitHub Actions job that updates an `sri-hashes.json` file used by your templates:

```yaml
name: build
on: [push]

jobs:
  sri:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: npm ci

      - name: Build assets
        run: npm run build

      - name: Generate SRI hashes
        run: |
          node scripts/generate-sri.js

      - name: Commit updated hashes
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: 'chore(ci): update SRI hashes'

```

`scripts/generate-sri.js` (kept below 20 lines) might look like this:

```javascript
import { createHash } from 'node:crypto'
import { readFileSync, writeFileSync } from 'node:fs'
import { globby } from 'globby'

function sri(file) {
  const data = readFileSync(file)
  const hash = createHash('sha384').update(data).digest('base64')
  return `sha384-${hash}`
}

const files = await globby(['dist/**/*.js', 'dist/**/*.css'])
const map = Object.fromEntries(files.map((f) => [f, sri(f)]))
writeFileSync('sri-hashes.json', JSON.stringify(map, null, 2))
console.table(map)

```

Your build template (e.g., Astro, Eleventy, Next.js) can then read the JSON and inject the correct hash automatically.

<span aria-hidden="true" id="load-scripts-dynamically"></span>

## Load scripts dynamically

For code-splitting or feature toggles you often create elements on the fly. Make sure to apply the hash programmatically as well:

```javascript
export async function loadScript(src, integrity) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script')
    Object.assign(script, {
      src,
      integrity,
      crossOrigin: 'anonymous',
    })

    script.addEventListener('load', () => resolve())
    script.addEventListener('error', () => reject(new Error(`Failed to load or verify ${src}`)))

    document.head.append(script)
  })
}

```

<span aria-hidden="true" id="monitor-cdn-assets-in-production"></span>

## Monitor CDN assets in production

A small helper class can keep an eye on frequently changing files and ping your observability stack when something looks fishy:

```javascript
class SRIMonitor {
  #entries = new Map()
  #intervalId
  constructor(interval = 5 * 60_000) {
    this.interval = interval
  }

  add(url, expectedHash) {
    this.#entries.set(url, expectedHash)
  }

  async #check(url, expected) {
    const actual = await generateSRIHash(url)
    if (actual !== expected) {
      console.warn(`[SRI] Mismatch for ${url}`)
      // Push to your SecOps webhook / Slack / PagerDuty …
      await fetch('/api/security-alerts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ url, expected, actual }),
      })
    }
  }

  start() {
    if (this.#intervalId) return
    this.#intervalId = setInterval(() => {
      this.#entries.forEach((hash, url) => this.#check(url, hash))
    }, this.interval)
  }

  stop() {
    clearInterval(this.#intervalId)
  }
}

```

<span aria-hidden="true" id="security-considerations"></span>

## Security considerations

* Always serve SRI-enabled resources over HTTPS.
* Include the `crossorigin="anonymous"` attribute when the resource is on a different domain.
* Consider using multiple hash algorithms for better browser compatibility (e.g., `sha384` and`sha512`).
* Be aware that SRI breaks if the resource changes—have a strategy for updating hashes, ideally automated in your CI/CD pipeline.
* Combine SRI with a Content Security Policy (CSP) that sets a denylist for inline scripts to further enhance security.

<span aria-hidden="true" id="troubleshoot-common-pitfalls"></span>

## Troubleshoot common pitfalls

|Symptom|Likely cause & fix|
|-|-|
|Script loads but doesn’t execute|Hash mismatch → regenerate & redeploy|
|Works locally, fails on PROD|Production CDN rewrites files (minification, compression)|
|Blocked by CORS policy|Missing crossorigin attribute or CDN lacks proper headers (e.g. Access-Control-Allow-Origin)|
|SRI ignored entirely|Page or resource served over HTTP instead of HTTPS|

<span aria-hidden="true" id="implement-fallbacks"></span>

## Implement fallbacks

If verification fails you can gracefully fall back to a mirror or a self-hosted copy:

```javascript
async function loadWithFallback(primary, backup, integrity) {
  try {
    await loadScript(primary, integrity)
  } catch {
    console.warn(`Primary failed, switching to ${backup}`)
    await loadScript(backup, integrity) // Consider if the same integrity hash applies or if backup has its own
  }
}

```

<span aria-hidden="true" id="how-transloadit-uses-file-hashing"></span>

## How Transloadit uses file hashing

At Transloadit, we provide the [🤖 /file/hash](/docs/robots/file-hash.md) Robot that supports multiple hash algorithms including SHA-384. While this Robot can be used as part of your media processing pipeline, for SRI generation specifically, you should use the methods described above since the Robot's primary purpose is for file integrity verification within Assemblies.

By adopting SRI with `sha384sum`—and automating it from development through deployment—you add an important extra layer of defense against supply-chain attacks without sacrificing performance.

<span aria-hidden="true" id="further-reading"></span>

## Further reading

* [MDN Subresource Integrity⁠](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource%5FIntegrity)
* [W3C Subresource Integrity Specification⁠](https://www.w3.org/TR/SRI/)
* [MDN Web Crypto API⁠](https://developer.mozilla.org/en-US/docs/Web/API/Web%5FCrypto%5FAPI)
* [MDN SubtleCrypto.digest()⁠](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest)

\#sha384sum#subresource-integrity#sri#cdn-security#web-crypto-api#browser-security#integrity-attribute#hash-verification#ci-cd-automation#javascript-security#media-cataloging-service

### 👩‍💻 Join 20k+ developers

Sign up for our [monthly newsletter](/newsletters.md) to receive direct links to 3 exclusive tech — and 2 product updates. No less, no more.

Your email:

Get access

## File uploading and encoding. Made simple.

Transloadit streamlines file handling for developers, trusted by brands like Coursera and The New York Times. We’re known for a reliable API, top-notch support, and a strong commitment to open source, with projects like [Uppy⁠](https://uppy.io) and [Tus⁠](https://tus.io) setting standards in file processing.

[Sign up](/c/)[Book a Demo](https://survey.typeform.com/to/kRg47Xi5)

No credit card needed

Cancel anytime
