Skip to content
Back to skills

Embed

ASecurity

Autonomously confirm, threat-model, root-cause, fix, security-review, and ship a reported vulnerability end to end — a passing review ends in a merged PR

  • 67 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 23, 2026
ai-agentsrustgobashgitsecurity

Works with

  • terminal
  • cli

Security analysis

A100/100

Pro scans all 21 files and shows the line behind each finding

Scanned October 2, 2026

npx -y skills add gethuman-sh/human --skill embed --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Embed?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Embed
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gethuman-sh-embed-789234d5/badge)](https://www.skillsdirectory.com/skills/gethuman-sh-embed-789234d5)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: human-security-fix
description: Autonomously confirm, threat-model, root-cause, fix, security-review, and ship a reported vulnerability end to end — a passing review ends in a merged PR
argument-hint: <security-ticket-key>
---

<!-- human:include launch-briefing -->

# Overview

Point this skill at a security ticket and it runs the full security-fix pipeline autonomously: **triage & threat-model → root-cause explanation on the ticket → verdict → (if a real vulnerability) plan → test-first fix on a branch → verify the exploit is closed → security review → (on a passing review) deploy: PR → CI gate → merge**. The whole trail is recorded on the tracker (comments + the plan — a separate engineering ticket in split topology, a `[human:plan]` comment on the security ticket itself otherwise), and every run that attempted a fix ends by posting a plain-language `[human:fix-summary]` comment on the ticket (Step 9).

This is the security counterpart to **human-autofix**: it shares the same pipeline, markers, and board plumbing (so the Bugs & Security view tracks a security fix exactly like a bug fix), but its triage builds a **threat model** and rates **severity**, its verify bar is "**the attack no longer works**", and its review adds a **security lens**. Where this document says the flow is "as in autofix", it is deliberately identical — only the security-specific stages differ.

The run does **not** end at the review handoff: exactly like the bug flow, the skill chains the fix into a review and, on a passing verdict, drives the same deploy pipeline (push → PR → CI gate → merge → close). A failing review or a red CI gate stops the run honestly with the handoff left standing for a human.

**Board-context exception**: when `<BOARD_CONTEXT>` is true (launched with `--board`; `HUMAN_AGENT_NAME` starting with `board-` is a fallback signal), this skill runs *as a board stage agent*. The container holds no push/PR credentials and the Deploy button owns shipping, so **end after the review (Step 7.3) and skip Step 8 (deploy) entirely** — and the run **ends** there: post the Step 9 summary, record `stage.implementation`, and terminate (see 'The end of the run'). The review runs **inline, in this warm container** (Steps 7.2–7.3) — one container startup, not two. Do NOT push, open, or merge a PR in board context.

This skill runs **without user interaction**. Do NOT use `AskUserQuestion` at any step — reach a verdict and act on it. Every run ends in exactly one verdict: **confirmed**, **not-a-bug** (not a real vulnerability), or **undetermined**.

**Handle exploit detail discreetly**: the commit log is public. Keep reproduction and exploit specifics on the tracker (comments), never in commit messages — a fix commit references the ticket and describes the hardening, not how to attack an unpatched install.

Follow these steps in order.

## Retry budgets and flakes

A stage that fails does **not** end the run on the first failure. Before charging an attempt, establish the failure is real:

1. Re-run the failing test or check **alone**. If it passes in isolation it is a **flake**: record it and do not charge the attempt.
2. Only a failure that reproduces identically twice counts as real.
3. Charge a real failure against the stage's budget, then retry until the budget is spent.

The budget is **3 real attempts per stage**, tracked in agent state so it survives a stage handoff or a container restart:

```bash
human state incr <SEC_KEY> budget.<stage>.flakes     # a failure that vanished in isolation
human state incr <SEC_KEY> budget.<stage>.attempts   # a failure that reproduced
human state get  <SEC_KEY> budget.<stage>.attempts --default 0
```

An infrastructure failure — a dead container, a network blip, a runner that never started — is never a real attempt. It is a `retryable` exit (see the exit contract below); say so instead of burning the budget on it.

Exhausting the budget is an honest `needs-human-work` ending, not a silent stop: post the terminal marker the step calls for and report what the three attempts each tried and why each failed.

## Recording the board stage outcome

In board context this run **is** the implementation stage, and the daemon reads that stage's exit from agent state key `stage.implementation` (the board stage name it looks up — distinct from the per-phase `stage.triage`/`stage.verify`/`stage.review` records this skill already writes). A run that stops without writing it hands the daemon nothing but the generic "agent finished without posting the stage handoff" diagnosis, and the retry loop charges on blind.

So: **a board-context run must never exit without writing `stage.implementation`.** At every point where the run STOPS in board context — the budget-spent stop (Step 6), a `DECISION REQUIRED` options post (Step 1a), an unreviewable, decision-required, or twice-failed review (Step 7.3), and the clean no-fix-needed terminal (Step 3a) — record the outcome in the stage's own terms *before* returning, alongside the marker that stop already posts:

```bash
human state set <SEC_KEY> stage.implementation --json --body-file - <<'EOF'
{"exit":"needs-human-work",
 "summary":"one line in the stage's own terms — e.g. verify budget spent after 3 real attempts; vulnerability still reachable: <…>",
 "evidence":"the marker just posted (e.g. [human:implementation-failed]) and the state keys that back it",
 "blocker":{"kind":"exhausted-fix-rounds",
   "evidence":"what still reproduces, verbatim — same text as the marker's evidence field",
   "attempted":"what each of the real attempts tried",
   "release":"what a person needs to resolve before a re-dispatch could pass verify"},
 "unchecked":"<dependent kinds this run could not determine, and why — empty if none>",
 "next":"what a human must decide or do"}
EOF
```

Use the exit vocabulary the board understands (`internal/daemon/board_retry.go`): `retryable`, `outage`, `needs-input`, `needs-human-work`, `done`. A clean resolved terminal (no-fix-needed, Step 3a) records `{"exit":"done", ...}` alongside its `[human:no-fix-needed]` marker; a spent budget records `needs-human-work` with the `blocker` object shown above (per the exit contract); an interrupted-substrate stop records `retryable`/`outage` and carries no `blocker`. This record is additive — it does not replace the phase records. Keep exploit specifics out of the `summary`/`evidence`/`blocker.evidence` when the ticket is publicly visible.

<!-- human:include dependents -->

<!-- human:include exit-contract -->

<!-- human:include model-tiers -->

The tiers this pipeline uses, unless you have a reason to differ: root-cause triage and every adversarial check at `fable`; planner, reviewer and preflight at `opus`; bug-fixer and security-verify at `sonnet`. Nothing in this pipeline inherits — a dispatch that names no model runs on whatever the account defaults to, which is a tier nobody chose.

## Step 1 — Parse argument

`$ARGUMENTS` is the security ticket key — the PM ticket — optionally followed by `--board`. Take the first non-flag token as `<SEC_KEY>`. Resolve the ticket with `human get <SEC_KEY>` — the CLI auto-detects the owning tracker from the key shape; `human tracker list` only enumerates trackers and must not be used to guess a key's owner. Call the tracker `<tracker>`.

Then take ownership: `human assign <SEC_KEY>`. Ownership records who is working the ticket; it sets the owner only, never the status, so it cannot block on an approval gate. In board context the daemon already took ownership at launch and this is a harmless no-op. A failure here never stops the run — note it and continue.

### Step 1a — Preflight (ask once, up front, or not at all)

Before any work, run preflight. It resolves what this run may do, settles what the evidence can settle, and surfaces a decision only a human can make **now** rather than halfway through:

```
Task(subagent_type="human-preflight", model="opus", prompt="Preflight security ticket <SEC_KEY> before an autonomous fix run: resolve capabilities, mirror decisions already made. There is no scope fork on a security ticket: the defect is fixed at its root cause and every Expected item is in scope; the only DECISION REQUIRED is a person's own open work on this ticket (step 6a).", run_in_background=false)
```

Read its outcome from state:

```bash
human state get <SEC_KEY> stage.preflight --field ready       # yes | no
human state get <SEC_KEY> stage.preflight --field question    # the fork, when ready is "no"
```

- **ready: yes** — the capability set and any prior decisions are recorded; continue to Step 2.
- **ready: no** — preflight returned a `DECISION REQUIRED:` terminal. Surface it as the **existing** up-front decision block and STOP; do not triage, plan, or fix into an unmade decision:

  ```bash
  human marker post <SEC_KEY> options \
    --field stage=implementation \
    --field context="<the DECISION REQUIRED one-liner>" \
    --field 1="<first option>" \
    --field 2="<second option>"
  ```

  Pass through every `waits-for-<id>:` line preflight emitted, as a field of the same name (`--field waits-for-1=<KEY>`). That line is what makes an answer meaning *"<KEY> goes first"* hold this ticket instead of starting it — dropping it turns the answer into its opposite.

  The board renders this as "Decision needed" and the card waits without being mistaken for a crash. When the human picks, the daemon records `[human:option-chosen]` and relaunches this run with the choice in hand; preflight then mirrors it into `decisions` and returns `ready: yes`. An answer that declared a wait is the exception: the daemon records it and starts nothing, and the relaunch comes later, once the ticket it deferred to is done. Do **not** invent a `needs-input` marker — this loop already exists. In board context, alongside the options post, record the stage outcome (`stage.implementation`, exit `needs-input`, per "Recording the board stage outcome") so the daemon reads the awaited decision rather than the generic diagnose line.

The capability set is the single source of truth for the rest of the run — do not infer permissions from flags or env vars:

```json
{"board_context": true, "can_push": false, "can_open_pr": false, "owns_deploy": false, "workspace": "bind-mounted"}
```

**The rule is one line: attempt nothing the capability set forbids, and treat a missing capability as a boundary, never as a failure.**

Set `<BOARD_CONTEXT>` to the set's `board_context`. (`--board` in `$ARGUMENTS` is the daemon's explicit signal and still forces it true.) In board context the container holds no push/PR credentials and the daemon's Deploy stage owns push → PR → CI → merge on the host: the run stops before deploy, having run the review inline.

### Step 1b — Resume from the furthest recorded step

This run may be a **recovery relaunch** of one that was interrupted mid-pipeline — a container that died, a daemon that restarted. A resumed run picks up where the account on the ticket stops; it does not start over, and a step that is already recorded is not paid for again. Read the ticket's markers once, then resume from the furthest recorded step:

```bash
human plan show <SEC_KEY>        # non-empty -> a [human:plan] comment exists; the plan is done
# a [human:bug-verdict] comment present -> triage is done
```

- **A `[human:bug-verdict]` comment already exists → skip Step 2 (triage).** The cause is recorded. Read the verdict, severity and root cause from `stage.triage` state (`human state get <SEC_KEY> stage.triage --field verdict`); if that state record is missing but the marker is present, read them from the marker body (`human marker show <SEC_KEY> bug-verdict`). Go straight to the verdict gate (Step 3). Do **not** re-run triage.
- **A `[human:plan]` comment already exists → skip Step 4 (plan).** The plan is recorded; proceed to Step 5 (fix) using it. Re-planning is only for a genuinely absent plan.
- **A `[human:ready-for-review]` handoff already exists → the fix is BUILT.** `human handoff show <SEC_KEY>` names the branch and the commits. Do not re-triage, do not re-plan, and above all do not rebuild it: go to Step 7's review, or — if the review is recorded too — to the Step 8 deploy. A pull request that will not merge, or a review that stopped without a verdict, is a fault in the **shipping** of that work, not in the work: the deploy gate rebases and dispatches a deploy-fix round for a content conflict, and the PR review loop re-drives an unfinished review. Re-implementing a fix cures neither, and it throws away a reviewed branch to get back to where it already was.
- **A missing later step is this run's own work, never a blocker.** This pipeline writes its own plan: **never** post `[human:needs-planning]` and **never** ask a human to run planning. If the plan is absent and triage confirmed the vulnerability, produce the plan yourself (Step 4) and continue.

## Step 2 — Phase 1: Triage & threat-model (verdict)

Delegate to the **human-security-triage** agent:

```
Task(subagent_type="human-security-triage", model="fable", prompt="Triage security ticket <SEC_KEY>: confirm whether the reported weakness is a real, exploitable vulnerability by tracing the source→sink data flow with file:line evidence, build the threat model (attacker, vector, asset, impact), rate severity, scan for sibling occurrences of the same weakness, and reach a verdict. Post the verdict comment on the ticket with a plain-language Explanation a non-engineer can follow. Keep exploit specifics on the tracker.", run_in_background=false)
```

It posts a `[human:bug-verdict] <verdict>` comment on the ticket — the permanent record: a plain-language explanation, the threat model, the severity, the source→sink cause chain, the regression window, and sibling occurrences. **Read the verdict from state, not from the agent's prose:**

```bash
human state get <SEC_KEY> stage.triage --field verdict     # confirmed | not-a-bug | undetermined
human state get <SEC_KEY> stage.triage --field severity    # critical | high | medium | low
human state get <SEC_KEY> stage.triage --field root_cause
```

The agent records `stage.triage` before returning (per the exit contract). Its message is for a human reader; the state record is what you branch on. If `stage.triage` is missing, the stage did not complete: treat that as `retryable` and re-dispatch rather than guessing a verdict from the text — **at most twice**. A record still missing after two re-dispatches is a broken state store (most often a daemon that predates `human state`); stop then with `needs-human-work`, naming state as the suspect:

```bash
human state incr <SEC_KEY> budget.triage.missing              # count this miss
human state get  <SEC_KEY> budget.triage.missing --default 0  # at 3, stop
```

If the recorded analysis stops at a proximate cause (a reachable sink without *why* the guard is missing or bypassable), re-dispatch the triage agent once, telling it which link is unproven — do not carry a shallow root cause into the plan.

## Step 3 — Verdict gate

- **confirmed** — continue to Step 4.
- **not-a-bug** or **undetermined** — do NOT act on the verdict yet. A no-fix verdict on a security report is the one outcome that can silently leave a live vulnerability open, so it must first survive an adversarial challenge (Step 3a).

### Step 3a — Adversarial challenge (not-a-bug / undetermined only)

Dispatch the skeptic against the verdict, with a security lens:

```
Task(subagent_type="human-verdict-skeptic", model="fable", prompt="Challenge the latest bug-verdict on security ticket <SEC_KEY>. Lens: try to reach the sink. Attempt to prove the path IS exploitable — an unsanitized source, an auth check that can be bypassed, a guard that does not cover the payload — before the vulnerability is dismissed.", run_in_background=false)
```

Read its outcome from state:

```bash
human state get <SEC_KEY> stage.challenge --field challenge   # upheld | refuted
```

- **UPHELD** — the verdict stands; act on it:
  - **not-a-bug** — make **no code changes**. Post the terminal marker FIRST: `human marker post <SEC_KEY> no-fix-needed --field verdict=not-a-bug --field challenge=upheld`. Only once it is on the ticket, close it with `human close <SEC_KEY>` (closed-type status, falling back to done-type). Then Report and STOP.
  - **undetermined** — make **no code changes**. Leave the ticket open for a human. Post `human marker post <SEC_KEY> no-fix-needed --field verdict=undetermined --field challenge=upheld`, then Report and STOP.
- **REFUTED** — the vulnerability is real after all. Post the skeptic's evidence as a confirmed verdict:

  ```bash
  human marker post <SEC_KEY> bug-verdict --head confirmed --body-file - <<'EOF'
  ## Verdict overturned on adversarial challenge
  <the skeptic's refutation: the reachable path, the bypassable guard, or the crafted payload that succeeds>
  EOF
  ```

  Then **continue to Step 4 as a confirmed vulnerability**, using the skeptic's exploit as the reproduction. The challenge runs ONCE — a refuted verdict never loops back through triage.

**The record comes before the close, always.** The marker is what the board reads to put the card on the resolved column and what a person re-opens from; the closed status is bookkeeping after it. Closing first and posting second means a refused or failed post leaves a closed ticket with no trace of why — three runs, twenty-five minutes and no result on LOC-34. If the `human close` fails after the marker is posted, the record already stands: say so in your report and STOP. Never fall back to a raw provider status command (`human local issue status …`, `human <tracker> issue status …`) — those are gated and prompt a person for a card that is already resolved.

The `[human:no-fix-needed]` marker is **mandatory in board context**: the pipeline runs under the board implementation-stage agent name, whose failure watcher treats any exit with no `[human:ready-for-review]` handoff as a crash and would loop forever re-triaging. This terminal marker signals the clean, resolved stop. Alongside it, record the stage outcome (`stage.implementation`, exit `done`, per "Recording the board stage outcome") so the daemon reads a resolved terminal rather than the generic diagnose line.

## Step 4 — Phase 2: Plan (topology decides where it lives)

1. Resolve the topology with `human tracker topology` — `{"topology":"single"|"split","pm":{...},"engineering":{...}}`.
   - **Split topology** — note `<ENG_TRACKER>` and `<ENG_PROJECT>`. The plan becomes a separate engineering ticket.
   - **Single-tracker topology** — the plan becomes a `[human:plan]` comment on the security ticket itself; no second ticket.
2. Delegate to the **human-planner** agent, seeding it with the triage threat model and root cause:

```
Task(subagent_type="human-planner", model="opus", prompt="Create an implementation plan to fix security ticket <SEC_KEY>. Decisions already settled (do not re-open): <paste `human state get <SEC_KEY> decisions --default '{}'`>. The threat model and source→sink root cause from triage:\n<paste the triage threat model + root cause + fix outline>\nThe plan's Changes section MUST begin with adding a SECURITY regression test that demonstrates the exploit and fails because of the vulnerability, then closing the root cause at the source (parameterize/encode/guard/tighten the check — not merely suppress the symptom). Address the sibling occurrences from triage or state them out of scope. Return the plan as output; do not write files or create tickets.", run_in_background=false)
```

Capture the output as `<PLAN_CONTENT>`. Ensure its header has a `**PM ticket**: <SEC_KEY>` line and, in split topology, an `**Engineering ticket**: TBD` line.

3. Attach the plan.

**Split topology** — create the engineering ticket:

```bash
human <ENG_TRACKER> issue create --project=<ENG_PROJECT> "Fix: <short vulnerability summary>" --description "$(cat <<'PLAN_EOF'
<PLAN_CONTENT>
PLAN_EOF
)"
```

Capture `<ENG_KEY>`, update its description so the `**Engineering ticket**:` line reads `<ENG_KEY>`. Set `<WORK_KEY>` to `<ENG_KEY>`.

**Single-tracker topology** — post the plan as a `[human:plan]` comment:

```bash
human marker post <SEC_KEY> plan --body-file - <<'PLAN_EOF'
<PLAN_CONTENT>
PLAN_EOF
```

Verify with `human plan show <SEC_KEY>`. Commits reference only `<SEC_KEY>`. Set `<WORK_KEY>` to `<SEC_KEY>`.

## Step 5 — Phase 3: Test-first fix

Delegate to the **human-bug-fixer** agent (shared with autofix — it writes the failing regression test and the root-cause fix; the security framing comes from the plan it reads). When `<BOARD_CONTEXT>` is true the fixer must NOT push — forward the board instruction explicitly (the fixer cannot see `$ARGUMENTS`):

```
Task(subagent_type="human-bug-fixer", model="sonnet", prompt="Fix ticket <WORK_KEY> (PM security ticket <SEC_KEY>) test-first on a feature branch, following the plan. The plan's first change is a SECURITY regression test that demonstrates the exploit and must fail before the fix. BOARD CONTEXT: do NOT run git push — leave the branch local; the daemon's Deploy stage ships it. Report the local branch name. Iterate on the fast test+lint tier (not the full `make check`) to go green — the verify gate runs the single full suite. Keep exploit specifics out of commit messages — reference the ticket and describe the hardening.", run_in_background=false)
```

Otherwise (standalone, `<BOARD_CONTEXT>` false) dispatch the push variant:

```
Task(subagent_type="human-bug-fixer", model="sonnet", prompt="Fix ticket <WORK_KEY> (PM security ticket <SEC_KEY>) test-first on a feature branch and push it, following the plan (its first change is a security regression test that demonstrates the exploit and fails before the fix). Iterate on the fast test+lint tier to go green — the verify gate runs the single full suite. Keep exploit specifics out of commit messages.", run_in_background=false)
```

It creates branch `autofix/<work-key>` (the key lowercased), writes the security regression test, implements the root-cause fix, confirms the suite is green, commits with subjects starting with the `human commits prefix <SEC_KEY> [<ENG_KEY>]` prefix, and returns the branch name. In a standalone run it pushes; in board context it leaves the branch local. If it reports it could not go green, STOP and report — do not open a PR.

## Step 6 — Phase 4: Verify (done gate — the attack no longer works)

Delegate to the **human-security-verify** agent:

```
Task(subagent_type="human-security-verify", model="sonnet", prompt="Verify ticket <WORK_KEY> (PM security ticket <SEC_KEY>): confirm the security regression test demonstrates the exploit and fails before / passes after the fix, the vulnerability from the triage threat model is actually closed (the attack no longer succeeds), the full suite is green, and no new weakness was introduced. Post the verdict as a comment on <SEC_KEY>.", run_in_background=false)
```

This is the pipeline's ONE full-suite pass; the fixer used the fast tier. Ensure the `[human:bug-verify]` comment records the `## Evidence` block and the `## Vulnerability closed` finding so the review can trust it without re-running the suite.

**Read the gate's outcome from state:**

```bash
human state get <WORK_KEY> stage.verify --field verdict   # DONE | NOT DONE
human state get <WORK_KEY> stage.verify --field gaps      # what is still missing, when NOT DONE
```

If the verdict is NOT DONE, re-run Step 5 to address the gaps, under the retry budget above. Once the budget is spent, do NOT stop silently — a silent stop freezes the card at "being fixed" forever. Post an explicit terminal marker carrying the same blocker the exit contract requires, so the board reds the card to a needs-attention/Retry badge:

```bash
human marker post <SEC_KEY> implementation-failed \
  --field reason="<one-line verdict headline — becomes the card's badge text>" \
  --field kind=exhausted-fix-rounds \
  --field evidence="<the security-verify gaps, verbatim: what is still NOT DONE — keep exploit specifics out when the ticket is publicly visible>" \
  --field attempted="<what each of the budget.implementation.attempts real attempts tried>" \
  --field release="<what a person needs to resolve before a re-dispatch could pass verify>" \
  --body-file - <<'EOF'
<the security-verify gaps: what is still NOT DONE and why the vulnerability is not yet closed, in full>
EOF
```

The `reason` field becomes the badge headline; the body carries the fuller detail. This is mandatory in board context. Then record the stage outcome (`stage.implementation`, exit `needs-human-work`, with the matching `blocker` object, per "Recording the board stage outcome") so the daemon reads the spent budget instead of the generic diagnose line, and STOP and report honestly without posting the handoff.

## Step 7 — Phase 5: Hand off and security review

Only after a DONE verdict.

### 7.1 Post the review handoff

Post the review handoff on the security (PM) ticket — the **same handoff the kanban executor posts**, so the trail and the board's `(R)` annotation work identically:

```bash
human handoff post <SEC_KEY> --engineering <ENG_KEY> --branch autofix/<work-key> --review inline   # split topology
human handoff post <SEC_KEY> --branch autofix/<work-key> --review inline                           # single-tracker: omit --engineering
```

The command derives `commits:` and `daemon:`, verifies every SHA is reachable on the branch (fetching origin first), and refuses to post otherwise. `--review inline` is **mandatory here and in every context**: this skill reviews the fix itself in Step 7.2, and the handoff is the only thing the daemon can read before your own `[human:review-started]` lands. Without it the daemon starts a second reviewer whose verdict overwrites yours. If the handoff cannot be posted (non-zero exit), STOP with an honest status report — **do not report success**.

**Board-context exception applies here**: when `<BOARD_CONTEXT>` is true, post the handoff (so `branch:`/`commits:` are recorded for the Deploy button), then CONTINUE to the inline review (Steps 7.2–7.3) in this same warm container. STOP after the review — the run ENDS there: Step 9's summary marker is its last act (do not run Step 8 / deploy; see 'The end of the run'). Do NOT push or `git ls-remote` — the branch is intentionally local. The `review: inline` line you posted in 7.1 is what stops the daemon launching a second review container.

**The handoff is posted once, and before the review checkpoint.** 7.1 precedes 7.2's `[human:review-started]`, so the branch and commits are on the ticket before anything judges them. Never post a second `[human:ready-for-review]` after the verdict: a handoff is the start of a round, and one posted after a review reads as a new round of work. If the reviewer commits on the branch, record those commits on the verdict (7.2), not by re-posting the handoff.

### 7.2 Security review by the reviewer agent

Chain straight into the review. This runs **inline in this same warm container in board context too**. Post the started marker, then dispatch the reviewer **with a security lens**:

```bash
human marker post <SEC_KEY> review-started
```

```
Task(subagent_type="human-reviewer", model="opus", prompt="Review changes for security ticket <WORK_KEY>: check out branch autofix/<work-key> and review its diff against main against the ticket's plan and acceptance criteria. Security lens: confirm the fix closes the vulnerability at its source (not just the reported payload), covers the sibling occurrences the triage found, and introduces no new weakness (an over-broad allow, a widened permission, a secret written to a log, a check that can still be bypassed). The security regression test must genuinely exercise the exploit.", run_in_background=false)
```

The reviewer writes `.human/reviews/<work-key>.md` and records its outcome in state. **Read the verdict from state, never from the file's prose:**

```bash
human state get <WORK_KEY> stage.review --field verdict   # pass | pass with notes | fail | incomplete | unreviewable | decision-required
human state get <WORK_KEY> stage.review --field reason    # why, when unreviewable; the one-line fork, when decision-required
human state get <WORK_KEY> stage.review --field unchecked  # dependent kinds the reviewer could not determine
```

A non-empty `unchecked` never changes the verdict routing — carry it into the run summary's "Along the way" so a kind nobody could query is part of the story of the run rather than a silence.

The verdicts mean: the change is good (`pass`), good with notes worth recording (`pass with notes`), it has problems to fix (`fail`), it was built correctly but not every ticket acceptance criterion was met (`incomplete`), or the code could not be obtained at all (`unreviewable`). `decision-required` is not a verdict at all: the reviewer examined the code and found a genuine product/scope fork it cannot resolve, exactly like the preflight fork in Step 1a.

Post the outcome on the security ticket with the reviewer's **full findings** inlined under a `## Findings` section (the board detail panel shows it without opening the local `.human/reviews/<work-key>.md`):

`commits:` names exactly what this verdict judged: the handoff's commits (`human handoff show <SEC_KEY>` returns them as JSON) plus any commit the reviewer itself made on the branch, comma-separated short SHAs. It is how the daemon tells a rebuild handed back from a record of what the branch holds — without it the comparison falls back to the clock and a later handoff re-opens a round that is over.

```bash
human marker post <SEC_KEY> review-complete \
  --field verdict="<verdict>" \
  --field commits="<the handoff's commits, plus any commit the reviewer made on the branch>" \
  --field reviews="<WORK_KEY>: <verdict> — .human/reviews/<work-key>.md" \
  --body-file - <<'REVIEW_EOF'
## Findings
<the reviewer's full findings, inlined: what was checked, every issue found
 (or "no issues"), and whether the vulnerability is closed at the source>
REVIEW_EOF
```

### 7.3 Review gate

- **pass** or **pass with notes** — a pass is about to be made irreversible by a merge. Before continuing, get one adversarial second opinion **through a security lens**:

  ```
  Task(subagent_type="human-second-opinion", model="fable", prompt="The pipeline is about to merge branch autofix/<work-key> for security ticket <WORK_KEY> on the strength of a passing review. Lens: is-the-vulnerability-actually-closed. Evidence: the ticket's threat model, the branch diff against main, and stage.review in agent state. Try to refute that the fix closes the source→sink path — look for a residual bypass, an uncovered sibling, or a new weakness the fix introduced. Do not read the reviewer's reasoning first.", run_in_background=false)
  ```

  ```bash
  human state get <WORK_KEY> stage.opinion --field opinion    # upheld | refuted
  ```

  - **upheld** — continue to Step 8.
  - **refuted** — treat it exactly like a failing review: feed its evidence back to the fixer under the review budget (the `fail` branch below). Do not merge on a refuted pass.

  Run this once per review verdict, not once per attempt.
- **unreviewable** — the reviewer could not obtain the code, so there are NO findings. Do NOT re-dispatch the fixer and do NOT post `[human:review-complete] verdict: fail`. Instead post `[human:review-failed]` naming the unreachable ref — `human marker post <SEC_KEY> review-failed --field reason="<reachability reason>"` — then record the stage outcome (`stage.implementation`, exit `retryable`, per "Recording the board stage outcome") and STOP (report per Step 9). No PR is merged.
- **decision-required** — the reviewer found no verdict to give, exactly like the Step 1a preflight fork: the ticket admits two legitimate, mutually exclusive directions and building one over the other is a product call, not something more rework fixes. Do NOT re-dispatch the fixer and do NOT post `[human:review-complete]`. Post the fork as the same up-front decision block Step 1a uses, on `<SEC_KEY>`:
  ```bash
  human marker post <SEC_KEY> options \
    --field stage=implementation \
    --field context="<the decision-required one-liner from stage.review --field reason>" \
    --field 1="<first option, one line>" \
    --field 2="<second option, one line>"
  ```
  Then record the stage outcome (`stage.implementation`, exit `needs-input`, per "Recording the board stage outcome") and STOP (report per Step 9). No PR is merged; the card waits on a person exactly like the Step 1a fork.
- **fail** or **incomplete** — feed the reviewer's findings back: re-dispatch the **human-bug-fixer** (Step 5) with the findings appended, re-run the verify gate (Step 6), then re-run the review (7.2, one new `[human:review-complete]` comment). An `incomplete` verdict means a ticket acceptance criterion was not built; route it identically to `fail` — re-dispatch the fixer with the unmet criterion appended, re-verify, and re-review under the same `budget.review.attempts`. This loops under the retry budget (`budget.review.attempts`). When the budget is spent, STOP honestly as `needs-human-work`: the handoff stays standing and NO pull request is merged.

## Step 8 — Phase 6: Deploy — end with a merged PR

Only after a passing review. This is the board's deploy pipeline (push → draft PR → machine PR review → CI gate → merge → close) driven from the skill:

1. Run the deploy gate:

   ```bash
   human deploy <SEC_KEY> --branch autofix/<work-key> --title "[<SEC_KEY>] [<ENG_KEY>] <short summary>"
   ```

   (single-tracker: only `[<SEC_KEY>]` in the title). Keep the title and PR body free of exploit detail. The command pushes the branch, opens its pull request in **draft**, launches the machine PR reviewer on it, and exits once that review has started (`Review started for <SEC_KEY> (<branch>): <PR_URL>`). The daemon's review→fix loop owns the rest: un-draft on approval, the CI wait, rebase-if-stale, merge, remote-branch cleanup, the `[human:deployed]` marker with its `pr:` line, and the ticket close — none of it happens in this run. A branch already merged into the base is a clean success. One more end is neither of those: if the base had advanced and needed a fixer before the review could even run, the command instead prints `Base merge needs a fixer for <SEC_KEY> (<branch>): <URL>` and exits 0 — the deploy-fix loop's agent is live and owns the card from there, so there is nothing left for this run to do. Never pass `--ready`: it ships without the machine PR review and is a person's override, not an agent's. A `[human:deploy-failed]` — from the command's own push/PR step, or from the loop later — is an honest needs-human end state, not a first-failure stop: do NOT merge by hand and do NOT re-implement the reviewed work; the PR stays open for a human with the named blocker.

   `human deploy` records the start on the ticket itself (`[human:deploy-started]`) before it touches the forge — do **not** post that marker by hand.

   One outcome is neither success nor failure: if the command exits with **`deploy refused: this ticket is waiting on a decision`**, an open `[human:options]` block is waiting on a person. That is not a crash and not a deploy failure — no `[human:deploy-failed]` is posted and the card is not red. Do **not** re-run with `--override-decision` (only a person may decide to ship past their own open question) and do **not** merge by hand. Post the Step 9 run summary, record the stage outcome as `needs-input` (per "Recording the board stage outcome"), and STOP, leaving the card paused where it is.

   A second refusal, **`deploy refused: the implementation container for this ticket still holds the checkout`**, means a run of this ticket's implementation stage is still finishing in the same working tree: nothing failed, no marker is posted, and the card is not red. Report it as `retryable` and STOP — the checkout is free once that run ends, and the next attempt proceeds on its own.
2. In split topology, close `<ENG_KEY>` as well: `human done <ENG_KEY>`.
3. For the Step 9 report, read `<PR_URL>` from the deployed marker if needed: `human marker show <SEC_KEY> deployed`.

## Step 9 — Run summary: ticket comment, then report

Once a fix was attempted (Step 4 ran), the ticket must carry a plain-language account of the run. Post it at EVERY terminal point after Step 4: the board-context stop after the inline review (7.3), a shipped fix (Step 8), and every honest STOP. Runs that end at the verdict gate (Step 3) post nothing here — the triage verdict comment already tells that story. **Keep exploit specifics out of this summary if the ticket is publicly visible — describe the risk and the hardening, not a working attack.**

```bash
human marker post <SEC_KEY> fix-summary --body-file - <<'SUMMARY_EOF'
## What happened
<2–4 sentences, plain language: what the vulnerability turned out to be, what was at risk, and what the fix closes. Written for the reporter, not an engineer.>

## Changes
- Branch: autofix/<work-key> — <left local for Deploy | pushed | merged as <PR_URL>>
- Commits: <short sha — one-line subject, per commit>
- <the areas of the product hardened, one line>

## Proof
- Security regression test: <name/location> — demonstrated the exploit; failed before the fix, passes after
- Vulnerability closed: <the source→sink path is now broken — one line>
- Checks: <suite/lint/coverage result>
- Review: <verdict, or "pending — daemon chains it" in board context>

## Dependents
- <the fixer's dispositions, verbatim: one examined-and-unchanged / examined-and-changed line per dependent>
- <unchecked: kind — why nobody could query it, if any (from stage.review --field unchecked and the fixer's report)>
- Keep exploit specifics out of this section — name the dependent and its disposition, not a working attack.

## Along the way
<the story of the run when it was not straight: a refuted verdict, a first verify that came back not-DONE because the exploit was still reachable, review findings addressed. If it went straight through, say exactly that.>

## Where it ended
<board: handoff posted, the Deploy button ships it | standalone: PR merged, ticket closed by the deploy gate | stopped at <step>: what a human needs to do next>
SUMMARY_EOF
```

Fill every section from what actually happened in THIS run — never leave template placeholders. If posting the summary fails, still produce the final report below.

Then report the verdict. For a confirmed, shipped fix, present the traceability chain:

```
Security fix complete for <SEC_KEY>

Verdict: confirmed (<severity>) — review: <verdict> — shipped
- PM ticket:  <tracker> <SEC_KEY>
- Threat:     [human:bug-verdict] comment on <SEC_KEY> (threat model + severity + cause chain)
- Plan:       <ENG_TRACKER> <ENG_KEY> (split topology) — or [human:plan] comment on <SEC_KEY>
- Branch:     autofix/<work-key>
- Review:     [human:review-complete] verdict: <verdict> on <SEC_KEY>
- PR:         <PR_URL> — draft, machine PR review running; the loop merges on approval
- Ticket:     closed by the deploy loop once merged
```

For a board-context run (the stop after the inline review, 7.3) or a failed review/deploy gate, report where the pipeline stopped, which marker records it, and what a human needs to do next.

<!-- human:include run-end -->

Files in this skill

  • brainstorm-codebase-agent.md3.3 KB
  • brainstorm-opportunities-agent.md3.7 KB
  • brainstorm-recon-agent.md3.3 KB
  • brainstorm-trajectory-agent.md3.4 KB
  • brainstorm-triage-agent.md4.5 KB
  • codenav-skill.md2.6 KB
  • features-recon-agent.md4.8 KB
  • features-synthesis-agent.md10.2 KB
  • findbugs-api-agent.md5.8 KB
  • findbugs-concurrency-agent.md5.6 KB
  • findbugs-errors-agent.md5 KB
  • findbugs-logic-agent.md4.8 KB
  • findbugs-recon-agent.md4.6 KB
  • findbugs-triage-agent.md4.7 KB
  • gardening-complexity-agent.md5.8 KB
  • gardening-duplication-agent.md5.2 KB
  • gardening-hygiene-agent.md6.6 KB
  • gardening-structure-agent.md5.3 KB
  • gardening-survey-agent.md7.1 KB
  • gardening-triage-agent.md8.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.