Output & CI
aur-scan is built to gate automation, not just to be read by a human. It emits
machine formats, returns meaningful exit codes, and — by design — fails
closed: when it can’t fully analyze something, it refuses rather than waves it
through.
Output formats
aur-scan scan takes --format, with three values:
aur-scan scan ./pkg --format text # default, human-readable
aur-scan scan ./pkg --format json # ScanResult document
aur-scan scan ./pkg --format sarif # SARIF 2.1.0For json and sarif, only the machine document goes to stdout — the human
summary and log lines are on stderr — so aur-scan scan ./pkg --format json | jq just works.
JSON
A json scan serializes the ScanResult:
| Field | Meaning |
|---|---|
package_name, package_version | what was scanned |
findings[] | each finding (below) |
scanned_files[] | every file that was read (scriptlets, hooks, local source=() scripts, committed binaries) |
timestamp | UTC, ISO-8601 |
scan_duration_ms | scan time |
Each finding carries id, severity (critical/high/medium/low/info),
category, title, description, location, recommendation, and cwe_id.
SARIF
The sarif format is SARIF 2.1.0 — runs[].tool.driver is aur-scan, with
rules and results[] built from the findings. Severity maps to SARIF levels:
critical/high → error, medium → warning, low/info → note. Rules are unique
and indexed, a finding without a line number carries no made-up startLine, and
URIs are relative to the scanned root. Upload it to GitHub code scanning or any
SARIF-aware dashboard.
JSON and SARIF always carry every finding. --severity and min_severity trim
only the text output; if they hide a finding that trips --fail-on, the text
output says so.
Exit codes & gating
| Invocation | Gate |
|---|---|
aur-scan scan … --fail-on <severity> | exits non-zero if any finding is at or above <severity> |
aur-scan check … --no-confirm --fail-on high | resolves the whole dep tree, no prompts, non-zero on threshold/error |
aur-scan install … --gate <severity> | fetch-scan-then-build; refuses to build if the gate trips |
aur-scan system --fail-on <severity> | non-zero at or above <severity> (default Critical), on an IOC match, or when an installed package couldn’t be scanned |
aur-scan diff <old> <new> --fail-on <severity> | 2.2.0+: non-zero only on newly added findings at or above <severity> |
--no-confirm is what you want in CI — there’s no TTY to answer a prompt, and a
gate that needs a human can’t run unattended. Pair it with --fail-on
to set the threshold you mean. Without one, 2.1.1 and later fail closed on a
Critical; 2.1.0 and earlier exited 0 even on a Critical.
Fail-closed in automation
The behavior that matters when there’s no human watching: the paru/yay
wrapper and the pacman hook deny instead of proceeding when they can’t get a
clean answer.
- A fetch or scan error marks the package unreviewed and blocks it. So does
a file the scanner couldn’t fully read (
SCAN-001), a dependency that resolves to nothing ([UNRESOLVED]), and a dependency tree cut short by the depth or size cap.timeout_secondsbounds each fetch and scan, and a timeout fails closed. - A non-interactive stdin (pipe, cron, CI) can’t confirm a risky package, so
it’s treated as a refusal — an empty read is never taken as “yes.”
checkaccepts consent only from a real terminal. min_severityis display-only: it can’t hide a finding from a gate.- The pacman hook aborts the transaction on any scan error, and exits rather than run with elevated privileges if its root-drop doesn’t stick.
A security gate that fails open isn’t a gate. If you drive paru/yay from a
script and an install can’t be analyzed, expect it to stop.
Shell-integration knobs
The shell integration reads a few environment variables:
| Variable | Effect |
|---|---|
AUR_SCAN_MODE | gate (default) scans then hands off to the helper; install routes installs through aur-scan install, the race-free path |
AUR_SCAN_SEVERITY | minimum severity to show and to block on (default high); the shell integrations pass it as both --severity and --fail-on |
AUR_SCAN_ENABLED | set to 0 to bypass temporarily |
AUR_SCAN_VERBOSE | more output |
AUR_SCAN_INTERACTIVE | 1 (default) prompts before proceeding; 0 doesn’t prompt |
AUR_SCAN_SCAN_UPGRADES | 1 (default) scans every AUR package with a pending update on -Syu; 0 skips them |
AUR_SCAN_SCAN_GETPKGBUILD | 1 also scans on -G/--getpkgbuild; off by default |
AUR_SCAN_ALLOW_MENU | 0 (default) refuses search-menu installs (paru <term>, yay <term>, yay -Y…, -S --interactive), because the package is picked after the scan; 1 scans the search term and leaves the final pick to the pacman hook |
AUR_SCAN_HOOK_STRICT | 1 makes the pacman hook abort, instead of warn, when a foreign package can’t be scanned (or its version differs, or no invoking user is found); /etc/aur-scanner/hook-strict does the same |
AUR_SCAN_VT_MAX_LOOKUPS | VirusTotal lookups per scan when threat intelligence is on (default 4; the config key vt_max_lookups wins) |
All four shell integrations (bash, zsh, fish, Nushell) and aur-scan-wrap honor
these. With no terminal on stdin the gate runs as --no-confirm: it denies
rather than guesses. The gate covers installs, upgrades, local builds and
repo/name operands, and refuses to run unscanned if aur-scan is missing
or the helper’s -Quaq upgrade query fails.
Using
AUR_SCAN_INTERACTIVE=0? Make sure you’re on 2.1.1 or later. Before 2.1.1 the integration rancheck --no-confirmwithout a gate, so a Critical didn’t stop paru/yay. Re-source your shell integration after upgrading.
Example: GitHub Actions gate
# Fail the job on high+ findings, and upload SARIF to code scanning.
- name: Scan PKGBUILD
run: aur-scan scan ./PKGBUILD --format sarif > aur-scan.sarif
- name: Enforce gate
run: aur-scan scan ./PKGBUILD --fail-on high
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: aur-scan.sarif