Files
safetynet/packages/core/agent-audit.cjs
T

14 lines
2.1 KiB
JavaScript

'use strict';
const {z}=require('zod');
const application=['application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group','library:organize-plan','library:organize-apply'];
const AGENT_SCOPES=Object.freeze({sessions:'sessions:list',navigate:'browser:navigate',back:'browser:navigate',forward:'browser:navigate',reload:'browser:navigate',read:'browser:read',inspect:'browser:read',scroll:'browser:interact',click:'browser:interact',type:'browser:interact',select:'browser:interact',check:'browser:interact',ai:'ai:request',...Object.fromEntries(application.map(action=>[action,action])),'agent-request':'agent:request'});
const AgentAuditSchema=z.object({action:z.enum(Object.keys(AGENT_SCOPES)),scope:z.enum([...new Set(Object.values(AGENT_SCOPES))]),outcome:z.enum(['authorized','completed','denied','failed']),sessionId:z.string().uuid().optional(),principalId:z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,79}$/).optional(),credentialGeneration:z.number().int().min(1).max(Number.MAX_SAFE_INTEGER).optional()}).strict().superRefine((value,ctx)=>{
if((value.principalId!==undefined)!==(value.credentialGeneration!==undefined))ctx.addIssue({code:'custom',message:'Agent principal attribution requires both identity and generation'});
if(AGENT_SCOPES[value.action]!==value.scope)ctx.addIssue({code:'custom',message:'Agent action scope mismatch'});
if(value.action==='agent-request'&&!['denied','failed'].includes(value.outcome))ctx.addIssue({code:'custom',message:'Unvalidated request cannot be authorized'});
});
// Only fixed control-plane facts cross the audit boundary; action arguments never do.
function agentAuditFacts(value){return AgentAuditSchema.parse({...value,scope:value?.scope??AGENT_SCOPES[value?.action]});}
function agentFactsForEvent(kind,value){if(!['agent.action','agent.denied'].includes(kind))return null;const result=AgentAuditSchema.safeParse(value);return result.success&&((kind==='agent.action')===['authorized','completed'].includes(result.data.outcome))?result.data:null;}
module.exports={AGENT_SCOPES,AgentAuditSchema,agentAuditFacts,agentFactsForEvent};