Files
safetynet/tests/contenteditable-clipboard.fixture.cjs
T

35 lines
4.5 KiB
JavaScript

const {app,BrowserWindow}=require('electron');
const assert=require('node:assert/strict');
const {clipboardScript}=require('../apps/browser/clipboard-dom.cjs');
const {createClipboardGuard}=require('../apps/browser/clipboard-guard.cjs');
app.disableHardwareAcceleration();app.enableSandbox();
app.whenReady().then(async()=>{
const {defaultPolicy}=await import('../packages/core/policy.mjs');const {inspectClipboard}=await import('../packages/core/clipboard.mjs');
const win=new BrowserWindow({show:false,webPreferences:{sandbox:true,nodeIntegration:false,contextIsolation:true}}),wc=win.webContents;
await wc.loadURL('data:text/html,<div id="editor" contenteditable="true" role="textbox"><p>Public draft</p></div><input id="other">');
const url=wc.getURL(),exec=code=>wc.executeJavaScriptInIsolatedWorld(999,[{code}],false);
const focus=async()=>exec("(()=>{const e=document.getElementById('editor');e.focus();const r=document.createRange();r.selectNodeContents(e);r.collapse(false);const s=getSelection();s.removeAllRanges();s.addRange(r);})()");
const dom=(mode,args)=>exec(clipboardScript(mode,{...args,url,limit:4096}));
const policy=defaultPolicy();policy.dlp.rules=[{id:'secret',label:'Secret',kind:'literal',value:'Secret Roadmap',action:'block'}];let paste=' public <img src=x onerror=alert(1)>',writes=[];
// Hidden native contents can report no focused frame; exercise the DOM under
// its real main frame, and use a context facade for native focus checks.
const facade={mainFrame:wc.mainFrame,focusedFrame:wc.mainFrame};
const args={url,token:'one',operation:'paste',limit:4096};
await focus();await dom('stage',args);await dom('commit',{...args,text:paste});
assert.equal(await exec("document.getElementById('editor').querySelector('img')===null"),true);
assert.equal((await exec("document.getElementById('editor').textContent")).includes(paste),true);
for(const change of ["document.getElementById('editor').append(document.createTextNode('changed'))","document.getElementById('other').focus()","getSelection().removeAllRanges()"]){await focus();await dom('stage',{...args,token:change});await exec(change);await assert.rejects(dom('commit',{...args,token:change,text:'should not insert'}));}
await focus();await dom('stage',{...args,token:'sensitive'});await exec("document.getElementById('editor').setAttribute('data-sensitive','true')");await assert.rejects(dom('commit',{...args,token:'sensitive',text:'no'}));await exec("document.getElementById('editor').removeAttribute('data-sensitive')");
// A real native DOM plus the production guard proves DLP runs before mutation.
const guarded=createClipboardGuard({getContext:()=>({ready:true,policy,url,tabId:'editor',activation:1,navigationId:1,webContents:facade}),stage:args=>dom('stage',args),validate:args=>dom('validate',args),commit:args=>dom('commit',args),discard:args=>dom('discard',args),readClipboard:async()=>paste,writeClipboard:async text=>writes.push(text),inspect:async(bytes,p,operation)=>inspectClipboard(bytes,p,operation),audit:async()=>{}});
paste='Secret Roadmap';await focus();const before=await exec("document.getElementById('editor').innerHTML");assert.equal((await guarded.perform('paste',facade)).completed,false);assert.equal(await exec("document.getElementById('editor').innerHTML"),before);
paste='powershell -EncodedCommand SQBFAFgAIAAoAGkAdwByACAAaAB0AHQAcAApAA==';assert.equal((await guarded.perform('paste',facade)).completed,false);assert.equal(await exec("document.getElementById('editor').innerHTML"),before);
paste=' benign AI prompt';assert.equal((await guarded.perform('paste',facade)).completed,true);assert.ok((await exec("document.getElementById('editor').textContent")).endsWith(paste));
await exec("(()=>{const e=document.getElementById('editor');e.textContent='Public editor selection';e.focus();const r=document.createRange();r.selectNodeContents(e);const s=getSelection();s.removeAllRanges();s.addRange(r);})()");
assert.equal((await guarded.perform('copy',facade)).completed,true);assert.equal(writes.at(-1),'Public editor selection');
assert.equal((await guarded.perform('cut',facade)).completed,true);assert.equal(writes.at(-1),'Public editor selection');assert.equal(await exec("document.getElementById('editor').textContent"),'');
await exec("document.getElementById('editor').setAttribute('aria-label','API secret')");await assert.rejects(dom('stage',{...args,token:'sensitive-stage'}));
console.log('CONTENTEDITABLE_CLIPBOARD_PASS');win.destroy();app.quit();
}).catch(error=>{console.error(error.stack);app.exit(1);});