--- name: unattended description: >- Unattended CVE remediation that runs scan/patch/validate/pr/backport phases end-to-end without interactive prompts. Creates or updates reviewable draft PRs for qualifying changes. Human-only merges. Designed for bots, CI pipelines, and noninteractive callers. --- # Unattended CVE Fix Workflow Runs the CVE fix phases without stopping for human input. Creates reviewable draft PRs for qualifying dependency changes. Humans merge; AI never merges. This workflow reuses the individual phase skills (`start.md`, `scan.md`, `patch.md`, `validate.md`, `pr.md`, `backport.md`, `close.md`) but orchestrates them sequentially with bounded retries instead of waiting for human input between phases. For the machine-readable request/result contract, see [unattended-contract.md](unattended-contract.md). ## Script Location All helper invocations use the absolute path to the installed workflow scripts directory. The script lives at `../scripts/unattended.py` relative to this skill file's installed location. Resolve the absolute path once at startup from the skill file you are reading: ```text # Relative to this skill file: ../scripts/unattended.py SCRIPT="{workflow-install-dir}/scripts/unattended.py" ``` All examples below use `"$SCRIPT"` as a placeholder for this resolved absolute path. The current working directory is the target repository checkout, NOT the workflow install directory. ## Input The caller provides context through the request contract or as structured input text. At minimum: CVE identifiers, Jira ticket keys, or an existing PR/work reference for continuation, plus the selected checkout. The workflow still researches the actual fix; the caller need not supply a fixed package version. ## Configuration Before starting, determine these values from the request, calling system instructions, or environment. Use defaults if not specified. | Setting | Default | Description | |---------|---------|-------------| | `operation` | **required** | One of: `remediate`, `backport`, `jira_action`. Must be explicitly set; normalize free-text input to one of these values before validation. | | `branch` | *(auto)* | Feature branch; auto-generated from context if omitted | | `base_branch` | *(caller-supplied or attended default)* | Target base branch for the PR. If omitted, use the same safe derivation as attended mode: current branch or Jira-suggested branch. If ambiguous, return `needs_input`. | | `max_retries` | 3 | Maximum retry attempts per feedback loop | | `time_budget_seconds` | `7200` (2h) | Deadline budget in seconds; return `partial` at phase boundary when exceeded. The default ensures bounded execution even when the caller omits this field. | | `stage` | *(none)* | Resume at a specific stage (omit to run from beginning) | | `pr_url` / `work_ref` | *(none)* | Existing PR or branch for continuation within `remediate` | | `target_branches` | `[]` | Release branches for backport operation | | `reviewer_policy` | *(none)* | Caller-supplied reviewer eligibility with `allowed_reviewer_ids` (v1). Omission returns `needs_input` for decisions needing a reviewer. | | `jira_action` | *(none)* | Explicit Jira action (see contract) | | `human_decisions` | `[]` | Reviewed decision responses from prior invocations | | `prior_result` | *(none)* | Prior invocation result for fresh-workspace handoff (see contract) | ## Ground Rules The authoritative unattended-mode policy is in [`../guidelines.md` § Unattended Mode](../guidelines.md): human-only merges, draft-only PRs, Jira read-only default, bounded execution, idempotent reruns, honest outcomes, source-before-backport, per-branch evidence, code vs infrastructure classification, machine-readable contract, human review integrity, and dependency-only test exemption. Those rules apply fully here and govern in case of conflict. Execution-specific additions: - **Do not stop for human input.** Run all applicable phases to completion or until a blocker requires human judgment. - **This workflow creates commits/branches/draft PRs** (unlike bugfix unattended). Return `awaiting_merge` if a merge is needed; do not poll. - **Jira write gate.** A `jira_action` must specify named tickets, nonblank target status (for transitions), and nonblank comment text (for comments). Finding approval is not Jira write permission. - **Write artifacts to `.artifacts/cve-fix/{context}/`.** Only dependency manifest/lock file changes and build reference changes go in the project source tree. - **Follow [`../guidelines.md`](../guidelines.md)** for principles, hard limits, and quality standards. Where guidelines say "stop and request human guidance," write an escalation result and terminate. Major version bumps and application code changes always escalate, even when "execute all strategies" is set. - **Read and follow the project's `AGENTS.md`** if one exists. - **Time budget.** Check `time_budget_seconds` BEFORE and AFTER each phase; return `partial` with committed evidence if budget is exhausted. - **Live deduplication.** Live host PR state must be verified (not just local files); duplicates are prevented by the same exact_cve/bot_update/same_package policies used in attended `/pr`. Jira transitions check current ticket state; repeated comments require live read-based deduplication across invocations (not just within one invocation). Missing or errored live reads block the action. - **Preserve attended scope/defaults.** The selected checkout is the execution target. Do not introduce new repository/branch allowlists or product-specific policies. Existing attended defaults remain meaningful. ## Quick Start 1. Parse the request (structured JSON or text input). Normalize free-text operation descriptions to an explicit `operation` value before step 2. 2. Validate with `python3 "$SCRIPT" validate request.json` 3. Extract the context identifier (Jira key, CVE ID, or work reference) 4. Create the artifact directory: `mkdir -p .artifacts/cve-fix/{context}` 5. Check for competing work (local discovery only — live check still required): `python3 "$SCRIPT" check-scope --repo {repo} --base-branch {base} --cve-ids {ids} --packages {pkgs}` 6. Load state if resuming: `python3 "$SCRIPT" load-state .artifacts/cve-fix/{context}/ --stage {stage}` 7. Check time budget BEFORE starting first phase 8. Execute the appropriate operation flow below 9. Write the result: `python3 "$SCRIPT" make-result --status {status} --operation {op} --artifact-dir {dir} --data '{...}'` 10. Output the completion report ## Operation Flows ### `remediate` — New or Existing CVE Work Handles both new CVE/ticket/package bundles AND existing PR continuation. New vs existing is an input distinction, not a separate workflow: **New work** (request has `cve_ids` / `jira_tickets`, no `pr_url` / `work_ref`): Run the sequential phases from `start`. If `stage` is set, resume at that stage (prior artifacts must exist with valid scope/revision evidence, not just file existence). | Order | Phase | Skill File | Done Signal | Creates PR? | |-------|-------|------------|-------------|-------------| | 1 | Start | `start.md` | `context.md` written | No | | 2 | Scan | `scan.md` | `scan-result.json` written | No | | 3 | Patch | `patch.md` | `patch-log.md` written | No | | 4 | Validate | `validate.md` | `validation-results.md` written | Candidate only | | 5 | PR | `pr.md` | Draft PR created/updated | **Yes** | **Existing work** (request has `pr_url` / `work_ref`): 1. Locate the existing PR via `pr_url` or `work_ref` 2. Inspect PR state via the host. Determine: merged, closed-unmerged, or open. 3. If the PR was merged: for `remediate`, return `completed` with the merge info. For backport continuation, see `backport` operation. 4. If the PR was closed (not merged): return `blocked` with the closure reason. Do not silently create a replacement. 5. If the PR is open: verify it is still draft. Verify the repo, head branch/SHA, confirmed base, and CVE/package scope match the validated context. 6. If the remote head differs from the recorded candidate SHA: return `needs_input` explaining the drift. Do not silently overwrite human/remote changes. There is no implicit reconciliation stage. 7. Rerun the applicable stages (from `stage` or auto-detected resume point) 8. Update the PR body/description if changes were made 9. The identified verified target PR is permitted to update after live checks; unrelated/competing matches still block per `/pr` rules. **Before /scan — restore prior decisions:** If the request contains `prior_result` with a `scan_snapshot`, restore the scan identity from the supplied snapshot rather than unconditionally generating a new scan ID. For each reviewed response in `human_decisions`: 1. Match the response to its original OUT request using `match_decision_response_to_request(response, prior_result.decision_requests)` 2. Materialize the matched original request and response as temporary JSON files from the supplied content (NOT from old workspace paths) 3. Verify the scan snapshot's scope and candidate_sha against the current live checkout state (repo, base branch, HEAD). If the live state has changed, reject the stale approval and return `needs_human_review` with a new decision request. 4. If still current, validate with all mandatory binding arguments: ```text python3 "$SCRIPT" validate-decision response.json \ --scan-id {prior_result.scan_snapshot.scan_id} \ --scope '{current_canonical_scope_json}' \ --original-request matched-original-request.json \ --authority-context '{"allowed_reviewer_ids": [...]}' ``` 5. Check that `action_authorized` is `true` in the CLI output (not merely `valid: true` with `accepted: false`). Only `action_authorized: true` permits applying the accepted effect. 6. Record the corrected justification under the SAME scan ID. Do not generate a new scan ID for an already-reviewed snapshot. 7. Apply the authorized effect and skip `/scan` for this CVE. If no matching prior decision exists or validation fails, proceed to `/scan` normally. **After scan:** If the CVE is absent or informational, do NOT create a PR. If a matching accepted `close_vex` decision was supplied and validated (`action_authorized: true`): record the approved VEX rationale in the result evidence and return `completed` with no PR (valid no-code completion). Do NOT write to Jira; ticket closure requires a separate `jira_action` request. If no matching decision exists, or the decision is missing, unauthorized, stale, or rejected: return `needs_human_review` with the VEX justification as a new decision request. **After patch:** If no fix strategy succeeded, return `failed` with details. Do not create an empty PR. ### `backport` — Merged Source to Target Branches 1. Verify the source PR (from `work_ref`/`pr_url`) state via the host: - Merged: proceed to step 2 - Open (not merged): return `awaiting_merge` and exit - Closed (not merged): return `blocked` — do not wait 2. For each target branch in `target_branches`: a. Verify the branch exists on the remote b. Check for existing backport PRs: - Already-merged coverage: `merged` no-op for that branch - Closed-unmerged: `blocked` for that branch - Existing open draft: fetch the existing branch, check out the existing backport branch, revalidate, and update. Do NOT run `git checkout -b` or re-cherry-pick the same source commit. c. **New target only**: verify per-branch released/supported input availability BEFORE editing, then: - `git fetch origin {target_branch}` - `git checkout -b cve-fix/{context}-{branch} origin/{target_branch}` - `git cherry-pick -x {merge_commit}` (resolve dependency conflicts if possible; abort and report `conflict` for app-code conflicts) d. Run branch-specific build, tests, vulnerability scan, and self-review gate (max 2 rounds) with the branch's own candidate e. Create/update a per-branch draft backport PR only after validation f. Record the branch result with per-branch evidence (PR URL, head SHA, base, test/scan/build results, platforms) 3. Main's evidence is NOT backport evidence; each branch needs its own. 4. Handle conflicts honestly: dependency-file conflicts may be resolved; app-code conflicts return `conflict` for that branch. 5. A missing remote branch is reported as `failed`, not silently omitted. 6. Partial branch outcomes survive the summary; outer success requires all requested branches to complete. ### `jira_action` — Explicit Jira Disposition 1. Validate the `jira_action` request: named tickets with valid Jira keys, nonblank `target_status` for transitions, nonblank `comment` for comments 2. Verify current merge/evidence gates hold for each ticket 3. Execute the action at most once per ticket; check current ticket state before transitioning. For comments, perform a live read of existing comments to deduplicate across invocations. Missing or errored live reads block the action. 4. Only the explicit `jira_action` in the current request authorizes writes; no prior "make progress" request carries forward ## How to Execute a Phase 1. Check `time_budget_seconds` BEFORE the phase. If budget is exhausted, return `partial` immediately with committed evidence. 2. Resolve phase overrides using `../../_shared/recipes/phase-override-resolution.md` with `WORKFLOW=cve-fix` BEFORE reading the phase file. 3. Announce the phase: *"Starting /scan (unattended mode)."* 4. Read the resolved skill file. While executing it, apply these overrides: - "Never auto-advance" / "Stop and wait" / "return to the controller" / "re-read the controller" — ignore; proceed without pausing - "Confirm with user" for branch/base selection — use the request's configured values, or the attended default derivation when not supplied. Do NOT blanket-ignore safety/authorization confirmations: major version bumps, application code changes, and downstream verification blockers still escalate. - "Stop and request human guidance" (escalation) — write an escalation result and terminate 5. Execute the skill's steps fully, including its Output section 6. Check `time_budget_seconds` AFTER the phase; return `partial` at this phase boundary if exceeded 7. Proceed to the next phase in the operation flow ## Phase Overrides These override instructions in the individual skills when running unattended: ### /start - Use `cve_ids`, `jira_tickets`, and `packages` from the request instead of prompting. If Jira access fails, fall back to CVE ID input with the provided identifiers. - Use `repo_path` for repository confirmation. For `base_branch`, use the caller-supplied value if present; otherwise use the same attended default (current branch or Jira-suggested branch). If ambiguous, return `needs_input`. - Skip interactive "confirm" steps for non-safety choices. ### /scan - Run the scan. If the verdict is `absent` or `informational`: do NOT proceed to `/patch`. Check if a matching reviewed `human_decisions` response was supplied (see "Before /scan" above for the full prior-decision restoration and validation path). If authorization was granted (`action_authorized: true`): apply the accepted effect. For `close_vex`: record the reviewed VEX justification and artifact outcome in the result only — do NOT write to Jira. Actual ticket closure requires a subsequent explicit `jira_action` request with named tickets, target status, and evidence (see Ground Rules on Jira writes). If not supplied or not authorized: return `needs_human_review` with the decision request. - If `in_base_image`: return `blocked` — the base image team must act. - If `scan_failed`: continue to `/patch` with caution and document the scan failure in the result. ### /patch - Use the ascending-risk strategy order without user confirmation. - Major version bumps and application code changes still escalate per `../guidelines.md` — do not suppress these safety gates. - On downstream verification blocked/unknown: return `blocked` with the missing evidence. Do not apply the patch. - If no strategy succeeds: return `failed`. Do not create an empty PR. ### /validate - Run validation fully. Use the candidate build handoff from [downstream-verification.md](downstream-verification.md) when external pipeline submission is needed. - If the build is pending, return `partial` with the build ID and committed evidence; do not remain running. - Do not prompt for test commands; use those recorded in `context.md`. - If validation fails: classify as code failure vs infrastructure error (see Feedback Loops). Do not change code to mask infrastructure failure. ### /pr - Use the request's `base_branch` if supplied; otherwise use attended defaults. Do not prompt for confirmation. - Run the full duplicate check per `pr.md` rules. For an existing verified target PR (identified via `pr_url`/`work_ref` with matching repo/branch/base/full-scope/head): update that PR after live checks. All other exact_cve/bot_update matches still block. Do not overwrite unexpected remote changes. - Run the self-review gate (max 2 rounds). Self-review fixes require revalidation. - Stage, commit, push, and create/update the draft PR. - Record the PR URL in `context.md` and the result. ### /backport - Verify source PR is merged via the host. If not merged: return `awaiting_merge`. If closed-unmerged: return `blocked`. - Use `target_branches` from the request. - For each branch: verify per-branch released/supported downstream inputs BEFORE cherry-picking. For new targets, cherry-pick using attended git commands (not `backport.py` which always pushes). For existing open backport branches, fetch and check out the existing branch — do not unconditionally re-create. Run branch-specific build/test/scan and self-review, then publish. Do not push until validation passes. - Dependency-file conflicts: attempt resolution. App-code conflicts: report as `conflict` for that branch. - Each branch gets its own draft PR with independent evidence. ### /close (jira_action only) - Execute only when `operation` is `jira_action` with explicit action. - VEX closure requires a reviewed human decision with matching scan ID, nonblank evidence, named reviewer, and bound justification. Do not fabricate approval or infer it from a generic PR merge. ## Feedback Loops Phase failures trigger bounded retries before continuing: - `/validate` fails → classify as **code failure** or **infrastructure error**: - **Code failure** (test failure, dependency issue): return to `/patch`, rework, rerun `/validate`. Repeat up to `max_retries`. If still failing, return `failed` with details. - **Infrastructure error** (network, tool, build service): retry `/validate` up to `max_retries`. If still failing, return `error` with details. Do NOT return to `/patch` for infrastructure errors. - Self-review gate finds CRITICAL/HIGH issues → return to `/patch`, revise, rerun `/validate` and self-review. Repeat up to `max_retries`. Retry-limit exhaustion for validation/review is NOT a pass. The result status is `failed`, `partial`, or `error`, never `completed`. Record the unresolved concerns. A degraded output after exhaustion is not a CVE PASS. The completion report must not display all-passed phases for a non-success result. ## Human Review Handoff When a decision requires human judgment, the result includes a caller-neutral decision request. See [unattended-contract.md](unattended-contract.md) for the full schema. The decision request includes: - Exact question to answer - Proposed VEX justification (if applicable) - Evidence supporting the proposal - Selected CVE/package/repo scope and candidate/scan evidence - Current scan identity (scan_id) - Requested effect and permitted alternatives - Reviewer requirements from the caller's `reviewer_policy`; if omitted, return `needs_input` rather than inventing a universal default On a subsequent invocation, the caller supplies reviewed decisions in the request's `human_decisions` array with `prior_result` for fresh-workspace handoff. See "Before /scan" in Operation Flows above for the full restoration, validation, and authorization path. Enforcement (via `python3 "$SCRIPT" validate-decision`): field-level validation rules are defined in [unattended-contract.md § Enforcement rules](unattended-contract.md) (authoritative for schema enforcement). **Execution gate:** CLI output `action_authorized: true` (not merely `valid: true`) is required before applying an effect. If no code change or PR is needed (CVE absent): when a matching accepted `close_vex` decision was supplied and authorized (`action_authorized: true`), return `completed` with the approved rationale recorded in the result — no PR, no Jira write. When no eligible decision exists or authorization fails: return `needs_human_review` with a new decision request. Do NOT manufacture an empty PR in either case. ## Escalation When `../guidelines.md` escalation criteria are triggered (major version bumps, app code changes, security concerns), write an escalation result and terminate. Do not suppress these gates even in unattended mode. ## Completion Report When phases finish, write the structured result to `.artifacts/cve-fix/{context}/unattended-result.json` and output the report. The result distinguishes operation completion from full CVE lifecycle completion via `lifecycle_complete`. For backport operations, pass `target_branches` (the requested branches from the original request) in the make-result `--data` JSON; `lifecycle_complete: true` requires `target_branches` with one merged child per requested branch. Next permitted operations list prerequisites (e.g., backport requires main PR merged first). ```text ## Unattended CVE Fix Result Operation: {operation} Scope: {cve_ids} in {repo} @ {base_branch} Status: {status} Phases: {phase statuses — only display ✓ for actually passed phases} PR: {pr_url} (draft) or "none" Evidence: scan={scan_verdict}, validation={validation_verdict}, tests={test_result} Retries: {retry details or "none"} Lifecycle: {operation complete / awaiting merge+backport+close} Next permitted: {operations with prerequisites} Artifacts: .artifacts/cve-fix/{context}/ ``` ## Guidelines For principles, hard limits, safety, and quality rules, follow `../guidelines.md`. Where guidelines say "stop and request human guidance," return an escalation result and terminate (see Escalation above).