tc39 / proposal-import-attributes

Proposal for syntax to import ES modules with assertions

Home Page:https://tc39.es/proposal-import-attributes/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Assert integrity (SRI)

gullerya opened this issue · comments

I've not found this mentioned, so if this already was discussed, please point me to the existing discussion.

Intro

There is an SRI feature, fortifying security of the CDN delivered modules via integrity attribute, using hash value known upfront. More info here.

Today, this feature practically restricted to the <script> tag, while most of the web moves to the import from modules syntax.
CDN delivered modules / components are also seemingly gathering more and more traction.
There is a limited workaround which can not be counted on, as one can see in this Stackoverflow discussion.

Proposal

Seems like import assertions could be a perfect match for this use-case:

import 'foo.js' assert { integrity: 'sha512-....' };

This seems like a useful feature to me, although not sufficient on its own since this regresses the original value proposition of import maps in permitting independent caching between dependent modules and their dependencies.

But it could be a great initial feature to be combined with out of band mechanisms in future, so I'd support this.

Thanks, first.
Even for my own education, since I've lost you in the part with "regression as of independent caching", what do you mean?

As of now my understanding is, that:

  • integrity, although specified in the import statement, is to be applied to the finally resolved resource, to be sure (probably this is somewhat distant and therefore obscured?)
  • at some point each fetched module's specifier is resolved into fetch URL, regardless of be it via the import map or otherwise
  • each unique URL is fetched only once throughout the app's lifespan (per frame?, be it from network or cache)
  • the very first time the URL is fetched, it can be integrity-verified

Import maps allow dependencies to be cached separately to their importers when using far-future expires, as the same import specifier can resolve to a different module as configured in the import map. Inline integrity with these same workflows would break that separate caching as the integrity needs to be changed each time the dependency changes. This then creates a strong incentive for out-of-band integrity mechanisms, possibly as a generic out-of-band import attribute mechanism.

I'll try one more clarification just to make sure we are on the same page (and probably I'm totally off the rail here in terms of the internal discuss about the whole issue).

If I understand you right, you are talking about a general fact that if the effectively imported resource is being changed, then the author of the import assert will have to touch his own script as well (thus invalidating the cache but even worse - bleeding things up-down), not to say that if we are talking about microfrontend approach or some solid 3rd party library, author may not even know ahead of time what to assert.

Right, for those cases this mechanism is not applicable and I presume will actually logically won't be used by the author from the beginning, I mean for anyone who opens his import's to be provided by the hosting consumer it is quite clear that he/she may not assert on integrity this way.

But, this proposal will solve many other cases, where author of microfrontend or 3rd party lib would actually like to lock down the dependency (CDN delivered one, that self points to other CDN inner dependencies and do not need import maps intervention).

Thus, when used consciously this API will be helpful, and when misused, will at most force consumer to ensure that the desired, locked-down, dependency is still provided to the library / microfrontend.

WDYT?

Also need this feature, but after thinking, I think it's better to do this in the global importmap-like layer? (WICG/import-maps#221)

I'm aware of the proposal of @guybedford and definitely import maps could/should be one of the places to define the assertion.

Yet, I'd like to open this possibility also to the simple imports, the cases when import maps are not used, since at least for a simple web apps import maps might be an overkill.

Unfortunately it’s unworkable to include the hash in the JS file, as circular references would become unresolvable. Consider file A that imports B that imports C, and C imports from B. If you put hashes in all three files, how can you determine the hash to put in B (describing C) and vice versa? As soon as you add C’s hash into B, B’s hash will be different; and once B’s hash is different, C would need to change to use the new hash; and now C’s hash is different, so B needs to be updated; and on and on. cc @MylesBorins

Although it does not work with circular references, but it works for CDN files.

While agree on the bare fact of the circular reference issue, IMO it can be seen as a valid exception from the rule: circular reference scenario can only happen when doing this within the sources of the same project, which is anyway trusted area.

This whole feature, I mean SRI in general and not just in context of the present discussion, mostly/only about 3rd party CDN imports (cause even for the checked-in 3rd parties shipped with the author's codebase it is already makes little sense, if any).

An out-of-band location for the SRI hashes, such as in an import map, doesn’t have the circular reference issue and works for both first- and third-party imports. Such a solution also allows a module to be updated without that change cascading up the tree. Consider a module graph like A that imports B that imports C. You add a line of code to C, which means you need to update C’s hash as written in B, which in turn changes B and so now you need to update B’s hash as written in A; and now you’ve changed every file in your app. Out-of-band is the proper place for this data.

the same import specifier can resolve to a different module as configured in the import map

@guybedford I've just gone over this whole discussion and think that I probably mis-addressed your concern here. Clearly the literally equal specifier might be 'translated' in its context to the different source as per context provided import map/s. I think this can be solved straight forward by: (A) first translate the specifier to its final ref, if needed; (B) only then apply integrity check; (C) if the integrity was specified in both - the import statement and the source map - some precedence rule should be defined or error thrown or else?

@GeoffreyBooth TBS, I've agreed on the technical concern of the scenario that you describe, and I'm not against having SRI specified in import map. I just think that we should have them both.

All I'm saying is that while use-case when one writes C as part of their own code-base and then reference it with SRI it very unlikely (I'm working many years on huge enterprise applications of any kind, never ever seen anyone mis-trust own modules and willing to generate and use SRIs as part of the workflow).

Much likely, it is all about third/second parties, referenced from CDN, which are not simply changing the line of code but the whole new version release cycle is involved and as such of course it'll involve editing of all the refs.

If the SRI is stored in the source code, then that means every update of the thing you're importing - even nonbreaking ones - requires that your source code have a corresponding diff. That will be unbelievably noisy.

The only place this kind of information belongs is out-of-band.

I see strong objections, which is fine, just can't understand why not having them both?

It can't be harmful, it might be noisy if misused, like anything, and it can be very useful if used reasonably and by spec.

@gullerya it will be quite harmful to allow them inline in source code, for all the reasons indicated. It's far better to have one clear way to do it, that works for all use cases.

While this proposal is at stage 3, the import maps proposal is not yet even on track, please prove me if I'm wrong here.
Also, even the authors of the import maps have expressed some reservations as of using them as out-of-band metadata supplier, see here.
Also, some web app authors won't use import maps as it's an opt-in feature, thus they'll be left without SRI assertion option, what can you suggest them to do?

Let's recap regarding the reasons why not, I've seen so far only 2:

  • circular dependency - I've addressed it as an exception, that should indeed be not supported and also not really a use case for this feature
  • 'noise' - if one don't like it, they may opt it out; also, many times you'll import 3rd party only once, wrap it into your own service/API and use your own service everywhere (keeping the 3rd part abstracted away as internal implementation)

What else have I missed?

This problem is like the deno problem. They use URLs to import dependencies, and if you want to pin the version, when you upgrade the version, you have to upgrade all files.

It's easy to open your editor and replace all references to this URL, but they have another approach.

Create a deps file and re-export the library from that file, therefore you only write the full URL once.

export * as lodash from 'https://...'

I think this method also apply to the SRI.

That is untenable as one file (because it avoids any optimization, by forcing every single third party dep in your program to be loaded all at once) and untenable as one-file-per-dep.