Machine-speed containment reasoning. Human-approved action.
A public, portfolio-grade demonstration of human-approved agentic AI containment during a cyber incident β built around the Incident Commander.
Simulated containment actions only. No production systems are modified.
Containment Command Lab demonstrates how agentic AI can support Incident Commanders during one of the hardest moments in cyber response: deciding what to contain, when to contain it, who must approve it, what evidence may be lost, and what business operations may be disrupted.
It runs entirely on mock data with a deterministic, key-free AI core, so it works the moment
you npm install β no API keys, no external services, no execution surface. On top of the workflow
sits a bounded eight-agent reasoning layer that triages, maps blast radius, weighs business
impact and proposes a containment strategy β and still cannot move without a recorded human approval
chain. The agents advise; people approve; a ledger attests.
This project is part of the Cyber Resilience in the Age of AI body of work by RedCon1 Response.
In a live incident, containment is rarely a purely technical decision. The Incident Commander is making a high-stakes call under time pressure and incomplete information, balancing five questions at once:
- What do we contain? Isolate the wrong asset and you blind the investigation; too little and the threat keeps moving.
- When do we act? Every minute of deliberation can be spread β but acting on a bad read is worse.
- Who must approve? A targeted action and a business-halting one demand very different authority.
- What evidence is lost? Containing too early can destroy volatile memory, sessions and logs β and the attribution that lives in them.
- What business breaks? Containment that stops payroll, production or a board-critical wire has a cost the security team cannot own alone.
AI is genuinely useful here β but only if it compresses the reasoning, never the accountability. That thesis is what this lab makes tangible.
| Capability | What it shows |
|---|---|
| AI-assisted triage | Facts, assumptions and unknowns separated and scored β deterministically, no API keys. |
| Bounded agentic layer | Eight cooperating agents reason over the incident and propose a strategy. They advise; humans approve. |
| Blast-radius mapping | Interactive dependency graph of identities, assets and services, with suspected lateral movement flagged. |
| Containment options | Minimal / Targeted / Aggressive / Staged, each with explicit security, business, evidence and recovery trade-offs. |
| Human-approved routing | Approval chains from IR Lead to Executive; a code-level guardrail blocks even simulated execution until approvals exist. |
| Audit-ready ledger | Every drafted, approved and simulated action appended to a hash-chained containment ledger. |
| Five seed scenarios | Endpoint, cloud identity, ransomware, OAuth-consent and BEC β each a full, switchable mock incident. |
Shipped as design-accurate SVG placeholders generated from the app's own design tokens (
scripts/gen-screenshots.py). To capture real PNGs locally, runnpm run screenshots(see How to run all validation checks).
| Overview (landing) | Incident Commander |
|---|---|
| Agentic Analysis | Blast Radius Map |
|---|---|
| Containment Ledger | |
|---|---|
- Pick a scenario on the landing page (or via the top-bar switcher) β e.g. Finance endpoint compromise, racing a payroll deadline.
- Incident Commander (
/commander) gives the single operating picture: severity, blast radius, the business clock, decisions in flight. Click Run Agentic Analysis. - Agentic Analysis (
/agents) shows all eight agents' findings, recommendations, warnings, confidence and human-review flags β advisory only. - Triage (
/triage) separates known facts, assumptions and unknowns, and lists the evidence to preserve before any disruptive action. - Blast Radius (
/blast-radius) maps how the threat could propagate, with suspected paths dashed. - Containment (
/containment) presents four scored options plus the agent-proposed strategy and the decision authority required. - Approvals (
/approvals) routes a chosen option through its approval chain. The guardrail refuses simulated execution until every required approval is recorded. - Brief (
/brief) generates a leadership-ready situation report from live state. - Ledger (
/ledger) shows the append-only, hash-chained record of everything that happened.
Action lifecycle: Recommended β Drafted β Awaiting Approval β Approved β Simulated Executed, with
Rejected and Rolled Back as terminal branches.
A fully static Next.js app with no backend, no connectors and no execution surface. All logic is client-side and deterministic.
flowchart TB
subgraph Client["Browser β static Next.js 15 app (no backend)"]
UI["UI screens<br/>Commander Β· Triage Β· Blast Radius Β· Containment Β· Approvals Β· Brief Β· Ledger"]
Store["Zustand store (persisted)<br/>workflow Β· ledger Β· agent runs Β· selected scenario"]
subgraph Core["Deterministic core β no network, no API keys"]
Data["Incident data<br/>5 mock scenarios"]
Scoring["Scoring<br/>blast radius Β· recommendation"]
Agents["Agentic layer<br/>8 bounded agents"]
Guard["Execution guardrail<br/>canSimulateExecute()"]
end
end
UI --> Store
Store --> Data
Store --> Agents
Agents --> Scoring
Agents --> Data
Store --> Guard
Guard -. blocks until approved .-> Sim["Simulated execution<br/>(local state + ledger only)"]
Boundary["NOT PRESENT BY DESIGN:<br/>no EDR / SIEM / cloud / identity connectors Β· no execution surface Β· no exploit logic"]
src/
βββ app/ # Next.js App Router β one folder per screen
β βββ layout.tsx # Shell, self-hosted fonts, metadata
β βββ page.tsx # Overview / landing (+ scenario picker)
β βββ error.tsx # Route error boundary (on-brand)
β βββ not-found.tsx # 404 (on-brand)
β βββ intake/ commander/ agents/ triage/ blast-radius/
β βββ containment/ approvals/ brief/ ledger/ about/
β βββ globals.css
βββ components/
β βββ ui/ # Primitives (button, card, badge, stat)
β βββ layout/ # Sidebar, top bar (+ scenario switcher), safety banner, shell
β βββ domain/ # Containment cards, approval chain, ledger table, blast map,
β # agent output/run panels, scenario picker, badges
βββ lib/
βββ types.ts # Domain models + Zod intake schema
βββ mock-data.ts # Default incident + INCIDENTS registry + getIncident()
βββ seed-incidents.ts # The four additional scenarios
βββ scoring.ts # Blast-radius / recommendation / exposure scoring
βββ status.ts # Severity / status / tier / volatility tokens
βββ ai.ts # AiProvider interface + deterministic mock provider
βββ store.ts # Zustand store (persisted), keyed by scenario
βββ use-hydrated.ts # SSR-safe hydration gate
βββ utils.ts # cn(), demo hash chain, time helpers
βββ agents/ # Bounded agentic reasoning layer
βββ types.ts # AgentInput/Output/Finding/Recommendation/Run
βββ helpers.ts # Deterministic builders + advisory-safety guard
βββ provider.ts # LLM provider abstraction (disabled by default)
βββ guardrail.ts # canSimulateExecute() β the execution gate
βββ *-agent.ts # The eight agents
βββ index.ts # Registry, runAgents() orchestrator, selectors
βββ __tests__/ # Vitest unit tests (shape, determinism, safety)
Eight agents run in order, each consuming the outputs of those before it. The chain ends at a guardrail that defers to a human approval chain β nothing executes (even in simulation) without it.
flowchart LR
T[Triage] --> B[Blast Radius] --> E[Evidence] --> C[Containment Strategy]
C --> BI[Business Impact] --> DA[Decision Authority] --> BR[Briefing] --> G[Execution Guardrail]
G --> H{"Human approval chain<br/>IR Lead β Incident Commander β CISO β<br/>Business Owner β Legal β Executive"}
H -- all approvals recorded --> S["Simulated execution β hash-chained ledger"]
H -- approvals missing --> X["Blocked + logged by guardrail"]
Every agent returns the same shape: summary, findings[], recommendations[], warnings[],
confidence, requiredHumanReview, generatedAt. Findings are explicitly typed
fact / assumption / unknown / observation so an assumption never poses as a confirmed fact.
runAgents() is deterministic for a fixed clock; a deterministic timer can be injected in tests
while the UI keeps real per-agent timings.
All scenarios are typed Incident objects. The registry in src/lib/mock-data.ts exposes
INCIDENTS and getIncident(id); the four non-default scenarios live in src/lib/seed-incidents.ts.
interface Incident {
id: string;
scenario: string; // picker key, e.g. "cloud-identity"
scenarioLabel: string; // human label for cards/menus
title: string;
severity: "low" | "medium" | "high" | "critical";
detectedAt: string; // ISO
businessClock: { // the time-critical milestone the incident races
label: string; // e.g. "Payroll run", "Board meeting"
at: string; // ISO deadline (drives the live countdown)
note: string;
};
summary: string;
triage: { knownFacts: string[]; assumptions: string[]; unknowns: string[] };
identities: Identity[];
assets: Asset[];
services: BusinessService[];
evidence: EvidenceItem[]; // each flagged preserve-before-containment or not
options: ContainmentOption[];// minimal / targeted / aggressive / staged
graph: { nodes: GraphNode[]; edges: GraphEdge[] }; // blast-radius graph
}Each ContainmentOption is fully specified (security / business / evidence / recovery impact,
required approvals, rollback plan, confidence + riskOfDelay scores, and the simulatedSteps
appended to the ledger). Workflow state (ContainmentDecision, ApprovalStep, LedgerEntry) lives
in the Zustand store, persisted and keyed by the selected scenario.
| Containment Command Lab (this repo) | RecoverIQ Containment Command (proprietary) | |
|---|---|---|
| Data | Mock incidents only | Real telemetry via governed connectors |
| Integrations | None | Read-only EDR / SIEM / identity, policy-guardrailed |
| Execution | Simulated only (local state) | Governed, reversible playbooks with attestation |
| AI | Deterministic, key-free | Pluggable LLM behind the same interfaces |
| Purpose | Show the thinking, in the open | Operate it safely in production |
The lab is where the ideas are demonstrated working; RecoverIQ is the separate, commercial direction they point toward. Nothing proprietary ships in this repo.
This is a public demonstration lab, not a production IR platform, and not production IR advice. The safety properties are structural, not promises:
- No execution surface. There is no integration layer and no outbound action capability. The "containment engine" only appends descriptive, simulated steps to local state.
- Human-in-the-loop, enforced in code. A single guardrail (
canSimulateExecute) is consulted by both the UI and the store; even a simulated execution is refused until every required approval is recorded, and bypass attempts are written to the ledger. - No offensive capability or exploit logic anywhere in the codebase.
- Agents are advisory.
enforceAdvisorySafetyforces approval on every containment/guardrail recommendation and constrains recommendations to a fixed, benign set of kinds. Verified by tests. - Facts vs. assumptions vs. unknowns are kept explicitly separate.
- Illustrative ledger hash. The hash chain is a non-cryptographic demonstration of tamper-evidence, clearly labelled as such β not a real integrity control.
- No secrets, no network at runtime. Fonts are self-hosted; the deterministic AI needs no keys.
The full reasoning lives on the in-app About & Safety Model screen.
- Next.js 15.5.x (security line) β clears the high/critical advisories present on older 14.x.
- postcss pinned to the patched line via an
overridesentry (covers Next's nested copy). - Vitest 4 β clears the dev-only esbuild/vite advisory.
npm auditandnpm audit --omit=devboth report 0 vulnerabilities at time of writing.
Requires Node 18.18+ (Node 20+ recommended).
npm ci # clean, reproducible install from package-lock.json
npm run typecheck # tsc --noEmit
npm run lint # next lint (no warnings or errors)
npm run test # vitest run (agent shape, determinism, safety)
npm run build # production build β 14 static routes, no hang
npm run validate # all four of the above in sequence
npm audit --omit=dev # expect: found 0 vulnerabilities
npm run dev # http://localhost:3000
# Optional β capture real screenshots locally (needs the Playwright browser):
npm run build && npm run start &
npx playwright install chromium
npm run screenshots # writes public/screenshots/*.pngThe previous
no-page-custom-fontlint warning is gone: fonts are now self-hosted via@fontsourceinstead of a runtime Google Fonts<link>, which also removes a build-time network dependency. React Flow is loaded withssr: false, so static generation never blocks on it.Expected, harmless notices (no code warnings or errors):
next lintprints a notice that it is deprecated and will be removed in Next.js 16, andnpm cinotes thateslint@8is no longer supported. Both are unavoidable while pinning the matchingeslint-config-next@15(which targets ESLint 8); they do not affect the build, tests, or the zero-vulnerability audit, and the linter still reports no warnings or errors.
v0.1 β this lab
- Deterministic mock AI triage, blast-radius mapping and option scoring
- Bounded eight-agent reasoning layer (advisory, human-approved)
- Five seed scenarios spanning endpoint, identity, ransomware, OAuth and BEC
- Simulated approval routing, execution guardrail and a hash-chained ledger
Next β depth
- Pluggable LLM provider behind the existing provider interfaces
- Configurable approval policies and role-based views
- Richer scenario authoring and shareable incident snapshots
RecoverIQ Containment Command β proprietary
- Read-only connectors for EDR / SIEM / identity behind strict policy guardrails
- Governed, reversible containment playbooks with real approvals and full attestation
- Resilience metrics, post-incident learning loops and executive reporting
Next.js 15 (App Router) Β· React 18 Β· TypeScript (strict) Β· Tailwind CSS (custom design system) Β· Zustand (persisted) Β· @xyflow/react (React Flow, dynamically loaded) Β· Zod Β· lucide-react Β· self-hosted @fontsource (Sora / IBM Plex Sans / IBM Plex Mono) Β· Vitest.
Source-available for review and demonstration only. Β© RedCon1 Response. You may read, run and
learn from this code; it is not licensed for production use, redistribution as a product, or
commercial deployment, and it carries no warranty. The "RecoverIQ Containment Command" product
direction is proprietary and not part of this grant. See LICENSE.
Containment Command Lab is a public demonstration by RedCon1 Response. It uses mock data only, performs no real security actions, contains no offensive or exploit logic, and is not production incident-response advice. RecoverIQ Containment Command is a separate, proprietary product direction.