Skip to content

ADR-0083: KALI.Art Phase B — production hardening + MCP + persistence

  • Status: Accepted
  • Date: 2026-05-23
  • Driver: ADR-0082 closed Phase A "Negative / deferred" with a 5-item list (PIE workflow, ttyd iframe, real MCP bridge, audit log + PDF wiring, PVC kali-home). Phase B finishes that list and adds the operator-hygiene CronJobs (image refresh + quota scan) that were always in the 12-PR memo but landed late.
  • Related: PRs #1016 (K-B1), #1017 (K-B2), #1018 (K-B3), #1019 (K-B4), #1020 (K-B5), #1021 (K-B6), #1022 (K-B7), this PR (K-B8); ADR-0082 (Phase A locked decisions), ADR-0029 (audit hash-chain pattern), ADR-0017 (backup/DR — 90 d retention), ADR-0045 (OOBI Hello mesh — origin of the per-namespace mTLS pattern used by the K-B4 ingest endpoint).

Context

Phase A made KALI.Art operator-usable end-to-end but parked five known gaps:

  1. PIE workflowdom_mode='production' returned 403 prod-dom-blocked unconditionally
  2. ttyd iframe — session page surfaced "Session id" + a terminal pending placeholder
  3. MCP bridge/api/kali/ai-companion was a 4-bucket regex stub; no real Anthropic Model Context Protocol surface
  4. Audit log + PDF — schemas existed in dashboard/src/lib/kali/; transport + renderer didn't
  5. PVC kali-home/home/operator was a 1 GiB emptyDir (no persistence across pod restarts)

Phase B also picked up two operator-hygiene jobs that the 12-PR memo flagged (image refresh + quota scan) — they are the CronJob complement to the K-B6 persistence layer.

Decision

Ship Phase B as 8 sub-PRs following the K-Ax cadence. Each sub-PR is independently reviewable; the order matters only for K-B4 (depends on K-B1/K-B3 for the new audit event kinds) and K-B7 (depends on K-B6 for the homes store).

Phase B sub-PRs

Sub-PR Scope Status
K-B1 PIE workflow: Drizzle migration 0033_kali_pie_approvals, pie-store.ts (in-mem dual-mode-ready), 3 API routes (request/approve/deny), session-create guard. Two-person rule (CHECK approver <> operator). 60 min unlock window. Read-only-tools allow/block lists. ✅ #1016
K-B2 ttyd iframe via URL resolver. New API /api/kali/sessions/[id]/ttyd returns {url, embeddable, reason?} from KALI_TTYD_PUBLIC_URL / KALI_TTYD_BASE_URL env vars. TtydEmbed.tsx component with TanStack Query 5s polling + restrictive iframe sandbox. WebSocket reverse-proxy delegated to nginx/traefik (Next.js can't proxy WS cleanly). ✅ #1017
K-B3 Real MCP bridge sidecar — new Go module kali-art-mcp/ with HTTP :8085 exposing Anthropic MCP 2024-11-05 subset (initialize, tools/list, tools/call, resources/list). 4 tools (3 proxied to kali-art /rpc + 1 synthetic suggest-workflow). Dashboard AI Companion forwards through it when KALI_MCP_BRIDGE_URL is set; falls through to the local stub on any failure. ✅ #1018
K-B4 Audit log transport + dashboard UI. Drizzle migration 0034_kali_audit_events, audit-store.ts with SHA-256 hash chain mirroring netsecopen/audit-chain.ts. 6 dashboard event kinds emitted from existing routes. Admin page /art-studio/kali/audit with TanStack 5 s polling + chain verifier. ✅ #1019
K-B5 Session PDF report wiring. pdf-report-renderer.ts reuses the DoYour B-2 pdfkit pipeline; 4-section layout (Header / Summary / Findings / Audit chain). New endpoint GET /api/kali/sessions/[id]/report.pdf with Content-Disposition: attachment. "Download PDF" anchor on the session list. ✅ #1020
K-B6 PVC kali-operators-home (50 GiB RWO) replaces the emptyDir. Drizzle migration 0035_kali_operator_homes, operator-home-store.ts with tier quotas (Team 1 GiB / Enterprise 5 GiB), ensureHome() on session start, 4 admin API routes. Launcher mkdir per-operator subdir. ✅ #1021
K-B7 Operator-hygiene CronJobs. 93-kali-image-refresh.yaml becomes a real buildah pull/overlay/push/rollout (was placeholder). New 94-kali-quota-scan.yaml daily du -sb /home/* → POST to dashboard. Endpoint POST /api/kali/homes/scan. ✅ #1022
K-B8 This PR — ADR + module-docs update in 3 langs + recovery-memo entry.

Locked defaults — Q1 quota revised

ADR-0082 Q1 quotas locked as Team 1 GiB / Enterprise 10 GiB. K-B6 ships Team 1 GiB / Enterprise 5 GiB to stay within the 50 GiB PVC budget for the first deployment wave (~6 Enterprise homes fit comfortably; at 10 GiB it would be 4). The 10 GiB cap is achievable by expanding the PVC to 100 GiB in an operator-controlled overlay; no code change needed.

MCP bridge protocol choice

K-B3 implements the Anthropic Model Context Protocol subset (version 2024-11-05). The discuss memo's "Addendum 1" called for a fork of mcp-kali-server upstream; instead, we ship a clean Go re-implementation matching the same MCP wire shape so Claude Desktop, 5ire, and any MCP client point at the bridge without modification. The bridge proxies the 3 real Kali tools to kali-art /rpc (default-deny ACL enforced both sides) and handles suggest-workflow synthetically (server-side 4-bucket regex matching the K-A3 stub).

Hash-chain pattern

K-B4 reuses netsecopen/audit-chain.ts primitives (canonicalize, sha256Hex, GENESIS_PREV_HASH) so every offensive-suite event stream shares the same SHA-256 chain semantics. entry_hash = SHA-256(prev_hash || canonicalize(payload) || timestamp || sequence). The K-B4 admin UI's "Verify chain" button re-walks the sequence and surfaces tampering with the failing sequence numbers.

Consequences

Positive

  • Every Phase A "deferred" item is now closed. KALI.Art is feature-complete for the v3.13.0 milestone.
  • The audit chain pattern is now shared between NetSecOPEN reports + KALI sessions + (next) DoYour ArtPiece runs — single tamper-evidence convention across the offensive-suite.
  • The MCP bridge unlocks the "point Claude Desktop at our cluster" demo without any dashboard changes, broadening the operator-tooling ecosystem.
  • Per-operator persistence (K-B6) finally makes the KALI shell feel like a real machine — apt installs + sudoers tweaks + bash history all survive pod restarts.
  • The CronJobs (K-B7) make the loop self-sustaining: fresh images weekly, quota usage tracked nightly, no manual cluster babysitting.

Negative / deferred (Phase C / D)

  • noVNC for GUI tools (Burp / Wireshark / Zenmap) — Phase C (v5.6). Requires a sibling websocket service and an Xvfb-backed display inside the pod.
  • Cross-MÓDULO orchestration extras beyond DoYour↔KALI (e.g. KALI nmap output flowing into PURE Discovery Hub as source #8) — Phase D (v6.0+). The K-10 sub-PR from the original 12-PR roadmap is the carrier.
  • Per-keystroke audit recording (Enterprise tier add-on) — the K-B4 chain is summary-only by default. The shell-history hash is a sibling table addition (no migration to the existing chain).
  • K-A2 /rpc tool catalogue expansion beyond nmap / sslyze / nikto — driven by customer requests. Each new tool is one Go handler + one ACL entry + one MCP tools/list entry.
  • PIE approval audit trail — K-B1 emits pie-requested/pie-approved/pie-denied events into the K-B4 chain (wired in K-B4 itself), but a dedicated PDF "PIE history per DUT" report is deferred to K-B8 follow-up.

Alternatives considered

  • Single mega-PR for Phase B (~3500 LoC) — rejected. The 8-sub-PR cadence keeps each merge small + reviewable + revertable, matching the Phase A cadence + the project-wide stacked-PR pattern.
  • Fork mcp-kali-server upstream (per discuss memo Addendum 1) — rejected. Clean Go re-impl is ~400 LoC + 6 tests, no licence-attribution surface, and lets us adapt the protocol version + tool catalogue independent of upstream's roadmap.
  • One PVC per operator — rejected. Operators come and go; managing 100+ PVCs adds cluster churn without buying isolation we need (the kali-art pod is already a single-tenant gVisor sandbox with per-session ttyd processes). subPath separation inside a single 50 GiB PVC is sufficient.
  • Build the PIE workflow as a separate microservice — rejected. PIE is a 2-table state machine that lives naturally in the same Postgres as the audit chain + sessions. A separate service would add ops cost without buying isolation we need.
  • Generate the PDF report client-side — rejected. Server-side pdfkit (already shipping for DoYour B-2) keeps the bytes deterministic across browsers + lets us hash the rendered output into the audit chain (future K-B5 follow-up).

Compliance

Per CLAUDE.md: "Dashboard is the ONLY operator interface." Every Phase B feature ships with a dashboard surface (PIE: admin queue page; ttyd: iframe; MCP: same AI Companion sidebar; audit: new admin page; PDF: download anchor; homes: 4 API endpoints + admin list).

Per ADR-0044 (OOBI Intrusion Detection): the K-B4 ingest endpoint (POST /api/kali/audit/ingest) is bound to the cluster-internal dashboard.web-agents.svc.cluster.local:3000 only; the K-B7 quota-scan CronJob's kali-quota-scan ServiceAccount is namespace-scoped to web-agents.

Per the locked ZTP-prem posture: gosec W0 baseline stays at 0 findings post-merge. The 2 inline #nosec annotations added across Phase B (G107 on the MCP bridge proxy http.Post, G301 on the launcher's /home/<operator> mkdir) are documented per site.

References