Skip to content

Commit

Permalink
feat: support SRI verification of script tags
Browse files Browse the repository at this point in the history
  • Loading branch information
falsandtru authored and Jonathan Ginsburg committed Jun 14, 2022
1 parent 5e71cf5 commit 6a54b1c
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 12 deletions.
7 changes: 4 additions & 3 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ try {
} catch {}

class Pattern {
constructor (pattern, served, included, watched, nocache, type, isBinary) {
constructor (pattern, served, included, watched, nocache, type, isBinary, integrity) {
this.pattern = pattern
this.served = helper.isDefined(served) ? served : true
this.included = helper.isDefined(included) ? included : true
Expand All @@ -41,6 +41,7 @@ class Pattern {
this.weight = helper.mmPatternWeight(pattern)
this.type = type
this.isBinary = isBinary
this.integrity = integrity
}

compare (other) {
Expand All @@ -49,8 +50,8 @@ class Pattern {
}

class UrlPattern extends Pattern {
constructor (url, type) {
super(url, false, true, false, false, type)
constructor (url, type, integrity) {
super(url, false, true, false, false, type, undefined, integrity)
}
}

Expand Down
4 changes: 2 additions & 2 deletions lib/file-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ class FileList {

let lastCompletedRefresh = this._refreshing
lastCompletedRefresh = Promise.all(
this._patterns.map(async ({ pattern, type, nocache, isBinary }) => {
this._patterns.map(async ({ pattern, type, nocache, isBinary, integrity }) => {
if (helper.isUrlAbsolute(pattern)) {
this.buckets.set(pattern, [new Url(pattern, type)])
this.buckets.set(pattern, [new Url(pattern, type, integrity)])
return
}

Expand Down
4 changes: 3 additions & 1 deletion lib/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const path = require('path')
* File object used for tracking files in `file-list.js`.
*/
class File {
constructor (path, mtime, doNotCache, type, isBinary) {
constructor (path, mtime, doNotCache, type, isBinary, integrity) {
// used for serving (processed path, eg some/file.coffee -> some/file.coffee.js)
this.path = path

Expand All @@ -29,6 +29,8 @@ class File {

// Tri state: null means probe file for binary.
this.isBinary = isBinary === undefined ? null : isBinary

this.integrity = integrity
}

/**
Expand Down
7 changes: 4 additions & 3 deletions lib/middleware/karma.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,12 @@ function createKarmaMiddleware (
scriptTags.push(`<link href="${filePath}" rel="import">`)
} else {
const scriptType = (SCRIPT_TYPE[fileType] || 'text/javascript')
const crossOriginAttribute = includeCrossOriginAttribute ? 'crossorigin="anonymous"' : ''
const crossOriginAttribute = includeCrossOriginAttribute ? ' crossorigin="anonymous"' : ''
const integrityAttribute = file.integrity ? ` integrity="${file.integrity}"` : ''
if (fileType === 'module') {
scriptTags.push(`<script onerror="throw 'Error loading ${filePath}'" type="${scriptType}" src="${filePath}" ${crossOriginAttribute}></script>`)
scriptTags.push(`<script onerror="throw 'Error loading ${filePath}'" type="${scriptType}" src="${filePath}"${integrityAttribute}${crossOriginAttribute}></script>`)
} else {
scriptTags.push(`<script type="${scriptType}" src="${filePath}" ${crossOriginAttribute}></script>`)
scriptTags.push(`<script type="${scriptType}" src="${filePath}"${integrityAttribute}${crossOriginAttribute}></script>`)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion lib/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ const { URL } = require('url')
* Url object used for tracking files in `file-list.js`.
*/
class Url {
constructor (path, type) {
constructor (path, type, integrity) {
this.path = path
this.originalPath = path
this.type = type
this.integrity = integrity
this.isUrl = true
}

Expand Down
19 changes: 17 additions & 2 deletions test/unit/middleware/karma.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ describe('middleware.karma', () => {
let response

class MockFile extends File {
constructor (path, sha, type, content) {
super(path, undefined, undefined, type)
constructor (path, sha, type, content, integrity) {
super(path, undefined, undefined, type, undefined, integrity)
this.sha = sha || 'sha-default'
this.content = content
}
Expand Down Expand Up @@ -230,6 +230,21 @@ describe('middleware.karma', () => {
callHandlerWith('/__karma__/context.html')
})

it('should serve context.html with script tags with integrity checking', (done) => {
includedFiles([
new MockFile('/first.js', 'sha123'),
new MockFile('/second.js', 'sha456', undefined, undefined, 'sha256-XXX')
])

response.once('end', () => {
expect(nextSpy).not.to.have.been.called
expect(response).to.beServedAs(200, 'CONTEXT\n<script type="text/javascript" src="/__proxy__/__karma__/absolute/first.js?sha123" crossorigin="anonymous"></script>\n<script type="text/javascript" src="/__proxy__/__karma__/absolute/second.js?sha456" integrity="sha256-XXX" crossorigin="anonymous"></script>')
done()
})

callHandlerWith('/__karma__/context.html')
})

it('should serve context.html with replaced link tags', (done) => {
includedFiles([
new MockFile('/first.css', 'sha007'),
Expand Down

0 comments on commit 6a54b1c

Please sign in to comment.