Skip to content

Commit 6e6e212

Browse files
authored
Refuse to migrate from top into non-top documents (#42)
1 parent fa4643f commit 6e6e212

File tree

3 files changed

+26
-3
lines changed

3 files changed

+26
-3
lines changed

packages/core/src/element.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@ export const distraction = invoker(creator({
5353
'top': '-10px', 'right': '-10px', 'position': 'fixed',
5454
// font-size smaller than 1px fails to be a distraction on Firefox
5555
'font-size': '1px',
56-
}, () => 'span', all));
56+
}, () => 'span', all));
57+
58+
export const loadable = invoker(creator({
59+
'display': 'none',
60+
}, () => 'iframe'));

packages/core/src/lavadome.mjs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import {
99
appendChild,
1010
replaceChildren,
1111
textContentSet,
12+
addEventListener,
13+
ownerDocument,
1214
navigation,
1315
url, destination, includes,
1416
preventDefault, stopPropagation,
1517
} from './native.mjs';
16-
import {distraction, hardened} from './element.mjs';
18+
import {distraction, loadable, hardened} from './element.mjs';
1719
import {getShadow} from './shadow.mjs';
1820

1921
// text-fragments links can be abused to leak shadow internals - block in-app redirection to them
@@ -37,6 +39,18 @@ export function LavaDome(host, opts) {
3739
const shadow = getShadow(host, opts);
3840
replaceChildren(shadow);
3941

42+
// fire every time instance is reloaded and abort loading for non-top documents
43+
const iframe = loadable();
44+
addEventListener(iframe, 'load', () => {
45+
const ownerDoc = ownerDocument(iframe);
46+
if (ownerDoc !== document) {
47+
replaceChildren(shadow);
48+
throw new Error(`LavaDomeCore: ` +
49+
`The document to which LavaDome was originally introduced ` +
50+
`must be the same as the one this instance is inserted to`);
51+
}
52+
});
53+
4054
// child of the shadow, where the secret is set, must be hardened
4155
const child = hardened();
4256
appendChild(shadow, child);
@@ -54,6 +68,9 @@ export function LavaDome(host, opts) {
5468
return textContentSet(child, text);
5569
}
5670

71+
// attach loadable only once per instance to avoid excessive load firing
72+
appendChild(shadow, iframe);
73+
5774
// place each char of the secret in its own LavaDome protection instance
5875
map(from(text), char => {
5976
const span = createElement(document, 'span');
@@ -66,4 +83,4 @@ export function LavaDome(host, opts) {
6683
// add a distraction against side channel leaks attack attempts
6784
appendChild(child, distraction());
6885
}
69-
}
86+
}

packages/core/src/native.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const { stringify } = JSON;
2020
const n = (obj, prop, accessor) =>
2121
obj && Function.prototype.call.bind(getOwnPropertyDescriptor(obj, prop)[accessor]);
2222

23+
export const ownerDocument = n(globalThis?.Node?.prototype, 'ownerDocument', 'get');
24+
export const addEventListener = n(globalThis?.EventTarget?.prototype, 'addEventListener', 'value');
2325
export const replaceChildren = n(globalThis?.DocumentFragment?.prototype, 'replaceChildren', 'value');
2426
export const attachShadow = n(globalThis?.Element?.prototype, 'attachShadow', 'value');
2527
export const createElement = n(globalThis?.Document?.prototype, 'createElement', 'value');

0 commit comments

Comments
 (0)