Skip to content

Commit

Permalink
Refuse to migrate from top into non-top documents (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
weizman authored Oct 4, 2024
1 parent fa4643f commit 6e6e212
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 3 deletions.
6 changes: 5 additions & 1 deletion packages/core/src/element.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,8 @@ export const distraction = invoker(creator({
'top': '-10px', 'right': '-10px', 'position': 'fixed',
// font-size smaller than 1px fails to be a distraction on Firefox
'font-size': '1px',
}, () => 'span', all));
}, () => 'span', all));

export const loadable = invoker(creator({
'display': 'none',
}, () => 'iframe'));
21 changes: 19 additions & 2 deletions packages/core/src/lavadome.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import {
appendChild,
replaceChildren,
textContentSet,
addEventListener,
ownerDocument,
navigation,
url, destination, includes,
preventDefault, stopPropagation,
} from './native.mjs';
import {distraction, hardened} from './element.mjs';
import {distraction, loadable, hardened} from './element.mjs';
import {getShadow} from './shadow.mjs';

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

// fire every time instance is reloaded and abort loading for non-top documents
const iframe = loadable();
addEventListener(iframe, 'load', () => {
const ownerDoc = ownerDocument(iframe);
if (ownerDoc !== document) {
replaceChildren(shadow);
throw new Error(`LavaDomeCore: ` +
`The document to which LavaDome was originally introduced ` +
`must be the same as the one this instance is inserted to`);
}
});

// child of the shadow, where the secret is set, must be hardened
const child = hardened();
appendChild(shadow, child);
Expand All @@ -54,6 +68,9 @@ export function LavaDome(host, opts) {
return textContentSet(child, text);
}

// attach loadable only once per instance to avoid excessive load firing
appendChild(shadow, iframe);

// place each char of the secret in its own LavaDome protection instance
map(from(text), char => {
const span = createElement(document, 'span');
Expand All @@ -66,4 +83,4 @@ export function LavaDome(host, opts) {
// add a distraction against side channel leaks attack attempts
appendChild(child, distraction());
}
}
}
2 changes: 2 additions & 0 deletions packages/core/src/native.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const { stringify } = JSON;
const n = (obj, prop, accessor) =>
obj && Function.prototype.call.bind(getOwnPropertyDescriptor(obj, prop)[accessor]);

export const ownerDocument = n(globalThis?.Node?.prototype, 'ownerDocument', 'get');
export const addEventListener = n(globalThis?.EventTarget?.prototype, 'addEventListener', 'value');
export const replaceChildren = n(globalThis?.DocumentFragment?.prototype, 'replaceChildren', 'value');
export const attachShadow = n(globalThis?.Element?.prototype, 'attachShadow', 'value');
export const createElement = n(globalThis?.Document?.prototype, 'createElement', 'value');
Expand Down

0 comments on commit 6e6e212

Please sign in to comment.