Add protected interaction evidence and measured debugging phases
This commit is contained in:
1 parent
ea448de2d4
commit
244f01630b
42 files changed
+640
-87
No files matched your search
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
const {z}=require('zod');
|
||||
const InteractionEvidenceSchema=z.object({
|
||||
pageUrl:z.string().max(4096).refine(value=>{try{const u=new URL(value);return ['http:','https:'].includes(u.protocol)&&!u.username&&!u.password&&!u.search&&!u.hash;}catch{return false;}},'Evidence URL must exclude credentials, query and fragment'),
|
||||
action:z.enum(['click','copy','cut','paste','submit']),sessionId:z.string().uuid(),outcome:z.literal('completed'),captureSource:z.enum(['guarded-clipboard','agent-adapter']),bytes:z.number().int().min(0).max(16777216).optional(),
|
||||
target:z.object({tag:z.enum(['A','BUTTON','INPUT','TEXTAREA','SELECT','TEXT','OTHER','DIV','SPAN','P','BODY','UNKNOWN']),role:z.enum(['link','button','checkbox','radio','textbox','combobox','text','other','menuitem','option']).optional(),x:z.number().finite().min(0).max(10000000).optional(),y:z.number().finite().min(0).max(10000000).optional()}).strict().optional(),sensitiveField:z.literal(false)
|
||||
}).strict();
|
||||
module.exports={InteractionEvidenceSchema};
|
||||
@@ -0,0 +1,13 @@
|
||||
import {z} from 'zod';
|
||||
|
||||
// Collection controls are signed policy, independent of diagnostics and of
|
||||
// each other. Neither permission is inferred from the global switch.
|
||||
export const EvidencePolicySchema=z.object({
|
||||
enabled:z.boolean().default(false),
|
||||
captureDownloads:z.boolean().default(false),
|
||||
captureInteractions:z.boolean().default(false)
|
||||
}).strict().prefault({});
|
||||
|
||||
// Browser and server share the exact strict payload boundary.
|
||||
import payloads from './evidence-payload.cjs';
|
||||
export const InteractionEvidenceSchema=payloads.InteractionEvidenceSchema;
|
||||
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
const {z}=require('zod');
|
||||
const InteractionFactsSchema=z.object({action:z.enum(['copy','cut','paste','click','submit']),outcome:z.enum(['authorized','completed','denied']),sessionId:z.string().uuid()}).strict();
|
||||
function interactionFacts(value){return InteractionFactsSchema.parse(value);}
|
||||
function interactionFactsForEvent(kind,value){
|
||||
const parsed=InteractionFactsSchema.safeParse(value);if(!parsed.success)return null;
|
||||
const facts=parsed.data;
|
||||
if(kind==='clipboard.block')return facts.outcome==='denied'&&['copy','cut','paste'].includes(facts.action)?facts:null;
|
||||
return ['clipboard.copy','clipboard.cut','clipboard.paste'].includes(kind)&&facts.action===kind.split('.')[1]&&['authorized','completed'].includes(facts.outcome)?facts:null;
|
||||
}
|
||||
module.exports={InteractionFactsSchema,interactionFacts,interactionFactsForEvent};
|
||||
@@ -11,6 +11,7 @@ import {FileTransfersSchema} from './file-transfer.mjs';
|
||||
import {UserAccessSchema} from './user-profiles.mjs';
|
||||
import {LibraryPolicySchema} from './library.mjs';
|
||||
import {BrandSchema,StartPageSchema} from './branding.mjs';
|
||||
import {EvidencePolicySchema} from './evidence.mjs';
|
||||
|
||||
const host = z.string().max(253).regex(/^(\*\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i);
|
||||
export const PolicySchema = z.object({
|
||||
@@ -18,7 +19,7 @@ export const PolicySchema = z.object({
|
||||
assignment:GroupAssignmentSchema.optional(),
|
||||
userAccess:UserAccessSchema,
|
||||
diagnostics:DiagnosticsSchema.prefault({}),
|
||||
evidence:z.object({enabled:z.boolean().default(false),captureDownloads:z.boolean().default(false)}).strict().prefault({}),
|
||||
evidence:EvidencePolicySchema,
|
||||
fileTransfers:FileTransfersSchema.prefault({}),
|
||||
library:LibraryPolicySchema,
|
||||
expiresAt: z.string().datetime(),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import interactionAudit from './interaction-facts.cjs';
|
||||
import agentAudit from './agent-audit.cjs';
|
||||
import transferFacts from './transfer-facts.cjs';
|
||||
import requestTransferFacts from './request-transfer-facts.cjs';
|
||||
const requestFacts=(metadata,kind)=>{if(!kind?.startsWith('upload.'))return null;const old=metadata.file;const parsed=requestTransferFacts.RequestFactsSchema.safeParse(metadata.request||(old?{bytes:old.bytes,detectedType:old.detectedType}:undefined));return parsed.success?parsed.data:null;};
|
||||
const facts=metadata=>{const parsed=transferFacts.FileFactsSchema.safeParse(metadata.file);return parsed.success?parsed.data:null;};
|
||||
export function toECS(event,installationId='safetynet') {
|
||||
const m=event.metadata,agent=agentAudit.agentFactsForEvent(event.kind,event.metadata.agent),block=event.kind.includes('block')||event.kind.endsWith('.denied')||m.decision==='block',failure=event.kind.endsWith('.failure')||agent?.outcome==='failed',alert=event.kind==='sigma.alert';
|
||||
const m=event.metadata,interaction=interactionAudit.interactionFactsForEvent(event.kind,event.metadata.interaction),agent=agentAudit.agentFactsForEvent(event.kind,event.metadata.agent),block=event.kind.includes('block')||event.kind.endsWith('.denied')||m.decision==='block',failure=event.kind.endsWith('.failure')||agent?.outcome==='failed',alert=event.kind==='sigma.alert';
|
||||
const config=event.kind.startsWith('policy.')||event.kind.startsWith('enrollment.')||event.kind==='device.revoked';
|
||||
const auth=event.kind.startsWith('sso.'),clipboard=event.kind.startsWith('clipboard.');
|
||||
return {
|
||||
@@ -19,7 +20,7 @@ export function toECS(event,installationId='safetynet') {
|
||||
...(event.kind.startsWith('download.')&&facts(m)?{file:{size:facts(m).bytes,type:facts(m).detectedType,hash:{sha256:facts(m).sha256}}}:{}),
|
||||
...(/^[a-f0-9]{32}$/.test(m.traceId||'')?{trace:{id:m.traceId}}:{}),
|
||||
...(/^[a-f0-9]{16}$/.test(m.spanId||'')?{span:{id:m.spanId}}:{}),
|
||||
safetynet:{...(agent?{agent:{action:agent.action,scope:agent.scope,outcome:agent.outcome,...(agent.sessionId?{session_id:agent.sessionId}:{}),...(agent.principalId?{principal_id:agent.principalId,credential_generation:agent.credentialGeneration}:{})}}:{}),...(requestFacts(m,event.kind)?{request:{bytes:requestFacts(m,event.kind).bytes,detected_type:requestFacts(m,event.kind).detectedType}}:{}),...(event.audit?{audit:event.audit}:{}),policy_version:m.policyVersion??m.version??null,decision:m.decision??(block?'block':null),
|
||||
safetynet:{...(interaction?{interaction:{action:interaction.action,outcome:interaction.outcome,session_id:interaction.sessionId}}:{}),...(agent?{agent:{action:agent.action,scope:agent.scope,outcome:agent.outcome,...(agent.sessionId?{session_id:agent.sessionId}:{}),...(agent.principalId?{principal_id:agent.principalId,credential_generation:agent.credentialGeneration}:{})}}:{}),...(requestFacts(m,event.kind)?{request:{bytes:requestFacts(m,event.kind).bytes,detected_type:requestFacts(m,event.kind).detectedType}}:{}),...(event.audit?{audit:event.audit}:{}),policy_version:m.policyVersion??m.version??null,decision:m.decision??(block?'block':null),
|
||||
content_type:m.contentType??null,model:m.model??null,coverage:m.coverage??null,classification:m.classification==='local-only'?'local-only':null,
|
||||
...(alert?{source_event_id:`${installationId}:${m.sourceEventId}`,severity:m.level}:{})}
|
||||
};
|
||||
@@ -28,10 +29,10 @@ const header=v=>String(v).replace(/\\/g,'\\\\').replace(/\|/g,'\\|').replace(/[\
|
||||
const ext=v=>String(v).replace(/\\/g,'\\\\').replace(/=/g,'\\=').replace(/\r/g,'\\r').replace(/\n/g,'\\n');
|
||||
const severity=level=>({low:3,medium:5,high:7,critical:10}[level]??3);
|
||||
export function toCEF(event,installationId='safetynet') {
|
||||
const m=event.metadata,agent=agentAudit.agentFactsForEvent(event.kind,event.metadata.agent),blocked=event.kind.includes('block')||event.kind.endsWith('.denied')||m.decision==='block',alert=event.kind==='sigma.alert';
|
||||
const m=event.metadata,interaction=interactionAudit.interactionFactsForEvent(event.kind,event.metadata.interaction),agent=agentAudit.agentFactsForEvent(event.kind,event.metadata.agent),blocked=event.kind.includes('block')||event.kind.endsWith('.denied')||m.decision==='block',alert=event.kind==='sigma.alert';
|
||||
// suid identifies the verified agent capability principal, not an employee account.
|
||||
// suser, when present, remains the independently supplied human actor.
|
||||
const fields={...(agent?{cs2Label:'AgentAction',cs2:agent.action,cs3Label:'AgentScope',cs3:agent.scope,cs6Label:'AgentOutcome',cs6:agent.outcome,...(agent.sessionId?{requestContext:agent.sessionId}:{}),...(agent.principalId?{suid:agent.principalId,flexNumber1Label:'AgentCredentialGeneration',flexNumber1:agent.credentialGeneration}:{})}:{}),...(requestFacts(m,event.kind)?{cn3Label:'RequestBytes',cn3:requestFacts(m,event.kind).bytes,cs6Label:'DetectedRequestType',cs6:requestFacts(m,event.kind).detectedType}:{}),...(event.audit?{flexString1Label:'AuditRecordHash',flexString1:event.audit.hash,flexString2Label:'AuditSequence',flexString2:event.audit.sequence}:{}),externalId:`${installationId}:${event.id}`,rt:Date.parse(event.time),act:event.kind,
|
||||
const fields={...(interaction?{cs2Label:'InteractionAction',cs2:interaction.action,cs3Label:'InteractionOutcome',cs3:interaction.outcome,requestContext:interaction.sessionId}:{}),...(agent?{cs2Label:'AgentAction',cs2:agent.action,cs3Label:'AgentScope',cs3:agent.scope,cs6Label:'AgentOutcome',cs6:agent.outcome,...(agent.sessionId?{requestContext:agent.sessionId}:{}),...(agent.principalId?{suid:agent.principalId,flexNumber1Label:'AgentCredentialGeneration',flexNumber1:agent.credentialGeneration}:{})}:{}),...(requestFacts(m,event.kind)?{cn3Label:'RequestBytes',cn3:requestFacts(m,event.kind).bytes,cs6Label:'DetectedRequestType',cs6:requestFacts(m,event.kind).detectedType}:{}),...(event.audit?{flexString1Label:'AuditRecordHash',flexString1:event.audit.hash,flexString2Label:'AuditSequence',flexString2:event.audit.sequence}:{}),externalId:`${installationId}:${event.id}`,rt:Date.parse(event.time),act:event.kind,
|
||||
...(event.device_id?{deviceExternalId:event.device_id}:{}),...(m.actor?{suser:m.actor}:{}),
|
||||
...(m.ruleId?{cs1Label:'RuleId',cs1:m.ruleId}:{}),...(m.host?{dhost:m.host}:{}),
|
||||
...(m.policyVersion||m.version?{cn1Label:'PolicyVersion',cn1:m.policyVersion??m.version}:{}),
|
||||
|
||||
Reference in new issue
Block a user