/** * securityStore.ts - Security Status and Audit Log Management * * Manages ZCLAW security layers, security status, and audit logs. * Uses local security checks (security-index.ts + Tauri commands) instead of REST API. */ import { create } from 'zustand'; import { invoke } from '@tauri-apps/api/core'; import type { GatewayClient } from '../lib/gateway-client'; // === Types === export interface SecurityLayer { name: string; enabled: boolean; description?: string; } export interface SecurityStatus { layers: SecurityLayer[]; enabledCount: number; totalCount: number; securityLevel: 'critical' | 'high' | 'medium' | 'low'; } export interface AuditLogEntry { id: string; timestamp: string; action: string; actor?: string; result?: 'success' | 'failure'; details?: Record; // Merkle hash chain fields hash?: string; previousHash?: string; } // === Helpers === function calculateSecurityLevel(enabledCount: number, totalCount: number): 'critical' | 'high' | 'medium' | 'low' { if (totalCount === 0) return 'low'; const ratio = enabledCount / totalCount; if (ratio >= 0.875) return 'critical'; // 14-16 layers if (ratio >= 0.625) return 'high'; // 10-13 layers if (ratio >= 0.375) return 'medium'; // 6-9 layers return 'low'; // 0-5 layers } /** * Check if OS Keyring (secure store) is available via Tauri command. * Returns false if not in Tauri environment or if keyring is unavailable. */ async function checkKeyringAvailable(): Promise { try { return await invoke('secure_store_is_available'); } catch { // Not in Tauri environment or command failed return false; } } /** * Check if the ZCLAW Kernel is initialized via Tauri command. */ async function checkKernelInitialized(): Promise { try { const status = await invoke<{ initialized: boolean }>('kernel_status'); return status.initialized; } catch { return false; } } /** * Build the 16-layer security model from local security checks. */ async function buildLocalSecurityLayers(): Promise { // Gather local security status let auditEnabled = false; let keychainAvailable = false; let chatStorageInitialized = false; try { const { getSecurityStatus } = await import('../lib/security-index'); const status = await getSecurityStatus(); auditEnabled = status.auditEnabled; keychainAvailable = status.keychainAvailable; chatStorageInitialized = status.chatStorageInitialized; } catch { // Security module not available - use defaults } // Check OS Keyring availability directly via Tauri const keyringAvailable = await checkKeyringAvailable(); const kernelInitialized = await checkKernelInitialized(); // Use keychainAvailable from security-index as primary, keyringAvailable as fallback const hasSecureStorage = keychainAvailable || keyringAvailable; // Map local security capabilities to the 16-layer security model const layers: SecurityLayer[] = [ { name: 'input.validation', enabled: true, description: 'security-utils.ts provides input validation and sanitization', }, { name: 'output.filter', enabled: true, description: 'security-utils.ts provides output sanitization and content filtering', }, { name: 'rate.limit', enabled: true, description: 'security-utils.ts provides rate limiting', }, { name: 'auth.identity', enabled: hasSecureStorage, description: hasSecureStorage ? 'OS Keyring available for secure identity storage' : 'OS Keyring not available', }, { name: 'incident.response', enabled: auditEnabled, description: auditEnabled ? 'Automated incident detection and alerting via audit events' : 'Requires audit logging for incident response', }, { name: 'session.management', enabled: true, description: 'Session management is always active', }, { name: 'auth.rbac', enabled: hasSecureStorage, description: hasSecureStorage ? 'Device authentication and role-based access available' : 'Requires OS Keyring for device authentication', }, { name: 'encryption', enabled: chatStorageInitialized, description: chatStorageInitialized ? 'Encrypted chat storage is initialized (AES-256-GCM)' : 'Encrypted storage not yet initialized', }, { name: 'audit.logging', enabled: auditEnabled, description: auditEnabled ? 'Security audit logging is active' : 'Audit logging is disabled', }, { name: 'integrity', enabled: auditEnabled, description: auditEnabled ? 'Integrity verification enabled via audit log' : 'Requires audit logging for integrity verification', }, { name: 'sandbox', enabled: true, description: 'Tauri sandbox provides process isolation', }, { name: 'network.security', enabled: true, description: 'WSS enforced, CSP headers active', }, { name: 'resource.limits', enabled: true, description: 'Path validation and timeout limits active', }, { name: 'capability.gates', enabled: kernelInitialized, description: kernelInitialized ? 'Kernel capability gates active' : 'Kernel not yet initialized', }, { name: 'prompt.defense', enabled: true, description: 'Input sanitization includes prompt injection defense', }, { name: 'anomaly.detection', enabled: auditEnabled, description: auditEnabled ? 'Anomaly detection via security audit events' : 'Requires audit logging for anomaly detection', }, ]; return layers; } // === Client Interface === interface SecurityClient { getSecurityStatus(): Promise<{ layers?: SecurityLayer[] } | null>; getAuditLogs(opts?: { limit?: number; offset?: number }): Promise<{ logs?: AuditLogEntry[] } | null>; } // === Store Interface === export interface SecurityStateSlice { securityStatus: SecurityStatus | null; securityStatusLoading: boolean; securityStatusError: string | null; auditLogs: AuditLogEntry[]; auditLogsLoading: boolean; } export interface SecurityActionsSlice { loadSecurityStatus: () => Promise; loadAuditLogs: (opts?: { limit?: number; offset?: number }) => Promise; } export type SecurityStore = SecurityStateSlice & SecurityActionsSlice & { client: SecurityClient | null }; // === Store Implementation === export const useSecurityStore = create((set, get) => ({ // Initial state securityStatus: null, securityStatusLoading: false, securityStatusError: null, auditLogs: [], auditLogsLoading: false, client: null, loadSecurityStatus: async () => { set({ securityStatusLoading: true, securityStatusError: null }); try { const layers = await buildLocalSecurityLayers(); const enabledCount = layers.filter(l => l.enabled).length; const totalCount = layers.length; const securityLevel = calculateSecurityLevel(enabledCount, totalCount); set({ securityStatus: { layers, enabledCount, totalCount, securityLevel }, securityStatusLoading: false, securityStatusError: null, }); } catch (err: unknown) { const message = err instanceof Error ? err.message : String(err); set({ securityStatusLoading: false, securityStatusError: message || 'Failed to detect security status', }); } }, loadAuditLogs: async (opts?: { limit?: number; offset?: number }) => { const client = get().client; if (!client) return; set({ auditLogsLoading: true }); try { const result = await client.getAuditLogs(opts); set({ auditLogs: (result?.logs || []) as AuditLogEntry[], auditLogsLoading: false }); } catch { set({ auditLogsLoading: false }); /* ignore if audit API not available */ } }, })); // === Client Injection === function createSecurityClientFromGateway(client: GatewayClient): SecurityClient { return { getSecurityStatus: () => client.getSecurityStatus() as Promise<{ layers?: SecurityLayer[] } | null>, getAuditLogs: (opts) => client.getAuditLogs(opts) as Promise<{ logs?: AuditLogEntry[] } | null>, }; } export function setSecurityStoreClient(client: unknown): void { const securityClient = createSecurityClientFromGateway(client as GatewayClient); useSecurityStore.setState({ client: securityClient }); }