Problem
The sonarqube job in continuous-integration.yml runs on PRs from forks, but GitHub does not expose repository secrets to pull_request workflows triggered from a fork. SONAR_TOKEN is therefore empty and the scanner fails at startup, before analysing anything:
ERROR Error retrieving feature flags, using default analyzer behavior
Not authorized or project not found. Please check the 'SONAR_TOKEN' environment
variable, the 'sonar.projectKey' and 'sonar.organization' properties, or contact
the project administrator to verify the token's permissions.
INFO Check ALM binding of project 'scrumlr_scrumlr' -> Detected project binding: ERROR
INFO EXECUTION FAILURE (Total time: 14.650s)
##[error]Action failed: sonar-scanner failed with exit code 3
The result is a red check that carries no information: external contributions get no Sonar analysis at all, and the failure looks identical whether the code is clean or not.
Affected PRs
#6488, #6489, #6490 — all from almacbe/scrumlr.io. In all three:
| Check |
Result |
| Build and Test – Backend |
pass |
| Build and Test – Frontend |
pass |
| SonarQube |
fail (~14s, exit 3) |
| package, Cypress, e2e, deploy, docs |
skipping |
Root cause
The job is triggered by pull_request, which runs in the fork's context without access to secrets:
# .github/workflows/continuous-integration.yml:111
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
On same-repo branches the scanner runs to completion (EXECUTION SUCCESS) and any failure is a genuine Quality Gate result. On forks it never gets that far.
Note that the existing dependabot exclusion is not the same problem and should stay: those PRs only bump dependency versions and never touch repository code, so there are no Sonar metrics worth measuring. Fork PRs are the opposite case — they are exactly the contributions that need the quality gate — so skipping them is not an acceptable fix.
Options
-
Split the analysis into a workflow_run workflow (GitHub's recommended pattern for handling untrusted PR builds). The pull_request workflow builds and uploads the compiled output, coverage reports and PR metadata as artifacts; a second workflow triggered on workflow_run: completed runs in the base repo's context, has access to SONAR_TOKEN, downloads those artifacts and submits the analysis. Untrusted code is never executed with secrets in scope. Cost: one extra workflow file and some artifact plumbing.
-
Use pull_request_target for the Sonar job. Simpler to wire up, but it runs with secrets available in a context where the PR's own code is checked out, so it needs careful handling (pinned checkout of the base ref, no execution of fork-supplied scripts) to avoid secret exfiltration. Only worth it if option 1 is considered too heavy.
Option 1 is the safer of the two and the one I would suggest. Happy to open the PR implementing it if you agree on the approach.
Edited: option 1 originally read "SonarSource's recommended pattern for forks". That attribution was wrong and has been corrected above — see this comment.
Problem
The
sonarqubejob incontinuous-integration.ymlruns on PRs from forks, but GitHub does not expose repository secrets topull_requestworkflows triggered from a fork.SONAR_TOKENis therefore empty and the scanner fails at startup, before analysing anything:The result is a red check that carries no information: external contributions get no Sonar analysis at all, and the failure looks identical whether the code is clean or not.
Affected PRs
#6488, #6489, #6490 — all from
almacbe/scrumlr.io. In all three:Root cause
The job is triggered by
pull_request, which runs in the fork's context without access to secrets:On same-repo branches the scanner runs to completion (
EXECUTION SUCCESS) and any failure is a genuine Quality Gate result. On forks it never gets that far.Note that the existing dependabot exclusion is not the same problem and should stay: those PRs only bump dependency versions and never touch repository code, so there are no Sonar metrics worth measuring. Fork PRs are the opposite case — they are exactly the contributions that need the quality gate — so skipping them is not an acceptable fix.
Options
Split the analysis into a
workflow_runworkflow (GitHub's recommended pattern for handling untrusted PR builds). Thepull_requestworkflow builds and uploads the compiled output, coverage reports and PR metadata as artifacts; a second workflow triggered onworkflow_run: completedruns in the base repo's context, has access toSONAR_TOKEN, downloads those artifacts and submits the analysis. Untrusted code is never executed with secrets in scope. Cost: one extra workflow file and some artifact plumbing.Use
pull_request_targetfor the Sonar job. Simpler to wire up, but it runs with secrets available in a context where the PR's own code is checked out, so it needs careful handling (pinned checkout of the base ref, no execution of fork-supplied scripts) to avoid secret exfiltration. Only worth it if option 1 is considered too heavy.Option 1 is the safer of the two and the one I would suggest. Happy to open the PR implementing it if you agree on the approach.
Edited: option 1 originally read "SonarSource's recommended pattern for forks". That attribution was wrong and has been corrected above — see this comment.