Getting started

Markdown

Configuration reference

Excubia reads one optional file, excubia.json, at the root of the scanned project (or the file passed with --config), and a few environment variables. Without the file every default below applies.

  • The file is JSON so it works without booting Laravel and is easy for agents to edit. JSON Schema: excubia.schema.json. Add "$schema" to get completion in your editor.
  • Unknown keys, wrong types and out-of-range values are a configuration error: the command prints which key and exits 2.
  • In a Laravel app the service provider reads the default excubia.json of the app base path once per boot. An invalid file there disables the opt-in features (the app still boots); php artisan excubia:check shows the error.
  • Command-line options win over the file: --fail-on over failOn, --no-dev over includeDev. --strict can only turn strict on.

Full example

Every key with its default value, except $schema, ignore and app, which have no default:

{
  "$schema": "https://raw.githubusercontent.com/qiick-io/excubia-core/main/excubia.schema.json",
  "failOn": "high",
  "includeDev": true,
  "strict": false,
  "checks": {
    "composer-stale": { "staleAfterMonths": 24 },
    "php-eol": { "warnDaysBeforeEol": 180 },
    "laravel-eol": { "warnDaysBeforeEol": 180 },
    "node-eol": { "enabled": true },
    "laravel-scheduler-heartbeat": { "staleAfterMinutes": 10 },
    "laravel-failed-jobs": { "highSeverityAfterHours": 24 }
  },
  "ignore": [
    {
      "checkId": "composer-advisories",
      "subject": "vendor/package",
      "advisoryId": "GHSA-xxxx-xxxx-xxxx",
      "reason": "Not reachable from our code",
      "until": "2027-01-31"
    }
  ],
  "cache": { "directory": null, "ttlHours": 24 },
  "privacy": { "skipPackages": [] },
  "app": { "url": "https://excubia.example.com" },
  "healthEndpoint": { "enabled": false, "path": "excubia/health" },
  "runtime": {
    "enabled": false,
    "bufferMaxBytes": 1048576,
    "requests": { "enabled": true, "sampleRate": 1 },
    "slowQueries": { "enabled": true, "sampleRate": 1, "thresholdMs": 1000 },
    "jobs": { "enabled": true, "sampleRate": 1 },
    "scheduler": { "enabled": true, "sampleRate": 1 },
    "outgoingHttp": { "enabled": true, "sampleRate": 1 }
  },
  "exceptions": {
    "enabled": false,
    "maskPatterns": [],
    "ignore": [],
    "bufferMaxBytes": 1048576
  }
}

General

KeyTypeDefaultMeaning
$schemastring—Schema URL for editors. Ignored by Excubia.
failOncritical, high, medium, low or nonehighA finding at or above this severity makes check exit 1. none never fails on findings. --fail-on wins.
includeDevbooleantrueScan packages-dev too. --no-dev sets it to false.
strictbooleanfalseExit 3 when a check was skipped because a data source failed; push then sends nothing. Same as --strict, which wins over false.

Checks

checks holds per-check settings. Only these checks have settings; any other check id here is an error. To turn a check off, use --skip (or node-eol.enabled).

KeyTypeDefaultMeaning
checksobject{}Per-check settings.
checks.composer-staleobjectSettings of composer-stale.
checks.composer-stale.staleAfterMonthsinteger ≥ 124Warn (low) when a package had no release for more than this many months.
checks.php-eolobjectSettings of php-eol.
checks.php-eol.warnDaysBeforeEolinteger ≥ 0180Warn (medium) when PHP security support ends within this many days.
checks.laravel-eolobjectSettings of laravel-eol.
checks.laravel-eol.warnDaysBeforeEolinteger ≥ 0180Warn (medium) when Laravel security fixes end within this many days.
checks.node-eolobjectSettings of node-eol.
checks.node-eol.enabledbooleantruefalse reports node-eol as skipped (not-applicable).
checks.laravel-scheduler-heartbeatobjectSettings of laravel-scheduler-heartbeat.
checks.laravel-scheduler-heartbeat.staleAfterMinutesinteger ≥ 110Warn when the heartbeat written by excubia:heartbeat is older than this many minutes.
checks.laravel-failed-jobsobjectSettings of laravel-failed-jobs.
checks.laravel-failed-jobs.highSeverityAfterHoursinteger ≥ 124Failed jobs warn at medium, and at high once the oldest is at least this many hours old.

Ignore rules

ignore is a list of findings you accept. A rule matches a finding with the same checkId and subject, and, when advisoryId is set, the same advisory. Ignored findings are removed from the report, the exit code and the health endpoint.

KeyTypeDefaultMeaning
ignorelist of objects[]Accepted findings.
ignore[].checkIdstring, requiredCheck id, for example composer-advisories.
ignore[].subjectstring, requiredThe finding subject: a package (vendor/package), a runtime (php, laravel/framework, node) or, for the posture checks, app.debug, config-cache, route-cache, event-cache, migrations, failed-jobs, scheduler or maintenance-mode.
ignore[].advisoryIdstring or nullnullMatch only this advisory (GHSA or CVE id as shown in the report). null matches every finding of the subject.
ignore[].reasonstring, requiredWhy it is accepted. Printed when the rule expires.
ignore[].untilYYYY-MM-DD or nullnullLast day the rule applies (inclusive, UTC). After it the finding is active again and a warning is printed on stderr. null never expires.

Response cache

Data-source responses are cached on disk so repeated runs and --offline work without network.

KeyTypeDefaultMeaning
cacheobjectCache settings.
cache.directorystring or nullnullCache directory. null uses qiick-excubia-<uid> in the system temporary directory, so nothing is written to the project; it is skipped when it is a symlink, owned by another user or writable by others.
cache.ttlHoursinteger ≥ 024Age after which a cached response is fetched again. --offline uses only responses younger than this; older ones count as missing.

--no-cache bypasses the cache for one run. vendor/bin/excubia cache:clear deletes the cache entries (only files named like cache entries).

Privacy

Packages the lockfile shows as not served by Packagist or the public npm registry are never sent to a public data source; see private packages. privacy.skipPackages adds names the lockfile cannot tell apart, such as a private package published on the public npm registry.

KeyTypeDefaultMeaning
privacyobjectWhat the checks may send to public data sources.
privacy.skipPackageslist of strings[]Package name globs never sent to Packagist, OSV.dev or GitHub, for example "acme/*" or "@acme/*". * matches any characters (/ included), ? exactly one; the whole name must match, case-insensitively. Only letters, digits, @ / . _ ~ - and the two wildcards are allowed. Matching packages appear in one skipped row per check.

excubia-app

The target of excubia push, excubia:push and excubia:push-runtime. See the push guide.

KeyTypeDefaultMeaning
appobjectYour own excubia-app.
app.urlstring—Base URL of the app, for example https://excubia.example.com. HTTPS, or plain HTTP to localhost, 127.0.0.1 or [::1] only. EXCUBIA_APP_URL wins.
app.tokenstring—Project push token from the app. EXCUBIA_APP_TOKEN wins. Prefer the environment variable so the token is not committed; check, push, excubia:check, excubia:push and excubia:push-runtime print a warning on stderr when it is set.

Health endpoint

See the health endpoint guide. The secret is not a key: it comes from EXCUBIA_HEALTH_SECRET only.

KeyTypeDefaultMeaning
healthEndpointobjectOh Dear-compatible endpoint, Laravel only.
healthEndpoint.enabledbooleanfalseRegister the GET route (only when EXCUBIA_HEALTH_SECRET is set too, at least 32 characters).
healthEndpoint.pathstringexcubia/healthURL path of the route. Leading and trailing / are ignored. Letters, digits, ., _, ~, - and / only.

Runtime monitoring

See the runtime monitoring guide. Off by default; only runtime.enabled is needed to turn it on.

KeyTypeDefaultMeaning
runtimeobjectOpt-in runtime aggregates, Laravel only.
runtime.enabledbooleanfalseRegister the listeners and schedule excubia:push-runtime every minute.
runtime.bufferMaxBytesinteger ≥ 40961048576Size cap of storage/excubia/runtime/. The oldest entries are dropped first.
runtime.requestsobjectIncoming HTTP requests.
runtime.requests.enabledbooleantrueCollect requests while runtime.enabled is true.
runtime.requests.sampleRatenumber 0–11Share of requests recorded.
runtime.slowQueriesobjectDatabase queries at or above the threshold.
runtime.slowQueries.enabledbooleantrueCollect slow queries.
runtime.slowQueries.sampleRatenumber 0–11Share of slow queries recorded.
runtime.slowQueries.thresholdMsinteger ≥ 01000A query this slow or slower is recorded.
runtime.jobsobjectQueued jobs.
runtime.jobs.enabledbooleantrueCollect jobs.
runtime.jobs.sampleRatenumber 0–11Share of jobs recorded.
runtime.schedulerobjectScheduled tasks.
runtime.scheduler.enabledbooleantrueCollect scheduled tasks.
runtime.scheduler.sampleRatenumber 0–11Share of task runs recorded.
runtime.outgoingHttpobjectCalls made with Laravel's HTTP client.
runtime.outgoingHttp.enabledbooleantrueCollect outgoing calls.
runtime.outgoingHttp.sampleRatenumber 0–11Share of outgoing calls recorded.

Exception capture

See the exception capture guide. Off by default and independent of runtime.

KeyTypeDefaultMeaning
exceptionsobjectOpt-in exception capture, Laravel only.
exceptions.enabledbooleanfalseAdd the reportable() callback and schedule excubia:push-runtime every minute.
exceptions.maskPatternslist of strings[]Extra PCRE patterns with delimiters, for example "/ORD-[0-9]+/". Matches in messages become [masked], before the built-in masking. An invalid pattern is a configuration error.
exceptions.ignorelist of strings[]Exception classes never captured, subclasses included, for example "App\\Exceptions\\PaymentDeclinedException". A leading \ is ignored.
exceptions.bufferMaxBytesinteger ≥ 40961048576Size cap of storage/excubia/exceptions/. The oldest entries are dropped first.

Environment variables

Excubia reads these from the process environment (getenv()), never from Laravel config. With php artisan config:cache, Laravel no longer loads .env into the process, so set them in the real environment: the PHP-FPM pool, the container, the systemd unit or the CI job.

VariableUsed byMeaning
EXCUBIA_APP_URLpush, excubia:push, excubia:push-runtimeBase URL of your excubia-app. Wins over app.url.
EXCUBIA_APP_TOKENpush, excubia:push, excubia:push-runtimeProject push token. Wins over app.token. Never printed, cached or written to a report.
EXCUBIA_HEALTH_SECREThealth endpointSecret expected in the oh-dear-health-check-secret header, at least 32 characters. Without it, or when it is shorter, the route is not registered.
GITHUB_TOKENcomposer-staleOptional. Raises the GitHub API rate limit for the archived-repository lookup. Sent only to api.github.com; never printed, cached or written to a report.
HTTPS_PROXYall HTTP requestsProxy for the data sources and the push, read by Symfony HttpClient.
NO_COLORtable formatTurns colours off.

Empty values count as unset.


Source: docs/guides/configuration.md in excubia-core.

Previous
Introduction