Skip to content

Add support for nonce attributes in runtime checking #304

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

Merged
merged 2 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions integration-test/test-runtime-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,4 +324,65 @@ describe('Runtime checks: should allow element modification', () => {
})
expect(scriptResult5).toBe(true)
})

it('Script should support trusted types', async () => {
const port = server.address().port
const page = await browser.newPage()
await gotoAndWait(page, `http://localhost:${port}/blank.html`, {
site: {
enabledFeatures: ['runtimeChecks']
},
featureSettings: {
runtimeChecks: {
taintCheck: 'enabled',
matchAllDomains: 'enabled',
matchAllStackDomains: 'enabled',
overloadInstanceOf: 'enabled'
}
}
})
const scriptResult6 = await page.evaluate(
() => {
// @ts-expect-error Trusted types are not defined on all browsers
const policy = window.trustedTypes.createPolicy('test', {
createScriptURL: (url) => url
})
const myScript = document.createElement('script')
myScript.src = policy.createScriptURL('http://example.com')
const srcVal = myScript.src

myScript.setAttribute('src', policy.createScriptURL('http://example2.com'))
const srcVal2 = myScript.getAttribute('src')
const srcVal3 = myScript.src

document.body.appendChild(myScript)

// After append
myScript.setAttribute('src', policy.createScriptURL('http://example3.com'))
const srcVal4 = myScript.getAttribute('src')
const srcVal5 = myScript.src

myScript.src = policy.createScriptURL('http://example4.com')
const srcVal6 = myScript.getAttribute('src')
const srcVal7 = myScript.src
return {
srcVal,
srcVal2,
srcVal3,
srcVal4,
srcVal5,
srcVal6,
srcVal7
}
})
expect(scriptResult6).toEqual({
srcVal: 'http://example.com',
srcVal2: 'http://example2.com',
srcVal3: 'http://example2.com',
srcVal4: 'http://example3.com/',
srcVal5: 'http://example3.com/',
srcVal6: 'http://example4.com/',
srcVal7: 'http://example4.com/'
})
})
})
62 changes: 59 additions & 3 deletions src/features/runtime-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,22 @@ function shouldFilterKey (tagName, filterName, key) {
let elementRemovalTimeout
const featureName = 'runtimeChecks'
const symbol = Symbol(featureName)
const supportedSinks = ['src']

class DDGRuntimeChecks extends HTMLElement {
#tagName
#el
#listeners
#connected
#sinks

constructor () {
super()
this.#tagName = null
this.#el = null
this.#listeners = []
this.#connected = false
this.#sinks = {}
}

/**
Expand Down Expand Up @@ -106,9 +109,20 @@ class DDGRuntimeChecks extends HTMLElement {
}

// Reflect all props to the new element
for (const param of Object.keys(this)) {
if (shouldFilterKey(this.#tagName, 'property', param)) continue
el[param] = this[param]
const props = Object.keys(this)

// Nonce isn't enumerable so we need to add it manually
props.push('nonce')

for (const prop of props) {
if (shouldFilterKey(this.#tagName, 'property', prop)) continue
el[prop] = this[prop]
}

for (const sink of supportedSinks) {
if (this.#sinks[sink]) {
el[sink] = this.#sinks[sink]
}
}

// Reflect all listeners to the new element
Expand Down Expand Up @@ -155,8 +169,46 @@ class DDGRuntimeChecks extends HTMLElement {

/* Native DOM element methods we're capturing to supplant values into the constructed node or store data for. */

set src (value) {
const el = this.#getElement()
if (el) {
el.src = value
return
}
this.#sinks.src = value
}

get src () {
const el = this.#getElement()
if (el) {
return el.src
}
// @ts-expect-error TrustedScriptURL is not defined in the TS lib
// eslint-disable-next-line no-undef
if ('TrustedScriptURL' in window && this.#sinks.src instanceof TrustedScriptURL) {
return this.#sinks.src.toString()
}
return this.#sinks.src
}

getAttribute (name, value) {
if (shouldFilterKey(this.#tagName, 'attribute', name)) return
if (supportedSinks.includes(name)) {
return this[name]
}
const el = this.#getElement()
if (el) {
return el.getAttribute(name)
}
return super.getAttribute(name)
}

setAttribute (name, value) {
if (shouldFilterKey(this.#tagName, 'attribute', name)) return
if (supportedSinks.includes(name)) {
this[name] = value
return
}
const el = this.#getElement()
if (el) {
return el.setAttribute(name, value)
Expand All @@ -166,6 +218,10 @@ class DDGRuntimeChecks extends HTMLElement {

removeAttribute (name) {
if (shouldFilterKey(this.#tagName, 'attribute', name)) return
if (supportedSinks.includes(name)) {
delete this[name]
return
}
const el = this.#getElement()
if (el) {
return el.removeAttribute(name)
Expand Down