██████╗██╗ ██╗ █████╗ ██████╗ ███████╗ ██╔════╝██║ ██║██╔══██╗██╔═══██╗██╔════╝ ██║ ███████║███████║██║ ██║███████╗ ██║ ██╔══██║██╔══██║██║ ██║╚════██║ ╚██████╗██║ ██║██║ ██║╚██████╔╝███████║ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ██████╗ ██████╗ ███╗ ██╗████████╗██████╗ ██████╗ ██╗ ██╗ ███████╗██████╗ ██╔════╝██╔═══██╗████╗ ██║╚══██╔══╝██╔══██╗██╔═══██╗██║ ██║ ██╔════╝██╔══██╗ ██║ ██║ ██║██╔██╗ ██║ ██║ ██████╔╝██║ ██║██║ ██║ █████╗ ██████╔╝ ██║ ██║ ██║██║╚██╗██║ ██║ ██╔══██╗██║ ██║██║ ██║ ██╔══╝ ██╔══██╗ ╚██████╗╚██████╔╝██║ ╚████║ ██║ ██║ ██║╚██████╔╝███████╗███████╗███████╗██║ ██║ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝
🎯 A two-sided chaos engineering console. Break a distributed system from the Red Team panel, then watch the Blue Team's circuit breakers, token buckets and autoscalers fight back. Every failure is simulated in your browser — no cluster, no cloud bill, no blast radius.
🪫 Reliability is a skill you only get to practise during an outage — the worst possible time to be learning it. Chaos Controller hands you the outage on demand: kill the primary, partition the network, flood the ingress, then watch what the error budget does about it. Nothing real breaks, so you can break it as often as you like.
⚠️ Project status — early work-in-progress. This is an actively developed portfolio / learning project, not a finished product. Working today: the client-side simulation core — timed failure state machines, a real token-bucket rate limiter, circuit breaker, autoscaler, and SLO / cost model — plus an optional Encore.ts metrics backend. Not done yet: there are no frontend tests, several panels are staged with placeholder data, and a few controls are inert. The full, honest breakdown is in Status & limitations — please read it before judging completeness. But do explore — WIP is not off-limits. Try the client-only simulator in 60 seconds ↓
Try it • Repository map • Toolkit • Architecture • Quick Start • Development • Safety • Status • License
No backend, no cloud, no config — the simulator runs entirely in your browser. This is the fastest way to see where the project is headed.
git clone https://github.com/AkashVarma007/Chaos-Controller.git
cd Chaos-Controller && pnpm install && pnpm devOpen http://localhost:5173, then click "Full Chaos Dashboard" in the header. That view is the fully client-side simulator and needs no server. (The default Blue Team view expects the optional backend and shows a metrics error until you start one — that is expected, not a bug.)
Then poke at it:
- Fire a red attack — Traffic Flood or Kill Database. Latency and queue charts move within a second; the event log appends a timestamped line.
- Flip a blue mitigation — open the circuit breaker or tighten the rate limiter, and the next tick visibly reacts to it.
- Run a scenario — Traffic Flood + Autoscale plays a scripted incident end to end, then auto-stops.
That is the high-level direction. The toolkit breaks down every action, and Status is honest about what is real versus staged today.
Chaos-Controller/
├── index.html Vite entry document
├── src/ React frontend — 96 TS/TSX files, ~4.5k lines
│ ├── app/ entry point, root component, global stylesheet
│ ├── components/
│ │ ├── red/ 17 attack-side components
│ │ ├── blue/ 35 defence-side components (incl. charts/)
│ │ ├── common/ 3 shared metric/chart wrappers
│ │ └── ui/ 6 presentational primitives
│ ├── core/ ports + adapters, and framework re-exports
│ ├── store/ Zustand store, failure logic, scenarios (1,075 lines)
│ └── lib/ small pure helpers
├── server/ Encore.ts backend — 6 TS files, ~300 lines
│ ├── metrics/ in-memory aggregation + order ingestion
│ └── hello/ starter smoke-test endpoint
├── docs/ backend requirements + implementation checklist
└── .github/ CI workflow, PR and issue templates
| Path | What it is | Tracked? |
|---|---|---|
src/ |
The console itself. All chaos logic lives here. | ✅ |
server/ |
Optional Encore.ts backend. Feeds the Blue Team view real request metrics. Started separately — the root pnpm dev does not launch it. |
✅ |
docs/ |
Design docs for the backend. The checklist is a partly stale work log, not a spec — see Status. | ✅ |
node_modules/, server/node_modules/ |
Dependencies. | ❌ ignored |
dist/ |
Vite production output. | ❌ ignored |
server/.encore/, server/encore.gen/ |
Generated by the Encore CLI. | ❌ ignored |
.env |
Local config. Copy from the tracked .env.example. |
❌ ignored |
There are two views, toggled from the header, and they run on completely different data. This is the most important thing to understand about the project:
| View | Renders | Data source | Cadence |
|---|---|---|---|
| Blue Team Dashboard (default) | A single live system-overview card | Real HTTP metrics from the Encore backend | polls /metrics/blue/snapshot every 5 s |
| Full Chaos Dashboard | Everything else — every red and blue panel below | Client-side simulator (Zustand) | ticks every 1 s |
The simulator loop starts only while the Full Chaos Dashboard is mounted;
switching back tears the interval down (src/app/App.tsx). The Blue Team view is
deliberately thin — it is the slice that talks to a real server, and it shows a
fetch error until you start one.
Each is a logic* function in src/store/simulationLogic.ts, dispatched through
the store as a handle* action. They are timed state machines, not one-shot
toggles — most inject damage, hold it, then decay:
| Action | What it does, concretely |
|---|---|
| Flush cache | +120 ms latency now, +80 ms more at 2 s, cache rebuilt at 5 s |
| Terminate node | Marks the first active node dead, +40 ms latency; a replacement is scheduled at 4 s |
| Kill database | FAILING_OVER + DEGRADED; every 800 ms adds +30 ms latency and +8 queue depth; recovers at 6 s |
| Region outage | OFFLINE now, region flip at 2 s, healthy at 6 s |
| Traffic flood | Ramps every 300 ms for 4 s (queue → 500, latency → 600 ms), then drains for 5 s. Refuses to start unless the system is HEALTHY |
| Latency inject | +200 / +120 / +60 ms by severity; only ~70 % is reversed at 4 s, so damage lingers |
| Network partition | Drops 300 / 180 / 90 per 500 ms tick for 5 s |
| Error inject | +10 / +5 / +2 % error rate, reversed at 6 s |
| Disk saturation | +80 / +40 / +20 queue depth every 600 ms for 6 s, latency capped at 800 ms |
| Queue jam | +200 / +120 / +60 queue depth, 80 % reversed at 5 s |
Attacks are first-class records rather than fire-and-forget buttons: each carries
a status (RUNNING / PAUSED / COMPLETED / ABORTED), a rolling 120-point
series of sent / allowed / dropped, a peak-RPS marker, a geo heat map, and a cost
breakdown across compute, network and other. They can be paused, resumed,
aborted, snapshotted, and replayed into a fresh attack.
These controls write to the same store the simulator reads, so a mitigation genuinely changes what the charts do on the next tick.
| Control | Behaviour |
|---|---|
| Circuit breaker | CLOSED / OPEN / HALF_OPEN. Open suppresses the error rate to zero; half-open lets it climb back at +2 %/tick |
| Rate limiter | A real token bucket — refills perSecond / 2 per tick, capped at burst, drops the overflow. Adaptive mode trims the limit 5 % on drops and raises it 5 % when the queue sits below half its target |
| Autoscaling | desired vs actual replicas converging one step per tick against a target queue depth, with a scale-event log |
| Cache | TTL clamped to 30–3600 s, plus a warm-up that adds 10 points of hit ratio |
| Queue / DLQ | Pause consumers, change concurrency, poison messages in, reprocess, or purge |
| Region & DNS | Force a failover between us-east-1 and us-west-2; cycle routing policy latency → geo → weighted |
| Database | Flip the endpoint between read-write and read-only |
| Brownout | Four graded levels for shedding non-essential work |
| Feature flags | Percentage rollouts, persisted to localStorage |
| Deployments | Canary or blue-green rollout |
Everything is scored against an SLO (99.9 % availability by default) with a depleting error budget, and a cost model that recomputes estimated hourly spend from replica count, queue depth and latency every tick.
Steps fire at an atMs offset against the store; the run auto-stops at
durationMs.
| Scenario | Duration | Shape |
|---|---|---|
| Traffic Flood + Autoscale | 15 s | Flood at t=0, autoscaler responds |
| DB Failover | 12 s | Kill the primary at t=0 |
| Region Outage | 12 s | Full region loss at t=0 |
| Cache Stampede | 8 s | Cold cache at t=0 |
| Circuit Breaker + Brownout | 14 s | Open → brownout L2 at 5 s → half-open at 9 s → closed at 12 s → brownout off at 13 s |
| Rate Limiting + DLQ | 16 s | Clamp to 50/s at t=0 → 25 poison messages at 2 s → reprocess 15 at 10 s |
| Canary Rollout | 18 s | Start canary → 10 % at 4 s → 50 % at 8 s → 100 % at 12 s → finish at 16 s |
Presets can be duplicated, edited and deleted at runtime — built-ins are delete-protected — so you can compose your own game day. Custom scenarios live in memory only and do not survive a reload.
Three roles (viewer / operator / admin) gate destructive actions through a
hasPermission map: killing the database and triggering a region outage require
admin, terminating a node needs operator. Attack actions append to a
500-entry audit log with user, params, outcome and timestamp, and a session can
be exported as JSON with its full metric history.
Be precise about what this is: the permission check is enforced in the manual chaos grid only. The same actions remain reachable unchecked via the keyboard shortcuts, the detailed attack configurator, and scenario presets. It is an incident-response rehearsal surface, not access control — see Safety model.
┌──────────────────────────── Browser ─────────────────────────────┐
│ │
│ Header ──▶ view toggle │
│ │ │
│ ┌──────────┴───────────┐ │
│ ▼ ▼ │
│ BlueTeamDashboard MonitoringDashboard │
│ └─ SystemOverviewLite (Full Chaos — every other panel) │
│ │ the only component │ │
│ │ that calls the server ▼ │
│ │ ┌─────────────────────┐ │
│ │ │ Zustand store │◀── red/ panels │
│ │ │ simulationStore.ts │ dispatch handle* │
│ │ │ │ │
│ │ │ 1 s tick: │──▶ blue/ panels │
│ │ │ token bucket │ render 90-point │
│ │ │ autoscaling │ histories │
│ │ │ error rate │ │
│ │ │ cost model │ │
│ │ └─────────┬───────────┘ │
│ │ │ │
│ │ ▼ │
│ │ simulationLogic.ts ◀── scenarios.ts steps │
│ │ (timed failure state machines) │
│ │ │
│ │ useBlueSnapshot() — 5 s poll │
│ ▼ │
│ blueMetricsClient.ts ───────┐ │
└──────────────────────────────┼────────────────────────────────────┘
│ HTTP
▼
┌──────────────────── Encore.ts server (localhost:4000) ────────────┐
│ │
│ POST /ecom/orders ──┐ │
│ GET /ecom/health ──┴──▶ recordRequest() │
│ │ │
│ ▼ │
│ in-memory ring buffers │
│ 1000 events · 300 history points │
│ │ │
│ 5 s aggregation over a 60 s window │
│ │ │
│ ▼ │
│ GET /metrics/blue/snapshot ──▶ status · rps · error % · latency │
│ GET /metrics/blue/history ──▶ time series (no client yet) │
└────────────────────────────────────────────────────────────────────┘
Ports and adapters. src/core/ is organised as a seam: each concern is a
directory with a port.ts type, an adapters/ directory, and an index.ts that
picks the active implementation, so swapping one means changing a single
re-export.
Honestly scoped: four of these are load-bearing — state, time, storage
and rng are consumed throughout the store and components. The other five
(cache, config, flags, http, observability) are implemented but have
no call sites anywhere in the app. They are scaffolding for a composition
root that was never wired up. Notably, the one real HTTP call in the project
(blueMetricsClient.ts) uses fetch directly rather than the http port, and
event logging goes through the store's own addLog rather than the
observability port.
Requirements: Node.js 22+ and pnpm 10+. The backend is optional.
git clone https://github.com/AkashVarma007/Chaos-Controller.git
cd Chaos-Controller
pnpm install
pnpm dev✅ Verify — Vite prints ready in … and serves on http://localhost:5173.
The app opens on the Blue Team Dashboard, which will show a metrics error until you start the backend — that is expected. Click Full Chaos Dashboard to reach the simulator, which needs no server at all.
✅ Verify — on the Full Chaos Dashboard, trigger any red action. The event log should append a timestamped line immediately, and the latency and queue charts should move within a second.
Needs the Encore CLI — Encore's runtime
library cannot be started by plain node.
cp .env.example .env # VITE_METRICS_API_BASE=http://localhost:4000
cd server
npm install
encore run✅ Verify — the endpoint answers:
curl http://localhost:4000/ecom/health
# {"status":"ok"}Now generate traffic and watch the Blue Team Dashboard follow it:
for i in $(seq 1 20); do
curl -s -X POST http://localhost:4000/ecom/orders \
-H 'Content-Type: application/json' \
-d '{"userId":"u1","items":[{"productId":"p1","quantity":1}]}' > /dev/null
done
curl http://localhost:4000/metrics/blue/snapshot✅ Verify — totalRequests has climbed, and within one 5 s aggregation tick
currentRps and avgLatencyMs become non-zero. Send a deliberately invalid body
(omit items) a few times to push the error rate past 5 % and watch
systemStatus fall to DEGRADED.
Full endpoint reference: server/README.md.
| Command | What it does |
|---|---|
pnpm dev |
Vite dev server with HMR on port 5173 |
pnpm typecheck |
tsc --noEmit against strict TypeScript |
pnpm build |
Typecheck, then production bundle into dist/ |
pnpm preview |
Serve the built bundle |
cd server && npm run typecheck |
Typecheck the backend |
cd server && encore test |
Backend tests — requires the Encore CLI |
pnpm typecheck and pnpm build are the entire automated safety net, and CI runs
exactly those plus the backend typecheck. CONTRIBUTING.md has
the conventions and a walkthrough of adding a new failure mode.
Read this before drawing any conclusion from the word "attack".
- Nothing is actually attacked. Every red action mutates local Zustand state in your browser tab. No packets are sent, no processes spawned, no infrastructure touched. There is no load-generation code in this repository.
- The RBAC is a rehearsal prop. It is enforced in one panel and bypassable from three others, and the role lives in browser memory where devtools can change it in a line. Do not copy the pattern anywhere it guards something real.
- The backend has no authentication. All five endpoints are exposed with no
auth handler, and
/metrics/blue/*will hand aggregate traffic data to any caller that reaches it. It is a localhost development tool. VITE_-prefixed variables ship to the browser in plaintext, inlined at build time. Never put a credential in one.
The full model — local-storage handling, input validation, and how to report a vulnerability privately — is in SECURITY.md.
Verified on the current commit:
pnpm typecheck— clean, 0 errors understrict.pnpm build— succeeds. 2,868 modules, 718 kB JS / 25.6 kB CSS, ~4 s.cd server && npm run typecheck— clean.- Zero
TODO,FIXME, or@ts-ignoremarkers anywhere insrc/.
This is an actively work-in-progress portfolio and learning project. The honest state: the simulation core is solid, while parts of the surrounding UI are staged and there is no automated frontend test coverage yet. Known gaps, in rough order of how much they'd matter to you:
Testing and tooling
- No frontend tests and no linter. Typecheck and build are the only automated checks. This is the largest gap in the project.
- Backend coverage is one assertion on the starter
helloendpoint;metricsandecommercehave none.encore testalso could not be executed while this README was written — the Encore CLI was not installed on that machine — so the suite is unverified, not "passing". - The bundle is one 718 kB chunk. No code splitting; Vite warns every build.
Wiring gaps
GET /metrics/blue/historyhas no client. The endpoint works, butblueMetricsClient.tsimplements onlyfetchBlueSnapshot, so no chart consumes real history yet.BlueSnapshotLiteis declared twice — inserver/metrics/metrics.tsandsrc/core/blueMetricsClient.ts— and hand-synchronised. Change one, the other drifts silently.- Five of nine
core/ports have no consumers (cache,config,flags,http,observability), as described in Architecture. - Some state is never populated.
tracingSpansstays empty, so the tracing timeline and quick-look always render blank;dependencyEdgesandbulkheadsare set once and never mutated;complianceandconfigDriftDetectedare read by nothing.
Staged UI
- Several panels display placeholder maths rather than real statistics: the p95/p99 lines are derived as fixed multiples of the mean, the query-latency histogram is a hardcoded array, and per-node CPU sparklines are random jitter regenerated each render. Panels that do this generally say "simulated" on their own labels.
BlueAIEventNarratoris not AI. Despite the name it is rule-based string matching over the audit log — no model, no API call.- Some controls are inert: the replay transport buttons have no handlers, the
breaker's "trip threshold" input is never compared against anything, the
Real/Demo-onlymode selector is read by no logic, and "View trace" is permanently disabled becausetraceIdis never set. - Six components are unreachable — three are never imported
(
ChaosControls, the non-ProCircuitBreakerPanel,AutoMasonry) and three are imported but never rendered (the non-ProRateLimitPanel,BattlegroundCard,AttackConfigurator), each superseded by a "Pro" or detailed variant. - Two empty directories (
src/components/hud/,src/components/timeline/).
Build and docs
framer-motionis pinned via an override. 12.23.24 breaks against currentmotion-domreleases ("activeAnimations" is not exported), somotion-domandmotion-utilsare pinned inpackage.json. Removing the override breaks the build.- The backend persists nothing. Orders are validated, acknowledged and dropped; metrics live in module memory and reset on restart.
docs/ecommerce-metrics-implementation-checklist.mdis stale — steps are unchecked despite being partly built, and it specifies auseBlueMetricshook that shipped asuseBlueSnapshotwithout history support.- No CI badge above, because the workflow has not run yet. Add one once the first run is green.
Released under the MIT License © 2026 Akash Varma.
The server/ tree began as the Encore TypeScript starter template; its hello
service is what remains of it.
| 🤝 Contributing | CONTRIBUTING.md |
| 🔐 Security | SECURITY.md |
| 📜 Code of Conduct | CODE_OF_CONDUCT.md |
| 🖥️ Backend docs | server/README.md |
Break it here, so it doesn't break there.