Skip to content

Commit 30e1dfb

Browse files
Add basic support for trusted types (#303)
1 parent d1170ec commit 30e1dfb

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

integration-test/test-runtime-checks.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,4 +324,65 @@ describe('Runtime checks: should allow element modification', () => {
324324
})
325325
expect(scriptResult5).toBe(true)
326326
})
327+
328+
it('Script should support trusted types', async () => {
329+
const port = server.address().port
330+
const page = await browser.newPage()
331+
await gotoAndWait(page, `http://localhost:${port}/blank.html`, {
332+
site: {
333+
enabledFeatures: ['runtimeChecks']
334+
},
335+
featureSettings: {
336+
runtimeChecks: {
337+
taintCheck: 'enabled',
338+
matchAllDomains: 'enabled',
339+
matchAllStackDomains: 'enabled',
340+
overloadInstanceOf: 'enabled'
341+
}
342+
}
343+
})
344+
const scriptResult6 = await page.evaluate(
345+
() => {
346+
// @ts-expect-error Trusted types are not defined on all browsers
347+
const policy = window.trustedTypes.createPolicy('test', {
348+
createScriptURL: (url) => url
349+
})
350+
const myScript = document.createElement('script')
351+
myScript.src = policy.createScriptURL('http://example.com')
352+
const srcVal = myScript.src
353+
354+
myScript.setAttribute('src', policy.createScriptURL('http://example2.com'))
355+
const srcVal2 = myScript.getAttribute('src')
356+
const srcVal3 = myScript.src
357+
358+
document.body.appendChild(myScript)
359+
360+
// After append
361+
myScript.setAttribute('src', policy.createScriptURL('http://example3.com'))
362+
const srcVal4 = myScript.getAttribute('src')
363+
const srcVal5 = myScript.src
364+
365+
myScript.src = policy.createScriptURL('http://example4.com')
366+
const srcVal6 = myScript.getAttribute('src')
367+
const srcVal7 = myScript.src
368+
return {
369+
srcVal,
370+
srcVal2,
371+
srcVal3,
372+
srcVal4,
373+
srcVal5,
374+
srcVal6,
375+
srcVal7
376+
}
377+
})
378+
expect(scriptResult6).toEqual({
379+
srcVal: 'http://example.com',
380+
srcVal2: 'http://example2.com',
381+
srcVal3: 'http://example2.com',
382+
srcVal4: 'http://example3.com/',
383+
srcVal5: 'http://example3.com/',
384+
srcVal6: 'http://example4.com/',
385+
srcVal7: 'http://example4.com/'
386+
})
387+
})
327388
})

src/features/runtime-checks.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,22 @@ function shouldFilterKey (tagName, filterName, key) {
2222
let elementRemovalTimeout
2323
const featureName = 'runtimeChecks'
2424
const symbol = Symbol(featureName)
25+
const supportedSinks = ['src']
2526

2627
class DDGRuntimeChecks extends HTMLElement {
2728
#tagName
2829
#el
2930
#listeners
3031
#connected
32+
#sinks
3133

3234
constructor () {
3335
super()
3436
this.#tagName = null
3537
this.#el = null
3638
this.#listeners = []
3739
this.#connected = false
40+
this.#sinks = {}
3841
}
3942

4043
/**
@@ -111,6 +114,12 @@ class DDGRuntimeChecks extends HTMLElement {
111114
el[param] = this[param]
112115
}
113116

117+
for (const sink of supportedSinks) {
118+
if (this.#sinks[sink]) {
119+
el[sink] = this.#sinks[sink]
120+
}
121+
}
122+
114123
// Reflect all listeners to the new element
115124
for (const [...args] of this.#listeners) {
116125
if (shouldFilterKey(this.#tagName, 'listener', args[0])) continue
@@ -155,8 +164,46 @@ class DDGRuntimeChecks extends HTMLElement {
155164

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

167+
set src (value) {
168+
const el = this.#getElement()
169+
if (el) {
170+
el.src = value
171+
return
172+
}
173+
this.#sinks.src = value
174+
}
175+
176+
get src () {
177+
const el = this.#getElement()
178+
if (el) {
179+
return el.src
180+
}
181+
// @ts-expect-error TrustedScriptURL is not defined in the TS lib
182+
// eslint-disable-next-line no-undef
183+
if ('TrustedScriptURL' in window && this.#sinks.src instanceof TrustedScriptURL) {
184+
return this.#sinks.src.toString()
185+
}
186+
return this.#sinks.src
187+
}
188+
189+
getAttribute (name, value) {
190+
if (shouldFilterKey(this.#tagName, 'attribute', name)) return
191+
if (supportedSinks.includes(name)) {
192+
return this[name]
193+
}
194+
const el = this.#getElement()
195+
if (el) {
196+
return el.getAttribute(name)
197+
}
198+
return super.getAttribute(name)
199+
}
200+
158201
setAttribute (name, value) {
159202
if (shouldFilterKey(this.#tagName, 'attribute', name)) return
203+
if (supportedSinks.includes(name)) {
204+
this[name] = value
205+
return
206+
}
160207
const el = this.#getElement()
161208
if (el) {
162209
return el.setAttribute(name, value)
@@ -166,6 +213,10 @@ class DDGRuntimeChecks extends HTMLElement {
166213

167214
removeAttribute (name) {
168215
if (shouldFilterKey(this.#tagName, 'attribute', name)) return
216+
if (supportedSinks.includes(name)) {
217+
delete this[name]
218+
return
219+
}
169220
const el = this.#getElement()
170221
if (el) {
171222
return el.removeAttribute(name)

0 commit comments

Comments
 (0)