key = "security_hardening" title = "Security Hardening" blurb = "Scan the repo for vulnerabilities, research patches, then apply + verify." requires_repo = true default_team_template = "rust_sdlc" # ── What is real here, and what is decoration ──────────────────────── # # The scanners themselves are REAL: `gitleaks`, `trivy`, `semgrep` and # `cargo-audit` are installed in the runtime image and `runtime_preflight` # probes for all four at boot, naming the consequence when one is absent. An # agent in a security_scan phase can run them from Bash today. # # `tools` is REAL too: `security_scan::run` reads it to pick which scanners to # run and upserts each finding as a mission_task. What was missing was anything # that FIRED it — it was reachable only from an operator button, so this # workflow's scan phase never scanned. `phase_runner::scan_finished_security_phases` # now runs it when the phase finishes, guarded on a completion marker so a # clean repo is not rescanned forever. # # What remains decoration is annotated inline below. And the part that actually # mattered: no phase carried a `task` or a `done_when`. # A phase with no `done_when` never enters `evaluating`, is never judged, and # reports `completed` whatever it did. So this recipe could run all three # phases, scan nothing, patch nothing, and go green. That is the same defect # `research_and_code.toml` was fixed for, and it is why the tasks below name # the scanners explicitly rather than trusting `tools` to deliver them. [[phases]] kind = "security_scan" order_idx = 0 [phases.config] # READ by `security_scan::run` — this list gates which scanners the platform # runs against the checkout after the phase finishes. The agent ALSO runs them # itself during the phase (see the task): the platform pass is the independent # record, the agent pass is what lets it write a report about what it found. tools = ["cargo_audit", "gitleaks", "trivy_fs", "semgrep"] produces = ["md"] task = """ Scan this repository for security problems and write findings down. Run the scanners that are installed in your container — `cargo audit`, \ `gitleaks detect`, `trivy fs .` and `semgrep --config auto` — from the repo \ root. If one is missing or errors, say so explicitly in the report rather than \ omitting it: a section absent because a tool failed reads identically to a \ section absent because nothing was found, and those are opposite conclusions. Write SECURITY-FINDINGS.md with one entry per finding: the identifier \ (CVE / RUSTSEC / rule id), the file and line, what an attacker could actually \ do with it, and whether it is reachable in our code or sits in an unused \ dependency path. Rank by exploitability, not by the scanner's severity field. A clean scan is a real and useful result. Say which tools ran, on what, and \ that they found nothing — do not manufacture findings to fill the report. """ done_when = "SECURITY-FINDINGS.md exists and states, for each of the four scanners, whether it ran and what it found, with every reported finding carrying an identifier, a location, and a reachability judgement" max_iterations = 2 commit_policy = "always" [[phases]] kind = "research" order_idx = 1 [phases.config] # `pdf` dropped: PDF rendering was removed from the delivery path (artifacts are # served as Markdown), so asking for it produced a format nothing generates. produces = ["md"] default_topology = "hub_spoke" # NOTE: `input_from_phase` is INERT — DECLARED_BUT_UNREAD. Phases do not receive # a structured hand-off from a named predecessor; the next phase reads the # previous phase's committed FILES out of the shared checkout. That is why the # task names SECURITY-FINDINGS.md by path. input_from_phase = "security_scan" task = """ Turn the scan findings into a patch strategy. Read SECURITY-FINDINGS.md from the repo root — that is the previous phase's \ output, committed to this mission's branch. For each finding that is actually \ reachable, write into SECURITY-PLAN.md: the fix, the specific file and \ function it touches, what could break, and how the fix will be verified. Where the fix is a dependency bump, check what the new version changes — a \ major bump presented as "update the version" is how a security patch becomes \ an outage. Where a finding is not worth fixing, say so and say why; an \ unreachable advisory in a dev-dependency is a legitimate "no action". """ done_when = "SECURITY-PLAN.md exists and gives, for every reachable finding in SECURITY-FINDINGS.md, either a named fix with the file it touches and how it will be verified, or an explicit justification for taking no action" max_iterations = 2 commit_policy = "always" [[phases]] kind = "coding" order_idx = 2 [phases.config] # NOTE: `loop` is INERT — DECLARED_BUT_UNREAD ("phase iteration uses # max_iterations + done_when"). Kept so the intent stays visible beside the two # keys that actually drive the loop. loop = "until_all_findings_closed" max_iterations = 3 # Security requires reviewer approval on top of green tests. commit_policy = "on_reviewer_approval" # NOTE: `mcp_bundles` is INERT AT PHASE LEVEL — bundles come from the TEAM # template (`mission_orchestrator` binds `template.mcp_bundles`). This phase # gets nothing from this line. `gitea_forge` and `security_scan` were removed # from it entirely: neither is defined anywhere, and now that provision_claw # HONOURS the team template's bundle list, naming a bundle that does not exist # stopped being harmlessly inert. mcp_bundles = ["clawmates_door", "clawmates_skills"] task = """ Apply the patch strategy and prove it worked. Work through SECURITY-PLAN.md. Make the smallest change that closes each \ finding, and run the test suite after each one so a regression is attributable \ to a single fix rather than to the batch. Then RE-RUN the scanner that produced each finding and record the new output in \ SECURITY-FINDINGS.md under a "after remediation" heading. A fix that was not \ re-scanned is a claim, not a result — and this phase's whole value is the \ difference between those two. If a fix cannot be made safely, leave the finding open and say why. An open \ finding that is documented is worth more than a closed one that is not true. """ done_when = "every finding in SECURITY-PLAN.md marked for fixing is either closed with a re-run of the scanner that found it recorded in SECURITY-FINDINGS.md, or left open with a stated reason, and the test suite passes"