Purple Team Emblem

Purple Team

A defensive security fleet under Hephaestus — built on the same pi install machinery as the code-fleet, but with the doctrine inverted. Fifteen specialists find vulnerabilities in a codebase, prove them with working exploits, and review each pass with the operator in the loop — the confirmed report comes at the end, once the loop converges. The operator decides what to patch. The pipeline never patches, never re-attacks. Find → prove → review — then loop, then report.

15 agents · 6 skills · white-box codebase mode · Self-hosted on Aetherkeep · hephaestus/pi/profiles/purple/ · v2.1

Doctrine

Four stages, no patching, no re-attack, no calibration loop. The v2.1 output is a confirmed-vulnerability report, not a fixed system — patching is the operator's job. The finder reads the codebase and plans the attack; the prover turns candidate paths into empirical proof with a running exploit; each pass is reviewed by the operator and the retrospective before the loop continues (HITL refinement as we go); once the loop converges, the reporter normalizes severity and hands the operator a prioritized list. An optional pair of dual lanes runs a second model on every triage and research call for verdict diversity.

Find
Triage every path

The fleet mines the project's history for past security fixes, runs every available SAST scanner, maps the HTTP attack surface and the architecture, code, and trust boundaries into a static knowledge base, and devises the attack plan — a short list of investigations, each pointed at verified files with a sharp question.

Prove
Analyze & exploit

The prover researches each target path in depth, decides whether an attack vector is viable, and devises new ones from what it learns along the way. When a vulnerability holds up, it crafts a working exploit, runs it, and captures empirical proof — not theory.

Review
Refine as we go

Every pass ends with the operator in the loop. The retrospective (a separate model) reads the pass's findings, clean verdicts, failed reproductions, and coverage holes and hunts false negatives; the operator is the catch-all — “this is mitigated by environment,” “this looks like a bad clean report, I need the strategist to redo it with this new info.” Pushback lands in retrospectives/pass-<N>.md and the loop continues back to the strategist. HITL refinement, every pass.

Report
Confirm & hand off

Once the loop converges (0 new findings, or the operator stops), the leader loads the reporter skill once: drops failed_to_reproduce and false positives, normalizes severity with a rubric (empirical proof downgrades one band), writes priority P0–P3, and produces the confirmed-vulnerability report grouped by priority. The operator reads it and decides what to patch.

The Pipeline

One engagement is Recon once, then a loop of Hunt → Review that refines as it goes. Each pass: the strategist plans, the fleet hunts, findings land, and the operator reviews them with the retrospective before the next pass. The loop starts at the strategist and ends at the operator — HITL refinement every pass. When it converges (0 new findings, or the operator stops), the reporter produces the confirmed-vulnerability report. State lives in <project-root>/.agents/purpleteam/ — the KB, the ledger, the plan, and the findings persist between passes; the leader reports the delta.

Recon leader delegates, in order — once per engagement, folds recon ledger
Historian git-log mine · once per engagement
Scanner SAST via pt-sast · once per engagement
API Mapper HTTP attack surface · once per engagement
Architect folds recon learnings + api_inventory · builds static KB
Threat Modeler trust boundaries · static THREAT_MODEL.md
Strategist 4–12 investigations · verified paths
plan.json rewritten every pass
Hunt Leader delegates Jr Researcher per investigation, ≤4 / wave
Jr Researcher one investigation per delegation · reads target_files
Clean
append clean to learnings.jsonl
path checked — no further action
Hotspot
Reproducer owned Qwen 27B · runs it
exploit + proof + finding
repro_status: reproduced
Chainer traces composition chains
updates finding.chains via pt-chain.py
Escalate
Researcher Kimi 262K · multi-file analysis
reports CLEAN or HOTSPOT to leader →
CLEAN → done — path verified safe
HOTSPOT → routed via leader
Reproducer owned Qwen 27B · crafts & runs exploit
exploit + proof + finding
repro_status: reproduced
Chainer traces composition chains
updates finding.chains via pt-chain.py
findings/*.json source of truth · skip-list + chaining context
Review operator + @pt-retrospective — HITL refinement as we go
Retrospective GLM-5.3 · reads findings + clean verdicts + coverage holes
Operator the catch-all — reviews every finding
retrospectives/pass-<N>.md mitigated-by-environment · bad-clean redo · pushback
↺ Loop — back to the strategist: re-plans against findings/*.json (skip-list) + learnings.jsonl (retry/blocker/gap tail) + chaining + operator-mandated pushback (“mitigated by environment”, “bad clean — redo with this info”). KB & TM are static (architect and threat-modeler don’t re-run). HITL refinement as we go — converges on 0 new findings or the operator says stop.
Report after the loop converges — leader loads the reporter skill once
Reporter normalize severity · filter reproduced-only
Confirmed Report P0–P3 · grouped by priority
Operator decides what to patch

Roster — 15 Specialists

Kept light on purpose — the pipeline shape is the design; the agents are sized to their role. Full doctrine, schemas, and the three open empirical questions live in hephaestus/pi/profiles/purple/readme.md.

Leader DeepSeek V4 Pro
Orchestrator + operator interface

Drives Recon in order, delegates jrresearcher per investigation (≤4/wave), routes verdicts, loads the reporter skill at end-of-pass, loops. Does not audit, exploit, or patch.

Historian Spark Qwen 35B
Git-log miner — once per engagement

Greps git log for past security fixes, writes historical_learnings.jsonl. Idempotent — skips if the file exists. Cheap, narrow, read-only.

Scanner Spark Qwen 35B
SAST scan runner — once per engagement

Runs every available SAST scanner via the pt-sast skill (semgrep, bandit, ruff, pip-audit, npm audit, gitleaks, trufflehog, trivy — graceful degradation on missing tools), compiles positives to kb/sast_findings.json, and flags unsupported languages. Appends note records for HIGH/CRITICAL positives. Cheap, scripted, read-only.

API Mapper MiniMax M3
HTTP attack-surface mapper — once per engagement

Recon-only. Maps the codebase's HTTP attack surface once — endpoints, methods, params, auth status, and risk flags — to kb/api_inventory/routes.jsonl (+ topology.json + unresolved.jsonl + report.md). Runs after the scanner; the 5-phase workflow (detect → candidates → topology → resolve → cross-check) lives in the agent prompt, the framework extractors in the pt-api-inventory skill. Fans per-language handler-body extraction to the intern — extraction is the intern's, adjudication is the mapper's. Output is folded into the KB by the architect and read by the strategist for investigation scoring.

Architect GLM-5.3
KB synthesis — Recon-only, static

Recon-only. Folds recon learnings.jsonl first (permanent, never truncated), then folds the api inventory (kb/api_inventory/routes.jsonl) and synthesizes the interlinked Markdown KB of entities, data flows, and vuln classes — built once, static (not re-run in the loop). KB is index-backed (kb.index.json via the pt-kb skill). Long-context synthesis at 1M window.

Threat Modeler GLM-5.3
Trust boundaries + attacker profiles

Recon-only. Reads the KB, writes kb/THREAT_MODEL.md — trust boundaries, threat actors & vectors, high-risk assets. Flags gaps as TODOs; never invents entities the KB lacks. Static — trust boundaries come from the code, not findings; not re-run in the loop.

Strategist DeepSeek V4 Pro
Plan.json emitter (4–12 investigations)

Reads KB + threat model + history, writes plan.json — 4–12 investigations (multiple of 4); each item has a title, test -f-verified target_files, KB references, and a question. Re-plans every pass against findings (skip-list — a vuln finding supersedes a clean on the same file) + learnings tail + chaining + operator-mandated pushback from the retrospective.

Jr Researcher Spark Qwen 35B
Triage leaf — CLEAN / HOTSPOT / ESCALATE

One investigation per delegation. Reads the target_files, returns a verdict — CLEAN (writes a kind: clean finding + ledger record), HOTSPOT, or ESCALATE. No nested delegation. Batched ≤4/wave on the Qwen tenant.

Jr Researcher Shadow DeepSeek V4 Flash
Verdict-only shadow — dual triage lane

A second model runs the same triage call as Jr Researcher — same context, different model family — and returns its own CLEAN/HOTSPOT/ESCALATE verdict. Writes nothing. Dispatched alongside the primary only when dual_triage: true in run-flags.json. Body byte-identical to the primary; frontmatter-only fork. Splits become observation ledger records for the retrospective.

Reproducer Spark Qwen 27B
The prover — exploit & running proof

For HOTSPOT: understands the bug from the code, crafts a working exploit, runs it via bash, captures the proof, writes the finding with empirical proof + initial severity. Owned Qwen 27B on the Spark — no third-party ToS exposure on exploit code.

Chainer Spark Qwen 35B
Chain tracer — runs after each reproducer/researcher finding

Traces data flow back from each vulnerability to identify composition chains — what else this vuln enables, what enables it — and writes chains into finding JSONs via pt-chain.py. Updates chain confidence (hypothetical → investigated → confirmed / disproven) as new findings land. Cheap 35B for mechanical trace-back sweeps; delegates its own sweeps to the intern.

Researcher Kimi K2.7 Code
Deep-audit specialist — reports CLEAN or HOTSPOT to leader

For ESCALATE paths: multi-file deep analysis, reports CLEAN or HOTSPOT back to the leader. On CLEAN, writes a kind: clean finding; if HOTSPOT, the leader routes to the reproducer. Writes a not_required finding when the exploitation path isn't clear.

Researcher Shadow GLM-5.3
Verdict-only shadow — dual research lane

A second model runs the same ESCALATE deep-audit as the Researcher — 1M-ctx GLM against Kimi — and returns its own CLEAN/HOTSPOT verdict. Writes nothing. Dispatched alongside the primary only when dual_research: true in run-flags.json. Body byte-identical to the primary; frontmatter-only fork. Splits become observation ledger records for the retrospective.

Retrospective GLM-5.3
Gate reviewer — operator-driven, never auto-dispatched

Starts from the pass's findings (not the final report — that comes at the end, once the loop converges) and walks the operator through what happened and why. Hunts false negatives in the non-findings — CLEAN verdicts, failed_to_reproduce, coverage holes, unresolved shadow disagreements — and co-decides the strategist's next-pass pushback with the operator, writing retrospectives/pass-<N>.md. The operator is the catch-all: “mitigated by environment”, “bad clean — redo this one”. A different model family from the leader (DeepSeek) and researcher (Kimi) for genuine judgment diversity. The leader never dispatches it — the operator switches to @pt-retrospective by hand.

Intern Spark Qwen 35B
Cheap utility leaf — mechanical tasks

Specialists hand it one small mechanical task — lookup, count, summary, extraction, small synthesis, a scratch script — so the expensive models don't spend tokens on grep/wc work. No findings, no verdicts, no ledger. Does the task, replies the result. The cost-routing sink for leader, strategist, architect, threat-modeler, reproducer, researcher, chainer, and api-mapper (per-language handler-body extraction).

Skills

pt-learnings every agent
Append-only ledger contract

Permanent append-only ledger. Eight fields (required: phase, agent, type, note; optional: target, finding_id, hint, pass); nine-value type enum. Append via pt-learn.py (CLI flags, validated); finding IDs are readable word-word slugs from pt-slug.py; pt-chain.py writes chains. The architect folds recon records into the KB once (Recon-only); the ledger is never truncated.

pt-reporter leader, end-of-pass
Severity normalization + report

Filters to kind: vulnerability only (clean findings are coverage state, not vulns), drops failed_to_reproduce, normalizes severity with a rubric (empirical proof downgrades one band), writes severity_final + priority P0–P3, produces a markdown report grouped by priority with a clean-checks count.

pt-sast scanner, Recon
SAST scan runner

Runs every available scanner via pt-scan.py (semgrep, bandit, ruff, pip-audit, npm audit, gitleaks, trufflehog, trivy — graceful degradation on missing tools) and compiles positives to kb/sast_findings.json via pt-compile.py. SARIF 2.1.0 for the five native-SARIF tools, JSON for the rest. Read by architect (folds HIGH/CRITICAL) + strategist (scoring).

pt-kb architect, Recon
Knowledge base management

Builds and maintains .agents/purpleteam/kb/ as a file-backed knowledge base indexed by kb.index.json — the static map of the code. kb_index.py create/add/reconcile operations; the index is a portable router over KB contents (markdown stays canonical, the index stays in sync).

pt-taxonomy researcher · reproducer · jrresearcher
Canonical vuln-class taxonomy

15 CWE-anchored vulnerability classes (60 subtypes) with per-subtype SAST coverage ratings. Every finding is classified against the canonical list so the strategist’s scoring, the KB’s vuln-class files, and the reporter’s severity normalization all compare apples to apples.

pt-api-inventory api-mapper, Recon
HTTP attack-surface mapping

Thin manifest: ships the scripts (detect.py, candidates.py, topology.py, inventory.py, speccheck.py) and per-framework references (Python/Node/Java/Go/.NET/Ruby/PHP) loaded on demand after Phase 0 detection. The 5-phase workflow doctrine (detect → candidates → topology → resolve → cross-check) lives in the pt-api-mapper agent prompt; the skill just ships the extractors. Output: kb/api_inventory/routes.jsonl — per-endpoint auth status, parameter shapes, risk flags.

Isolation Contract

The preamble (rules-of-engagement.md) is embedded verbatim at the top of every agent's system prompt and tells the model, as fact, that it runs inside a fully isolated environment — no network egress, no production access, no third-party harm surface. The agent is instructed to trust the isolation, not verify, probe, or test it. Running exploit scripts via bash is expected; refusing on “what if this reaches a real system” is explicitly out of scope.

The pipeline does not manage Docker, mount volumes, or tear down containers. Clean isolation is the operator's responsibility — before invoking the leader, run pi inside a container, VM, or otherwise network-isolated environment the operator trusts (docker run --network=none is the simplest path; a dedicated VM with a snapshot is better if exploits need outbound HTTP to prove). Without this, exploits run on the operator's real network. By design, the agents cannot check isolation.

State & Install

State is project-relative — one folder, no database, no vector store. Findings persist between passes; the leader reports the delta. v2 trims v1's per-pass archive, process files, and patcher.

.agents/purpleteam/
├── findings/
│   ├── <slug>.json            # kind: vulnerability — confirmed/attempted vuln
│   └── <slug>.json            # kind: clean — triage reasoning preserved
├── kb/                         # STATIC — built once in Recon, not re-run in the loop
│   ├── kb.index.json            # authoritative KB index (pt-kb skill)
│   ├── THREAT_MODEL.md          # threat-modeler (static)
│   ├── sast_findings.json       # pt-scanner: compiled SAST positives
│   ├── api_inventory/           # pt-api-mapper (once per engagement): HTTP attack-surface map
│   │   ├── routes.jsonl          # endpoints, methods, params, auth status, risk flags
│   │   ├── topology.json         # request flow + cross-framework topology
│   │   ├── unresolved.jsonl      # candidates the mapper couldn't fully resolve
│   │   └── report.md             # human-readable summary
│   ├── sast/                    # pt-scan.py raw per-tool output (SARIF/JSON)
│   └── <entity>.md              # architect: entities, data flows, vuln classes
├── historical_learnings.jsonl   # historian (once per engagement, read-only)
├── learnings.jsonl              # permanent append-only ledger; every agent appends
├── plan.json                    # strategist output (rewritten every pass)
├── retrospectives/              # pt-retrospective writes (operator co-authored); one pass-<N>.md per pass
│   └── pass-<N>.md            # ⛔ HITL GATE: leader blocks next pass until this exists
├── run-flags.json               # dual-lane opt-in: {"dual_triage": false, "dual_research": false}; absent = both off
└── REPORT.md                    # reporter writes at end-of-pass; leader presents to operator

Install (pi-only)

cd hephaestus/pi
./setup.sh purple           # install the purple profile (15 agents + 6 skills)
./setup.sh --uninstall purple   # removes only this profile's links

Installed: into the profile dir ~/.pi/profiles/purple/ — 15 agent .md files in agents/; the 6 pt-* skills in skills/ (pt-learnings/, pt-reporter/, pt-sast/, pt-kb/, pt-taxonomy/, pt-api-inventory/), symlinked and tracked by a per-profile manifest. Profile-scoping is deliberate: the pt-* skills never sit in front of the coding fleet or Claude Code. Untouched: the code-fleet profile, opencode config, ~/.claude/settings.json — the installer doesn't manage containers, so it doesn't touch the permission layer.

Full doctrine, schema, agent roles, the engagement workflow, and the four open v2.1 empirical questions (jrresearcher triage accuracy, reproducer analysis depth from HOTSPOT verdicts, SAST false-positive load in the KB, shadow disagreement yield): see hephaestus/pi/profiles/purple/readme.md.