Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括: - 配置文件中的项目名称 - 代码注释和文档引用 - 环境变量和路径 - 类型定义和接口名称 - 测试用例和模拟数据 同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
287 lines
8.5 KiB
TypeScript
287 lines
8.5 KiB
TypeScript
/**
|
|
* 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<string, unknown>;
|
|
// 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<boolean> {
|
|
try {
|
|
return await invoke<boolean>('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<boolean> {
|
|
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<SecurityLayer[]> {
|
|
// 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<void>;
|
|
loadAuditLogs: (opts?: { limit?: number; offset?: number }) => Promise<void>;
|
|
}
|
|
|
|
export type SecurityStore = SecurityStateSlice & SecurityActionsSlice & { client: SecurityClient | null };
|
|
|
|
// === Store Implementation ===
|
|
|
|
export const useSecurityStore = create<SecurityStore>((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 });
|
|
}
|