@@ -9,11 +9,13 @@ import {
9
9
appendChild ,
10
10
replaceChildren ,
11
11
textContentSet ,
12
+ addEventListener ,
13
+ ownerDocument ,
12
14
navigation ,
13
15
url , destination , includes ,
14
16
preventDefault , stopPropagation ,
15
17
} from './native.mjs' ;
16
- import { distraction , hardened } from './element.mjs' ;
18
+ import { distraction , loadable , hardened } from './element.mjs' ;
17
19
import { getShadow } from './shadow.mjs' ;
18
20
19
21
// 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) {
37
39
const shadow = getShadow ( host , opts ) ;
38
40
replaceChildren ( shadow ) ;
39
41
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
+
40
54
// child of the shadow, where the secret is set, must be hardened
41
55
const child = hardened ( ) ;
42
56
appendChild ( shadow , child ) ;
@@ -54,6 +68,9 @@ export function LavaDome(host, opts) {
54
68
return textContentSet ( child , text ) ;
55
69
}
56
70
71
+ // attach loadable only once per instance to avoid excessive load firing
72
+ appendChild ( shadow , iframe ) ;
73
+
57
74
// place each char of the secret in its own LavaDome protection instance
58
75
map ( from ( text ) , char => {
59
76
const span = createElement ( document , 'span' ) ;
@@ -66,4 +83,4 @@ export function LavaDome(host, opts) {
66
83
// add a distraction against side channel leaks attack attempts
67
84
appendChild ( child , distraction ( ) ) ;
68
85
}
69
- }
86
+ }
0 commit comments