60 lines
6.1 KiB
JavaScript
60 lines
6.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {EventEmitter} from 'node:events';
|
|
import {createRequire} from 'node:module';
|
|
import vm from 'node:vm';
|
|
const require=createRequire(import.meta.url);
|
|
const {attachPageClipboardGuard,guardedScreenshot,disposePageClipboardGuard,createNativeTransportChannel}=require('../apps/browser/page-clipboard-guard.cjs');
|
|
const {capturePNG}=require('../apps/browser/visual-capture.cjs');
|
|
function fixture(valid=true){
|
|
const api=new EventEmitter(),commands=[];let attached=false,detaches=0,failures=0;
|
|
api.isAttached=()=>attached;api.attach=version=>{assert.equal(version,'1.3');attached=true;};
|
|
api.detach=()=>{attached=false;detaches++;api.emit('detach');};
|
|
const png=Buffer.from('Public bounded screenshot fixture');
|
|
api.sendCommand=async(method,parameters)=>{commands.push({method,parameters});if(method==='Page.addScriptToEvaluateOnNewDocument')return valid?{identifier:'fixed-guard'}:{};if(method==='Page.captureScreenshot')return {data:png.toString('base64')};return {};};
|
|
const contents={debugger:api,isDestroyed:()=>false};
|
|
return {api,contents,commands,png,detachCount:()=>detaches,failureCount:()=>failures,initialize:()=>attachPageClipboardGuard(contents,()=>failures++)};
|
|
}
|
|
test('private transport lease exposes only fixed commands and releases waiting targets without detaching clipboard protection',async()=>{
|
|
const f=fixture();await f.initialize();const channel=createNativeTransportChannel(f.contents),messages=[];
|
|
assert.equal(channel.attach,undefined);assert.equal(channel.detach,undefined);assert.equal(channel.sendCommand,undefined);
|
|
assert.throws(()=>createNativeTransportChannel(f.contents));channel.subscribeMessage((_event,method)=>messages.push(method));
|
|
f.api.emit('message',{},'Network.requestWillBeSent',{request:{url:'https://private.invalid/?token=secret',postData:'secret'}});
|
|
f.api.emit('message',{},'Target.attachedToTarget',{sessionId:'worker-1',targetInfo:{type:'worker'}});
|
|
assert.deepEqual(messages,['Target.attachedToTarget']);await channel.enableNetwork('worker-1');await channel.autoAttachTargets('worker-1');await channel.resumeTarget('worker-1');
|
|
assert.deepEqual(f.commands.slice(2).map(c=>c.method),['Network.enable','Target.setAutoAttach','Runtime.runIfWaitingForDebugger']);
|
|
const before=f.commands.length;await assert.rejects(channel.resumeTarget('private;command'));assert.equal(f.commands.length,before);
|
|
channel.release();await new Promise(resolve=>setImmediate(resolve));assert.equal(channel.isHealthy(),false);assert.equal(f.detachCount(),0);assert.equal(f.api.isAttached(),true);
|
|
assert.equal(f.commands.at(-1).parameters.autoAttach,false);assert.ok(f.commands.slice(before).some(c=>c.method==='Runtime.runIfWaitingForDebugger'));
|
|
assert.deepEqual(await capturePNG(f.contents),f.png);await assert.rejects(channel.enableNetwork());disposePageClipboardGuard(f.contents);assert.equal(f.failureCount(),0);
|
|
});
|
|
test('transport cannot borrow an unguarded or revoked native debugger',async()=>{
|
|
const f=fixture();assert.throws(()=>createNativeTransportChannel(f.contents));await f.initialize();const channel=createNativeTransportChannel(f.contents);let detach=0;channel.subscribeDetach(()=>detach++);f.api.detach();assert.equal(detach,1);assert.equal(channel.isHealthy(),false);await assert.rejects(channel.enableNetwork());channel.release();assert.equal(f.failureCount(),1);
|
|
});
|
|
test('static page guard registers only fixed initialization and shares screenshot without detaching',async()=>{
|
|
const f=fixture();await f.initialize();assert.deepEqual(f.commands.map(c=>c.method),['Page.enable','Page.addScriptToEvaluateOnNewDocument']);
|
|
assert.equal(/require\(|ipcRenderer|process\./.test(f.commands[1].parameters.source),false);
|
|
assert.deepEqual(await capturePNG(f.contents),f.png);assert.equal(f.commands.at(-1).method,'Page.captureScreenshot');assert.equal(f.detachCount(),0);
|
|
disposePageClipboardGuard(f.contents);assert.equal(f.detachCount(),1);assert.equal(f.failureCount(),0);assert.equal(guardedScreenshot(f.contents,{}),null);
|
|
});
|
|
test('unknown attachment and invalid initialization cannot gain trusted screenshot ownership',async()=>{
|
|
const occupied=fixture();occupied.api.attach('1.3');await assert.rejects(occupied.initialize());assert.equal(occupied.commands.length,0);
|
|
const invalid=fixture(false);await assert.rejects(invalid.initialize());assert.equal(invalid.detachCount(),1);assert.equal(guardedScreenshot(invalid.contents,{}),null);
|
|
});
|
|
test('unexpected disconnect revokes ownership and reports failure; deliberate disposal is idempotent',async()=>{
|
|
const f=fixture();await f.initialize();f.api.detach();assert.equal(f.failureCount(),1);assert.equal(guardedScreenshot(f.contents,{}),null);disposePageClipboardGuard(f.contents);assert.equal(f.detachCount(),1);
|
|
});
|
|
test('new-frame source freezes clipboard and replacement primitives against page tampering',async()=>{
|
|
const f=fixture();await f.initialize();const native=[];
|
|
class Document{execCommand(command){native.push(command);return true;}open(){return 'opened';}write(){}writeln(){}}
|
|
const listeners=[],realm={Document,DOMException,window:{addEventListener:(name,handler,capture)=>listeners.push({name,handler,capture})}};
|
|
vm.createContext(realm);vm.runInContext(f.commands[1].parameters.source,realm);
|
|
const doc=new Document();for(const name of ['copy','cut','paste','PasteAndMatchStyle'])assert.equal(doc.execCommand(name),false);
|
|
assert.equal(doc.execCommand('insertText'),true);assert.deepEqual(native,['insertText']);
|
|
for(const name of ['open','write','writeln'])assert.throws(()=>doc[name](),error=>error.name==='SecurityError');
|
|
vm.runInContext('Reflect.apply=()=>true;RegExp.prototype.test=()=>false;',realm);assert.equal(doc.execCommand('copy'),false);
|
|
assert.throws(()=>Object.defineProperty(Document.prototype,'execCommand',{value:()=>true}));
|
|
for(const name of ['copy','cut','paste','dragstart','drop']){let prevented=false,stopped=false;const listener=listeners.find(item=>item.name===name);assert.equal(listener.capture,true);listener.handler({preventDefault:()=>prevented=true,stopImmediatePropagation:()=>stopped=true});assert.equal(prevented&&stopped,true);}
|
|
disposePageClipboardGuard(f.contents);
|
|
});
|