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.0

For 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:

FieldMeaning
package_name, package_versionwhat was scanned
findings[]each finding (below)
scanned_files[]every file that was read (scriptlets, hooks, local source=() scripts, committed binaries)
timestampUTC, ISO-8601
scan_duration_msscan 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

InvocationGate
aur-scan scan … --fail-on <severity>exits non-zero if any finding is at or above <severity>
aur-scan check … --no-confirm --fail-on highresolves 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_seconds bounds 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.” check accepts consent only from a real terminal.
  • min_severity is 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:

VariableEffect
AUR_SCAN_MODEgate (default) scans then hands off to the helper; install routes installs through aur-scan install, the race-free path
AUR_SCAN_SEVERITYminimum severity to show and to block on (default high); the shell integrations pass it as both --severity and --fail-on
AUR_SCAN_ENABLEDset to 0 to bypass temporarily
AUR_SCAN_VERBOSEmore output
AUR_SCAN_INTERACTIVE1 (default) prompts before proceeding; 0 doesn’t prompt
AUR_SCAN_SCAN_UPGRADES1 (default) scans every AUR package with a pending update on -Syu; 0 skips them
AUR_SCAN_SCAN_GETPKGBUILD1 also scans on -G/--getpkgbuild; off by default
AUR_SCAN_ALLOW_MENU0 (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_STRICT1 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_LOOKUPSVirusTotal 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 ran check --no-confirm without 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