'use strict'; const {randomUUID}=require('node:crypto'); const credential=/password|passwd|api.?key|access.?token|refresh.?token|client.?secret|authorization|credential|session.?id|-----BEGIN|\b(?:sk-|gh[pousr]_)[A-Za-z0-9_-]{16,}/i; // eslint-disable-next-line no-control-regex -- Explicitly reject control characters in security-sensitive input. function safeText(value,max){if(typeof value!=='string'||value.length>max||/[\u0000-\u001f]/.test(value))throw Error('Invalid session metadata');let decoded=value;for(let i=0;i<4;i++){if(credential.test(decoded))throw Error('Sensitive session metadata');try{const next=decodeURIComponent(decoded);if(next===decoded)break;decoded=next;}catch{break;}}return value;} function sensitiveURL(value){for(let i=0;i<5;i++){if(/[?&#](?:password|passwd|token|access[_-]?token|refresh[_-]?token|api[_-]?key|secret|client[_-]?secret|code|authorization|credential|session|sid|auth|samlresponse|samlrequest|assertion|ticket|id_token)=/i.test(value)||/\/(?:login|signin|sign-in|auth|oauth|sso|password|reset|callback)(?:\/|[?#]|$)/i.test(value))return true;try{const next=decodeURIComponent(value);if(next===value)break;value=next;}catch{return true;}}return false;} function safeTab(row){if(!row||Object.keys(row).some(k=>!['url','title','group'].includes(k)))throw Error('Invalid session metadata');const title=safeText(row.title,200),raw=safeText(row.url,4096),url=new URL(raw);if(!['http:','https:'].includes(url.protocol)||url.username||url.password||sensitiveURL(raw)||/\/(?:login|signin|sign-in|auth|oauth|sso|password|reset|callback)(?:\/|$)/i.test(decodeURIComponent(url.pathname)))throw Error('Unsafe session destination');return {url:url.href,title,...(row.group===undefined?{}:{group:safeText(row.group,80)})};} function createAgentSessionAdapters({getContext,getSessions,contextCurrent,getOwner,store,inspect,restore,traceOperation=async(_name,fn)=>fn(),now=Date.now}={}){ if([getContext,getSessions,contextCurrent,getOwner,inspect,restore].some(fn=>typeof fn!=='function')||!store)throw TypeError('Session callbacks required'); const plans=new Map(); const snapshot=(action,signal)=>{const c=getContext();if(signal?.aborted||!c?.ready||!c.policy?.agents?.enabled||!c.policy.agents.scopes?.includes(action)||(c.policy.expiresAt&&Date.parse(c.policy.expiresAt)<=now()))throw Error('Agent session scope denied');const owner=getOwner(c);if(!owner||typeof owner.deviceId!=='string'||typeof owner.ownerId!=='string')throw Error('Session owner unavailable');return {...c,action,owner,actorBinding:JSON.stringify(c.actor??null),version:c.policyVersion??c.policy.version};}; const current=(c,signal,allowActivation=false)=>{const n=snapshot(c.action,signal);if(n.policy!==c.policy||n.version!==c.version||n.actorBinding!==c.actorBinding||JSON.stringify(n.owner)!==JSON.stringify(c.owner)||(c.token&&!contextCurrent(allowActivation&&typeof c.token==='object'?{...c.token,foreground:false,activation:null}:c.token)))throw Error('Session authority changed');}; const destination=(tab,c)=>{const url=new URL(tab.url),hosts=c.policy.agents.allowedHosts;if(typeof c.destinationAllowed!=='function'||c.destinationAllowed(tab.url,c.policy)!==true||!Array.isArray(hosts)||(hosts.length&&!hosts.some(h=>url.hostname===h||(h.startsWith('*.')&&url.hostname.endsWith(h.slice(1))))))throw Error('Session destination denied');}; const classify=async(value,c,signal)=>{const text=JSON.stringify(value);if(text.length>200000)throw Error('Session metadata too large');const v=await traceOperation('inspect',()=>inspect(text,c));current(c,signal);if(v?.decision!=='allow'||v.coverage?.complete===false||/uninspectable|size-limit|inspection-incomplete|unavailable|failure|failed|timeout|unsupported|encrypted|parser/i.test(v.ruleId||''))throw Error('Session metadata inspection denied');}; const load=async(c,id)=>{const result=await traceOperation('load',()=>store.load({...c.owner,id}));if(!result||typeof result.revision!=='string')throw Error('Session collection unavailable');const {revision,...collection}=result;return {revision,collection};}; return { 'sessions:save':async(args={}, {signal}={})=>{const c=snapshot('sessions:save',signal),name=safeText(args.name,80);if(!name.trim()||!Array.isArray(args.sessionIds)||args.sessionIds.length<1||args.sessionIds.length>32||new Set(args.sessionIds).size!==args.sessionIds.length)throw Error('Invalid session selection');const inventory=getSessions(),bindings=[];const tabs=args.sessionIds.map(id=>{const row=inventory.find(r=>r.id===id),tab=getContext(id);if(!row||!tab?.ready||!tab.token||!contextCurrent(tab.token))throw Error('Session selection changed');const value=safeTab({title:row.title,url:tab.webContents.getURL(),...(row.group===undefined?{}:{group:row.group})});destination(value,c);bindings.push({id,token:tab.token,url:value.url,title:row.title,group:row.group});return value;});const assertCurrent=()=>{current(c,signal);const rows=getSessions();for(const b of bindings){const tab=getContext(b.id);if(!contextCurrent(b.token)||tab?.webContents?.getURL()!==b.url||rows.find(r=>r.id===b.id)?.title!==b.title||rows.find(r=>r.id===b.id)?.group!==b.group)throw Error('Session selection changed');}};await classify({name,tabs},c,signal);assertCurrent();const existing=await store.list(c.owner);assertCurrent();if(args.expectedRevision!==undefined&&args.expectedRevision!==existing.revision)throw Error('Session collection changed');const activeIndex=Math.max(0,args.sessionIds.indexOf(c.sessionId));const result=await traceOperation('save',()=>store.save({...(args.id?{id:args.id}:{}),name,tabs,activeIndex},{...c.owner,expectedRevision:existing.revision,assertCurrent}));assertCurrent();return {id:result.id,revision:result.revision,tabCount:tabs.length};}, 'sessions:list':async(_args={}, {signal}={})=>{const c=snapshot('sessions:list',signal),result=await traceOperation('list',()=>store.list(c.owner));current(c,signal);if(!Array.isArray(result.collections)||result.collections.length>16)throw Error('Invalid session collections');const collections=result.collections.map(row=>({id:row.id,name:safeText(row.name,80),tabs:row.tabs.map(safeTab),activeIndex:row.activeIndex}));for(const row of collections)for(const tab of row.tabs)destination(tab,c);await classify(collections,c,signal);current(c,signal);if((await store.list(c.owner)).revision!==result.revision)throw Error('Session collection changed');current(c,signal);return {revision:result.revision,collections};}, 'sessions:restore':async(args={}, {signal}={})=>{const c=snapshot('sessions:restore',signal);if(!args.planId){const result=await load(c,args.id);current(c,signal);const collection={id:result.collection.id,name:safeText(result.collection.name,80),tabs:result.collection.tabs.map(safeTab),activeIndex:result.collection.activeIndex};if(!collection.tabs.length||collection.tabs.length>32)throw Error('Invalid session collection');for(const tab of collection.tabs)destination(tab,c);await classify(collection,c,signal);if((await store.list(c.owner)).revision!==result.revision)throw Error('Session collection changed');current(c,signal);for(const [id,p] of plans)if(p.expires<=now())plans.delete(id);while(plans.size>=32)plans.delete(plans.keys().next().value);const planId=randomUUID();plans.set(planId,{c,collection,revision:result.revision,expires:now()+60000});return {planId,id:collection.id,tabCount:collection.tabs.length};}const plan=plans.get(args.planId);plans.delete(args.planId);if(!plan||plan.expires<=now()||plan.c.policy!==c.policy||plan.c.actorBinding!==c.actorBinding||JSON.stringify(plan.c.owner)!==JSON.stringify(c.owner))throw Error('Session restore plan unavailable');current(plan.c,signal);const result=await load(c,plan.collection.id);if(result.revision!==plan.revision||JSON.stringify(result.collection)!==JSON.stringify(plan.collection))throw Error('Session collection changed');for(const tab of plan.collection.tabs)destination(tab,c);await classify(plan.collection,c,signal);if((await store.list(c.owner)).revision!==plan.revision)throw Error('Session collection changed');current(c,signal);return traceOperation('restore',()=>restore(plan.collection.tabs,{activeIndex:plan.collection.activeIndex,signal,assertCurrent:()=>{current(c,signal,true);for(const tab of plan.collection.tabs)destination(tab,c);},revision:plan.revision,owner:c.owner}));} }; } module.exports={createAgentSessionAdapters,safeSessionTab:safeTab};