Audit native transports and retain secure export diagnostics

This commit is contained in:
Karl committed 2026-10-14 00:37:32 +02:00
1 parent 8a253eda89
commit 0a5df7398e
28 files changed
+636 -53

No files matched your search

+34 -2
View File
@@ -24,7 +24,7 @@ function deadline(operation){let timer;return Promise.race([operation,new Promis
async function attachPageClipboardGuard(contents,onFailure){
const api=contents.debugger;
if(!api||api.isAttached()||owners.has(contents))throw Error('Page security initialization unavailable');
const owner={api,healthy:false};
const owner={api,healthy:false,onFailure};
const detached=()=>{owners.delete(contents);owner.healthy=false;if(!contents.isDestroyed())onFailure();};
owner.detached=detached;
api.on('detach',detached);
@@ -49,9 +49,41 @@ function guardedScreenshot(contents,parameters){
}
function disposePageClipboardGuard(contents){
const owner=owners.get(contents);if(!owner)return;
owner.releaseTransport?.();
owners.delete(contents);owner.healthy=false;owner.api.removeListener('detach',owner.detached);
if(owner.api.isAttached())owner.api.detach();
}
function createNativeTransportChannel(contents){
const owner=owners.get(contents),api=owner?.api;
if(!owner?.healthy||api!==contents.debugger||!api.isAttached()||owner.transportLease)throw Error('Private transport channel unavailable');
const lease=Symbol('native-transport'),subscriptions=new Set(),targets=new Set();let released=false;
owner.transportLease=lease;
const healthy=()=>!released&&owners.get(contents)===owner&&owner.healthy&&owner.transportLease===lease&&!contents.isDestroyed()&&api.isAttached();
const session=value=>value===undefined||typeof value==='string'&&value.length>0&&value.length<=128&&/^[a-zA-Z0-9._:-]+$/.test(value);
const command=(method,parameters,sessionId)=>{if(!healthy()||!session(sessionId))return Promise.reject(Error('Private transport channel unavailable'));return api.sendCommand(method,parameters,sessionId);};
const subscribe=(name,listener)=>{if(!healthy()||typeof listener!=='function')throw Error('Private transport subscription unavailable');api.on(name,listener);let removed=false;const remove=()=>{if(removed)return;removed=true;api.removeListener(name,listener);subscriptions.delete(remove);};subscriptions.add(remove);return remove;};
const messages=new Set(['Target.attachedToTarget','Target.detachedFromTarget','Network.webTransportCreated','Network.webTransportConnectionEstablished','Network.webTransportClosed']);
function release(){
if(released)return;released=true;for(const remove of [...subscriptions])remove();
if(owner.transportLease===lease){owner.transportLease=null;owner.releaseTransport=null;}
// Release private observation without detaching the clipboard security owner.
// Fresh workers may still be waiting for startup; resume before dropping targets.
if(api.isAttached()){
const send=(method,parameters,id)=>{try{return Promise.resolve(api.sendCommand(method,parameters,id));}catch{return Promise.reject(Error('Private transport cleanup unavailable'));}};
const pending=[];for(const id of targets)pending.push(send('Runtime.runIfWaitingForDebugger',{},id));
pending.push(send('Target.setAutoAttach',{autoAttach:false,waitForDebuggerOnStart:false,flatten:true}));
void Promise.allSettled(pending).then(results=>{if(results.some(result=>result.status==='rejected')&&owners.get(contents)===owner&&owner.healthy&&!contents.isDestroyed())owner.onFailure();});
}
targets.clear();
}
owner.releaseTransport=release;
return Object.freeze({isHealthy:healthy,
enableNetwork:sessionId=>command('Network.enable',{maxPostDataSize:0},sessionId),
autoAttachTargets:sessionId=>command('Target.setAutoAttach',{autoAttach:true,waitForDebuggerOnStart:true,flatten:true},sessionId),
resumeTarget:sessionId=>sessionId===undefined?Promise.reject(Error('Private transport target required')):command('Runtime.runIfWaitingForDebugger',{},sessionId),
subscribeMessage:listener=>subscribe('message',(event,method,params,sessionId)=>{if(!messages.has(method))return;if(method==='Target.attachedToTarget'&&session(params?.sessionId)&&params.sessionId!==undefined)targets.add(params.sessionId);if(method==='Target.detachedFromTarget')targets.delete(params?.sessionId);listener(event,method,params,sessionId);}),
subscribeDetach:listener=>subscribe('detach',()=>listener()),release});
}
async function guardedVisualStructure(contents){
const owner=owners.get(contents);
if(!owner?.healthy||!owner.api.isAttached()||contents.isDestroyed())throw Error('Managed visual export refused');
@@ -71,4 +103,4 @@ async function guardedVisualStructure(contents){
}
return {safe:true,nodes};
}
module.exports={attachPageClipboardGuard,guardedScreenshot,guardedVisualStructure,disposePageClipboardGuard};
module.exports={attachPageClipboardGuard,guardedScreenshot,guardedVisualStructure,disposePageClipboardGuard,createNativeTransportChannel};