- Layer 4 EntropyScanner: Shannon entropy, Base32/Base64 detection, CVE-2025-55284 ping/nslookup exfil, EchoLeak markdown pattern, DNS tunneling (iodine/dnscat) - Layer 5 UnicodeScanner: ASCII Smuggling (U+E0000 Tags Block), Variant Selectors, Zero-Width steganography, CamoLeak image-ordering (CVE-2025-53773), homoglyphs, BiDi override, high-entropy URL params - 30 DNS covert channel rules (dns-001 to dns-030) - ATLASMapper: 29 techniques (ATLAS v5.4.0 Feb 2026), added AML.T0062 (Agent Tool Invocation), AML.TA0015 (C2 tactic), memory poisoning, multi-agent trust, CamoLeak, Unicode steganography mappings - Rule count: 72 → 102 - Build: tsup 316ms, zero TypeScript errors
159 lines
5.9 KiB
TypeScript
159 lines
5.9 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { HealingOrchestrator } from '../../../src/healing/HealingOrchestrator.js'
|
|
import type { KillChainPhase, ThreatLevel, HealingAction } from '../../../src/types/detection.js'
|
|
|
|
describe('HealingOrchestrator', () => {
|
|
let orchestrator: HealingOrchestrator
|
|
|
|
beforeEach(() => {
|
|
orchestrator = new HealingOrchestrator()
|
|
})
|
|
|
|
describe('determineAction()', () => {
|
|
describe('default action matrix', () => {
|
|
it('should return "allow" for no threat on any phase', () => {
|
|
const phases: KillChainPhase[] = [
|
|
'none', 'initial_access', 'privilege_escalation',
|
|
'reconnaissance', 'persistence', 'command_and_control',
|
|
'lateral_movement', 'actions_on_objective',
|
|
]
|
|
for (const phase of phases) {
|
|
const action = orchestrator.determineAction('none', phase)
|
|
expect(action).toBe('allow')
|
|
}
|
|
})
|
|
|
|
it('should return "sanitize" for initial_access + medium threat', () => {
|
|
const action = orchestrator.determineAction('medium', 'initial_access')
|
|
expect(action).toBe('sanitize')
|
|
})
|
|
|
|
it('should return "block" for initial_access + critical threat', () => {
|
|
const action = orchestrator.determineAction('critical', 'initial_access')
|
|
expect(action).toBe('block')
|
|
})
|
|
|
|
it('should return "block" for privilege_escalation + high threat', () => {
|
|
const action = orchestrator.determineAction('high', 'privilege_escalation')
|
|
expect(action).toBe('block')
|
|
})
|
|
|
|
it('should return "reset" for persistence + medium threat', () => {
|
|
const action = orchestrator.determineAction('medium', 'persistence')
|
|
expect(action).toBe('reset')
|
|
})
|
|
|
|
it('should return "reset" for persistence + critical threat', () => {
|
|
const action = orchestrator.determineAction('critical', 'persistence')
|
|
expect(action).toBe('reset')
|
|
})
|
|
|
|
it('should return "incident" for command_and_control + high threat', () => {
|
|
const action = orchestrator.determineAction('high', 'command_and_control')
|
|
expect(action).toBe('incident')
|
|
})
|
|
|
|
it('should return "incident" for lateral_movement + medium threat', () => {
|
|
const action = orchestrator.determineAction('medium', 'lateral_movement')
|
|
expect(action).toBe('incident')
|
|
})
|
|
|
|
it('should return "incident" for actions_on_objective + critical threat', () => {
|
|
const action = orchestrator.determineAction('critical', 'actions_on_objective')
|
|
expect(action).toBe('incident')
|
|
})
|
|
|
|
it('should return "warn" for low threats on most phases', () => {
|
|
expect(orchestrator.determineAction('low', 'initial_access')).toBe('warn')
|
|
expect(orchestrator.determineAction('low', 'privilege_escalation')).toBe('warn')
|
|
expect(orchestrator.determineAction('low', 'reconnaissance')).toBe('warn')
|
|
expect(orchestrator.determineAction('low', 'persistence')).toBe('warn')
|
|
})
|
|
|
|
it('should return "block" for low command_and_control', () => {
|
|
expect(orchestrator.determineAction('low', 'command_and_control')).toBe('block')
|
|
})
|
|
})
|
|
|
|
describe('phase 1 = sanitize (initial_access)', () => {
|
|
it('should default to sanitize for initial_access medium', () => {
|
|
expect(orchestrator.determineAction('medium', 'initial_access')).toBe('sanitize')
|
|
})
|
|
})
|
|
|
|
describe('phase 7 = incident (actions_on_objective)', () => {
|
|
it('should escalate to incident for actions_on_objective high/critical', () => {
|
|
expect(orchestrator.determineAction('high', 'actions_on_objective')).toBe('incident')
|
|
expect(orchestrator.determineAction('critical', 'actions_on_objective')).toBe('incident')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('custom phase strategies override defaults', () => {
|
|
it('should use custom strategy when provided', () => {
|
|
const customOrchestrator = new HealingOrchestrator({
|
|
healing: {
|
|
phaseStrategies: {
|
|
initial_access: 'block',
|
|
},
|
|
},
|
|
})
|
|
const action = customOrchestrator.determineAction('low', 'initial_access')
|
|
expect(action).toBe('block')
|
|
})
|
|
|
|
it('should fall back to default when custom strategy not defined for phase', () => {
|
|
const customOrchestrator = new HealingOrchestrator({
|
|
healing: {
|
|
phaseStrategies: {
|
|
initial_access: 'block',
|
|
},
|
|
},
|
|
})
|
|
// persistence has no custom override
|
|
const action = customOrchestrator.determineAction('high', 'persistence')
|
|
expect(action).toBe('reset')
|
|
})
|
|
})
|
|
|
|
describe('session reset trigger for persistence phase', () => {
|
|
it('should return reset for persistence phase at medium+', () => {
|
|
expect(orchestrator.determineAction('medium', 'persistence')).toBe('reset')
|
|
expect(orchestrator.determineAction('high', 'persistence')).toBe('reset')
|
|
expect(orchestrator.determineAction('critical', 'persistence')).toBe('reset')
|
|
})
|
|
})
|
|
|
|
describe('incident reporting for critical phases', () => {
|
|
const criticalPhases: KillChainPhase[] = [
|
|
'command_and_control',
|
|
'lateral_movement',
|
|
'actions_on_objective',
|
|
]
|
|
|
|
for (const phase of criticalPhases) {
|
|
it(`should trigger incident for ${phase} at high threat`, () => {
|
|
expect(orchestrator.determineAction('high', phase)).toBe('incident')
|
|
})
|
|
|
|
it(`should trigger incident for ${phase} at critical threat`, () => {
|
|
expect(orchestrator.determineAction('critical', phase)).toBe('incident')
|
|
})
|
|
}
|
|
})
|
|
|
|
describe('getSessionManager()', () => {
|
|
it('should return a SessionManager instance', () => {
|
|
const sm = orchestrator.getSessionManager()
|
|
expect(sm).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('getIncidentReporter()', () => {
|
|
it('should return an IncidentReporter instance', () => {
|
|
const ir = orchestrator.getIncidentReporter()
|
|
expect(ir).toBeDefined()
|
|
})
|
|
})
|
|
})
|