Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subresource Integrity #323

Closed
Gargron opened this issue May 3, 2017 · 22 comments
Closed

Subresource Integrity #323

Gargron opened this issue May 3, 2017 · 22 comments

Comments

@Gargron
Copy link

Gargron commented May 3, 2017

Sprockets supported subresource integrity out of the box. It would be nice if

javascript_pack_tag 'vendor', integrity: true, crossorigin: 'anonymous'

worked the same. There is a webpack plugin but I am not sure how to hook it up to Rails to put the integrity hashes into the template.

@gauravtiwari
Copy link
Member

@Gargron Will take a look at this 👍

@Miaourt
Copy link

Miaourt commented May 7, 2017

Any new on this ? ^-^

@dhh
Copy link
Member

dhh commented May 7, 2017 via email

@gauravtiwari
Copy link
Member

Just to update everyone here, this feature is on hold for now as I couldn't find any straightforward solution to this problem (something that doesn't make things complicated).

Feel free to leave any suggestions/ideas here though 👍

@renchap
Copy link
Contributor

renchap commented May 22, 2017

I just looked at my production Sprockets manifest and I can see that it contains the SRI digest for my packs:

{
  "files": {
    "manifest-d0ff5974b6aa52cf562bea5921840c032a860a91a3512f7fe8f768f6bbe005f6.js": {
      "logical_path": "manifest.js",
      "mtime": "2017-03-23T17:25:57+00:00",
      "size": 0,
      "digest": "d0ff5974b6aa52cf562bea5921840c032a860a91a3512f7fe8f768f6bbe005f6",
      "integrity": "sha256-0P9ZdLaqUs9WK+pZIYQMAyqGCpGjUS9/6Pdo9rvgBfY="
    },
}

I know that Webpacker does not depend on Sprockets, but this seems the way to go. I am not sure why this is generated, but I think we should use the same mechanism: put the SRI digests (and in the future other informations?) in the manifest generated by Webpack.

The current webpack-manifest-plugin has a different format which does not allow other informations than the full path, but I dont think it would be hard to fork it and improve the manifest format, or output another manifest.

Some Webpack plugins already store the SRI attributes in Webpack'sstats object:
https://github.com/mikechau/sri-stats-webpack-plugin
https://github.com/waysact/webpack-subresource-integrity

I wont have time to work on this, but hopefully here are some ideas!

@renchap
Copy link
Contributor

renchap commented Aug 10, 2017

A new reduce option has been added to webpack-manifest-plugin.
This allows to customize the manifest, for example to include the SRI hash: shellscape/webpack-manifest-plugin#35 (comment)

Webpacker could ship with a webpack-manifest-plugin configuration that adds the needed attributes to the manifest, and then pass them to javascript_include_tag and stylesheet_pack_tag.

@mvastola
Copy link
Contributor

I would like to +1 @renchap's suggestion to use the new feature of webpack-manifest-plugin to generate multi-dimensional hashes in manifest.json. This approach strikes me as highly robust, with use cases extending beyond SRI hashes.

With a simple change of the structure of manifest.json from:

{
  "application.js": "/packs/application-[DIGEST].js"
}

.. to:

{
  "application.js": {
    "path": "/packs/application-[DIGEST].js"
  }
}

.. it immediately becomes possible to add all kinds of metadata into the manifest that could be appended by webpack plugins and then accessed through a universal interface provided by helpers.

Integrity hashes is just one piece of data that could be passed in here. Other data that comes to mind includes:

  • Image metadata -- sizes could be automagically determined when using image_tag and even the alt attribute could gathered EXIF data.
  • Dependencies -- so that, for instance, when I call *_pack_tag any images/etc the stylesheet/javascript depends on can be optionally prefectched with <link rel="preload" href="[FOO]" as="[BAR]"> tags being inserted alongside the stylesheet/javascript include.

I can probably think of more, (and I'm sure there are many custom situations) but you get the jist.

@mvastola
Copy link
Contributor

@gauravtiwari what are your thoughts on my comment above? I might be interested in making a PR (at least to preliminarily change the format of manifest.json and changing the ruby library to parse the different manifest format, but I don't want to step on your toes, nor do I don't want to submit a PR if it is going to be viewed as "too complicated".

@renchap
Copy link
Contributor

renchap commented Nov 30, 2017

Bump here. I think this would be awesome to first use the new structure for the manifest file, and then add others features based on it, starting with SRI :)
This does not look like a big change.

@renchap
Copy link
Contributor

renchap commented Apr 17, 2018

The new manifest plugin used in Webpacker 4 (https://github.com/webdeveric/webpack-assets-manifest) supports an integrity option. This adds all the needed hashes to the manifest, which can then be passed to the *_tag Rails helpers.

@gauravtiwari
Copy link
Member

Yep, that's why used it so we can get this feature too at some point. Initially, I added the option but then it changes the manifest schema, which means rethinking the way the manifest class is structured in the gem and it's responsibilities (integration with the helper module) so this feature is slightly more involved.

@mvastola
Copy link
Contributor

mvastola commented Jun 6, 2018

@gauravtiwari, would a PR be accepted that implements this feature? Or does more discussion need to take place first (regarding the helper module, etc -- perhaps with the Rails team)? If the latter, where would be the best place to start it?

@l4j3b
Copy link

l4j3b commented Mar 27, 2019

Any updates?

@timonbandit
Copy link
Contributor

@jebenois
you can try my way.

in environment.js add next lines:

const manifestPlugin = environment.plugins.get('Manifest');
manifestPlugin.options.integrity = true;

and i created the new helper to replace stylesheet_pack_tag and javascript_pack_tag

def webpack_stylesheet_pack_tag filename, fallback = ""
    if current_webpacker_instance.config.extract_css?
      if Webpacker.manifest.lookup(filename)
        stylesheet_link_tag(Webpacker.manifest.lookup(filename)["src"], integrity: Webpacker.manifest.lookup(filename)["integrity"], crossorigin: "anonymous")
      else
        stylesheet_link_tag(Webpacker.manifest.lookup(fallback)["src"], integrity: Webpacker.manifest.lookup(fallback)["integrity"], crossorigin: "anonymous")
      end
    end
  end

  def webpack_javascript_pack_tag filename, fallback = ""
    if Webpacker.manifest.lookup(filename)
      javascript_include_tag(Webpacker.manifest.lookup(filename)["src"], integrity: Webpacker.manifest.lookup(filename)["integrity"], crossorigin: "anonymous")
    else
      javascript_include_tag(Webpacker.manifest.lookup(fallback)["src"], integrity: Webpacker.manifest.lookup(fallback)["integrity"], crossorigin: "anonymous")
    end
  end

and use this methoud instead of webpacker methods.

But currently, it generated incorrect SHA sum :-(
I think there is a problem in the manifest plugin.

@gsar
Copy link

gsar commented Oct 1, 2019

@gauravtiwari any news on making SRI functional with webpacker? i am willing to help with a PR if you can point to what needs addressing.

@gsar
Copy link

gsar commented Oct 29, 2019

fwiw, we are using this somewhat hacky monkeypatch to make it work:

app/helpers/asset_helper.rb:

module AssetHelper
  # this monkeypatch is needed to set the integrity attr to html tags for SRI
  class Webpacker::Manifest
    unless Webpacker::Manifest.instance_methods(false).include?(:original_lookup)
      alias original_lookup lookup
    end

    def lookup(name, pack_type = {})
      asset = original_lookup(name, pack_type)
      (asset.respond_to?(:dig) && asset.dig('src')) || asset      # rubocop:disable Style/SingleArgumentDig
    end

    def lookup_integrity(name, pack_type = {})
      asset = original_lookup(name, pack_type)
      (asset.respond_to?(:dig) && asset.dig('integrity')) || nil  # rubocop:disable Style/SingleArgumentDig
    end
  end

  module Webpacker::Helper
    def javascript_pack_tag(*names, **options)
      generic_packs_with_chunks_tag(:javascript, false, *names, **options)
    end

    def javascript_packs_with_chunks_tag(*names, **options)
      generic_packs_with_chunks_tag(:javascript, true, *names, **options)
    end

    def stylesheet_pack_tag(*names, **options)
      generic_packs_with_chunks_tag(:stylesheet, false, *names, **options)
    end

    def stylesheet_packs_with_chunks_tag(*names, **options)
      generic_packs_with_chunks_tag(:stylesheet, true, *names, **options)
    end

    private

    def generic_packs_with_chunks_tag(type, chunks, *names, **options)
      return if type == :stylesheet && !current_webpacker_instance.config.extract_css?

      entries = if chunks
                  sources_from_manifest_entrypoints(names, type: type)
                else
                  sources_from_manifest_entries(names, type: type)
                end
      entries.map do |entry|
        name = entry.match(%r{\A.+/([^/]+)(?:-[a-z0-9]{8,20})(?:\.chunk)?\.(?:js|css)\z}) && Regexp.last_match(1)
        if name
          integrity = Webpacker.manifest.lookup_integrity(name, type: type)
          if integrity
            options = options.merge(integrity: integrity, crossorigin: 'anonymous')
          end
        else
          logger.error { "ERROR failed to find #{entry} in manifest" }
        end
        case type
        when :stylesheet
          stylesheet_link_tag(entry, **options)
        when :javascript
          javascript_include_tag(entry, **options)
        else
          raise "unknown type: #{type}"
        end
      end.join("\n").html_safe # rubocop:disable Rails/OutputSafety
    end
  end
end

config/webpack/environment.js:

// Webpacker uses this plugin to generate its manifest but
// at present it does not generate the integrity for each asset.
const manifestPlugin = environment.plugins.get('Manifest');
manifestPlugin.options.integrity = true;
manifestPlugin.options.integrityHashes = ['sha256'];

@cirdes
Copy link

cirdes commented Feb 19, 2020

@gsar, thanks for your solution. Unfortunately, it wasn't working for me. I'm on Rails 6.0.2.1

Any plans to officially support this?

@gsar
Copy link

gsar commented Jul 11, 2021

@cirdes i can confirm the monkeypatch i showed above has been working for a couple of years now. we are currently on webpacker 5.4.0 and rails 6.1.4. official support would be great to see!

@cirdes
Copy link

cirdes commented Jul 12, 2021

@gsar, I'm running with webpacker 6.0.0.beta.7 and rails 6.1.4. I will try to apply the monkeypatch again. Thanks!

@justin808
Copy link
Contributor

@cirdes @gsar any update? is this still an issue for v6?

@jscheid
Copy link

jscheid commented Jan 13, 2022

A word of caution: if all you do is to set the integrity and crossOrigin attributes on top-level assets, as the monkey patch above does, any code loaded dynamically will be unprotected. You really want to use a plugin such as webpack-subresource-integrity unless you're certain that your JS code (and its dependencies) doesn't use dynamic imports. Your integrity checks are only as strong as the weakest link.

(I'm the principal maintainer of webpack-subresource-integrity.)

I also recommend reading our list of pitfalls which we've distilled from years worth of feedback. Most of them would also apply here. Cache settings in particular are worth paying close attention to.

@mvastola
Copy link
Contributor

mvastola commented Jan 19, 2022

@dhh can you elaborate on why this was closed?

Disregard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests