# SonarQube Scanner Cheat Sheet

> **Tool:** SonarScanner CLI (sonar-scanner)
> **Category:** Security & Compliance
> **Verified against:** SonarScanner CLI 8.1.0.6389, flags verified via `sonar-scanner -h` run locally against
> the official Linux x64 distribution; Quality Gate/PR decoration behavior cross-checked against
> docs.sonarsource.com's Quality Gates and PR decoration documentation, 2026-08-29
> **Official docs:** https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/scanners/sonarscanner/

## What it is and where it fits

SonarQube is a code-quality-and-security platform; SonarScanner CLI is its generic command-line analyzer —
the one to reach for when a project isn't already covered by a language-specific scanner (Maven, Gradle,
.NET, npm plugins exist too, and are usually preferred when applicable). It combines two things a lot of teams
treat as separate concerns but SonarQube unifies: **code quality** (duplication, complexity, code smells, test
coverage) and **security** (vulnerabilities, security hotspots requiring manual review). Where Semgrep is a
fast, rule-pattern SAST engine you run standalone, SonarQube is a server-backed platform with a persistent
history — trends over time, Quality Gates that block a release, and PR decoration that comments directly on a
pull request. The CLI is thin by design: it packages up the project, uploads it, and the actual analysis
(mostly) happens or is aggregated server-side.

## Installation

```bash
# Download the Linux x64 distribution directly (no package manager release)
curl -sLo sonar-scanner-cli.zip \
  https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip
unzip sonar-scanner-cli.zip
export PATH="$PATH:$PWD/sonar-scanner-8.1.0.6389-linux-x64/bin"

sonar-scanner -v          # confirm install (also confirms Java is reachable — sonar-scanner is a JVM app)
```

Get the current version number from the download page (URL pattern is `sonar-scanner-cli-<VERSION>-linux-x64.zip`)
rather than hardcoding one — SonarSource ships new scanner releases regularly. There's no official curl
install-script or apt/brew package for the CLI itself; the zip download is the documented method for Linux.
You also need a running SonarQube Server (self-hosted) or a SonarQube Cloud account — the scanner is useless
without a destination to upload analysis results to.

## Core concepts

- **Project key** — a unique id for the analyzed project within the SonarQube instance; every analysis run
  attaches to one project key.
- **Quality Gate** — a set of pass/fail conditions ("is my project ready for release?") evaluated after each
  analysis. The same Quality Gate definition is used for every analysis of a project, but the way conditions
  are checked differs: **branch analysis** evaluates both overall-code and new-code conditions; **pull request
  analysis** evaluates *only* new-code conditions — you're never blocked by pre-existing debt on a PR, only by
  what the PR itself introduces.
- **New code** — a configurable definition (default: since the previous version, or a fixed number of days)
  that scopes what counts as "new" for new-code Quality Gate conditions. This is the mechanism that lets a
  large legacy codebase adopt strict quality/security gates without needing to fix years of backlog first.
- **Security Hotspot** vs **Vulnerability** — SonarQube deliberately separates the two: a Vulnerability is a
  confirmed exploitable issue; a Security Hotspot is security-sensitive code that *needs a human to review the
  context* (e.g., a regex that might be a ReDoS risk depending on where the input comes from) before it's
  classified either way.

## Configuration

Every project needs either a `sonar-project.properties` file or the equivalent `-D` flags:

```properties
# sonar-project.properties
sonar.projectKey=my-project
sonar.projectName=My Project
sonar.sources=.
sonar.exclusions=**/vendor/**,**/*.min.js
sonar.host.url=http://localhost:9000
sonar.token=<SONAR_TOKEN>
```

```bash
export SONAR_TOKEN=<token>                          # preferred over -Dsonar.token on the command line (avoids shell history/CI logs)
sonar-scanner                                        # reads sonar-project.properties in the current directory
sonar-scanner -Dsonar.projectKey=my-project \
              -Dsonar.sources=. \
              -Dsonar.host.url=http://localhost:9000  # override/skip the properties file entirely
```

## Common overrides

```bash
# Branch analysis — track quality over time on a long-lived non-main branch (needs Developer Edition+ on self-hosted)
sonar-scanner -Dsonar.branch.name=feature/x

# Pull request analysis — decorates the PR itself rather than tracking a persistent branch
sonar-scanner -Dsonar.pullrequest.key=42 \
              -Dsonar.pullrequest.branch=feature/x \
              -Dsonar.pullrequest.base=main

# Scope what gets analyzed
sonar-scanner -Dsonar.exclusions=**/vendor/**,**/*.min.js       # exclude paths from analysis entirely
sonar-scanner -Dsonar.coverage.exclusions=**/*_test.go           # exclude from coverage metrics specifically, still analyzed for issues
sonar-scanner -Dsonar.test.inclusions=**/*_test.py                # tell Sonar which files are tests, not production code

# Feed in coverage/test reports from your own test runner (Sonar doesn't run tests itself)
sonar-scanner -Dsonar.python.coverage.reportPaths=coverage.xml
sonar-scanner -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info

# Block CI on the Quality Gate result
sonar-scanner -Dsonar.qualitygate.wait=true
sonar-scanner -Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=300   # max seconds to wait for the gate to compute (default 300)
```

`sonar.qualitygate.wait=true` is what actually gates a CI pipeline on quality — without it, `sonar-scanner`
exits 0 the moment analysis upload succeeds, before the Quality Gate has even been evaluated server-side
(evaluation happens asynchronously after the scanner's HTTP upload returns).

## Memory tuning

```bash
export SONAR_SCANNER_JAVA_OPTS="-Xmx2048m"        # scanner ≥ 6.0; increase for large monorepos that OOM during analysis packaging
```

## Real-world scenario: PR decoration with a strict new-code gate

Connect SonarQube to GitHub/GitLab/Bitbucket/Azure DevOps once (server-side ALM integration), then every PR
analysis automatically posts a summary comment, inline issue annotations on changed lines, and a status check:

```bash
sonar-scanner \
  -Dsonar.pullrequest.key=$PR_NUMBER \
  -Dsonar.pullrequest.branch=$HEAD_BRANCH \
  -Dsonar.pullrequest.base=$BASE_BRANCH \
  -Dsonar.qualitygate.wait=true
```

Because PR analysis only evaluates new-code conditions, this is safe to make strict (e.g., "0 new
vulnerabilities, 80% coverage on new code") even on a codebase whose overall metrics are far from that bar —
the gate only judges what the PR itself changed.

## Real-world scenario: GitHub Actions CI recipe

```yaml
# .github/workflows/sonar.yml
name: SonarQube
on: [pull_request]
jobs:
  sonarqube:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0        # SonarQube needs full git history for accurate blame/new-code detection
      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v4
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
      - uses: SonarSource/sonarqube-quality-gate-action@v1   # separate step: fail the job explicitly on a red gate
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
```

`fetch-depth: 0` matters more than it looks — a shallow checkout (the CI default) breaks SonarQube's ability
to correctly compute new-code / blame information, which silently skews Quality Gate results toward "nothing
is new."

## Flags

```bash
sonar-scanner -h              # help
sonar-scanner -v              # version
sonar-scanner -X              # debug output — first thing to reach for when analysis silently produces nothing
sonar-scanner -D key=value    # define/override any sonar.* property inline
```

## Common pitfalls

- **Forgetting `-Dsonar.qualitygate.wait=true`** — the scan "succeeds" (exit 0) the instant the upload
  finishes, regardless of whether the Quality Gate later fails. This is the single most common
  "why didn't this block the merge" report.
- **Shallow git clones** breaking new-code/blame detection (see the CI recipe above).
- **Missing coverage/test report paths** — SonarQube never runs your tests itself; if you don't feed it a
  coverage report path for your language, coverage-based gate conditions will always show 0% regardless of
  actual test coverage.
- **Branch analysis on Community Edition** — branch analysis (tracking a long-lived non-main branch over time)
  requires Developer Edition or above on self-hosted SonarQube Server; Community Edition only analyzes the
  default branch plus pull requests.

## When to reach for something else

SonarQube's security coverage is real but broader/shallower than a dedicated SAST engine like Semgrep for
security-specific rule depth, and it doesn't do dependency (SCA), container, or IaC scanning at all — those
are separate tools (Snyk/Trivy, Trivy/Grype, Checkov/tfsec respectively) that a security-conscious pipeline
runs alongside SonarQube, not instead of it.
