Skip to content

Commit f44c890

Browse files
Testing and cleanup
1 parent 529a6f4 commit f44c890

File tree

8 files changed

+198
-11
lines changed

8 files changed

+198
-11
lines changed

integration-test/test-pages.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ describe('Test integration pages', () => {
2727
'runtime-checks/pages/replace-element.html': 'runtime-checks/config/replace-element.json',
2828
'runtime-checks/pages/filter-props.html': 'runtime-checks/config/filter-props.json',
2929
'runtime-checks/pages/shadow-dom.html': 'runtime-checks/config/shadow-dom.json',
30-
'runtime-checks/pages/script-overload.html': 'runtime-checks/config/script-overload.json'
30+
'runtime-checks/pages/script-overload.html': 'runtime-checks/config/script-overload.json',
31+
'runtime-checks/pages/generic-overload.html': 'runtime-checks/config/generic-overload.json'
3132
}
3233
for (const pageName in pages) {
3334
const configName = pages[pageName]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"features": {
3+
"runtimeChecks": {
4+
"state": "enabled",
5+
"exceptions": [],
6+
"settings": {
7+
"taintCheck": "enabled",
8+
"matchAllDomains": "enabled",
9+
"matchAllStackDomains": "enabled",
10+
"overloadInstanceOf": "enabled",
11+
"domains": [
12+
],
13+
"stackDomains": [
14+
],
15+
"scriptOverload": {
16+
},
17+
"breakpoints": [
18+
{"height": 768, "width": 1024},
19+
{"height": 1024, "width": 768},
20+
{"height": 375, "width": 812},
21+
{"height": 812, "width": 375}
22+
],
23+
"injectGenericOverloads": {
24+
"Date": {},
25+
"Date.prototype.getTimezoneOffset": {},
26+
"NavigatorUAData.prototype.getHighEntropyValues": {},
27+
"localStorage": {
28+
"scheme": "session"
29+
},
30+
"sessionStorage": {
31+
"scheme": "memory"
32+
},
33+
"innerHeight": {
34+
"offset": 100
35+
},
36+
"innerWidth": {
37+
"offset": 100
38+
},
39+
"Screen.prototype.height": {},
40+
"Screen.prototype.width": {}
41+
}
42+
}
43+
}
44+
}
45+
}

integration-test/test-pages/runtime-checks/index.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,34 @@
77
</head>
88
<body>
99
<p><a href="../../index.html">[Home]</a></p>
10+
<script>
11+
function makeLinkElement (link) {
12+
const listItem = document.createElement('li')
13+
const linkElement = document.createElement('a')
14+
linkElement.href = link.href
15+
linkElement.innerText = link.text
16+
}
17+
async function init () {
18+
console.log('init')
19+
const response = await fetch('./pages.json')
20+
const pages = await response.json()
21+
const ulElement = document.querySelector('ul')
22+
for (const page of pages) {
23+
const linkElement = makeLinkElement(page)
24+
ulElement.appendChild(linkElement)
25+
}
26+
}
27+
init()
28+
</script>
1029

1130
<p>Runtime element interrogation (runtimeChecks) allows our clients the ability to validate, inspect and modify elements as they get injected into a web page by website scripts.</p>
1231
<ul>
32+
<!--
1333
<li><a href="./pages/basic-run.html">Basic Run</a> - <a href="./config/basic-run.json">Config</a></li>
1434
<li><a href="./pages/filter-props.html">Filter props</a> - <a href="./config/filter-props.json">Config</a></li>
1535
<li><a href="./pages/script-overload.html">Script overloading</a> - <a href="./config/script-overload.json">Config</a></li>
1636
<li><a href="./pages/shadow-dom.html">Shadow dom support</a> - <a href="./config/shadow-dom.json">Config</a></li>
37+
-->
1738
</ul>
1839

1940
</body>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"Basic Run": {
3+
"html": "runtime-checks/pages/basic-run.html",
4+
"config": "runtime-checks/config/basic-run.json"
5+
},
6+
"Replace element": {
7+
"html": "runtime-checks/pages/replace-element.html",
8+
"config": "runtime-checks/config/replace-element.json"
9+
},
10+
"Filter props": {
11+
"html": "runtime-checks/pages/filter-props.html",
12+
"config": "runtime-checks/config/filter-props.json"
13+
},
14+
"Shadow DOM": {
15+
"html": "runtime-checks/pages/shadow-dom.html",
16+
"config": "runtime-checks/config/shadow-dom.json"
17+
},
18+
"Script overload": {
19+
"html": "runtime-checks/pages/script-overload.html",
20+
"config": "runtime-checks/config/script-overload.json"
21+
},
22+
"Generic overload": {
23+
"html": "runtime-checks/pages/generic-overload.html",
24+
"config": "runtime-checks/config/generic-overload.json"
25+
}
26+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width">
6+
<title>Runtime checks</title>
7+
<link rel="stylesheet" href="../../shared/style.css">
8+
</head>
9+
<body>
10+
<script src="../../shared/utils.js"></script>
11+
<p><a href="../index.html">[Runtime checks]</a></p>
12+
13+
<p>This page verifies that script overloading works <a href="../config/script-overload.json">config</a></p>
14+
15+
<script>
16+
test('Script should have generic overloaded changes', async () => {
17+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
18+
window.scriptOutput = false;
19+
window.scriptyRan = false;
20+
const scriptElement = document.createElement('script');
21+
scriptElement.src = './../../shared/replay.js';
22+
scriptElement.id = 'overloadedScript';
23+
let resolver
24+
const promise = new Promise((resolve) => {
25+
resolver = resolve
26+
})
27+
scriptElement.onload = () => {
28+
resolver()
29+
}
30+
document.body.appendChild(scriptElement);
31+
await promise
32+
console.log(window.scriptyRan, window.replayScript)
33+
localStorage.clear()
34+
replayScript(`
35+
localStorage.clear();
36+
localStorage.setItem('test', 'test');
37+
window.scriptyRan = true;
38+
window.scriptOutput = {
39+
item: localStorage.getItem('test')
40+
}
41+
`)
42+
43+
const scripty = document.querySelector('script#overloadedScript');
44+
const nodeAndFakeNodeMatch = scripty === scriptElement;
45+
46+
return [
47+
{ name: 'script ran', result: window.scriptyRan, expected: true },
48+
{ name: 'node and fake node match', result: nodeAndFakeNodeMatch, expected: false },
49+
{ name: 'expected localStorage first response', result: window.scriptOutput, expected: {
50+
item: 'test'
51+
}},
52+
{ name: 'did not globally store', result: localStorage.getItem('test'), expected: null }
53+
];
54+
});
55+
56+
renderResults();
57+
</script>
58+
</body>
59+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function replayScript (scriptText) {
2+
const scriptElement = document.createElement('script')
3+
scriptElement.innerText = scriptText
4+
document.head.appendChild(scriptElement)
5+
}
6+
window.replayScript = replayScript

src/features/runtime-checks.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* global TrustedScriptURL, TrustedScript */
22

3-
import { glob } from 'typedoc/dist/lib/utils/fs.js'
43
import ContentFeature from '../content-feature.js'
5-
import { DDGProxy, getStackTraceOrigins, getStack, matchHostname, injectGlobalStyles, createStyleElement, postDebugMessage, taintSymbol, hasTaintedMethod, defineProperty, getTabHostname } from '../utils.js'
4+
import { DDGProxy, getStackTraceOrigins, getStack, matchHostname, injectGlobalStyles, createStyleElement, postDebugMessage, taintSymbol, hasTaintedMethod, taintedOrigins } from '../utils.js'
5+
import { defineProperty } from '../wrapper-utils.js'
66
import { wrapScriptCodeOverload } from './runtime-checks/script-overload.js'
77
import { Reflect } from '../captured-globals.js'
88

@@ -185,8 +185,8 @@ class DDGRuntimeChecks extends HTMLElement {
185185
}
186186
try {
187187
const origin = this.src && new URL(this.src, window.location.href).hostname
188-
if (origin && navigator?.duckduckgo?.taintedOrigins) {
189-
navigator.duckduckgo.taintedOrigins.add(origin)
188+
if (origin && taintedOrigins()) {
189+
taintedOrigins()?.add(origin)
190190
}
191191
} catch {}
192192
}
@@ -694,7 +694,7 @@ export default class RuntimeChecks extends ContentFeature {
694694
this.overrideStorage(config, key, storage)
695695
}
696696

697-
overloadStorageWithSession (key, config) {
697+
overloadStorageWithSession (config, key) {
698698
const storage = globalThis.sessionStorage
699699
this.overrideStorage(config, key, storage)
700700
}
@@ -724,7 +724,7 @@ export default class RuntimeChecks extends ContentFeature {
724724
* @param {string} key
725725
* @param {number} [offset]
726726
*/
727-
overloadScreenSizes (config, breakpoints, screenSize, key, offset) {
727+
overloadScreenSizes (config, breakpoints, screenSize, key, offset = 0) {
728728
const closest = findClosestBreakpoint(breakpoints, screenSize)
729729
if (!closest) {
730730
return

src/utils.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -351,18 +351,47 @@ export function getContextId (scope) {
351351
}
352352
}
353353

354-
const taintedOrigins = createSet()
354+
/**
355+
* Returns a set of origins that are tainted
356+
* @returns {Set<string> | null}
357+
*/
358+
export function taintedOrigins () {
359+
return getGlobalObject('taintedOrigins')
360+
}
361+
362+
/**
363+
* Returns a set of taints
364+
* @returns {Set<string> | null}
365+
*/
366+
export function taints () {
367+
return getGlobalObject('taints')
368+
}
369+
370+
/**
371+
* @param {string} name
372+
* @returns {any | null}
373+
*/
374+
function getGlobalObject (name) {
375+
if ('duckduckgo' in navigator &&
376+
typeof navigator.duckduckgo === 'object' &&
377+
navigator.duckduckgo &&
378+
name in navigator.duckduckgo &&
379+
navigator.duckduckgo[name]) {
380+
return navigator.duckduckgo[name]
381+
}
382+
return null
383+
}
384+
355385
export function hasTaintedMethod (scope, shouldStackCheck = false) {
356386
if (document?.currentScript?.[taintSymbol]) return true
357387
if ('__ddg_taint__' in window) return true
358388
if (getContextId(scope)) return true
359-
if (!shouldStackCheck || !navigator?.duckduckgo?.taintedOrigins) {
389+
if (!shouldStackCheck || !taintedOrigins()) {
360390
return false
361391
}
362392
const stackOrigins = getStackTraceOrigins(getStack())
363393
for (const stackOrigin of stackOrigins) {
364-
if (navigator.duckduckgo.taintedOrigins.has(stackOrigin)) {
365-
console.log('found tainted origin', stackOrigin)
394+
if (taintedOrigins()?.has(stackOrigin)) {
366395
return true
367396
}
368397
}

0 commit comments

Comments
 (0)