Files
safetynet/apps/browser/clipboard-dom.cjs
T
2026-10-13 10:34:48 +02:00

45 lines
4.9 KiB
JavaScript

'use strict';
function clipboardDOM(mode,url,token,operation,payload,limit){
if(mode==='discard'){if(globalThis.__safetynetClipboardSnapshot?.token===token)delete globalThis.__safetynetClipboardSnapshot;return {completed:true};}
if(location.href!==url||window.top!==window)throw Error('Clipboard frame changed');
const field=document.activeElement;
const editable=e=>e&&(e.tagName==='TEXTAREA'||e.tagName==='INPUT'&&/^(text|search|email|url|tel)$/i.test(e.type))&&!e.disabled&&!e.readOnly;
const sensitive=e=>{for(let p=e;p&&p!==document.body;p=p.parentElement)if(/password|passwd|secret|token|credential|api.?key|auth|one.?time|otp|master/i.test(['name','id','autocomplete','aria-label'].map(a=>p.getAttribute(a)||'').join(' ')))return true;return /^(password|file|hidden)$/i.test(e?.type||'');};
const visible=e=>{if(!e||e.closest('[hidden],[aria-hidden="true"],[inert]'))return false;let p=e,count=0;while(p&&count++<64){const s=getComputedStyle(p);if(s.display==='none'||s.visibility==='hidden'||s.opacity==='0')return false;p=p.parentElement;}return !p&&e.getClientRects().length>0;};
const rangeSafe=range=>{const ancestor=range.commonAncestorContainer.nodeType===1?range.commonAncestorContainer:range.commonAncestorContainer.parentElement;if(!ancestor?.isConnected||!visible(ancestor)||sensitive(ancestor)||ancestor.closest('input,textarea,[contenteditable],script,style,template,noscript')||ancestor.querySelector?.('input,textarea,[contenteditable]'))return false;const walker=document.createTreeWalker(ancestor,NodeFilter.SHOW_ELEMENT);let e,visited=0;while((e=walker.nextNode())){if(++visited>2048)return false;if(range.intersectsNode(e)&&(!visible(e)||sensitive(e)||/^(SCRIPT|STYLE|TEMPLATE|NOSCRIPT)$/.test(e.tagName)))return false;}return true;};
if(mode==='stage'){
const snapshot={token,operation,field};let text='';
if(field&&(field.tagName==='INPUT'||field.tagName==='TEXTAREA'||field.isContentEditable)){
if(!editable(field)||sensitive(field)||!visible(field))throw Error('Clipboard target denied');
snapshot.kind='field';snapshot.value=field.value;if(snapshot.value.length>limit)throw Error('Clipboard target size limit');snapshot.start=field.selectionStart;snapshot.end=field.selectionEnd;
if(!Number.isInteger(snapshot.start)||!Number.isInteger(snapshot.end))throw Error('Clipboard selection unsupported');
text=operation==='paste'?'':snapshot.value.slice(snapshot.start,snapshot.end);
}else{
if(operation!=='copy')throw Error('Clipboard edit target unsupported');
const selection=getSelection();if(!selection||selection.rangeCount!==1||selection.isCollapsed)throw Error('Clipboard selection unavailable');
const range=selection.getRangeAt(0),ancestor=range.commonAncestorContainer.nodeType===1?range.commonAncestorContainer:range.commonAncestorContainer.parentElement;
if(!rangeSafe(range))throw Error('Clipboard rich selection unsupported');
snapshot.kind='range';snapshot.range=range.cloneRange();text=range.toString();
}
if(typeof text!=='string'||text.length>limit||text.includes('\0'))throw Error('Clipboard size limit');
snapshot.text=text;globalThis.__safetynetClipboardSnapshot=snapshot;return {text};
}
const snapshot=globalThis.__safetynetClipboardSnapshot;if(mode!=='validate')delete globalThis.__safetynetClipboardSnapshot;
if(!snapshot||snapshot.token!==token||snapshot.operation!==operation)throw Error('Clipboard snapshot unavailable');
if(snapshot.kind==='field'){
const e=snapshot.field;if(e!==document.activeElement||!e.isConnected||e.value!==snapshot.value||e.selectionStart!==snapshot.start||e.selectionEnd!==snapshot.end||!editable(e)||sensitive(e)||!visible(e))throw Error('Clipboard target changed');
if(mode!=='validate'&&operation!=='copy'){
const value=operation==='paste'?payload:'';if(typeof value!=='string'||value.length>limit)throw Error('Clipboard payload denied');
e.setRangeText(value,snapshot.start,snapshot.end,'end');e.dispatchEvent(new Event('input',{bubbles:true}));
}
}else{
const selection=getSelection(),range=selection?.rangeCount===1?selection.getRangeAt(0):null,original=snapshot.range;
if(!range||range.startContainer!==original.startContainer||range.endContainer!==original.endContainer||range.startOffset!==original.startOffset||range.endOffset!==original.endOffset||range.toString()!==snapshot.text)throw Error('Clipboard selection changed');
const ancestor=range.commonAncestorContainer.nodeType===1?range.commonAncestorContainer:range.commonAncestorContainer.parentElement;
if(!rangeSafe(range))throw Error('Clipboard selection changed');
}
return {completed:true};
}
const clipboardScript=(mode,{url,token,operation,text='',limit})=>`(${clipboardDOM.toString()})(${JSON.stringify(mode)},${JSON.stringify(url)},${JSON.stringify(token)},${JSON.stringify(operation)},${JSON.stringify(text)},${JSON.stringify(limit)})`;
module.exports={clipboardScript};