From 44113e84def534854301b2cf35b2525f02b11b7a Mon Sep 17 00:00:00 2001 From: jerrrrycho Date: Sun, 19 Jul 2026 10:22:51 +0200 Subject: [PATCH] ci: fix sast from forked repos (#142) --- .../workflows/{sonarcloud.yml => build.yml} | 61 +++++--- .github/workflows/sonar.yml | 130 ++++++++++++++++++ 2 files changed, 172 insertions(+), 19 deletions(-) rename .github/workflows/{sonarcloud.yml => build.yml} (57%) create mode 100644 .github/workflows/sonar.yml diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/build.yml similarity index 57% rename from .github/workflows/sonarcloud.yml rename to .github/workflows/build.yml index 9c2a903c..4c91a1b0 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,9 @@ -name: SonarQube Cloud +# .github/workflows/build.yml +# UNTRUSTED CONTEXT. This workflow runs fork-authored code. +# It must never reference `secrets.*` (other than the default GITHUB_TOKEN, +# which is read-only here). Do not add SONAR_TOKEN to this file. + +name: Build on: push: @@ -7,14 +12,15 @@ on: pull_request: types: [ opened, reopened, synchronize ] +permissions: + contents: read + jobs: - sonar: - name: Analyze + build: + name: Build under Build Wrapper runs-on: ubuntu-24.04 env: BUILD_WRAPPER_OUT_DIR: bw-output - SONAR_EXCLUSIONS: betterRenderer/**,ref/**,imgui/**,stb/**,build/**,bw-output/** - SONAR_SCANNER_JAVA_OPTS: -Xmx5g steps: - uses: actions/checkout@v5 with: @@ -46,7 +52,7 @@ jobs: sudo ldconfig # A little bit of magic for unbreaking GCC. Not an issue for Clang and MSVC. - # Fix for specific hard-errors for a combo of: varagrs and always_inline + # Fix for specific hard-errors for a combo of: varargs and always_inline - name: Patch GLM for GCC run: | sed -i 's/GLM_FUNC_QUALIFIER std::string format(const char\* message, \.\.\.)/inline std::string format(const char* message, ...)/' \ @@ -71,17 +77,34 @@ jobs: --out-dir "${BUILD_WRAPPER_OUT_DIR}" \ cmake --build build --config Release -j"$(nproc)" - # sonar.cfamily.taintAnalysis=false - # it's a deliberate choice, due to memory exhausting of a Github-hosted runner. - # - # Why off?: The jobs were OOM-killed. It's a trade-off, we still get everything else. - # Most bugs are caught though. - - name: SonarQube Cloud scan - uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: https://sonarcloud.io + # Metadata the trusted workflow needs to identify the PR. + # Treated as UNVERIFIED input there and re-checked against the API. + - name: Record event metadata + run: | + mkdir -p .sonar-meta + echo "${{ github.event_name }}" > .sonar-meta/event + echo "${{ github.event.pull_request.number }}" > .sonar-meta/pr_number + echo "${{ github.event.pull_request.head.sha }}" > .sonar-meta/head_sha + echo "${{ github.event.pull_request.base.ref }}" > .sonar-meta/base_ref + echo "${{ github.event.pull_request.head.ref }}" > .sonar-meta/head_ref + echo "${{ github.sha }}" > .sonar-meta/sha + + # The C/C++ analyzer needs the sources AND the build-wrapper output. + # compile_commands.json holds absolute paths, so the trusted workflow must + # unpack this at the same $GITHUB_WORKSPACE path — same repo, so it matches. + - name: Pack analysis input + run: | + tar -czf /tmp/workspace.tar.gz \ + --exclude='./build/CMakeFiles' \ + --exclude='*.o' \ + --exclude='*.a' \ + --exclude='*.so' \ + -C "$GITHUB_WORKSPACE" . + + - name: Upload analysis input + uses: actions/upload-artifact@v4 with: - args: > - --define sonar.cfamily.compile-commands=${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json - --define sonar.cfamily.taintAnalysis=false + name: sonar-input + path: /tmp/workspace.tar.gz + retention-days: 1 + if-no-files-found: error diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 00000000..92d73e5c --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,130 @@ +# .github/workflows/sonar.yml +# TRUSTED CONTEXT. Has SONAR_TOKEN. +# Rule: never execute anything that came out of the artifact. No build, no +# npm/pip install, no scripts, no PR-supplied scanner config. Data only. +# +# NOTE: workflow_run workflows are only picked up from the DEFAULT branch. +# Changes to this file take effect after they land on master. + +name: SonarQube Cloud + +on: + workflow_run: + workflows: [ "Build" ] + types: [ completed ] + +permissions: + contents: read + actions: read + pull-requests: write + +jobs: + sonar: + name: Analyze + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-24.04 + env: + BUILD_WRAPPER_OUT_DIR: bw-output + SONAR_EXCLUSIONS: betterRenderer/**,ref/**,imgui/**,stb/**,build/**,bw-output/** + SONAR_SCANNER_JAVA_OPTS: -Xmx5g + steps: + # Trusted copy of the repo config, used to overwrite anything the PR + # might have tampered with (sonar-project.properties etc). + - name: Checkout trusted config from master + uses: actions/checkout@v5 + with: + ref: master + path: .trusted + + - name: Download analysis input + uses: actions/download-artifact@v4 + with: + name: sonar-input + path: /tmp/sonar-input + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Unpack analysis input + run: | + set -euo pipefail + tar -xzf /tmp/sonar-input/workspace.tar.gz -C "$GITHUB_WORKSPACE" + # Strip PR-controlled scanner configuration, restore the trusted one. + rm -f "$GITHUB_WORKSPACE/sonar-project.properties" \ + "$GITHUB_WORKSPACE/.sonarcloud.properties" + if [ -f "$GITHUB_WORKSPACE/.trusted/sonar-project.properties" ]; then + cp "$GITHUB_WORKSPACE/.trusted/sonar-project.properties" "$GITHUB_WORKSPACE/" + fi + + - name: Resolve and verify PR metadata + id: meta + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + cd "$GITHUB_WORKSPACE" + event=$(tr -d '\r\n' < .sonar-meta/event) + echo "event=$event" >> "$GITHUB_OUTPUT" + + if [ "$event" != "pull_request" ]; then + echo "Branch build, no PR parameters." + exit 0 + fi + + pr=$(tr -d '\r\n' < .sonar-meta/pr_number) + sha=$(tr -d '\r\n' < .sonar-meta/head_sha) + + # The artifact is attacker-controlled: validate shape, then confirm + # against the API that this PR really has this head commit. + [[ "$pr" =~ ^[0-9]+$ ]] || { echo "::error::bad PR number"; exit 1; } + [[ "$sha" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::bad head sha"; exit 1; } + + api_sha=$(gh api "repos/$REPO/pulls/$pr" --jq '.head.sha') + base=$(gh api "repos/$REPO/pulls/$pr" --jq '.base.ref') + head=$(gh api "repos/$REPO/pulls/$pr" --jq '.head.ref') + + if [ "$api_sha" != "$sha" ]; then + echo "::error::head sha mismatch (artifact=$sha api=$api_sha)" + exit 1 + fi + + { + echo "pr=$pr" + echo "base=$base" + echo "head=$head" + } >> "$GITHUB_OUTPUT" + + # sonar.cfamily.taintAnalysis=false + # it's a deliberate choice, due to memory exhausting of a Github-hosted runner. + # + # Why off?: The jobs were OOM-killed. It's a trade-off, we still get everything else. + # Most bugs are caught though. + - name: SonarQube Cloud scan (pull request) + if: steps.meta.outputs.event == 'pull_request' + uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: https://sonarcloud.io + with: + args: > + --define sonar.cfamily.compile-commands=${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json + --define sonar.cfamily.taintAnalysis=false + --define sonar.exclusions=${{ env.SONAR_EXCLUSIONS }} + --define sonar.scm.revision=${{ github.event.workflow_run.head_sha }} + --define sonar.pullrequest.key=${{ steps.meta.outputs.pr }} + --define sonar.pullrequest.branch=${{ steps.meta.outputs.head }} + --define sonar.pullrequest.base=${{ steps.meta.outputs.base }} + + - name: SonarQube Cloud scan (branch) + if: steps.meta.outputs.event != 'pull_request' + uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: https://sonarcloud.io + with: + args: > + --define sonar.cfamily.compile-commands=${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json + --define sonar.cfamily.taintAnalysis=false + --define sonar.exclusions=${{ env.SONAR_EXCLUSIONS }} + --define sonar.branch.name=${{ github.event.workflow_run.head_branch }} + --define sonar.scm.revision=${{ github.event.workflow_run.head_sha }}