Serving

Subresource integrity

Integrity hashes in the import map and the preload links, so the browser refuses a module that was tampered with.

Enabling integrity#

Opt in globally, then control it per pin.

Integrity calculation is off until you call enable_integrity! in config/importmap.rb. With it on, integrity: true (the default for every pin) computes a hash for each local asset through the asset pipeline; integrity: "sha384-…" uses the value you give; integrity: false or nil turns it off for that pin.

config/importmap.rb
# Enable integrity calculation globally
enable_integrity!

# With integrity enabled, these auto-calculate integrity hashes
pin "application"                                               # auto-calculated
pin "admin", to: "admin.js"                                     # auto-calculated
pin_all_from "app/javascript/controllers", under: "controllers" # auto-calculated

# Explicit control
pin "cdn_package", to: "https://cdn.example.com/cdn_package.js", integrity: "sha384-abc123..." # pre-calculated hash
pin "no_integrity_package", integrity: false     # explicitly disabled
pin "nil_integrity_package", integrity: nil      # explicitly disabled

This is particularly useful for local JavaScript files managed by the asset pipeline, for bulk pin_all_from pins where computing hashes by hand would be tedious, and in development where file contents change often. External CDN packages should provide their own hashes.

Propshaft#

SRI needs Propshaft 1.2+ and an integrity hash algorithm configured in your application; without it integrity is disabled by default under Propshaft. Sprockets has integrity support out of the box.

config/application.rb
config.assets.integrity_hash_algorithm = "sha256" # or "sha384", "sha512"

What the browser gets#

The hashes are included in the import map's integrity section and on each module preload link, and the browser validates them as it loads the modules:

{
  "imports": {
    "lodash": "https://ga.jspm.io/npm:[email protected]/lodash.js",
    "application": "/assets/application-abc123.js",
    "controllers/hello_controller": "/assets/controllers/hello_controller-def456.js"
  },
  "integrity": {
    "https://ga.jspm.io/npm:[email protected]/lodash.js": "sha384-PkIkha4kVPRlGtFantHjuv+Y9mRefUHpLFQbgOYUjzy247kvi16kLR7wWnsAmqZF",
    "/assets/application-abc123.js": "sha256-xyz789...",
    "/assets/controllers/hello_controller-def456.js": "sha256-uvw012..."
  }
}
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/lodash.js" integrity="sha384-PkIkha4kVPRlGtFantHjuv+Y9mRefUHpLFQbgOYUjzy247kvi16kLR7wWnsAmqZF">
<link rel="modulepreload" href="/assets/application-abc123.js" integrity="sha256-xyz789...">

When bin/importmap rewrites a pin#

integrity: true and integrity: false are settings, and they survive a rewrite by pin, update or pristine the way preload: does. An explicit hash string belongs to one particular file, so it is dropped when the URL changes — the old hash would no longer match. Pin a fresh hash after an update of a remote pin that carried one.