Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions .github/workflows/sonarqube-manual.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: SonarQube Manual Scan

on:
workflow_call:
inputs:
quality_profile:
description: SonarCloud quality profile name (e.g. Sonar way)
required: true
type: string
sonar_language:
description: SonarCloud language key for the quality profile (e.g. php, java)
required: false
type: string
default: php
ref:
description: Branch, tag, or SHA to analyze (defaults to caller ref)
required: false
type: string
default: ""
fetch_depth:
description: Git fetch depth (0 = full history for Sonar SCM)
required: false
type: number
default: 0
runs_on:
required: false
type: string
default: ubuntu-latest-8-cores
sonar_scanner_opts:
description: JVM options for the Sonar scanner (e.g. -Xmx4000m)
required: false
type: string
default: -Xmx4000m
secrets:
sonar_token:
required: true

permissions:
contents: read

jobs:
sonarqube:
runs-on: ${{ inputs.runs_on }}
name: SonarQube Manual Scan (${{ inputs.quality_profile }})
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref != '' && inputs.ref || github.ref }}
fetch-depth: ${{ inputs.fetch_depth }}

- name: Cache SonarQube scanner data
uses: actions/cache@v5
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar-${{ hashFiles('sonar-project.properties') }}
restore-keys: |
${{ runner.os }}-sonar-

- name: Switch SonarCloud quality profile
id: sonar_profile
env:
SONAR_TOKEN: ${{ secrets.sonar_token }}
run: |
set -euo pipefail

if [ ! -f sonar-project.properties ]; then
echo "sonar-project.properties not found in repository root" >&2
exit 1
fi

PROJECT_KEY=$(grep -E '^sonar\.projectKey=' sonar-project.properties | head -1 | cut -d= -f2- | tr -d ' \r')
ORGANIZATION=$(grep -E '^sonar\.organization=' sonar-project.properties | head -1 | cut -d= -f2- | tr -d ' \r')
LANGUAGE="${{ inputs.sonar_language }}"
PROFILE="${{ inputs.quality_profile }}"

RESPONSE=$(curl -sf -u "${SONAR_TOKEN}:" \
"https://sonarcloud.io/api/qualityprofiles/search?project=${PROJECT_KEY}&organization=${ORGANIZATION}")
PREVIOUS=$(echo "$RESPONSE" | jq -r --arg lang "$LANGUAGE" \
'.profiles[] | select(.language == $lang) | .name' | head -1)

echo "previous_profile=${PREVIOUS}" >> "$GITHUB_OUTPUT"
echo "project_key=${PROJECT_KEY}" >> "$GITHUB_OUTPUT"

curl -sf -u "${SONAR_TOKEN}:" -X POST \
"https://sonarcloud.io/api/qualityprofiles/add_project" \
--data-urlencode "project=${PROJECT_KEY}" \
--data-urlencode "qualityProfile=${PROFILE}" \
--data-urlencode "language=${LANGUAGE}"

- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v6.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.sonar_token }}
SONAR_SCANNER_OPTS: ${{ inputs.sonar_scanner_opts }}

- name: Restore SonarCloud quality profile
if: always() && steps.sonar_profile.outputs.previous_profile != ''
env:
SONAR_TOKEN: ${{ secrets.sonar_token }}
run: |
set -euo pipefail

curl -sf -u "${SONAR_TOKEN}:" -X POST \
"https://sonarcloud.io/api/qualityprofiles/add_project" \
--data-urlencode "project=${{ steps.sonar_profile.outputs.project_key }}" \
--data-urlencode "qualityProfile=${{ steps.sonar_profile.outputs.previous_profile }}" \
--data-urlencode "language=${{ inputs.sonar_language }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Racy quality profile restore

Medium Severity

The workflow reads the current SonarCloud profile, switches it for the scan, then restores it, with no concurrency guard. Overlapping manual runs—or a normal CI Sonar scan in the same window—can capture another run’s temporary profile and restore that instead, leaving the project stuck on the wrong profile.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4547cf3. Configure here.