Skip to main content

Reporting to GitHub code scanning

sqlens:security writes SARIF 2.1.0, which is the format GitHub's code-scanning tab reads. This page is the recipe: a workflow that runs, uploads, and does not lose its findings to an exit code.

The workflow

name: SQLens security

on:
pull_request:
push:
branches: [main]

permissions:
# Without this the upload step fails with a 403 that reads like an authentication problem
# and is not one. `contents: read` is needed too once you set any permissions block at all,
# because naming one permission drops every default.
contents: read
security-events: write

jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'

- run: composer install --no-interaction --prefer-dist

# `continue-on-error`, and it is the load-bearing line of this workflow. The command exits
# non-zero when it finds something above your gate — which is the normal, useful case — and a
# step that stopped there would skip the upload and leave the Security tab empty on exactly
# the runs that had something to say.
- name: Run SQLens
continue-on-error: true
run: php artisan sqlens:security --format=sarif --output=sqlens.sarif

- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: sqlens.sarif

What the exit codes mean

The upload runs regardless, but a workflow that wants to fail on findings needs to tell them apart:

CodeMeaning
0nothing above the gate
1findings above the gate — the ordinary "it worked and found things"
2misconfiguration: the run could not be set up as asked
3something was undetermined and the run is strict about that

2 and 3 are the two you should never let pass silently. A misconfigured run and a run that could not check something both produce a SARIF file, and both are smaller than the run you asked for.

security-severity is what GitHub ranks by, not level

This is the part that surprises people. The code-scanning UI sorts and filters alerts by a numeric property named security-severity; a rule with the right level and no security-severity arrives as an alert with no weight at all — sorted below everything that carried a number.

SQLens writes both, from its own security severity:

SQLens severitySARIF levelsecurity-severity
criticalerror9.0
higherror7.0
mediumwarning5.0
lowwarning3.0
infonote1.0

Only security and privacy findings carry a weight. A safety finding is measured by the strictness level rather than by a severity, so giving it a number would place it above real security findings in a security tab — which is the one place that ranking has to be right.

Narrowing what gets uploaded

php artisan sqlens:security --format=sarif --output=sqlens.sarif --min-severity=high

A floor keeps the tab useful on a database with a long tail of low-severity findings. Set it deliberately rather than by habit: an alert you never uploaded is an alert nobody triages, and the finding you filtered out is invisible in a way a closed alert is not.