Skip to content

sec(code-quality): whole-tree action-pin gate, advisory until the #1491 sweep lands (backend#1492, D10) - #159

Merged
LukasWodka merged 3 commits into
developfrom
sec/1492-action-pin-gate
Aug 5, 2026
Merged

sec(code-quality): whole-tree action-pin gate, advisory until the #1491 sweep lands (backend#1492, D10)#159
LukasWodka merged 3 commits into
developfrom
sec/1492-action-pin-gate

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Closes the recurrence class backend#1492 documents: while #1446 pinned an action, #1449 added a new unpinned call site of the same action in a non-overlapping hunk — both merged conflict-free; only a human diff-read caught it. The gate makes that silent recurrence impossible:

  • Whole-tree, never diff-scoped (the PR-mode-gitleaks lesson) — every workflow file, every run.
  • Strict line grammar, fail-closed: comment lines excluded; uses: values that cannot be verified (e.g. ${{ ... }} expressions) are findings, not skips (property 2). Allowed: ./local, tracebloc/*@main (Q3 — any other tracebloc ref is drift), owner/action@<40-hex> (D10), docker://…@sha256:….
  • Default-on for every caller at the next @main run, but advisory via action-pins-soft-fail: true (the format-soft-fail migration shape) so the armed repos don't redden while #1491's fleet sweep is still merging. Flip the default once that lands — that flip IS the arming step, and the ticket stays open until it happens.

The job's own annotations on this PR demonstrate the finding format against .github's own workflows.

Part of tracebloc/backend#1492.

🤖 Generated with Claude Code


Note

Medium Risk
Changes org-wide CI behavior for every code-quality caller (new default job), but findings are advisory by default; mis-parsing edge-case uses: syntax could produce false positives once soft-fail is turned off.

Overview
Adds an action-pins job to the reusable code-quality workflow so every run scans all workflow files for supply-chain pinning (D10), not just PR diffs—closing the case where a second PR could add a new unpinned uses: without conflicting with a pin fix.

Callers get new inputs action-pins (default on) and action-pins-soft-fail (default true, same migration pattern as format-soft-fail) so repos with an existing unpinned backlog get annotations and a step summary without failing until the #1491 fleet sweep finishes.

The scanner is an inline Python step: line-level uses: grammar, allows ./ local actions, tracebloc/*@main, 40-char SHA pins, and docker://…@sha256:…; flags unpinned refs, non-@main tracebloc refs, and unverifiable expression refs. Scan integrity errors (zero workflow files) exit 2 even under soft-fail.

Reviewed by Cursor Bugbot for commit 6eda0dd. Bugbot is set up for automated code reviews on this repo. Configure here.

…91 sweep lands (backend#1492, D10)
Hand-pinning does not hold (#1446/#1449: a second unpinned call site of
the SAME action merged cleanly while the pin PR was open). New default-on
action-pins job: strict line grammar, comment-safe, whole-tree always;
tracebloc/*@main allowed by Q3, 40-hex pins by D10, digest-pinned
docker://, local ./ actions; expression refs are refused, not skipped
(property 2). action-pins-soft-fail defaults true (format-soft-fail's
migration shape) so armed repos do not redden before #1491 merges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread.github/workflows/code-quality.yml
…ion marker from parts (Bugbot + actionlint, #159)
A tracebloc ref frozen on a SHA is drift, not a pin - the owner check
must come first. And actionlint validates expressions inside run:
blocks, so the Python source must never contain a literal expression
opener even in a string.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 483b9f8. Configure here.

Comment thread.github/workflows/code-quality.yml Outdated
Comment thread.github/workflows/code-quality.yml Outdated
…mmary carries the full finding list (Bugbot round 2, #159)
Any caller repo has at least one workflow (its own caller), so an
empty glob means the checkout/cwd is wrong - fails even under
soft-fail, which governs findings, never scan integrity. And the
ten-annotation cap made a backlog repo show ten warnings and a bare
count - the summary now lists every finding like the sibling jobs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka
LukasWodka merged commit 8e89793 into developAug 5, 2026
2 of 4 checks passed
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

FR on staging → Ready for prod (staging FR sweep, 2026-08-06). Basis: CI/tooling-only change with no product runtime surface — verified green through code review + Bugbot on develop and the fr-gated staging promotion. No product-behavior verification required for this class.

LukasWodka added a commit that referenced this pull request Aug 6, 2026
First push of this branch produced a startup_failure on the reusable call
(run 31086491251): every other check reported normally and `Code quality`
never started. Cause: `action-pins` and `action-pins-soft-fail` were added in
#159, which is on `develop`; `main` still carries the five-job version of
code-quality.yml. Callers must reference @main (Q3), and passing an input the
@main callee does not declare fails the entire call before any job runs.
Same merge-order trap as the #160 audit red: a change becomes consumable by
@main callers when it is PROMOTED, not when it is merged.
The other five jobs still run armed, so most of backend#1603 closes now. The
arming diff is written verbatim in the file so the follow-up is mechanical
once #159 reaches main.
Refs backend#1603.
LukasWodka added a commit that referenced this pull request Aug 6, 2026
…1603) (#171)
* feat(ci): run the org's own code-quality suite on this repo (backend#1603)
This repo publishes code-quality.yml -- and its action-pins job -- to all 19
other repos, and was the only repo that never ran it. The exemption was
invisible because actionlint.yml looks like coverage: it type-checks every
expression, validates needs/uses wiring, and shellchecks every run: block.
What it does not do is look at action REFS. So the pinning rule this repo
defines was the one rule this repo was not subject to.
That is not theoretical. On 2026-08-06 .github#168 -- an otherwise correct
fix -- reverted actions/checkout from its pinned commit SHA back to the
mutable @v4 tag, in advance-deploy-env.yml, the most-consumed reusable
workflow in the org and one that runs with PROJECTS_KANBAN_TOKEN in scope.
action-pins exists to fail exactly that diff. It did not run. A human reading
the diff caught it, which is the review path D10 was written to stop
depending on.
Armed from day one rather than soft-failed into a backlog, because the tree
was measured first: shellcheck --severity=error clean over both shell
scripts, house-rules.sh clean over the same, and 24 `uses:` refs across 23
workflows with zero pin violations. ruff and gitleaks could not be run
locally and get their first honest look on this PR; whatever they surface is
fixed or baselined here rather than the gate being softened -- the same
bargain e2e-test-agent's caller records in its own comment.
action-pins-soft-fail is set to false explicitly rather than left to inherit
soft-fail. It is the check whose absence let #168 through, and every other
repo consumes these workflows at @main and inherits whatever refs they pin,
so this repo has a specific duty to enforce it on itself.
actionlint.yml stays separate. The two gates have deliberately different
postures -- actionlint blocks from day one against a tree cleaned in the same
change; code-quality ships soft-fail: true for repos still clearing backlog
-- and folding them together would force one posture onto both.
No paths: filter, so the check can be made required without leaving
workflow-free PRs waiting on a status that never reports.
Refs backend#1603, backend#1491 (D10), backend#1276.
* fix(ci): drop the action-pins inputs -- @main does not declare them yet
First push of this branch produced a startup_failure on the reusable call
(run 31086491251): every other check reported normally and `Code quality`
never started. Cause: `action-pins` and `action-pins-soft-fail` were added in
#159, which is on `develop`; `main` still carries the five-job version of
code-quality.yml. Callers must reference @main (Q3), and passing an input the
@main callee does not declare fails the entire call before any job runs.
Same merge-order trap as the #160 audit red: a change becomes consumable by
@main callers when it is PROMOTED, not when it is merged.
The other five jobs still run armed, so most of backend#1603 closes now. The
arming diff is written verbatim in the file so the follow-up is mechanical
once #159 reaches main.
Refs backend#1603.
LukasWodka added a commit that referenced this pull request Aug 6, 2026
This branch changed `actions/checkout@11d5960a…` to the mutable `@v4` tag in
advance-deploy-env.yml — the most-consumed reusable workflow in the org, and
one that runs with PROJECTS_KANBAN_TOKEN in scope. A floating major tag can
be repointed by the upstream owner at any commit, which is exactly what D10
pinning removes.
Nothing in CI would have caught it: tracebloc/.github runs only its own
actionlint.yml, which does not inspect action refs, and it does not call the
code-quality workflow whose action-pins job exists to fail this diff. That
gap is now backend#1603 (.github#171, merged) — but action-pins itself cannot
arm here until #159 reaches main, so for the moment this restore is the only
thing standing between the unpin and every repo that consumes these
workflows at @main.
No functional change to the backend#1600 fix.
LukasWodka added a commit that referenced this pull request Aug 6, 2026
… (backend#1600) (#168)
* fix(kanban): advance the ISSUES each promoted PR closes, not just the PR (backend#1600)
advance-deploy-env advanced only PRs; issues closed by a develop-merged PR were
parked at On dev by the closure-router and never advanced when their code shipped,
drifting permanently. For each PR in the push, resolve closingIssuesReferences
(cross-repo aware) and advance each closing issue with the same monotonic guard,
archived-skip and (new) dry-run behaviour. Adds a dry_run input; update_field
short-circuits under it so both the PR and issue paths can be tested without writes.
* fix(kanban): only advance CLOSED closing-issues (Bugbot .github#168 High)
PRs merge to develop (not the default branch), so closingIssuesReferences lists
still-OPEN issues; advancing those into deploy columns is wrong and reconcile
bounces them to Backlog. Gate the advancement on issue.state == CLOSED.
* sec(ci): restore the actions/checkout SHA pin (backend#1491 D10)
This branch changed `actions/checkout@11d5960a…` to the mutable `@v4` tag in
advance-deploy-env.yml — the most-consumed reusable workflow in the org, and
one that runs with PROJECTS_KANBAN_TOKEN in scope. A floating major tag can
be repointed by the upstream owner at any commit, which is exactly what D10
pinning removes.
Nothing in CI would have caught it: tracebloc/.github runs only its own
actionlint.yml, which does not inspect action refs, and it does not call the
code-quality workflow whose action-pins job exists to fail this diff. That
gap is now backend#1603 (.github#171, merged) — but action-pins itself cannot
arm here until #159 reaches main, so for the moment this restore is the only
thing standing between the unpin and every repo that consumes these
workflows at @main.
No functional change to the backend#1600 fix.
LukasWodka added a commit that referenced this pull request Aug 6, 2026
…backend#1603 step 2) (#178)
The second half of backend#1603. When code-quality-caller.yml landed in #171 it
could not arm action-pins: the job and its two inputs were added in #159 and
existed on `develop` only, and a caller must reference @main (Q3). Passing an
input the @main callee does not declare kills the entire reusable call with a
startup_failure - not the one job, the whole call - measured on that branch's
first push, run 31086491251.
#159 has since promoted, and `main` now carries the six-job version with both
inputs. Verified against the @main copy before flipping, rather than assuming
the promotion carried what it looks like it carried.
Hard-armed rather than left to inherit soft-fail. This is the check whose
absence let .github#168 revert actions/checkout from a pinned commit SHA to the
mutable @v4 tag with nothing in CI to object - in advance-deploy-env.yml, the
most-consumed reusable workflow in the org, running with PROJECTS_KANBAN_TOKEN
in scope. Every other repo consumes these workflows at @main and inherits
whatever refs they pin, so this repo has a specific duty to enforce the rule it
publishes.
Re-measured the tree before arming: 24 `uses:` refs across 23 workflows, zero
violations. Arming imports no backlog.
LukasWodka added a commit that referenced this pull request Aug 11, 2026
…d (backend#1668) (#215)
`.github`'s own entry said "action-pins itself stays unarmed until #159 reaches
main". #159 reached main and action-pins was armed on 2026-08-06 (backend#1603
step 2), but the line was never updated -- so the single source of truth denied a
control the repo actually has.
Measured on origin/develop: this repo's `code-quality-caller.yml` passes
`action-pins: true` + `action-pins-soft-fail: false`, and `quality / action-pins`
is a REQUIRED status check on develop.
The original caveat is kept rather than deleted, because it explains WHY arming
had to wait: a caller may only pass inputs the @main callee declares, or the
whole call dies with startup_failure.
Comment-only; selftest re-run: 97 pass, 0 fail.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka
LukasWodka deleted the sec/1492-action-pin-gate branch August 14, 2026 13:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@LukasWodka