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:
John Doe
2026-04-03 23:42:06 -04:00
parent 2b375942c7
commit 2f1cd507e4
2 changed files with 1292 additions and 0 deletions
@@ -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);
});
});
});