mirror of
https://github.com/Heretek-AI/heretek-openclaw-core.git
synced 2026-07-01 14:17:57 -04:00
feat: Add container isolation for Lobe Agents
- Implement ContainerIsolation system with resource limits - Add CPU, memory, disk, and network allocation checks - Implement security profiles (default, strict) - Add syscall and path blocking - Track violations and agent status - Generate container isolation reports - Add 51 comprehensive unit tests Phase 5 Security Hardening - P0 objective
This commit is contained in:
@@ -0,0 +1,682 @@
|
||||
/**
|
||||
* Container Isolation for Lobe Agents
|
||||
*
|
||||
* Implements process isolation, resource limits, and security boundaries
|
||||
* for individual Lobe Agents to prevent cross-agent contamination and
|
||||
* resource monopolization.
|
||||
*
|
||||
* @module container-isolation
|
||||
*/
|
||||
|
||||
export interface ContainerConfig {
|
||||
enabled: boolean;
|
||||
agentId: string;
|
||||
|
||||
// Resource limits
|
||||
cpuLimit: number; // CPU cores (0.5 = 50% of one core)
|
||||
memoryLimit: number; // Memory in MB
|
||||
diskLimit: number; // Disk space in MB
|
||||
networkLimit: number; // Network bandwidth in Mbps
|
||||
|
||||
// Process isolation
|
||||
namespace: string; // Process namespace
|
||||
cgroupPath: string; // Control group path
|
||||
rootfsPath: string; // Root filesystem path
|
||||
|
||||
// Security
|
||||
seccompProfile: string; // Seccomp security profile
|
||||
apparmorProfile: string; // AppArmor profile
|
||||
capabilities: string[]; // Linux capabilities to retain
|
||||
readOnlyRootfs: boolean; // Mount rootfs as read-only
|
||||
|
||||
// Execution
|
||||
maxExecutionTime: number; // Max execution time in seconds
|
||||
maxConcurrentTasks: number; // Max concurrent tasks
|
||||
allowedCommands: string[]; // Whitelist of allowed commands
|
||||
}
|
||||
|
||||
export interface ContainerMetrics {
|
||||
agentId: string;
|
||||
cpuUsage: number; // Current CPU usage percentage
|
||||
memoryUsage: number; // Current memory usage in MB
|
||||
diskUsage: number; // Current disk usage in MB
|
||||
networkIn: number; // Network inbound in bytes
|
||||
networkOut: number; // Network outbound in bytes
|
||||
processCount: number; // Number of active processes
|
||||
startTime: Date;
|
||||
lastActivity: Date;
|
||||
}
|
||||
|
||||
export interface ContainerStatus {
|
||||
agentId: string;
|
||||
running: boolean;
|
||||
healthy: boolean;
|
||||
resourceViolations: number;
|
||||
securityViolations: number;
|
||||
lastHealthCheck: Date;
|
||||
status: 'running' | 'stopped' | 'failed' | 'restricted';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface ResourceCheckResult {
|
||||
allowed: boolean;
|
||||
reason: string;
|
||||
currentUsage: number;
|
||||
limit: number;
|
||||
usagePercentage: number;
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
export interface SecurityProfile {
|
||||
name: string;
|
||||
description: string;
|
||||
seccompRules: string[];
|
||||
apparmorRules: string[];
|
||||
blockedSyscalls: string[];
|
||||
blockedPaths: string[];
|
||||
allowedCapabilities: string[];
|
||||
}
|
||||
|
||||
export interface ContainerIsolationState {
|
||||
config: ContainerConfig;
|
||||
metrics: Map<string, ContainerMetrics>;
|
||||
status: Map<string, ContainerStatus>;
|
||||
securityProfiles: Map<string, SecurityProfile>;
|
||||
violationLog: ViolationEntry[];
|
||||
maxViolationLog: number;
|
||||
}
|
||||
|
||||
export interface ViolationEntry {
|
||||
timestamp: Date;
|
||||
agentId: string;
|
||||
violationType: 'resource' | 'security' | 'execution';
|
||||
severity: 'low' | 'medium' | 'high' | 'critical';
|
||||
description: string;
|
||||
details?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default container configuration
|
||||
*/
|
||||
export function createContainerConfig(overrides?: Partial<ContainerConfig>): ContainerConfig {
|
||||
const defaultConfig: ContainerConfig = {
|
||||
enabled: true,
|
||||
agentId: 'unknown',
|
||||
cpuLimit: 1.0,
|
||||
memoryLimit: 512,
|
||||
diskLimit: 1024,
|
||||
networkLimit: 100,
|
||||
namespace: 'lobe-agent',
|
||||
cgroupPath: '/sys/fs/cgroup/lobe-agents',
|
||||
rootfsPath: '/var/lib/lobe-agents/rootfs',
|
||||
seccompProfile: 'default',
|
||||
apparmorProfile: 'lobe-agent-restricted',
|
||||
capabilities: ['CAP_NET_BIND_SERVICE'],
|
||||
readOnlyRootfs: true,
|
||||
maxExecutionTime: 300, // 5 minutes
|
||||
maxConcurrentTasks: 3,
|
||||
allowedCommands: ['node', 'npm', 'git', 'cat', 'ls', 'echo'],
|
||||
};
|
||||
|
||||
return { ...defaultConfig, ...overrides };
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize container isolation state
|
||||
*/
|
||||
export function createContainerState(config?: Partial<ContainerConfig>): ContainerIsolationState {
|
||||
const baseConfig = createContainerConfig(config);
|
||||
|
||||
return {
|
||||
config: baseConfig,
|
||||
metrics: new Map(),
|
||||
status: new Map(),
|
||||
securityProfiles: new Map(),
|
||||
violationLog: [],
|
||||
maxViolationLog: 1000,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register default security profiles
|
||||
*/
|
||||
export function registerDefaultSecurityProfiles(state: ContainerIsolationState): void {
|
||||
// Default restricted profile
|
||||
state.securityProfiles.set('default', {
|
||||
name: 'default',
|
||||
description: 'Default restricted profile for Lobe Agents',
|
||||
seccompRules: [
|
||||
'allow: read, write, open, close, stat, fstat',
|
||||
'allow: mmap, munmap, mprotect',
|
||||
'allow: exit, exit_group',
|
||||
'allow: getuid, getgid, geteuid, getegid',
|
||||
'deny: ptrace, process_vm_readv, process_vm_writev',
|
||||
'deny: mount, umount, umount2',
|
||||
'deny: reboot, sethostname, setdomainname',
|
||||
'deny: init_module, delete_module',
|
||||
'deny: kexec_load',
|
||||
],
|
||||
apparmorRules: [
|
||||
'include <abstractions/base>',
|
||||
'network inet tcp,',
|
||||
'network inet udp,',
|
||||
'deny network raw,',
|
||||
'deny network packet,',
|
||||
'deny @{PROC}/* w,',
|
||||
'deny /sys/* w,',
|
||||
'deny /dev/mem rw,',
|
||||
],
|
||||
blockedSyscalls: [
|
||||
'ptrace',
|
||||
'process_vm_readv',
|
||||
'process_vm_writev',
|
||||
'mount',
|
||||
'umount',
|
||||
'umount2',
|
||||
'reboot',
|
||||
'sethostname',
|
||||
'setdomainname',
|
||||
'init_module',
|
||||
'delete_module',
|
||||
'kexec_load',
|
||||
'acct',
|
||||
'settimeofday',
|
||||
'adjtimex',
|
||||
],
|
||||
blockedPaths: [
|
||||
'/etc/passwd',
|
||||
'/etc/shadow',
|
||||
'/etc/sudoers',
|
||||
'/root',
|
||||
'/home',
|
||||
'/proc/*/mem',
|
||||
'/proc/*/fd',
|
||||
'/sys/kernel',
|
||||
'/dev/mem',
|
||||
'/dev/kmem',
|
||||
],
|
||||
allowedCapabilities: ['CAP_NET_BIND_SERVICE'],
|
||||
});
|
||||
|
||||
// Strict profile for high-security operations
|
||||
state.securityProfiles.set('strict', {
|
||||
name: 'strict',
|
||||
description: 'Strict profile with minimal permissions',
|
||||
seccompRules: [
|
||||
'allow: read, write, open, close',
|
||||
'allow: exit',
|
||||
'allow: getuid, getgid',
|
||||
'deny: all others',
|
||||
],
|
||||
apparmorRules: [
|
||||
'include <abstractions/base>',
|
||||
'deny network,',
|
||||
'deny @{PROC}/**,',
|
||||
'deny /sys/**,',
|
||||
'deny /dev/**,',
|
||||
],
|
||||
blockedSyscalls: [
|
||||
'all',
|
||||
],
|
||||
blockedPaths: [
|
||||
'/**',
|
||||
],
|
||||
allowedCapabilities: [],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if agent can allocate additional CPU resources
|
||||
*/
|
||||
export function checkCpuAllocation(
|
||||
state: ContainerIsolationState,
|
||||
agentId: string,
|
||||
requestedCpu: number
|
||||
): ResourceCheckResult {
|
||||
const metrics = state.metrics.get(agentId);
|
||||
const currentUsage = metrics?.cpuUsage ?? 0;
|
||||
const limit = state.config.cpuLimit;
|
||||
const usagePercentage = (currentUsage / limit) * 100;
|
||||
|
||||
if (currentUsage + requestedCpu > limit) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `CPU allocation would exceed limit`,
|
||||
currentUsage: currentUsage,
|
||||
limit: limit,
|
||||
usagePercentage: usagePercentage,
|
||||
suggestions: [
|
||||
'Wait for current tasks to complete',
|
||||
'Reduce concurrent task count',
|
||||
'Optimize CPU-intensive operations',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
allowed: true,
|
||||
reason: 'CPU allocation within limits',
|
||||
currentUsage: currentUsage,
|
||||
limit: limit,
|
||||
usagePercentage: usagePercentage,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if agent can allocate additional memory
|
||||
*/
|
||||
export function checkMemoryAllocation(
|
||||
state: ContainerIsolationState,
|
||||
agentId: string,
|
||||
requestedMemory: number
|
||||
): ResourceCheckResult {
|
||||
const metrics = state.metrics.get(agentId);
|
||||
const currentUsage = metrics?.memoryUsage ?? 0;
|
||||
const limit = state.config.memoryLimit;
|
||||
const usagePercentage = (currentUsage / limit) * 100;
|
||||
|
||||
if (currentUsage + requestedMemory > limit) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Memory allocation would exceed limit (${limit}MB)`,
|
||||
currentUsage: currentUsage,
|
||||
limit: limit,
|
||||
usagePercentage: usagePercentage,
|
||||
suggestions: [
|
||||
'Release unused memory',
|
||||
'Process data in smaller batches',
|
||||
'Close unused file handles',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
allowed: true,
|
||||
reason: 'Memory allocation within limits',
|
||||
currentUsage: currentUsage,
|
||||
limit: limit,
|
||||
usagePercentage: usagePercentage,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if agent can use additional disk space
|
||||
*/
|
||||
export function checkDiskAllocation(
|
||||
state: ContainerIsolationState,
|
||||
agentId: string,
|
||||
requestedDisk: number
|
||||
): ResourceCheckResult {
|
||||
const metrics = state.metrics.get(agentId);
|
||||
const currentUsage = metrics?.diskUsage ?? 0;
|
||||
const limit = state.config.diskLimit;
|
||||
const usagePercentage = (currentUsage / limit) * 100;
|
||||
|
||||
if (currentUsage + requestedDisk > limit) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Disk allocation would exceed limit (${limit}MB)`,
|
||||
currentUsage: currentUsage,
|
||||
limit: limit,
|
||||
usagePercentage: usagePercentage,
|
||||
suggestions: [
|
||||
'Clean up temporary files',
|
||||
'Archive old data',
|
||||
'Reduce logging verbosity',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
allowed: true,
|
||||
reason: 'Disk allocation within limits',
|
||||
currentUsage: currentUsage,
|
||||
limit: limit,
|
||||
usagePercentage: usagePercentage,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if command is allowed for agent
|
||||
*/
|
||||
export function checkCommandAllowed(
|
||||
state: ContainerIsolationState,
|
||||
command: string
|
||||
): boolean {
|
||||
const allowedCommands = state.config.allowedCommands;
|
||||
return allowedCommands.includes(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if path access is blocked by security profile
|
||||
*/
|
||||
export function checkPathBlocked(
|
||||
state: ContainerIsolationState,
|
||||
profileName: string,
|
||||
path: string
|
||||
): boolean {
|
||||
const profile = state.securityProfiles.get(profileName);
|
||||
if (!profile) return false;
|
||||
|
||||
return profile.blockedPaths.some((blockedPath) => {
|
||||
if (blockedPath === '/**') return true;
|
||||
if (blockedPath.endsWith('/**')) {
|
||||
const prefix = blockedPath.slice(0, -3);
|
||||
return path.startsWith(prefix);
|
||||
}
|
||||
return path === blockedPath || path.startsWith(blockedPath + '/');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if syscall is blocked by security profile
|
||||
*/
|
||||
export function checkSyscallBlocked(
|
||||
state: ContainerIsolationState,
|
||||
profileName: string,
|
||||
syscall: string
|
||||
): boolean {
|
||||
const profile = state.securityProfiles.get(profileName);
|
||||
if (!profile) return false;
|
||||
|
||||
return profile.blockedSyscalls.includes(syscall) ||
|
||||
profile.blockedSyscalls.includes('all');
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a resource or security violation
|
||||
*/
|
||||
export function recordViolation(
|
||||
state: ContainerIsolationState,
|
||||
agentId: string,
|
||||
violationType: 'resource' | 'security' | 'execution',
|
||||
severity: 'low' | 'medium' | 'high' | 'critical',
|
||||
description: string,
|
||||
details?: Record<string, unknown>
|
||||
): ViolationEntry {
|
||||
const entry: ViolationEntry = {
|
||||
timestamp: new Date(),
|
||||
agentId,
|
||||
violationType,
|
||||
severity,
|
||||
description,
|
||||
details,
|
||||
};
|
||||
|
||||
state.violationLog.push(entry);
|
||||
|
||||
// Trim log if over limit
|
||||
if (state.violationLog.length > state.maxViolationLog) {
|
||||
state.violationLog = state.violationLog.slice(-state.maxViolationLog);
|
||||
}
|
||||
|
||||
// Update agent status
|
||||
const status = state.status.get(agentId);
|
||||
if (status) {
|
||||
if (violationType === 'security') {
|
||||
status.securityViolations++;
|
||||
} else if (violationType === 'resource') {
|
||||
status.resourceViolations++;
|
||||
}
|
||||
|
||||
// Escalate status based on violations
|
||||
if (status.securityViolations >= 3 || status.resourceViolations >= 5) {
|
||||
status.status = 'restricted';
|
||||
status.message = 'Agent restricted due to repeated violations';
|
||||
}
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update container metrics for an agent
|
||||
*/
|
||||
export function updateContainerMetrics(
|
||||
state: ContainerIsolationState,
|
||||
agentId: string,
|
||||
metrics: Partial<ContainerMetrics>
|
||||
): ContainerMetrics {
|
||||
const existing = state.metrics.get(agentId);
|
||||
const now = new Date();
|
||||
|
||||
const updated: ContainerMetrics = {
|
||||
agentId,
|
||||
cpuUsage: metrics.cpuUsage ?? existing?.cpuUsage ?? 0,
|
||||
memoryUsage: metrics.memoryUsage ?? existing?.memoryUsage ?? 0,
|
||||
diskUsage: metrics.diskUsage ?? existing?.diskUsage ?? 0,
|
||||
networkIn: metrics.networkIn ?? existing?.networkIn ?? 0,
|
||||
networkOut: metrics.networkOut ?? existing?.networkOut ?? 0,
|
||||
processCount: metrics.processCount ?? existing?.processCount ?? 0,
|
||||
startTime: existing?.startTime ?? now,
|
||||
lastActivity: now,
|
||||
};
|
||||
|
||||
state.metrics.set(agentId, updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get container status for an agent
|
||||
*/
|
||||
export function getContainerStatus(
|
||||
state: ContainerIsolationState,
|
||||
agentId: string
|
||||
): ContainerStatus | null {
|
||||
return state.status.get(agentId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update container status for an agent
|
||||
*/
|
||||
export function updateContainerStatus(
|
||||
state: ContainerIsolationState,
|
||||
agentId: string,
|
||||
status: Partial<ContainerStatus>
|
||||
): ContainerStatus {
|
||||
const existing = state.status.get(agentId);
|
||||
const now = new Date();
|
||||
|
||||
const updated: ContainerStatus = {
|
||||
agentId,
|
||||
running: status.running ?? existing?.running ?? false,
|
||||
healthy: status.healthy ?? existing?.healthy ?? true,
|
||||
resourceViolations: status.resourceViolations ?? existing?.resourceViolations ?? 0,
|
||||
securityViolations: status.securityViolations ?? existing?.securityViolations ?? 0,
|
||||
lastHealthCheck: now,
|
||||
status: status.status ?? existing?.status ?? 'stopped',
|
||||
message: status.message ?? existing?.message,
|
||||
};
|
||||
|
||||
state.status.set(agentId, updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get violation history for an agent
|
||||
*/
|
||||
export function getAgentViolations(
|
||||
state: ContainerIsolationState,
|
||||
agentId: string
|
||||
): ViolationEntry[] {
|
||||
return state.violationLog.filter((v) => v.agentId === agentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get violation statistics
|
||||
*/
|
||||
export function getViolationStats(
|
||||
state: ContainerIsolationState
|
||||
): {
|
||||
totalViolations: number;
|
||||
resourceViolations: number;
|
||||
securityViolations: number;
|
||||
executionViolations: number;
|
||||
criticalViolations: number;
|
||||
violationsByAgent: Record<string, number>;
|
||||
} {
|
||||
const stats = {
|
||||
totalViolations: state.violationLog.length,
|
||||
resourceViolations: 0,
|
||||
securityViolations: 0,
|
||||
executionViolations: 0,
|
||||
criticalViolations: 0,
|
||||
violationsByAgent: {} as Record<string, number>,
|
||||
};
|
||||
|
||||
state.violationLog.forEach((v) => {
|
||||
if (v.violationType === 'resource') stats.resourceViolations++;
|
||||
if (v.violationType === 'security') stats.securityViolations++;
|
||||
if (v.violationType === 'execution') stats.executionViolations++;
|
||||
if (v.severity === 'critical') stats.criticalViolations++;
|
||||
|
||||
stats.violationsByAgent[v.agentId] = (stats.violationsByAgent[v.agentId] || 0) + 1;
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate container isolation report
|
||||
*/
|
||||
export function generateContainerReport(state: ContainerIsolationState): {
|
||||
summary: {
|
||||
totalAgents: number;
|
||||
runningAgents: number;
|
||||
healthyAgents: number;
|
||||
restrictedAgents: number;
|
||||
};
|
||||
resourceUsage: {
|
||||
totalCpuUsage: number;
|
||||
totalMemoryUsage: number;
|
||||
totalDiskUsage: number;
|
||||
avgCpuUsage: number;
|
||||
avgMemoryUsage: number;
|
||||
};
|
||||
violations: ReturnType<typeof getViolationStats>;
|
||||
recommendations: string[];
|
||||
} {
|
||||
const agents = Array.from(state.status.values());
|
||||
const metrics = Array.from(state.metrics.values());
|
||||
|
||||
const summary = {
|
||||
totalAgents: agents.length,
|
||||
runningAgents: agents.filter((a) => a.running).length,
|
||||
healthyAgents: agents.filter((a) => a.healthy).length,
|
||||
restrictedAgents: agents.filter((a) => a.status === 'restricted').length,
|
||||
};
|
||||
|
||||
const resourceUsage = {
|
||||
totalCpuUsage: metrics.reduce((sum, m) => sum + m.cpuUsage, 0),
|
||||
totalMemoryUsage: metrics.reduce((sum, m) => sum + m.memoryUsage, 0),
|
||||
totalDiskUsage: metrics.reduce((sum, m) => sum + m.diskUsage, 0),
|
||||
avgCpuUsage: metrics.length > 0
|
||||
? metrics.reduce((sum, m) => sum + m.cpuUsage, 0) / metrics.length
|
||||
: 0,
|
||||
avgMemoryUsage: metrics.length > 0
|
||||
? metrics.reduce((sum, m) => sum + m.memoryUsage, 0) / metrics.length
|
||||
: 0,
|
||||
};
|
||||
|
||||
const violations = getViolationStats(state);
|
||||
|
||||
const recommendations: string[] = [];
|
||||
|
||||
if (summary.restrictedAgents > 0) {
|
||||
recommendations.push(`Review ${summary.restrictedAgents} restricted agents`);
|
||||
}
|
||||
|
||||
if (violations.criticalViolations > 0) {
|
||||
recommendations.push(`Investigate ${violations.criticalViolations} critical security violations`);
|
||||
}
|
||||
|
||||
if (resourceUsage.avgCpuUsage > state.config.cpuLimit * 0.8) {
|
||||
recommendations.push('Consider increasing CPU limits or optimizing agent workloads');
|
||||
}
|
||||
|
||||
if (resourceUsage.avgMemoryUsage > state.config.memoryLimit * 0.8) {
|
||||
recommendations.push('Consider increasing memory limits or optimizing agent memory usage');
|
||||
}
|
||||
|
||||
if (violations.securityViolations > violations.resourceViolations) {
|
||||
recommendations.push('Security violations exceed resource violations - review security profiles');
|
||||
}
|
||||
|
||||
return {
|
||||
summary,
|
||||
resourceUsage,
|
||||
violations,
|
||||
recommendations,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export container state for serialization
|
||||
*/
|
||||
export function exportContainerState(state: ContainerIsolationState): Record<string, unknown> {
|
||||
return {
|
||||
config: state.config,
|
||||
metrics: Array.from(state.metrics.entries()),
|
||||
status: Array.from(state.status.entries()).map(([id, s]) => ({
|
||||
agentId: id,
|
||||
running: s.running,
|
||||
healthy: s.healthy,
|
||||
resourceViolations: s.resourceViolations,
|
||||
securityViolations: s.securityViolations,
|
||||
lastHealthCheck: s.lastHealthCheck.toISOString(),
|
||||
status: s.status,
|
||||
message: s.message,
|
||||
})),
|
||||
securityProfiles: Array.from(state.securityProfiles.entries()),
|
||||
violationLog: state.violationLog.map((v) => ({
|
||||
...v,
|
||||
timestamp: v.timestamp.toISOString(),
|
||||
})),
|
||||
maxViolationLog: state.maxViolationLog,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import container state from serialization
|
||||
*/
|
||||
export function importContainerState(data: Record<string, unknown>): ContainerIsolationState {
|
||||
const config = data.config as ContainerConfig;
|
||||
const metricsData = data.metrics as Array<[string, ContainerMetrics]>;
|
||||
const statusData = data.status as Array<Record<string, unknown>>;
|
||||
const profilesData = data.securityProfiles as Array<[string, SecurityProfile]>;
|
||||
const violationData = data.violationLog as Array<Record<string, unknown>>;
|
||||
|
||||
const metrics = new Map(metricsData);
|
||||
|
||||
const status = new Map<string, ContainerStatus>();
|
||||
statusData.forEach((entry) => {
|
||||
const s = entry as Record<string, unknown>;
|
||||
const agentId = s.agentId as string;
|
||||
status.set(agentId, {
|
||||
agentId,
|
||||
running: s.running as boolean,
|
||||
healthy: s.healthy as boolean,
|
||||
resourceViolations: s.resourceViolations as number,
|
||||
securityViolations: s.securityViolations as number,
|
||||
lastHealthCheck: new Date(s.lastHealthCheck as string),
|
||||
status: s.status as ContainerStatus['status'],
|
||||
message: s.message as string | undefined,
|
||||
});
|
||||
});
|
||||
|
||||
const securityProfiles = new Map(profilesData);
|
||||
|
||||
const violationLog = violationData.map((v) => ({
|
||||
...v,
|
||||
timestamp: new Date(v.timestamp as string),
|
||||
})) as ViolationEntry[];
|
||||
|
||||
return {
|
||||
config,
|
||||
metrics,
|
||||
status,
|
||||
securityProfiles,
|
||||
violationLog,
|
||||
maxViolationLog: data.maxViolationLog as number,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,610 @@
|
||||
/**
|
||||
* Unit tests for Container Isolation System
|
||||
* Tests for process isolation, resource limits, and security boundaries
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import {
|
||||
createContainerConfig,
|
||||
createContainerState,
|
||||
registerDefaultSecurityProfiles,
|
||||
checkCpuAllocation,
|
||||
checkMemoryAllocation,
|
||||
checkDiskAllocation,
|
||||
checkCommandAllowed,
|
||||
checkPathBlocked,
|
||||
checkSyscallBlocked,
|
||||
recordViolation,
|
||||
updateContainerMetrics,
|
||||
getContainerStatus,
|
||||
updateContainerStatus,
|
||||
getAgentViolations,
|
||||
getViolationStats,
|
||||
generateContainerReport,
|
||||
exportContainerState,
|
||||
importContainerState,
|
||||
type ContainerIsolationState,
|
||||
} from '../../skills/agemem-governance/container-isolation';
|
||||
|
||||
describe('Container Isolation System', () => {
|
||||
describe('createContainerConfig', () => {
|
||||
it('should create default configuration', () => {
|
||||
const config = createContainerConfig();
|
||||
expect(config.enabled).toBe(true);
|
||||
expect(config.cpuLimit).toBe(1.0);
|
||||
expect(config.memoryLimit).toBe(512);
|
||||
expect(config.diskLimit).toBe(1024);
|
||||
expect(config.networkLimit).toBe(100);
|
||||
expect(config.maxExecutionTime).toBe(300);
|
||||
expect(config.maxConcurrentTasks).toBe(3);
|
||||
expect(config.readOnlyRootfs).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow overriding defaults', () => {
|
||||
const config = createContainerConfig({
|
||||
cpuLimit: 2.0,
|
||||
memoryLimit: 1024,
|
||||
maxExecutionTime: 600,
|
||||
});
|
||||
expect(config.cpuLimit).toBe(2.0);
|
||||
expect(config.memoryLimit).toBe(1024);
|
||||
expect(config.maxExecutionTime).toBe(600);
|
||||
expect(config.diskLimit).toBe(1024); // Default preserved
|
||||
});
|
||||
});
|
||||
|
||||
describe('createContainerState', () => {
|
||||
it('should create empty state with default config', () => {
|
||||
const state = createContainerState();
|
||||
expect(state.config.enabled).toBe(true);
|
||||
expect(state.metrics.size).toBe(0);
|
||||
expect(state.status.size).toBe(0);
|
||||
expect(state.violationLog.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should create state with custom config', () => {
|
||||
const state = createContainerState({
|
||||
cpuLimit: 0.5,
|
||||
memoryLimit: 256,
|
||||
});
|
||||
expect(state.config.cpuLimit).toBe(0.5);
|
||||
expect(state.config.memoryLimit).toBe(256);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerDefaultSecurityProfiles', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should register default security profile', () => {
|
||||
registerDefaultSecurityProfiles(state);
|
||||
expect(state.securityProfiles.has('default')).toBe(true);
|
||||
});
|
||||
|
||||
it('should register strict security profile', () => {
|
||||
registerDefaultSecurityProfiles(state);
|
||||
expect(state.securityProfiles.has('strict')).toBe(true);
|
||||
});
|
||||
|
||||
it('should populate default profile with rules', () => {
|
||||
registerDefaultSecurityProfiles(state);
|
||||
const profile = state.securityProfiles.get('default');
|
||||
expect(profile).toBeDefined();
|
||||
expect(profile?.seccompRules.length).toBeGreaterThan(0);
|
||||
expect(profile?.apparmorRules.length).toBeGreaterThan(0);
|
||||
expect(profile?.blockedSyscalls.length).toBeGreaterThan(0);
|
||||
expect(profile?.blockedPaths.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkCpuAllocation', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should allow allocation when under limit', () => {
|
||||
updateContainerMetrics(state, 'agent-1', { cpuUsage: 0.3 });
|
||||
const result = checkCpuAllocation(state, 'agent-1', 0.2);
|
||||
expect(result.allowed).toBe(true);
|
||||
expect(result.usagePercentage).toBe(30);
|
||||
});
|
||||
|
||||
it('should deny allocation when over limit', () => {
|
||||
updateContainerMetrics(state, 'agent-1', { cpuUsage: 0.9 });
|
||||
const result = checkCpuAllocation(state, 'agent-1', 0.2);
|
||||
expect(result.allowed).toBe(false);
|
||||
expect(result.usagePercentage).toBe(90);
|
||||
});
|
||||
|
||||
it('should handle unknown agent', () => {
|
||||
const result = checkCpuAllocation(state, 'unknown-agent', 0.5);
|
||||
expect(result.allowed).toBe(true);
|
||||
expect(result.currentUsage).toBe(0);
|
||||
});
|
||||
|
||||
it('should provide suggestions when denied', () => {
|
||||
updateContainerMetrics(state, 'agent-1', { cpuUsage: 0.95 });
|
||||
const result = checkCpuAllocation(state, 'agent-1', 0.1);
|
||||
expect(result.suggestions).toBeDefined();
|
||||
expect(result.suggestions?.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkMemoryAllocation', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should allow allocation when under limit', () => {
|
||||
updateContainerMetrics(state, 'agent-1', { memoryUsage: 200 });
|
||||
const result = checkMemoryAllocation(state, 'agent-1', 100);
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny allocation when over limit', () => {
|
||||
updateContainerMetrics(state, 'agent-1', { memoryUsage: 450 });
|
||||
const result = checkMemoryAllocation(state, 'agent-1', 100);
|
||||
expect(result.allowed).toBe(false);
|
||||
expect(result.reason).toContain('exceed limit');
|
||||
});
|
||||
|
||||
it('should handle unknown agent', () => {
|
||||
const result = checkMemoryAllocation(state, 'unknown-agent', 100);
|
||||
expect(result.allowed).toBe(true);
|
||||
expect(result.currentUsage).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkDiskAllocation', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should allow allocation when under limit', () => {
|
||||
updateContainerMetrics(state, 'agent-1', { diskUsage: 500 });
|
||||
const result = checkDiskAllocation(state, 'agent-1', 200);
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny allocation when over limit', () => {
|
||||
updateContainerMetrics(state, 'agent-1', { diskUsage: 900 });
|
||||
const result = checkDiskAllocation(state, 'agent-1', 200);
|
||||
expect(result.allowed).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkCommandAllowed', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should allow whitelisted command', () => {
|
||||
expect(checkCommandAllowed(state, 'node')).toBe(true);
|
||||
expect(checkCommandAllowed(state, 'npm')).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny non-whitelisted command', () => {
|
||||
expect(checkCommandAllowed(state, 'rm')).toBe(false);
|
||||
expect(checkCommandAllowed(state, 'sudo')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkPathBlocked', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
registerDefaultSecurityProfiles(state);
|
||||
});
|
||||
|
||||
it('should block sensitive paths', () => {
|
||||
expect(checkPathBlocked(state, 'default', '/etc/shadow')).toBe(true);
|
||||
expect(checkPathBlocked(state, 'default', '/root')).toBe(true);
|
||||
expect(checkPathBlocked(state, 'default', '/dev/mem')).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow non-blocked paths', () => {
|
||||
expect(checkPathBlocked(state, 'default', '/var/lib/lobe-agents/data')).toBe(false);
|
||||
});
|
||||
|
||||
it('should block all paths with strict profile', () => {
|
||||
expect(checkPathBlocked(state, 'strict', '/any/path')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkSyscallBlocked', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
registerDefaultSecurityProfiles(state);
|
||||
});
|
||||
|
||||
it('should block dangerous syscalls', () => {
|
||||
expect(checkSyscallBlocked(state, 'default', 'ptrace')).toBe(true);
|
||||
expect(checkSyscallBlocked(state, 'default', 'mount')).toBe(true);
|
||||
expect(checkSyscallBlocked(state, 'default', 'reboot')).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow safe syscalls', () => {
|
||||
expect(checkSyscallBlocked(state, 'default', 'read')).toBe(false);
|
||||
expect(checkSyscallBlocked(state, 'default', 'write')).toBe(false);
|
||||
});
|
||||
|
||||
it('should block all syscalls with strict profile', () => {
|
||||
expect(checkSyscallBlocked(state, 'strict', 'any_syscall')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recordViolation', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
updateContainerStatus(state, 'agent-1', { status: 'running' });
|
||||
});
|
||||
|
||||
it('should record resource violation', () => {
|
||||
const entry = recordViolation(state, 'agent-1', 'resource', 'medium', 'CPU limit exceeded');
|
||||
expect(entry.agentId).toBe('agent-1');
|
||||
expect(entry.violationType).toBe('resource');
|
||||
expect(entry.severity).toBe('medium');
|
||||
});
|
||||
|
||||
it('should record security violation', () => {
|
||||
const entry = recordViolation(state, 'agent-1', 'security', 'high', 'Blocked syscall attempt');
|
||||
expect(entry.violationType).toBe('security');
|
||||
expect(entry.severity).toBe('high');
|
||||
});
|
||||
|
||||
it('should update agent status on repeated violations', () => {
|
||||
// Record 3 security violations
|
||||
recordViolation(state, 'agent-1', 'security', 'high', 'Violation 1');
|
||||
recordViolation(state, 'agent-1', 'security', 'high', 'Violation 2');
|
||||
recordViolation(state, 'agent-1', 'security', 'high', 'Violation 3');
|
||||
|
||||
const status = getContainerStatus(state, 'agent-1');
|
||||
expect(status?.status).toBe('restricted');
|
||||
});
|
||||
|
||||
it('should trim violation log when over limit', () => {
|
||||
state.maxViolationLog = 10;
|
||||
for (let i = 0; i < 15; i++) {
|
||||
recordViolation(state, 'agent-1', 'resource', 'low', `Violation ${i}`);
|
||||
}
|
||||
expect(state.violationLog.length).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateContainerMetrics', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should create new metrics for agent', () => {
|
||||
const metrics = updateContainerMetrics(state, 'agent-1', {
|
||||
cpuUsage: 0.5,
|
||||
memoryUsage: 256,
|
||||
});
|
||||
expect(metrics.agentId).toBe('agent-1');
|
||||
expect(metrics.cpuUsage).toBe(0.5);
|
||||
expect(metrics.memoryUsage).toBe(256);
|
||||
});
|
||||
|
||||
it('should update existing metrics', () => {
|
||||
updateContainerMetrics(state, 'agent-1', { cpuUsage: 0.3 });
|
||||
const updated = updateContainerMetrics(state, 'agent-1', { cpuUsage: 0.6 });
|
||||
expect(updated.cpuUsage).toBe(0.6);
|
||||
});
|
||||
|
||||
it('should set lastActivity timestamp', () => {
|
||||
const before = new Date();
|
||||
updateContainerMetrics(state, 'agent-1', { cpuUsage: 0.5 });
|
||||
const metrics = state.metrics.get('agent-1');
|
||||
expect(metrics?.lastActivity.getTime()).toBeGreaterThanOrEqual(before.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('getContainerStatus', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should return null for unknown agent', () => {
|
||||
const status = getContainerStatus(state, 'unknown');
|
||||
expect(status).toBe(null);
|
||||
});
|
||||
|
||||
it('should return status for known agent', () => {
|
||||
updateContainerStatus(state, 'agent-1', { status: 'running' });
|
||||
const status = getContainerStatus(state, 'agent-1');
|
||||
expect(status).toBeDefined();
|
||||
expect(status?.agentId).toBe('agent-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateContainerStatus', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should create new status for agent', () => {
|
||||
const status = updateContainerStatus(state, 'agent-1', {
|
||||
running: true,
|
||||
healthy: true,
|
||||
status: 'running',
|
||||
});
|
||||
expect(status.agentId).toBe('agent-1');
|
||||
expect(status.running).toBe(true);
|
||||
expect(status.status).toBe('running');
|
||||
});
|
||||
|
||||
it('should update existing status', () => {
|
||||
updateContainerStatus(state, 'agent-1', { status: 'running', healthy: true });
|
||||
updateContainerStatus(state, 'agent-1', { status: 'restricted', healthy: false });
|
||||
const status = getContainerStatus(state, 'agent-1');
|
||||
expect(status?.status).toBe('restricted');
|
||||
expect(status?.healthy).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAgentViolations', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should return empty array for agent with no violations', () => {
|
||||
const violations = getAgentViolations(state, 'agent-1');
|
||||
expect(violations.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should return violations for agent', () => {
|
||||
recordViolation(state, 'agent-1', 'resource', 'low', 'Test 1');
|
||||
recordViolation(state, 'agent-1', 'security', 'high', 'Test 2');
|
||||
recordViolation(state, 'agent-2', 'resource', 'low', 'Test 3');
|
||||
|
||||
const violations1 = getAgentViolations(state, 'agent-1');
|
||||
const violations2 = getAgentViolations(state, 'agent-2');
|
||||
|
||||
expect(violations1.length).toBe(2);
|
||||
expect(violations2.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getViolationStats', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should return zero stats for no violations', () => {
|
||||
const stats = getViolationStats(state);
|
||||
expect(stats.totalViolations).toBe(0);
|
||||
});
|
||||
|
||||
it('should calculate correct statistics', () => {
|
||||
recordViolation(state, 'agent-1', 'resource', 'low', 'Test');
|
||||
recordViolation(state, 'agent-1', 'security', 'high', 'Test');
|
||||
recordViolation(state, 'agent-2', 'security', 'critical', 'Test');
|
||||
recordViolation(state, 'agent-2', 'execution', 'medium', 'Test');
|
||||
|
||||
const stats = getViolationStats(state);
|
||||
expect(stats.totalViolations).toBe(4);
|
||||
expect(stats.resourceViolations).toBe(1);
|
||||
expect(stats.securityViolations).toBe(2);
|
||||
expect(stats.executionViolations).toBe(1);
|
||||
expect(stats.criticalViolations).toBe(1);
|
||||
expect(stats.violationsByAgent['agent-1']).toBe(2);
|
||||
expect(stats.violationsByAgent['agent-2']).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateContainerReport', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should generate report with zero agents', () => {
|
||||
const report = generateContainerReport(state);
|
||||
expect(report.summary.totalAgents).toBe(0);
|
||||
expect(report.recommendations.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should generate report with multiple agents', () => {
|
||||
updateContainerStatus(state, 'agent-1', { status: 'running', running: true, healthy: true });
|
||||
updateContainerStatus(state, 'agent-2', { status: 'running', running: true, healthy: true });
|
||||
updateContainerMetrics(state, 'agent-1', { cpuUsage: 0.5, memoryUsage: 256 });
|
||||
updateContainerMetrics(state, 'agent-2', { cpuUsage: 0.3, memoryUsage: 128 });
|
||||
|
||||
const report = generateContainerReport(state);
|
||||
expect(report.summary.totalAgents).toBe(2);
|
||||
expect(report.summary.runningAgents).toBe(2);
|
||||
expect(report.summary.healthyAgents).toBe(2);
|
||||
expect(report.resourceUsage.totalCpuUsage).toBe(0.8);
|
||||
expect(report.resourceUsage.totalMemoryUsage).toBe(384);
|
||||
});
|
||||
|
||||
it('should include recommendations for restricted agents', () => {
|
||||
updateContainerStatus(state, 'agent-1', { status: 'restricted', healthy: false });
|
||||
const report = generateContainerReport(state);
|
||||
expect(report.recommendations.some(r => r.includes('restricted'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should include recommendations for critical violations', () => {
|
||||
recordViolation(state, 'agent-1', 'security', 'critical', 'Critical issue');
|
||||
const report = generateContainerReport(state);
|
||||
expect(report.recommendations.some(r => r.includes('critical'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should include recommendations for high resource usage', () => {
|
||||
updateContainerMetrics(state, 'agent-1', { cpuUsage: 0.9 });
|
||||
const report = generateContainerReport(state);
|
||||
expect(report.recommendations.some(r => r.includes('CPU'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exportContainerState', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
});
|
||||
|
||||
it('should export state to serializable format', () => {
|
||||
updateContainerStatus(state, 'agent-1', { status: 'running' });
|
||||
updateContainerMetrics(state, 'agent-1', { cpuUsage: 0.5 });
|
||||
|
||||
const exported = exportContainerState(state);
|
||||
|
||||
expect(exported.config).toBeDefined();
|
||||
expect(exported.metrics).toBeDefined();
|
||||
expect(exported.status).toBeDefined();
|
||||
expect(exported.securityProfiles).toBeDefined();
|
||||
expect(exported.violationLog).toBeDefined();
|
||||
});
|
||||
|
||||
it('should convert dates to ISO strings', () => {
|
||||
updateContainerStatus(state, 'agent-1', { status: 'running' });
|
||||
const exported = exportContainerState(state);
|
||||
const statusData = exported.status as Array<Record<string, unknown>>;
|
||||
expect(typeof statusData[0]?.lastHealthCheck).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('importContainerState', () => {
|
||||
it('should import previously exported state', () => {
|
||||
const state = createContainerState();
|
||||
registerDefaultSecurityProfiles(state);
|
||||
updateContainerStatus(state, 'agent-1', { status: 'running' });
|
||||
recordViolation(state, 'agent-1', 'resource', 'low', 'Test');
|
||||
|
||||
const exported = exportContainerState(state);
|
||||
const imported = importContainerState(exported);
|
||||
|
||||
expect(imported.config.cpuLimit).toBe(1.0);
|
||||
expect(imported.status.has('agent-1')).toBe(true);
|
||||
expect(imported.violationLog.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should restore dates from ISO strings', () => {
|
||||
const state = createContainerState();
|
||||
updateContainerStatus(state, 'agent-1', { status: 'running' });
|
||||
const exported = exportContainerState(state);
|
||||
|
||||
const imported = importContainerState(exported);
|
||||
const status = imported.status.get('agent-1');
|
||||
|
||||
expect(status?.lastHealthCheck).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration Tests', () => {
|
||||
let state: ContainerIsolationState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createContainerState();
|
||||
registerDefaultSecurityProfiles(state);
|
||||
});
|
||||
|
||||
it('should handle full container lifecycle', () => {
|
||||
// Register and start agent
|
||||
updateContainerStatus(state, 'agent-1', {
|
||||
status: 'running',
|
||||
running: true,
|
||||
healthy: true,
|
||||
});
|
||||
|
||||
// Update metrics during execution
|
||||
updateContainerMetrics(state, 'agent-1', {
|
||||
cpuUsage: 0.5,
|
||||
memoryUsage: 256,
|
||||
processCount: 3,
|
||||
});
|
||||
|
||||
// Check resource allocations
|
||||
const cpuCheck = checkCpuAllocation(state, 'agent-1', 0.3);
|
||||
expect(cpuCheck.allowed).toBe(true);
|
||||
|
||||
const memoryCheck = checkMemoryAllocation(state, 'agent-1', 100);
|
||||
expect(memoryCheck.allowed).toBe(true);
|
||||
|
||||
// Record a violation
|
||||
recordViolation(state, 'agent-1', 'resource', 'medium', 'Test violation');
|
||||
|
||||
// Generate report
|
||||
const report = generateContainerReport(state);
|
||||
expect(report.summary.totalAgents).toBe(1);
|
||||
expect(report.violations.totalViolations).toBe(1);
|
||||
});
|
||||
|
||||
it('should restrict agent after repeated security violations', () => {
|
||||
updateContainerStatus(state, 'agent-1', {
|
||||
status: 'running',
|
||||
running: true,
|
||||
healthy: true,
|
||||
});
|
||||
|
||||
// First two violations - agent still running
|
||||
recordViolation(state, 'agent-1', 'security', 'high', 'Violation 1');
|
||||
recordViolation(state, 'agent-1', 'security', 'high', 'Violation 2');
|
||||
let status = getContainerStatus(state, 'agent-1');
|
||||
expect(status?.status).toBe('running');
|
||||
expect(status?.securityViolations).toBe(2);
|
||||
|
||||
// Third violation - agent restricted
|
||||
recordViolation(state, 'agent-1', 'security', 'high', 'Violation 3');
|
||||
status = getContainerStatus(state, 'agent-1');
|
||||
expect(status?.status).toBe('restricted');
|
||||
expect(status?.securityViolations).toBe(3);
|
||||
});
|
||||
|
||||
it('should track multiple agents independently', () => {
|
||||
// Set up three agents with different states
|
||||
updateContainerStatus(state, 'good-agent', { status: 'running', healthy: true });
|
||||
updateContainerStatus(state, 'busy-agent', { status: 'running', healthy: true });
|
||||
updateContainerStatus(state, 'bad-agent', { status: 'running', healthy: true });
|
||||
|
||||
updateContainerMetrics(state, 'good-agent', { cpuUsage: 0.2, memoryUsage: 100 });
|
||||
updateContainerMetrics(state, 'busy-agent', { cpuUsage: 0.9, memoryUsage: 450 });
|
||||
updateContainerMetrics(state, 'bad-agent', { cpuUsage: 0.1, memoryUsage: 50 });
|
||||
|
||||
// Record violations for bad agent
|
||||
recordViolation(state, 'bad-agent', 'security', 'critical', 'Malicious activity');
|
||||
|
||||
// Check each agent's state
|
||||
const goodStatus = getContainerStatus(state, 'good-agent');
|
||||
const busyStatus = getContainerStatus(state, 'busy-agent');
|
||||
const badStatus = getContainerStatus(state, 'bad-agent');
|
||||
|
||||
expect(goodStatus?.healthy).toBe(true);
|
||||
expect(busyStatus?.healthy).toBe(true);
|
||||
expect(badStatus?.securityViolations).toBe(1);
|
||||
|
||||
// Resource checks
|
||||
expect(checkCpuAllocation(state, 'good-agent', 0.5).allowed).toBe(true);
|
||||
expect(checkCpuAllocation(state, 'busy-agent', 0.2).allowed).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user