Guides
MarkdownCI
Excubia is built to gate pipelines: it exits 1 when a finding is at or above --fail-on, and writes JSON, Markdown or SARIF for the job to keep. The standalone CLI does not boot the app, so a CI job needs only PHP and the project's composer.lock; no database, no .env, no composer install of the project itself.
GitHub Action
The composite action in action.yml runs excubia check, writes the Markdown report to the job summary and uploads the SARIF log to GitHub code scanning, so findings show up in the Security tab and on pull requests.
# .github/workflows/excubia.yml
name: Excubia
on:
push:
branches: [main]
pull_request:
schedule:
- cron: '0 6 * * 1' # weekly: new advisories and EOL dates appear without code changes
permissions:
contents: read
security-events: write # for the SARIF upload
jobs:
excubia:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: qiick-io/excubia-core@v1
with:
fail-on: critical
github-token: ${{ github.token }}
| Input | Default | Meaning |
|---|---|---|
path | . | Project directory to scan, relative to the workspace. |
fail-on | critical | critical, high, medium, low or none, as --fail-on. Note the default is critical here and high in the CLI. |
only | — | Comma-separated check ids to run. |
skip | — | Comma-separated check ids to skip. |
github-token | — | Optional. Raises the GitHub API rate limit for composer-stale. Passed to Excubia as GITHUB_TOKEN only. |
upload-sarif | true | false skips the code scanning upload (and the need for security-events: write). |
php-version | 8.4 | PHP used to run Excubia (8.2 or newer), not the scanned project's PHP. |
| Output | Meaning |
|---|---|
sarif-file | Path of the SARIF log. |
exit-code | Exit code of excubia check. |
How it works:
- Excubia is installed with Composer in
$RUNNER_TEMP/excubiafrom the action's own checkout, so the version matches the ref you use (@v1, a tag or a commit SHA). Its dependencies (symfony/console,symfony/http-client,composer/semver…) come from the lock committed in.github/action-install/and resolved for PHP 8.2, so the action runs the same reviewed versions on every run and everyphp-versionfrom 8.2 up; Composer plugins and scripts are disabled. The scanned project's code and dependencies are never installed or executed. - The SARIF log is uploaded even when the step then fails on
fail-on. Pull requests from forks get a read-only token, so the upload is skipped there. excubia.jsoninpathis read as usual, so ignore rules and thresholds apply.- Pin the action to a commit SHA if your policy requires it.
SARIF
--format=sarif writes a SARIF 2.1.0 log:
- Each check id is a rule; each
failorwarnfinding is a result.passandskippedrows and the upgrade plan are left out. - Levels:
criticalandhigh→error,medium→warning,lowandinfo→note. The Excubia severity stays inproperties.severity. - The location is the manifest the finding comes from (
composer.json,composer.lock,package-lock.json,.nvmrc,.node-versionorpackage.json; for the posture checksconfig/app.php,config/queue.php,database/migrationsorroutes/console.php), relative to the project. Never an absolute path. partialFingerprintscome from the check id, subject and advisory id, so an alert keeps its identity across runs.
Any other CI
Install Excubia as a dev dependency and run it. The exit code fails the job.
composer install --no-interaction --no-scripts # or only: composer require --dev qiick/excubia-core
vendor/bin/excubia check --no-banner --fail-on=high
Keep the reports as artifacts:
vendor/bin/excubia check --no-banner --format=json --output=excubia.json --fail-on=none
vendor/bin/excubia check --no-banner --format=markdown --output=excubia.md
The second run reuses the response cache of the first one. Typical choices:
| Need | Command |
|---|---|
| Fail only on critical findings | --fail-on=critical |
| Report, never fail | --fail-on=none |
Fail when a data source was down, rather than pass with skipped checks | --strict (exit 3) |
| Production dependencies only | --no-dev |
| No network in the job | Restore a cached directory set with cache.directory, then --offline |
| Scan a sub-directory | vendor/bin/excubia check path/to/app |
GitLab CI example:
excubia:
image: php:8.4-cli
before_script:
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- composer install --no-interaction --no-scripts --no-progress
script:
- vendor/bin/excubia check --no-banner --format=markdown --output=excubia.md
artifacts:
when: always
paths: [excubia.md]
Exit codes: 0 clean, 1 finding at or above --fail-on, 2 usage or configuration error, 3 data-source failure in strict mode. Precedence 2 > 1 > 3. See the README.
Push from CI
To send the report to your own excubia-app after a deploy, see Push to excubia-app.
Posture checks in CI
The six laravel-* posture checks read the booted app, so they only run through php artisan excubia:check. In CI they would describe the CI environment, not production, so use vendor/bin/excubia there (the posture checks are skipped), and run the posture checks on the server or through the scheduled push.
Source: docs/guides/ci.md in excubia-core.