'use strict'; // Runs only in a private isolated world. No bridge, page callback or caller code. function visualDocumentProbe(expectedURL) { if (location.href !== expectedURL || document.readyState !== 'complete') throw Error('Visual probe refused'); const key='__safetynetVisualProbeV1'; let state=globalThis[key]; if (!state) { state={id:crypto.randomUUID(),revision:0,roots:new WeakSet(),watches:[]}; const changed=()=>{state.revision++;}; state.watch=root=>{ if(state.roots.has(root))return; if(state.watches.length>=129)throw Error('Visual probe refused'); const observer=new MutationObserver(changed); observer.observe(root,{subtree:true,childList:true,characterData:true,attributes:true}); state.roots.add(root);state.watches.push({root,observer}); if(root!==document)changed(); }; state.watch(document); for (const name of ['input','change','scroll','resize']) addEventListener(name,changed,true); Object.defineProperty(globalThis,key,{value:state,writable:false,configurable:false}); } for(const watch of [...state.watches]){ if(watch.root!==document&&watch.root.host?.isConnected===false){watch.observer.disconnect();state.roots.delete(watch.root);state.watches.splice(state.watches.indexOf(watch),1);state.revision++;} else if(watch.observer.takeRecords().length)state.revision++; } const forbidden=/password|passwd|secret|token|credential|api.?key|auth|one.?time|otp|credit.?card|cc-number|cc-csc/i; let safe=true,visited=0; const visible=e=>{ for(let n=e;n;n=n.parentElement){const s=getComputedStyle(n);if(s.display==='none'||s.visibility==='hidden'||s.opacity==='0'||n.hidden)return false;} const r=e.getBoundingClientRect();return r.width>0&&r.height>0&&r.bottom>0&&r.right>0&&r.left20000){safe=false;return;} if(['IFRAME','FRAME','OBJECT','EMBED'].includes(e.tagName)){safe=false;return;} // Credential-bearing controls are refused even offscreen: a transient CSS // reveal must not evade the checks surrounding native surface capture. if(/^(INPUT|TEXTAREA|SELECT)$/.test(e.tagName)&& (e.type==='password'||['name','id','autocomplete','aria-label'].some(a=>forbidden.test(e.getAttribute(a)||'')))){safe=false;return;} if(e.hasAttribute('data-sensitive')&&visible(e)){safe=false;return;} if(e.shadowRoot){walk(e.shadowRoot);if(!safe)return;} } } walk(document); const width=Math.round(innerWidth),height=Math.round(innerHeight); // Active media/animations are not represented by the DOM revision counter. if(state.watches.some(w=>(w.root.getAnimations?.()||[]).some(a=>a.playState==='running'))|| [...document.querySelectorAll('video')].some(e=>visible(e)&&!e.paused))safe=false; return {safe,revision:[state.id,state.revision,width,height,scrollX,scrollY,devicePixelRatio,visualViewport?.scale||1].join(':'),width,height}; } async function probeVisualContext(context) { const contents=context.webContents,frame=contents.mainFrame; if(!frame||frame.isDestroyed()||frame.framesInSubtree.length!==1||contents.isLoading())throw Error('Managed visual export refused'); await require('./page-clipboard-guard.cjs').guardedVisualStructure(contents); const result=await contents.executeJavaScriptInIsolatedWorld(1004,[{code:`(${visualDocumentProbe.toString()})(${JSON.stringify(context.url)})`}],false); if(contents.isDestroyed()||contents.mainFrame!==frame||frame.isDestroyed()||frame.framesInSubtree.length!==1)throw Error('Managed visual export refused'); return result; } module.exports={probeVisualContext,visualDocumentProbe};