Repository navigation
fix(security): scope SONAR_TOKEN to scan steps and fail unanalysed fork PRs (#1390, #1392) - #1417
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Dev-Lead — waiting on PR blockers (intent: review-changes)PR: #1417 |
|
Note @don-petry I reviewed this PR and no code changes were needed, but I can't mark it done yet: required checks |
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-10-28T04:25:20Z. |
|
No description provided. |
This comment has been minimized.
This comment has been minimized.
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
|
No description provided. |
Dev-Lead — review-changes (no-changes)No changes were needed for this PR. |
|
No description provided. |
|
Code review could not complete — operational notice with no specific findings. No code changes required. |
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
|
Auto-rebase failed — merge conflict — this branch has conflicts with dev-lead will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
…rk PRs (#1390, #1392) job that pip-installs and runs pytest on the checked-out revision. Coverage now runs in a separate `coverage` job with no secrets and hands coverage.xml to the `SonarCloud` job as an artifact. SONAR_TOKEN is in the env of the token gate and the two scan steps only, in both this repo's workflow and the standards template. The SonarCloud job runs when coverage failed (!cancelled) and fails explicitly, so a skipped required job cannot report success. decides the required check: explicit bootstrap (vars.SONAR_BOOTSTRAP == 'true') passes with a notice; a fork pull request fails; a same-repo run (day-0 repo, Dependabot) passes with a warning, as before. Tests: test/scripts/standards-templates/sonarcloud-token-gate.bats and test/workflows/required-checks/sonarcloud-token-scope.bats extract and run the shipped gate script and assert token scoping via yq. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5031bce to
0e1c996
Compare
Dev-Lead — rebase (applied)Rebase completed and pushed; PR is no longer conflicting. |
|
No description provided. |
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-10-28T12:50:54Z. |
|
@donpetry-bot please review |
|
@don-petry I'm on it — starting a fresh review now. Results will appear in a few minutes. |
|
@donpetry-bot please review |
|
@don-petry I'm on it — starting a fresh review now. Results will appear in a few minutes. |
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-11-03T01:23:06Z. |
|
@donpetry-bot please review |
|
@don-petry I'm on it — starting a fresh review now. Results will appear in a few minutes. |
don-petry
left a comment
There was a problem hiding this comment.
Reviewed the workflow token-isolation split (#1392), the fork-PR token gate (#1390), and the new BATS regression suites. The core security fixes and test coverage look solid — only two minor consistency nits noted inline.
…ed step env; docs checkout matches (#1392) The repo-workflow guard only counted references, so a dropped env token plus a stray copy elsewhere still totalled 3. Add the positive check the template's sibling test has. The ci-standards reference snippet now sets persist-credentials: false like the standard it documents. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@donpetry-bot please review |
|
@don-petry I'm on it — starting a fresh review now. Results will appear in a few minutes. |
…1390) pr-review at d241384: the bootstrap check ran before the fork check, so a leftover SONAR_BOOTSTRAP=true let unanalysed fork PRs pass the required check. The fork case now fails first, in both the repo workflow and the template, and bootstrap applies only to same-repo runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-11-03T20:37:56Z. |
|
@donpetry-bot please review |
|
@don-petry I'm on it — starting a fresh review now. Results will appear in a few minutes. |
|
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-11-04T00:53:39Z. |



Summary
Two SonarCloud workflow fixes:
SONAR_TOKENno longer shares a job with code from the revision under test.SonarCloudcheck no longer passes a fork pull request that it could not analyse.Both fixes are applied to this repo's
.github/workflows/sonarcloud.ymland to thestandards/workflows/sonarcloud.ymltemplate. A test keeps the token gate script identical in the two files.Important
Needs a human decision: fork PRs now fail the required
SonarCloudcheck (#1390).GitHub never passes secrets to fork runs. Once the hole is closed, a fork PR without
vars.SONAR_BOOTSTRAP == 'true'cannot get a greenSonarCloudcheck, so contributors see a behaviour change. This PR implements the most conservative secure version, described under #1390 below. The alternatives are listed there too.#1392: SONAR_TOKEN reachable by the code under test
Fixes #1392
Confirmed. At
.github/workflows/sonarcloud.yml:24-25onmain,SONAR_TOKENwas set in the job-levelenv. The same job ranpip install -r scripts/pytest-requirements.txt(L32) andpytest test/feature-ideation/test_validate_signals.py(L37) on the checked-out PR ormerge_grouprevision, so every step inherited the token. The new guardtest/workflows/required-checks/sonarcloud-token-scope.batsfails onmainwith "job 'sonarcloud' runs repo code and references a secret". The template (standards/workflows/sonarcloud.yml:37-38) used the same job-levelenvpattern. The template runs no repo code itself, but any build or test step an adopting repo added would inherit the token.Fix
coveragejob (SonarCloud coverage). That job hascontents: read,persist-credentials: falseand no secrets. It uploadscoverage.xmlas a 1-day artifact.SonarCloudjob (needs: coverage) downloads the artifact to the pathsonar-project.propertiesalready points at. It runs no repo code.SONAR_TOKENnow appears only in the stepenvof the token gate and of the two scan steps, never in a job- or workflow-levelenv. The same change is made in the template.SonarCloudjob therefore runsif: ${{ !cancelled() }}and fails explicitly whenneeds.coverage.result != 'success'. As before, failing tests still failSonarCloud.actions/upload-artifactv7.0.2 andactions/download-artifactv8.0.2 reuse SHAs already pinned in this repo. Both were re-verified withgh api repos/actions/<name>/git/ref/tags/<tag>.#1390: required SonarCloud check passes fork PRs without analysis
Fixes #1390
Confirmed. In
standards/workflows/sonarcloud.ymlonmain, both scan steps are guarded byif: env.SONAR_TOKEN != ''(L52, L56) and nothing else fails the job. A fork PR receives an empty token, both steps are skipped, and the job reports success to the requiredSonarCloudcontext (standards/rulesets/code-quality.json). This repo's own workflow behaved the same way. The new template guardtest/scripts/standards-templates/sonarcloud-token-gate.batsfails onmain: there is no gate step and the token sits in the jobenv.Fix: a
Check SonarCloud tokengate step. It outputspresent=true|falseand never echoes the token. Both scan steps now run only whensteps.token.outputs.present == 'true'.vars.SONAR_BOOTSTRAP == 'true'::notice(new explicit bootstrap opt-in)::errorthat explains why and how a maintainer can get the change analysed::warning(unchanged)Why same-repo runs without a token still pass. Per
standards/ci-standards.md,SONAR_TOKENis an org-level secret, so same-repo runs normally have it. The main same-repo run without a token is a Dependabot PR, because Dependabot runs do not receive Actions secrets. Failing that case would block Dependabot auto-merge org-wide. The other case is a genuine day-0 repo. Neither path is reachable by someone without write access.Decision needed / alternatives
SONAR_BOOTSTRAPis set. This ends the bootstrap exception for real, but breaks Dependabot PRs unlessSONAR_TOKENis also added as an org Dependabot secret.::warning. This is non-breaking but leaves standards sonarcloud.yml passes the required check when SONAR_TOKEN is unavailable, so fork PRs skip analysis permanently #1390 open.workflow_run/pull_request_targetthat scans without executing fork code). That is a bigger design and is out of scope here.I did not verify whether
varsare exposed to forkpull_requestruns. If they are not,SONAR_BOOTSTRAP=truewill not rescue fork PRs on a repo that is still bootstrapping, and those PRs fail, which is the fail-closed direction.Docs
standards/ci-standards.md§3 snippet and prose are updated. The doc now covers: step-scoped token, the separate secret-free coverage job with a!cancelled()guard, and the gate's behaviour table. The doc snippet's older action SHAs are left as they were.Tests
New bats files. Each one fails on
origin/mainand passes with this change.test/scripts/standards-templates/sonarcloud-token-gate.bats(11 tests). It extracts the template's gaterun:withyqand executes it for each row of the table above, including deleted head repo,SONAR_BOOTSTRAPother than exactlytrue, and the token never printed or written toGITHUB_OUTPUT. It also asserts there is no job- or workflow-levelSONAR_TOKEN, that only the gate and the scan steps reference the secret, that the scans are gated onpresent, and that the job name is unchanged. It runs in Standards Templates Tests.test/workflows/required-checks/sonarcloud-token-scope.bats(10 tests). It covers the same scoping and gate assertions for this repo's workflow. It also checks that the job running pip/pytest references no secrets, that coverage arrives via an artifact, that a failed coverage job failsSonarCloud, and that the gate script is identical to the template's. It runs in Required Checks Tests.required-checks-tests.ymlnow also triggers onstandards/workflows/sonarcloud.yml.yq(mikefarah), which is preinstalled onubuntu-latest. Ifyqis missing, they fail loudly instead of skipping.Local runs:
bats test/workflows/required-checks/ test/scripts/standards-templates/: 52/52 ok. This includes the existingmerge-group.bats, which still findsname: SonarCloud.bats tests/test_emit_standard_file_workflows.bats test/workflows/dependency-hardening.bats test/scripts/deploy-standard-workflows/, run under GNU bash 5: 130/131 ok. The one failure isdry-run.bats"dry-run skips a stub that is already compliant". It is a macOS-onlybase64 -wincompatibility onadd-to-project.ymland is unrelated to this change.actionlint: no findings in the changed workflows (pre-existing findings elsewhere unchanged). actionlint's shellcheck pass covers the gate script.yamllintwith theci.ymlconfig (pinnedscripts/ci-yamllint-requirements.txt): clean for the changed files.markdownlint-cli2 standards/ci-standards.md: 0 errors.shellcheckon the new bats files: clean.Follow-ups / release notes
sonarcloud.ymlis not deployed bydeploy-standard-workflows.sh. It ships viacombsmith/repo-template, so the template copy there needs a sync PR. Repos that already adopted it keep their copy until updated.test/feature-ideation/test_validate_signals.py(touched by open PR feat: implement issue #1361 — SonarCloud: code security review (pythonsecurity) #1383) is not modified. Only the job that runs it changed.sonar-project.properties. The scanner is not meant to execute repo code, but its scanner-side properties were not reviewed one by one.🤖 Generated with Claude Code
Problem
See the linked issue.
Risk
Medium — changes GitHub Actions workflow behavior, which is exercised only post-merge; verify via the affected workflow runs.
Test plan
Tests added/updated:
test/scripts/standards-templates/sonarcloud-token-gate.bats,test/workflows/required-checks/sonarcloud-token-scope.bats. Verification:bash scripts/dev-lead-lint.sh(shellcheck --severity=warning) ran pre-commit; the bats suite runs in CI.Rollback
Revert this PR. No non-revertible side effects (no tags, migrations, or external state).
Monitoring
Watch the affected workflow run(s) in the Actions tab and this PR's Lint check for regressions.