Mission Specification: GitHub Actions Sonar hardening (validatable workflows)

Mission: ghactions-sonar-hardening-01M2FYX1 Issue: #4342 (GitHub Actions hardening cluster) — epic #1928, milestone 4.0.0 Origin: current-profile SonarCloud debt cluster on spec-kitty_spec-kitty (analysis 2026-09-14), replacing the retired pre-migration code-smell tickets.

Intent Summary

Harden the project's PR-CI-validated GitHub Actions workflows per the live SonarCloud githubactions findings, without changing any CI behavior. Primary actor: the CI/security maintainer. Trigger: SonarCloud flags 99 githubactions findings; ~25 in the validatable workflows are genuine hardening, the rest are safe-as-written (hotspot review) or false-positive. Desired outcome: the exposed inputs, unpinned actions, over-broad permissions, and unpinned 3rd-party installs in the eight PR-exercised workflows are hardened; the safe-as-written uv sync --frozen self-install hotspots and false-positives are adjudicated (no edit) with rationale. Invariant that must always hold: every edited workflow remains functionally identical — this is security hardening only, and the PR's own CI run (which exercises all eight in-scope workflows) must stay green.

Release-critical workflows are OUT of scope (release.yml, release-readiness.yml, ci-nightly.yml): they are not exercised by PR CI, so hardening edits there cannot be validated before merge — deferred to a separate follow-up with a manual dry-run. docs-pages.yml is excluded (under change in PR #4286).

Grounding (opus classification pass, read-only) established the load-bearing facts: the project self-installs via build-backend = hatchling.build, so uv sync --frozen must build — a --no-build/--only-binary "fix" would break every run; the lock carries 1420 sha256 hashes.

User Scenarios & Testing (mandatory)

User Story 1 - Workflow inputs can no longer be script-injected (Priority: P1)

A run: block interpolates ${{ inputs.X }} directly, so a crafted input could inject shell. After this mission those interpolations are routed through step env: vars referenced as "$VAR", closing the injection class without changing what the step does. Acceptance: every S7630 site in the eight in-scope workflows uses env:-indirection; the workflow's observable behavior (same commands, same args) is unchanged; PR CI green.

User Story 2 - Third-party actions and installs are pinned (Priority: P2)

Flagged third-party actions run at a moving tag, and a few 3rd-party pip/npm installs are unpinned. After this mission each flagged action is pinned to its own current tag's full-length commit SHA (same version, # vX trailing comment) and the bare 3rd-party installs get exact pins / --only-binary :all:. Acceptance: each S7637/S8544/S8543 in-scope site is pinned to a SHA/version that resolves to the same code it ran before; uv sync --frozen self-install steps are untouched; PR CI green.

User Story 3 - Workflow permissions follow least privilege (Priority: P2)

Workflow-level permissions grant more than a single job needs. After this mission the grant is relocated to the specific job(s) that require it, every needing job retaining its access. Acceptance: each S8264/S8233 in-scope workflow declares permissions at job scope; no job that needed a permission loses it; PR CI green.

User Story 4 - Safe-as-written findings are adjudicated, not churned (Priority: P1)

The 46 uv sync --frozen S8541 hotspots (self-install must build), the self-bootstrap/drift S8544 sites, the S6505 npm-lifecycle sites, and the ~6 false-positives are not edited; they are recorded with won't-fix rationale for Sonar UI + the PR body. Acceptance: an adjudication dossier lists each safe-as-written/FP finding with its rationale; zero code edits are made for them.

Edge Cases

env round-tripping must preserve JSON quoting.

  • SHA-pinning a moving tag must resolve the CURRENT tag SHA (no silent version bump).
  • Permission relocation must enumerate every job first — a missed job silently loses access.
  • module-tests.yml inputs (test_dirs, cov_target) feed a heredoc Python script as argv;

Requirements (mandatory)

Functional Requirements

IDRequirementStatus
FR-001Move every in-scope S7630 ${{ inputs.* }} interpolation out of run: into a step env: var referenced as "$VAR" (module-tests.yml ×9), behavior-identical.Proposed
FR-002Pin each in-scope S7637 third-party action to its OWN current tag's full-length commit SHA with a # vX comment, no version change (ci-quality.yml setup-uv ×4; ci-windows.yml paths-filter ×1).Proposed
FR-003Relocate in-scope S8264/S8233 workflow-level permissions to the specific job(s) needing them (ci-aggregate.yml, ci-fleet-verdict.yml, ci-windows.yml), every needing job retaining access.Proposed
FR-004Harden bare in-scope 3rd-party installs with exact pins and/or --only-binary :all: and pin the claude-code npm tool (ci-modules.yml pyyaml; check-spec-kitty-events-alignment.yml packaging; ci-aggregate.yml pyyaml + diff-cover/defusedxml; packs.yml claude-code S8543). MUST NOT touch uv sync --frozen self-install steps.Proposed
FR-005Produce an adjudication dossier (won't-fix rationale, no code edit) for the in-scope safe-as-written hotspots (46 uv sync --frozen S8541, S8544 self-bootstrap/drift, S6505 npm/npx) and the ~6 false-positives, for Sonar UI + PR body.Proposed

Non-Functional Requirements

IDRequirementThreshold / MeasureStatus
NFR-001No CI behavior change.The eight in-scope workflows remain functionally identical; the PR's own CI run (which exercises them) is green.Proposed
NFR-002Scope boundary respected.Zero edits to release.yml, release-readiness.yml, ci-nightly.yml, docs-pages.yml.Proposed
NFR-003Workflow YAML validity.actionlint / YAML parse clean on every edited file.Proposed

Constraints

IDConstraintStatus
C-001Code change and hotspot review are SEPARATE actions (charter Sonar expectations). Safe-as-written/FP findings get NO code edit — only adjudication.Active
C-002SHA-pin to each action's OWN current version SHA — never unify versions (a version change is out of scope).Active
C-003Permission relocation MUST enumerate every job that uses the grant before moving it; never drop a needed permission.Active
C-004uv sync --frozen (hatchling self-install) MUST NOT receive --no-build/--only-binary; it must build the project.Active

Key Entities

  • In-scope workflow — one of the eight PR-CI-validated files: ci-quality, ci-windows, module-tests, ci-aggregate, ci-fleet-verdict, ci-modules, check-spec-kitty-events-alignment, packs.
  • Adjudication finding — a safe-as-written or false-positive Sonar finding recorded with rationale, not edited.

Success Criteria (mandatory)

Measurable Outcomes

  • Every in-scope S7630/S7637/S8264/S8233/real-install finding is remediated with a behavior-identical edit; PR CI green.
  • The 46 uv sync --frozen S8541 hotspots + FPs are adjudicated with rationale and receive zero edits.
  • No edits to any out-of-scope (release-critical / deferred) workflow.