/** * OCSF (Open Cybersecurity Schema Framework) v1.3 types for audit events. * See https://schema.ocsf.io/ for the full spec. * * We implement a subset relevant to Superblocks audit logging: * - Category 3: Identity & Access Management * - Category 6: Application Activity */ export enum OCSFCategoryUid { IAM = 3, APPLICATION_ACTIVITY = 6 } export enum OCSFClassUid { ACCOUNT_CHANGE = 3001, AUTHENTICATION = 3002, ENTITY_MANAGEMENT = 3004, USER_ACCESS_MANAGEMENT = 3005, APPLICATION_LIFECYCLE = 6002, API_ACTIVITY = 6003 } export enum OCSFActivityId { UNKNOWN = 0, CREATE = 1, READ = 2, UPDATE = 3, DELETE = 4, EXECUTE = 5, LOGIN = 6, LOGOUT = 7, DEPLOY = 8, GRANT = 9, REVOKE = 10, OTHER = 99 } export enum OCSFStatusId { UNKNOWN = 0, SUCCESS = 1, FAILURE = 2, IN_PROGRESS = 99 } export enum OCSFSeverityId { UNKNOWN = 0, INFORMATIONAL = 1, LOW = 2, MEDIUM = 3, HIGH = 4, CRITICAL = 5, FATAL = 6 } export interface OCSFActor { user: { uid: string; name?: string; email_addr?: string; type?: string; }; session?: { uid: string; }; } export interface OCSFResource { uid: string; type: string; name?: string; } export interface OCSFApi { operation: string; service: { name: string; }; } export interface OCSFMetadata { product: { name: string; vendor_name: string; }; version: string; log_name: string; } /** * Core OCSF event shape stored in audit_event.data (jsonb). * `type_uid` = class_uid * 100 + activity_id (OCSF convention). */ export interface OCSFAuditEvent { category_uid: OCSFCategoryUid; class_uid: OCSFClassUid; activity_id: OCSFActivityId; type_uid: number; status_id: OCSFStatusId; severity_id: OCSFSeverityId; time: number; duration?: number; actor: OCSFActor; resource: OCSFResource; api?: OCSFApi; metadata: OCSFMetadata; unmapped?: Record; } export function computeTypeUid(classUid: OCSFClassUid, activityId: OCSFActivityId): number { return classUid * 100 + activityId; } // --------------------------------------------------------------------------- // Audit event type catalog // --------------------------------------------------------------------------- /** * Describes one group of audit events sharing a class, category, and resource type. * Used by the MCP `list_audit_event_types` tool so consumers can discover valid * filter values for `get_audit_events`. * * **Sync contract**: the server-side test `auditEventCatalog.test.ts` validates * that every `AuditOp` operation is present in this catalog. If you add a new * `AuditOp` or `auditResource` entry, add the operation here too — CI will * fail otherwise. */ export interface AuditEventTypeEntry { class_uid: OCSFClassUid; class_name: string; category_uid: OCSFCategoryUid; category_name: string; resource_type: string; operations: string[]; } /** * Complete catalog of OCSF audit event types emitted by Superblocks. * Derived from `auditResource` (class/category/resource) and `AuditOp` (operations) * in `packages/server/src/middleware/auditEvent.ts`. */ export const AUDIT_EVENT_TYPE_CATALOG: AuditEventTypeEntry[] = [ { class_uid: OCSFClassUid.API_ACTIVITY, class_name: 'API_ACTIVITY', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'API', operations: ['api.bulk_delete', 'api.create', 'api.delete', 'api.deploy', 'api.metadata.update', 'api.settings.update'] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'AppDatabase', operations: ['database.migrate', 'database.provision'] }, { class_uid: OCSFClassUid.APPLICATION_LIFECYCLE, class_name: 'APPLICATION_LIFECYCLE', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'Application', operations: [ 'application.clone', 'application.create', 'application.create_from_template', 'application.delete', 'application.deploy', 'application.git.connect', 'application.git.disconnect', 'application.metadata.update', 'application.npm_install.blocked', 'application.npm_install.overridden', 'application.npm_install.substituted', 'application.npm_install.triaged', 'application.policy_gate_check.start', 'application.settings.update', 'application.undeploy' ] }, { class_uid: OCSFClassUid.AUTHENTICATION, class_name: 'AUTHENTICATION', category_uid: OCSFCategoryUid.IAM, category_name: 'IAM', resource_type: 'Session', operations: ['email.verify', 'oauth.exchange_code', 'oauth.shared_token.revoke', 'oauth.user_token.revoke'] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'Billing', operations: ['billing.downgrade', 'billing.seat.assign', 'billing.seat.unassign', 'billing.subscription.update'] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.IAM, category_name: 'IAM', resource_type: 'Credential', operations: ['credential.delete', 'credential.upsert'] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'File', operations: ['file.delete', 'file.upload'] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'Folder', operations: ['folder.create', 'folder.delete', 'folder.update'] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'Integration', operations: [ 'integration.create', 'integration.delete', 'integration.ownership.update', 'integration.secret.view', 'integration.update' ] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'Knowledge', operations: ['knowledge.create', 'knowledge.delete', 'knowledge.update'] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'Organization', operations: [ 'organization.npm_allow_install_scripts_changed', 'organization.npm_registry.create', 'organization.npm_registry.delete', 'organization.npm_registry.update', 'organization.settings.update', 'organization.update' ] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'Profile', operations: ['profile.create', 'profile.delete', 'profile.manage', 'profile.update'] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'Repository', operations: [ 'repository.branch.delete', 'repository.branch_protection.update', 'repository.create', 'repository.delete', 'repository.update' ] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.APPLICATION_ACTIVITY, category_name: 'APPLICATION_ACTIVITY', resource_type: 'ReviewPolicy', operations: [ 'reviewpolicy.allowlist.applications.added', 'reviewpolicy.allowlist.applications.removed', 'reviewpolicy.create', 'reviewpolicy.delete' ] }, { class_uid: OCSFClassUid.ENTITY_MANAGEMENT, class_name: 'ENTITY_MANAGEMENT', category_uid: OCSFCategoryUid.IAM, category_name: 'IAM', resource_type: 'Token', operations: ['token.create', 'token.delete', 'token.regenerate', 'token.update'] }, { class_uid: OCSFClassUid.USER_ACCESS_MANAGEMENT, class_name: 'USER_ACCESS_MANAGEMENT', category_uid: OCSFCategoryUid.IAM, category_name: 'IAM', resource_type: 'Group', operations: [ 'group.create', 'group.delete', 'group.members.update', 'group.role.delete', 'group.role.update', 'group.update', 'scim.group.create', 'scim.group.delete', 'scim.group.update' ] }, { class_uid: OCSFClassUid.USER_ACCESS_MANAGEMENT, class_name: 'USER_ACCESS_MANAGEMENT', category_uid: OCSFCategoryUid.IAM, category_name: 'IAM', resource_type: 'Permission', operations: ['permission.create', 'permission.delete', 'permission.update'] }, { class_uid: OCSFClassUid.USER_ACCESS_MANAGEMENT, class_name: 'USER_ACCESS_MANAGEMENT', category_uid: OCSFCategoryUid.IAM, category_name: 'IAM', resource_type: 'Role', operations: ['role.create', 'role.delete', 'role.settings.update', 'role.update'] }, { class_uid: OCSFClassUid.USER_ACCESS_MANAGEMENT, class_name: 'USER_ACCESS_MANAGEMENT', category_uid: OCSFCategoryUid.IAM, category_name: 'IAM', resource_type: 'User', operations: [ 'scim.user.create', 'scim.user.delete', 'scim.user.update', 'user.activate', 'user.add', 'user.invite', 'user.role.update', 'user.roles.bulk_update' ] } ]; // --------------------------------------------------------------------------- // OCSF metadata helpers // --------------------------------------------------------------------------- const METADATA: Omit = { product: { name: 'Superblocks', vendor_name: 'Superblocks' }, version: '1.3.0' }; export function buildOCSFMetadata(logName: string): OCSFMetadata { return { ...METADATA, log_name: logName }; } // --------------------------------------------------------------------------- // npm install-blocked audit report (APPS-4191 / P6.3) // --------------------------------------------------------------------------- /** * Provisional install-time exploitability decision for a CVE finding. * Shared by the agent triage path and `NpmInstallBlockedAuditReport.triage`. */ export interface TriageVerdict { advisoryId: string; confidence: 'high' | 'low' | 'medium'; evaluatedAt: string; factors?: { appExposure?: string; intendedUse?: string; vulnerableFunctionalityInPlay?: boolean; }; model: string; package: string; reasoning: string; stage: 'install'; verdict: 'exploitable' | 'not_exploitable' | 'uncertain'; } /** * Wire payload for the historical `v1.audit.npmInstallBlocked` socket method. * * Emitted by the Clark dev-server runtime for blocked installs and successful * security decisions such as substitutions, triage, and user overrides. The * server derives org, actor, and app from the authenticated connection, * throttles per-org and globally, sanitizes via the P6.1 telemetry helpers, * and writes an application-scoped OCSF `audit_event` row. * * Carries only structured, low-risk fields — deliberately NO free-text error * message, so a registry token embedded in CLI output can never reach the wire. */ export interface NpmInstallBlockedAuditReport { /** `NpmInstallBlocked.reason`; normalized to the shared npm outcome enum server-side. */ reason: string; /** Raw registry host; the server buckets it to public_npm | private | unknown. */ registryHost?: string; /** Requested package names; the server sanitizes each per P6.1 and caps the array. */ packages: string[]; /** HTTP status from the failed registry fetch, when known. */ httpStatus?: number; /** Underlying npm/pnpm error code (e.g. E404), when known. Server caps length. */ npmErrorCode?: string; /** * Which controlled install path produced the block. The server allowlists * against `NPM_INSTALL_RUNNERS` and drops any other value. */ runner?: string; /** Epoch ms when the block occurred (client clock). */ occurredAt: number; /** * Vulnerability-index findings when `reason === "vulnerable_package"`. One * block can name several advisories across several packages, so each finding * carries its own package spec and a SIEM query on `advisory_id` matches * every row it should. Server allowlists `kind`, scrubs string fields, drops * off-shape entries rather than coercing them, and caps the array length. */ vulnerabilities?: Array<{ advisoryId: string; kind: 'cve' | 'malware'; package: string; severity?: string; suggestedVersion?: string; }>; /** * Silent version substitution when `reason === "package_substituted"` * (CVE safe-swap or quarantine hold-back). Server scrubs versions and * allowlists `reason`. One row per substitution — the client reports each * one separately with `packages` naming the swapped package, which is why * this object carries no name of its own. */ substitution?: { fromVersion: string; toVersion: string; reason: 'cve_swap' | 'quarantine'; }; /** Provisional install-time exploitability decision for a CVE finding. */ triage?: TriageVerdict; /** * Builder-approved proceed-with-reason override when `reason === * "vulnerability_user_override"`. Server scrubs `reason` and caps lengths. */ userOverride?: { packages: string[]; reason: string; }; // application attribution: NOT a wire field. The server derives it from the // trusted scoped-JWT claim (`ctx.jwtClaims.app_id`) — a payload-controlled // value would let any authenticated caller forge audit rows against another // app in the same org. }