Skip to content

CodeQL

CodeQL #240

Workflow file for this run

name: CodeQL
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
# Weekly schedule catches new CodeQL queries / CVE rules disclosed since
# the last PR landed. Wednesday 06:00 UTC — deliberately offset from
# security.yml's Monday slot so dependency-CVE forensics and source-code
# findings don't pile into the same morning's GitHub-Actions queue.
- cron: '0 6 * * 3'
concurrency:
group: codeql-${{ github.ref }}
# Don't cancel in-progress runs: CodeQL uploads SARIF on cancel, and a
# partial run lands as an empty-results SARIF (rules_count: 0,
# results_count: 0) that GitHub flags as "Code scanning configuration
# error" in the Code Scanning UI. During PR-merge cascades on develop
# (e.g. 5 PRs in 15 min on 2026-05-28, #154→#156→#155→#157→#159), this
# produced 3 empty SARIFs that needed manual cleanup. Better to queue
# the second and later runs — small runner-time cost during cascades,
# no UI banners, and every commit gets a full CodeQL analysis (matters
# for the Scorecard SAST coverage metric). See #160 for full reasoning.
cancel-in-progress: false
# Default to read-only at the workflow level; the analyze job below opts
# in to the narrow extra scopes it actually needs. Matches the OpenSSF
# Scorecard Token-Permissions recommendation that workflows declare the
# minimum at top level and elevate only at the job that uses it (SR-137).
permissions:
contents: read
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
# `actions: read` lets the CodeQL action introspect the workflow file
# for the matrix expansion and language detection.
actions: read
# `contents: read` is REQUIRED here because job-level `permissions:`
# REPLACES (not merges with) the workflow-level block — omitting it
# would deny content access in this job and break the checkout step.
# Also useful for grep-ability.
contents: read
# `security-events: write` is what CodeQL needs to upload SARIF
# results to GitHub Code Scanning. Held at job level (not top level)
# so no other job in this workflow inherits the write scope.
security-events: write
strategy:
fail-fast: false
matrix:
# CodeQL's combined language for the JVM ecosystem. A single
# `java-kotlin` database covers both languages — listing them
# separately is a deprecated configuration.
language: [java-kotlin]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: temurin
java-version: '21'
- uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
- name: Initialize CodeQL
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
languages: ${{ matrix.language }}
# security-and-quality bundles the security-extended queries plus
# maintainability/quality lints — broader than the default suite,
# appropriate for a small app where we want maximum signal.
queries: security-and-quality
config-file: ./.github/codeql/codeql-config.yml
# Build choice: manual `:app:assembleDebug` rather than `autobuild`,
# with the Kotlin compiler forced in-process AND the Gradle build
# cache disabled — both are required for CodeQL to see Kotlin code.
#
# Why not `autobuild`: it routinely fails to discover the AGP variant
# model on Android projects, producing an empty CodeQL DB.
#
# Why not `build-mode: none`: for the `java-kotlin` combined language,
# `none` only does dependency-graph analysis on the Java side and is
# not a Kotlin source extractor — on a 100% Kotlin codebase (this
# repo has zero `.java` sources) the finalize step fails with "no
# source code seen during build" because Kotlin extraction always
# requires a traced build.
#
# Why `kotlin.compiler.execution.strategy=in-process`: by default the
# Kotlin Gradle plugin spawns an out-of-process Kotlin compiler daemon
# for every build. That daemon JVM is started *outside* CodeQL's
# `LD_PRELOAD` tracer scope, so even though the Android Gradle build
# succeeds, the Kotlin compilation never gets traced.
#
# Why `--no-build-cache` AND `clean`: `gradle/actions/setup-gradle`
# restores Gradle's local build cache from previous workflow runs
# (including ci.yml). On a cache hit, `compileDebugKotlin` is served
# `FROM-CACHE` and no compiler process runs — leaving CodeQL's tracer
# with nothing to capture. Disabling the build cache + cleaning the
# task outputs forces a fresh in-process Kotlin compilation that the
# tracer can actually see. Canonical fix for the
# https://gh.io/troubleshooting-code-scanning/no-source-code-seen-during-build
# symptom on Kotlin Gradle projects where compileDebugKotlin shows
# `FROM-CACHE` in the build log.
- name: Build (Android debug, Kotlin compiler in-process, no cache)
run: |
chmod +x gradlew
./gradlew clean :app:assembleDebug --no-daemon --no-build-cache \
-Pkotlin.compiler.execution.strategy=in-process
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
category: "/language:${{ matrix.language }}"