feat(publish): require a SHA-pinned caller before signing - #858
Conversation
The publish workflows mint keyless cosign certificates whose SAN records github.job_workflow_ref, and the cluster's image and artifact trust rules verify against it. Accepting a branch or a moving tag lets a superseded revision of the workflow mint a signature the cluster still trusts. Require the caller to pin by 40-character commit SHA before either workflow checks out or signs anything, and prove it with a test that executes the guard script extracted from the workflow against every ref shape. Part of devantler-tech/platform#2818
✅MegaLinter analysis: Success
Notices📣 MegaLinter 9.5.0 is out! Discover the new features and security recommendations in the release announcement. (Skip this info by defining See detailed reports in MegaLinter artifacts Your project could benefit from a custom flavor, which would allow you to run only the linters you need, and thus improve runtime performances. (Skip this info by defining
|
github.job_workflow_ref is not exposed in the expression context: measured empty inside a called reusable workflow, while github.workflow_ref resolves. The first version of this guard would therefore have failed every real publish while passing CI, because the dry-run test never reaches the publish job. Resolve the ref from the OIDC token's job_workflow_ref claim instead, which is the same value Fulcio copies into the certificate SAN the cluster verifies.
The probe established the OIDC claim resolves inside a called reusable workflow and named the called workflow rather than the caller's entry workflow; its result is recorded on the pull request.
Verification recordThe first version of this guard was wrong, and only an E2E probe could show itI first read the caller ref from A temporary probe job inside The property is empty. The guard would have evaluated an empty ref, failed closed, and broken What the value actually isRe-probed against the OIDC token, which is where the claim really lives — and which is also the Two things this settles: the claim names the called workflow ( Note this PR-merge ref is a ref the guard rejects, which is correct: a Over-tightening controlEvery repo that calls a publish workflow today, and how it pins: Test and ablations
Each ablation was applied and re-run; every one turns the test red:
Two arms initially failed to apply (a Linters
Known limits, stated rather than papered over
|
CI is green at @coderabbitai review |
|
✅ Action performedReview finished.
|
|
Warning Review limit reached
Next review available in: 4 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThe publish workflows add an opt-in Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/tests/test-publish-caller-pin.sh:
- Around line 63-76: Add a caller resolve-step index lookup for the step with id
"caller" and assert it is present and precedes guard_index. Update the ordering
assertions around guard_index and checkout_index so the test verifies
caller_index < guard_index while preserving the existing guard-before-checkout
validation.
In @.github/workflows/publish-app.yaml:
- Around line 50-66: Add a bounded curl timeout to the OIDC token request in the
caller step, specifically the curl command assigning token, by including an
appropriate --max-time value while preserving its existing authorization,
audience, and fail-fast behavior.
In @.github/workflows/publish-manifests.yaml:
- Around line 73-86: Document this SHA-pinned caller check as an intentional
security exception to the default-off requirement for new reusable-workflow
behavior, explaining why it must remain unconditional and cannot be safely
disabled. Keep the validation in the “Require a SHA-pinned caller” step
unchanged.
- Around line 56-72: Add a connection and overall execution timeout to the curl
invocation that retrieves the OIDC token in the caller ref resolution step,
ensuring a stalled endpoint fails promptly while preserving the existing token
parsing and output behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: a97ff913-a922-470d-923a-87dd29ee4ab4
📒 Files selected for processing (4)
.github/tests/test-publish-caller-pin.sh.github/workflows/ci.yaml.github/workflows/publish-app.yaml.github/workflows/publish-manifests.yaml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
.github/workflows/*.yaml
📄 CodeRabbit inference engine (AGENTS.md)
.github/workflows/*.yaml: Keep all GitHub Actions workflows under.github/workflows/.
Reusable workflows must use theworkflow_calltrigger.
Pin every remote action reference to a full commit SHA with a version comment; do not use remote self-references.
Includestep-security/harden-runneras the first step of every reusable-workflow job, withegress-policy: audit.
Set top-level workflow permissions to{}and grant permissions per job.
Setpersist-credentials: falseonactions/checkoutunless the job must push.
Workflows used as organization-level rulesets must includepull_requestandmerge_grouptriggers in addition toworkflow_call.
For reusable workflows referencing a sibling action, check out the workflow repository at${{ job.workflow_sha }}into.devantler-tech-actions, then invoke the action locally; remove the checkout before workspace-wide scans or commits.
New reusable-workflow jobs, steps, or behaviors must be behind a default-off boolean opt-in input and guarded withif: ${{ inputs.<enable-x> }}.
When a workflow supports bothworkflow_dispatchandworkflow_call, normalize boolean inputs withinputs.<enable-x> == true || inputs.<enable-x> == 'true'.
Test both enabled and disabled states of every feature flag with CI test jobs.
Gating reusable workflows must have both a passing self-test and a failing-input self-test that verifies the expected finding; non-gating workflows require happy-path coverage.
Preserve tested consumer contracts, such asvalidate-go-project.yamlhonoring.govulncheck-allow.txt; update the corresponding self-tests whenever the implementation changes.
Files:
.github/workflows/publish-manifests.yaml.github/workflows/ci.yaml.github/workflows/publish-app.yaml
.github/workflows/ci.yaml
📄 CodeRabbit inference engine (AGENTS.md)
.github/workflows/ci.yaml: Add a test job for every action and reusable workflow, using local paths such asuses: ./<action>oruses: ./.github/workflows/<workflow>.yaml.
Wire every new test job intoci-required-checksboth throughneeds:and thejob-resultsinput ofaggregate-job-checks.
Files:
.github/workflows/ci.yaml
.github/tests/**
📄 CodeRabbit inference engine (AGENTS.md)
Place deliberately bad fixtures for gating-workflow tests outside the gate's normal scan scope.
Files:
.github/tests/test-publish-caller-pin.sh
🪛 ast-grep (0.45.0)
.github/tests/test-publish-caller-pin.sh
[error] 82-82: A variable, parameter expansion, or command-substitution result is passed as the command string to bash -c / sh -c, so its value is re-parsed by the shell. If any part of that value is attacker-controlled (arguments, environment, file contents, network output), it allows arbitrary command execution. Do not interpolate dynamic data into -c: pass the script as a fixed literal and forward untrusted values as positional arguments (bash -c 'program ""' _ "$value"), invoke the target program directly with proper quoting, or restrict input to a validated allowlist first.
Context: "$script"
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(bash-c-variable-injection-bash)
🔇 Additional comments (3)
.github/tests/test-publish-caller-pin.sh (1)
77-113: LGTM!.github/workflows/ci.yaml (1)
1903-1906: LGTM!.github/workflows/publish-app.yaml (1)
67-80: 🗄️ Data Integrity & IntegrationNo change needed for the caller-pin guard.
This step enforces an existing required-call contract and cannot be expressed safely behind a
job.workflow_sha/pull_request.event.repository.head_shaequivalent without changing the caller model.
Addresses the review: new reusable-workflow behaviour must not go live for every consumer on merge (AGENTS.md, 'Shipping a new capability behind an opt-in flag'). That applies with extra force here, because the guard only runs on a real publish and CI exercises these workflows in dry-run only, so a misfire would first surface at release time. Also bound the OIDC token request with --max-time so it cannot hang a publish job, and assert both the flag contract and the resolve-before-guard ordering.
All four findings from the previous round are fixed and their threads resolved. CI is green at Changes since the reviewed head: the guard now ships behind an opt-in @coderabbitai review |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git diff --find-renames --unified=80 ae5813cb3dd9ed525458a75c6c4fc53744f1c4da^ ae5813cb3dd9ed525458a75c6c4fc53744f1c4da -- \
.github/workflows/publish-app.yaml \
.github/workflows/publish-manifests.yaml \
.github/workflows/ci.yaml \
.github/tests/test-publish-caller-pin.sh
printf '\n--- workflow outlines ---\n'
ast-grep outline .github/workflows/publish-app.yaml --items all || true
ast-grep outline .github/workflows/publish-manifests.yaml --items all || true
ast-grep outline .github/workflows/ci.yaml --items all || true
printf '\n--- test file ---\n'
cat -n .github/tests/test-publish-caller-pin.sh
printf '\n--- reusable-workflow call sites ---\n'
rg -n -C 4 'uses:\s*.*\.github/workflows/(publish-app|publish-manifests)\.yaml@' .githubLength of output: 31791 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- enable-caller-pin references ---'
rg -n -C 5 --glob '*.y*ml' --glob '*.yaml' 'enable-caller-pin' .
printf '%s\n' '--- publish reusable-workflow references ---'
rg -n -C 8 --glob '*.y*ml' --glob '*.yaml' 'publish-(app|manifests)\.yaml@' .
printf '%s\n' '--- CI workflow ---'
cat -n .github/workflows/ci.yaml
printf '%s\n' '--- repository instructions ---'
if [ -f AGENTS.md ]; then
cat -n AGENTS.md
else
printf '%s\n' 'AGENTS.md is absent'
fi
printf '%s\n' '--- workflow input and condition values ---'
for workflow in .github/workflows/publish-app.yaml .github/workflows/publish-manifests.yaml; do
printf '\n%s\n' "$workflow"
yq -r '
.on.workflow_call.inputs["enable-caller-pin"],
(.jobs[].steps[] | select(.id == "caller" or .name == "🔒 Require a SHA-pinned caller") | {id, name, if})
' "$workflow"
done
printf '%s\n' '--- changed files at reviewed head ---'
git diff-tree --no-commit-id --name-status -r ae5813cb3dd9ed525458a75c6c4fc53744f1c4daLength of output: 1365 🏁 Script executed: #!/bin/bash
set -u -o pipefail
head='ae5813cb3dd9ed525458a75c6c4fc53744f1c4da'
printf '%s\n' '--- commit identity and available files ---'
git show -s --format='%H%n%P%n%s' "$head"
git ls-tree -r --name-only "$head" -- .github AGENTS.md | sed -n '1,240p'
printf '%s\n' '--- enable-caller-pin references at reviewed head ---'
git grep -n -C 5 -e 'enable-caller-pin' "$head" -- '*.yaml' '*.yml' || true
printf '%s\n' '--- publish reusable-workflow references at reviewed head ---'
git grep -n -E -C 8 'publish-(app|manifests)\.yaml@' "$head" -- '*.yaml' '*.yml' || true
printf '%s\n' '--- CI workflow at reviewed head ---'
git show "${head}:.github/workflows/ci.yaml" | cat -n
printf '%s\n' '--- repository instructions at reviewed head ---'
git show "${head}:AGENTS.md" 2>/dev/null | cat -n || printf '%s\n' 'AGENTS.md is absent at reviewed head'
printf '%s\n' '--- complete changed-file list at reviewed head ---'
git diff-tree --no-commit-id --name-status -r "$head"
printf '%s\n' '--- workflow input and condition values at reviewed head ---'
for workflow in .github/workflows/publish-app.yaml .github/workflows/publish-manifests.yaml; do
printf '\n%s\n' "$workflow"
git show "${head}:${workflow}" | yq -r '
.on.workflow_call.inputs["enable-caller-pin"],
(.jobs[].steps[] | select(.id == "caller" or .name == "🔒 Require a SHA-pinned caller") | {id, name, if})
'
doneLength of output: 214684
Add enabled-state test calls for both workflows. The calls can retain ✅ Action performedReview finished.
|
CodeRabbit is rate-limited at this head — "you've reached your PR review limit, so we couldn't start Cursor Bugbot is usage-limited portfolio-wide with no stated retry window, so it is a maintainer @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae5813cb3d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Addresses the Codex round: the enabled state had no standing coverage, the new input was undocumented, and a ci: commit would not cut a release for consumers to pin to. Add a CI job that runs the resolver EXTRACTED from publish-app.yaml against a real OIDC token and asserts the claim resolves, then asserts the shipped guard rejects the unpinned refs/pull/N/merge caller a CI run presents — the failure-mode coverage convention, and the only way to exercise this integration without a destructive publish. Document enable-caller-pin in both README input tables.
All three Codex findings are fixed and their threads resolved; CI is green at Since the last CodeRabbit review at @coderabbitai review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
head='c8eceff3a8165a0c5b524baa2bdf84f294e75758'
base='0b4f5bb604475f84c62d92f311be19e16dda8a3e'
printf '%s\n' '--- commit and changed files since prior reviewed head ---'
git show -s --format='%H%n%P%n%s' "$head"
git diff --name-status "$base" "$head"
printf '%s\n' '--- full diff since prior reviewed head ---'
git diff --find-renames --unified=100 "$base" "$head" -- \
.github/workflows/publish-app.yaml \
.github/workflows/publish-manifests.yaml \
.github/workflows/ci.yaml \
.github/tests/test-publish-caller-pin.sh \
README.md
printf '%s\n' '--- relevant workflow structures ---'
for file in .github/workflows/publish-app.yaml .github/workflows/publish-manifests.yaml .github/workflows/ci.yaml; do
printf '\n### %s\n' "$file"
git show "${head}:${file}" | yq -C '.' | sed -n '1,260p'
done
printf '%s\n' '--- caller-pin static test ---'
git show "${head}:.github/tests/test-publish-caller-pin.sh" | cat -n
printf '%s\n' '--- README caller-pin documentation ---'
git show "${head}:README.md" | rg -n -C 8 'enable-caller-pin|publish-app|publish-manifests' || true
printf '%s\n' '--- reusable workflow call sites at current head ---'
git grep -n -E -C 8 'uses:\s*.*\.github/workflows/publish-(app|manifests)\.yaml@' "$head" -- '*.yaml' '*.yml' || true
printf '%s\n' '--- repository instructions ---'
git show "${head}:AGENTS.md" 2>/dev/null | sed -n '1,220p' || trueLength of output: 64103 🏁 Script executed: #!/bin/bash
set -euo pipefail
head='c8eceff3a8165a0c5b524baa2bdf84f294e75758'
show_range() {
local path="$1"
local start="$2"
local end="$3"
printf '\n--- %s, lines %s-%s ---\n' "$path" "$start" "$end"
git show "${head}:${path}" | nl -ba | sed -n "${start},${end}p"
}
show_range ".github/workflows/publish-app.yaml" 1 130
show_range ".github/workflows/publish-manifests.yaml" 1 125
show_range ".github/workflows/ci.yaml" 2240 2395
show_range ".github/workflows/ci.yaml" 3000 3135
show_range ".github/tests/test-publish-caller-pin.sh" 1 210
show_range "AGENTS.md" 90 145
show_range "README.md" 515 635
printf '\n--- all current reusable publish workflow calls ---\n'
git grep -n -E -C 6 'uses:[[:space:]]*.*\.github/workflows/publish-(app|manifests)\.yaml@' \
"$head" -- '*.yaml' '*.yml' || true
printf '\n--- live OIDC and caller-pin references ---\n'
git grep -n -E -C 4 'test-publish-caller-pin-live|enable-caller-pin|job_workflow_ref|--max-time' \
"$head" -- '.github/workflows/*.yaml' '.github/tests/*.sh' || trueLength of output: 1440 🏁 Script executed: #!/bin/bash
set -euo pipefail
head='c8eceff3a8165a0c5b524baa2bdf84f294e75758'
show_range() {
local path="$1"
local start="$2"
local end="$3"
printf '\n--- %s, lines %s-%s ---\n' "$path" "$start" "$end"
git show "${head}:${path}" | cat -n | sed -n "${start},${end}p"
}
show_range ".github/workflows/publish-app.yaml" 1 130
show_range ".github/workflows/publish-manifests.yaml" 1 125
show_range ".github/workflows/ci.yaml" 2240 2395
show_range ".github/workflows/ci.yaml" 3000 3135
show_range ".github/tests/test-publish-caller-pin.sh" 1 210
show_range "AGENTS.md" 90 145
show_range "README.md" 515 635
printf '\n--- all current reusable publish workflow calls ---\n'
git grep -n -E -C 6 'uses:[[:space:]]*.*\.github/workflows/publish-(app|manifests)\.yaml@' \
"$head" -- '*.yaml' '*.yml' || true
printf '\n--- live OIDC and caller-pin references ---\n'
git grep -n -E -C 4 'test-publish-caller-pin-live|enable-caller-pin|job_workflow_ref|--max-time' \
"$head" -- '.github/workflows/*.yaml' '.github/tests/*.sh' || trueLength of output: 73785
The new Live OIDC job does not call either reusable workflow. It extracts scripts from
with:
dry-run: true
enable-caller-pin: trueThese calls validate the public You are interacting with an AI system. |
CodeRabbit chat-routed this request rather than queueing a review — an Analysis chain plus Codex previously reviewed at @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c8eceff3a8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The job holds id-token: write and executes the resolver extracted from publish-app.yaml. On a pull_request the workspace is PR-controlled, so a modified resolver could mint and exfiltrate an OIDC token — precisely the hazard this file's header names, and the reason the Zizmor self-test is already push-only. Gate it to push, matching that precedent. A regression now surfaces post-merge rather than on the PR; the static contract test still runs in the PR lane.
Taking ownership of this PR — the previous run's window elapsed at 12:30Z with no live process and State verified live at @coderabbitai review |
|
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/ci.yaml (1)
2267-2278: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winExercise the enabled state of
enable-caller-pinthrough the publicworkflow_callinterface.
test-publish-app(Line 2267) andtest-publish-manifests(Line 2347) call the reusable workflows only withdry-run: true. Neither setsenable-caller-pin: true. The enabled state of the flag is only exercised by directly executing extracted script fragments intest-publish-caller-pin.shandtest-publish-caller-pin-live, never through an actualworkflow_callinvocation.Add
enable-caller-pin: trueto both dry-run calls. Thepublishjob in each reusable workflow is skipped whendry-run: true(job-levelif: ${{ !inputs.dry-run }}), so this addition is safe. It validates that the input schema accepts the flag without a typo or type regression, which script-extraction tests cannot catch.🧪 Proposed diff
test-publish-app: ... with: app-name: app dry-run: true + enable-caller-pin: truetest-publish-manifests: ... with: dry-run: true + enable-caller-pin: trueAs per coding guidelines, "Test both enabled and disabled states of every feature flag with CI test jobs."
Also applies to: 2347-2357
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci.yaml around lines 2267 - 2278, Update both the test-publish-app and test-publish-manifests reusable workflow calls to pass enable-caller-pin: true alongside dry-run: true, exercising the enabled flag through the public workflow_call interface while preserving their existing dry-run behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In @.github/workflows/ci.yaml:
- Around line 2267-2278: Update both the test-publish-app and
test-publish-manifests reusable workflow calls to pass enable-caller-pin: true
alongside dry-run: true, exercising the enabled flag through the public
workflow_call interface while preserving their existing dry-run behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 7dd2cf31-531f-4ddf-946d-f34996a5cb33
📒 Files selected for processing (5)
.github/tests/test-publish-caller-pin.sh.github/workflows/ci.yaml.github/workflows/publish-app.yaml.github/workflows/publish-manifests.yamlREADME.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
.github/tests/**
📄 CodeRabbit inference engine (AGENTS.md)
Place deliberately bad fixtures for gating-workflow tests outside the gate's normal scan scope.
Files:
.github/tests/test-publish-caller-pin.sh
.github/workflows/*.yaml
📄 CodeRabbit inference engine (AGENTS.md)
.github/workflows/*.yaml: Keep all GitHub Actions workflows under.github/workflows/.
Reusable workflows must use theworkflow_calltrigger.
Pin every remote action reference to a full commit SHA with a version comment; do not use remote self-references.
Includestep-security/harden-runneras the first step of every reusable-workflow job, withegress-policy: audit.
Set top-level workflow permissions to{}and grant permissions per job.
Setpersist-credentials: falseonactions/checkoutunless the job must push.
Workflows used as organization-level rulesets must includepull_requestandmerge_grouptriggers in addition toworkflow_call.
For reusable workflows referencing a sibling action, check out the workflow repository at${{ job.workflow_sha }}into.devantler-tech-actions, then invoke the action locally; remove the checkout before workspace-wide scans or commits.
New reusable-workflow jobs, steps, or behaviors must be behind a default-off boolean opt-in input and guarded withif: ${{ inputs.<enable-x> }}.
When a workflow supports bothworkflow_dispatchandworkflow_call, normalize boolean inputs withinputs.<enable-x> == true || inputs.<enable-x> == 'true'.
Test both enabled and disabled states of every feature flag with CI test jobs.
Gating reusable workflows must have both a passing self-test and a failing-input self-test that verifies the expected finding; non-gating workflows require happy-path coverage.
Preserve tested consumer contracts, such asvalidate-go-project.yamlhonoring.govulncheck-allow.txt; update the corresponding self-tests whenever the implementation changes.
Files:
.github/workflows/publish-manifests.yaml.github/workflows/publish-app.yaml.github/workflows/ci.yaml
**/*
📄 CodeRabbit inference engine (AGENTS.md)
**/*: Prefer additive, backward-compatible changes because composite actions and reusable workflows affect every consumer repository; prominently call out deliberate breaking input/output changes.
Use Conventional Commit types for changes:featfor minor releases,fix/perffor patch releases, and breaking-change notation for major releases; usefixorfeatwhen consumers must receive a workflow or action change promptly.
Files:
README.md
.github/workflows/ci.yaml
📄 CodeRabbit inference engine (AGENTS.md)
.github/workflows/ci.yaml: Add a test job for every action and reusable workflow, using local paths such asuses: ./<action>oruses: ./.github/workflows/<workflow>.yaml.
Wire every new test job intoci-required-checksboth throughneeds:and thejob-resultsinput ofaggregate-job-checks.
Files:
.github/workflows/ci.yaml
🔇 Additional comments (5)
.github/workflows/publish-app.yaml (1)
19-23: LGTM!Also applies to: 55-72, 73-87
.github/workflows/publish-manifests.yaml (1)
26-30: LGTM!Also applies to: 61-78, 79-93
README.md (1)
411-411: LGTM!Also applies to: 452-452
.github/tests/test-publish-caller-pin.sh (1)
63-99: LGTM!Also applies to: 113-114
.github/workflows/ci.yaml (1)
2279-2346: LGTM!Also applies to: 3108-3108, 3205-3205
The dry-run callers passed only dry-run: true, so the flag's enabled state was
reached only by executing script fragments extracted from the reusable workflows
-- never through a real workflow_call. Passing enable-caller-pin: true proves the
input schema accepts the name and type.
The guard itself still does not run here: yq confirms both guard steps sit inside
jobs gated on `if: ${{ !inputs.dry-run }}`, so a dry-run call skips them. The
enabled BEHAVIOUR stays covered by test-publish-caller-pin.sh and the push-only
live OIDC job.
Ablated: typing the input name wrong makes actionlint report
"input ... is not defined in ... reusable workflow" (7 -> 8 findings); a wrong
TYPE is not caught by actionlint, so that half is covered at workflow_call time
by these jobs. actionlint is 7 on origin/main, 7 at the previous head and 7 here
-- none introduced.
Part of #2818
Round 5 (CodeRabbit @
|
| arm | actionlint |
|---|---|
| as written | 7 findings |
input name typoed (enable-caller-pinn) |
8 — input "enable-caller-pinn" is not defined in "./.github/workflows/publish-app.yaml" reusable workflow |
input type wrong ("yes-please" for a type: boolean) |
7 — not caught |
So the name half is caught statically, before CI even runs; the type half is not caught by
actionlint and is covered at workflow_call dispatch time by these very jobs. Both halves are real,
but they are enforced at different layers and it is worth saying which is which.
actionlint: 7 on origin/main, 7 at the previous head, 7 here — none introduced.
The 🔇 Additional comments (5) section is CodeRabbit's non-actionable category and carries no
finding. Zero unresolved threads.
Restarting the ordered review loop at CodeRabbit against the new head — every push stales the green.
Restarting the ordered loop at this head — the round-5 fix moved the head, so the previous result is State at @coderabbitai review |
|
✅ Action performedReview finished.
|
There is no review object at this head; the newest sits at the previous head Advancing to Codex per lane priority. Cursor Bugbot remains usage-limited portfolio-wide with no @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f331cebe9d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The previous commit set enable-caller-pin: true on both existing dry-run callers,
which added the enabled state and silently removed the only default-off coverage
of the workflow_call interface -- the repository requires both states be tested.
Codex caught it as a P2 on the exact line the previous fix added.
Restored by splitting rather than swapping: test-publish-app and
test-publish-manifests go back to OMITTING the input, which is the default-off
half and also proves the input is genuinely optional; two new sibling jobs pass
it explicitly for the enabled half. Both new jobs are wired into
ci-required-checks' needs list AND its results block -- derived structurally, not
by eye: 87 needs, 87 result references, zero on either side alone.
Neither new job runs the guard: publish-app.yaml and publish-manifests.yaml both
gate their publish job on `if: ${{ !inputs.dry-run }}`. The enabled BEHAVIOUR
stays covered by test-publish-caller-pin.sh and the push-only live OIDC job.
test-publish-caller-pin.sh and test-ci-merge-group-isolation.sh both pass;
actionlint 7, unchanged from origin/main.
Part of #2818
Round 6 (Codex @
|
| job | reusable workflow | enable-caller-pin |
|---|---|---|
test-publish-app |
publish-app.yaml |
omitted |
test-publish-app-caller-pin |
publish-app.yaml |
true |
test-publish-manifests |
publish-manifests.yaml |
omitted |
test-publish-manifests-caller-pin |
publish-manifests.yaml |
true |
The second half of the fix, which the finding did not ask for
A new job is only a test if something fails when it fails. ci-required-checks aggregates every
test job in both a needs: list and a results block, and a job missing from either is a job
whose failure passes. Both new jobs are wired into both — verified by deriving the two sets from the
parsed workflow rather than reading them: 87 needs, 87 result references, zero on either side
alone.
Neither new job runs the guard itself: both reusable workflows gate their publish job on
if: ${{ !inputs.dry-run }}. These remain schema checks; the enabled behaviour stays covered by
test-publish-caller-pin.sh and the push-only live OIDC job.
test-publish-caller-pin.sh and test-ci-merge-group-isolation.sh both pass. actionlint is 7,
unchanged from origin/main.
Restarting the ordered loop at CodeRabbit — though note it returned Review limit reached on this
PR at 12:29Z and again on platform#2900 at 12:39Z, so the org quota is currently exhausted; if it
answers the same way the loop records cr:no-gate and advances to Codex again.
Ownership: the tick-904 window on this PR expired at 14:00Z with no request at the current head; taking it over until 15:00Z. State at @coderabbitai review |
|
✅ Action performedReview finished.
|
Escalating to a full review — the incremental pass wedged. The 14:00:51Z request was acked 12 seconds later ("I will review the changes at @coderabbitai full review |
|
🐇🔎 ✅ Action performedFull review finished. Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 4 minutes. |
Re-triggering — the 14:19:57Z attempt was quota-blocked, not wedged after all. Its That window expired at ~14:24Z, and an included review was consumed on platform#2900 at 14:26:38Z, so the budget should have rolled over again by now. @coderabbitai full review |
|
Your plan includes PR reviews subject to rate limits. Reviews are available now. |
The middle one is why this took three attempts to call: a quota block and a wedge look identical from the visible headline, and only the Advancing to lane 2 rather than spending a fourth CodeRabbit trigger. Bugbot remains usage-limited portfolio-wide with no retry window, so it is escalation-only and not a third option here. @codex review |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Readiness condition 3 — re-recorded at
|

Why
Our cluster decides whether a first-party image or manifest bundle may run by checking who signed it. That check trusts a signature only if it came from these publish workflows — but it does not care which revision of them. So a caller that invokes a publish workflow by a branch or a moving tag can have an old, superseded revision mint a signature the cluster still accepts.
Every repo that publishes today already pins correctly, but nothing enforces it: the property held by convention only. Measured across the portfolio — all 7 callers pin by commit SHA, and none of the 5 product repos involved runs any workflow-security linting that would catch a regression.
What
The two publish workflows now refuse to run unless the caller pinned them to a commit SHA, checked before they check out or sign anything. Nothing to maintain: no list of approved revisions, and dependency automation already keeps the pins current.
Part of devantler-tech/platform#2818