pkglint

command module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: GPL-3.0 Imports: 12 Imported by: 0

README

pkglint

A security-focused linter for Arch Linux PKGBUILDs.

pkglint statically analyzes PKGBUILDs and their install scriptlets — without ever sourcing them — and reports findings about source integrity, build hermeticity, code execution, and persistence patterns, condensed into a letter grade per package. It also reproduces makepkg's own build-breaking metadata checks, so a PKGBUILD that would fail to build is caught (and, where the fix is mechanical, rewritten) before you run makepkg. It is built on a real bash AST (mvdan.cc/sh), so the quoting/line-continuation tricks that evade regex-based scanners don't work here.

$ pkglint ~/pkgbuilds/somepkg
somepkg: grade F, 3 finding(s)
  PKGBUILD:16:3: critical [PB304] a network download is piped straight into bash and executed
  PKGBUILD:11:1: error [PB101] remote source "http://..." has no checksum (SKIP): the download is never verified
  PKGBUILD:24:3: error [PB402] sudo escalates privileges during a build; ...

Install

go install github.com/jmelahman/pkglint@latest

Usage

pkglint [flags] [path ...]     # paths are package dirs or PKGBUILD files (default: .)

  --format text|json|sarif     # output format (sarif = SARIF 2.1.0, for code scanning)
  --fail-on SEVERITY           # exit 1 at or above: info, warn, error (default), critical, never
  --ignore PB105,PB206         # disable rules
  --rules                      # list every rule with its documentation
  --fix                        # apply safe auto-fixes in place
  --unsafe-fix                 # also apply behavior-changing fixes (implies --fix)
  --diff                       # with --fix/--unsafe-fix: show changes instead of writing
  --offline                    # with --fix: skip fixes needing network (e.g. VCS ref resolution)

Suppress a reviewed, intentional finding inline:

# pkglint: ignore=PB204
go build -o "$pkgname" .

The directive covers its own line and the line below it, in the file it appears in only: an ignore= in an .install scriptlet never affects the PKGBUILD, or vice versa.

Auto-fixing

--fix rewrites what it can and prints every change; --diff previews without writing. Fixes come in two tiers:

Tier Flag Rules What it does
Safe --fix PB103, PB203, PB205, PB705, PB708 Pin a mutable VCS tag/branch to its current commit (via git ls-remote); append --locked to cargo; delete Go verification-disabling env settings; strip a leading slash from backup entries; wrap a scalar list field (depends=foo) in an array (depends=(foo))
Unsafe --unsafe-fix PB204, PB206–PB209, PB403 Add -mod=vendor to go build; switch npm installci and yarn install--immutable; append --frozen-lockfile to pnpm/bun install, --no-scripts to composer install, --frozen to bundle install and uv sync; drop setuid/setgid mode bits

Safe fixes preserve behavior or restore a security default; unsafe fixes are mechanical but change what the build does, so review them. An inline # pkglint: ignore= on a finding's line also suppresses its fix. Findings whose remediation isn't a mechanical rewrite (checksums, .SRCINFO) print a one-line suggestion (updpkgsums, makepkg --printsrcinfo) instead.

Rules

Group Rules What they catch
Integrity PB101–PB113 SKIP/weak checksums, unpinned VCS sources, unencrypted transports, source/url domain and forge-owner mismatches, DLAGENTS and other makepkg.conf overrides, checksum-count mismatches, missing install scripts, PGP signatures without pinned keys, insecure signature transport, unused validpgpkeys
Hermeticity PB201–PB209 network access outside prepare(), pip/uv pip without --require-hashes, unlocked cargo/npm/yarn/pnpm/bun/composer/bundler/uv/poetry installs, implicit Go module downloads and mutable @latest refs, disabled checksum databases
Execution PB301–PB309 top-level code, eval, decode-and-execute, download-and-execute (including eval "$(curl ...)" and source <(wget ...)" variants), /dev/tcp, unresolvable command names, embedded payloads, makepkg-internal function overrides, hidden bidi/zero-width characters
Filesystem PB401–PB405 writes outside $srcdir/$pkgdir, privilege escalation, setuid files and setcap capability grants, install steps that skip $pkgdir, writes to pacman/dynamic-linker/sudoers config
Scriptlets PB501–PB502 network access and persistence (crontabs, systemd units, shell profiles, login-capable users) in .install files running as root
Consistency PB601–PB603 PKGBUILD / .SRCINFO drift, network access in pkgver(), provides/replaces/conflicts claims on core system packages
Correctness PB701–PB710 makepkg build-breakers: invalid pkgname/pkgver/pkgrel/epoch, backup leading slash, unknown options, provides comparison operators, scalar-vs-array field types, schema variables set inside package(), missing/duplicate/mixed arch

pkglint --rules prints the full documentation for each.

Grading: any critical → F, any error → D, 3+ warns → C, 1–2 warns → B, otherwise A.

A grade is a static hygiene score, not a malware verdict — it measures how reviewable and reproducible a PKGBUILD is. A low grade means "worth reviewing", never "malicious", and a high grade is not an endorsement. Static analysis cannot catch a malicious upstream release pinned with a perfectly valid checksum.

Report card site

site/ generates a static "AUR Report Card" — grades, per-package finding pages, a rule reference with a flagged/preferred example for every check, results.json, and embeddable SVG badges. Findings whose rule has an auto-fix are tagged with a --fix/--unsafe-fix badge so it's clear at a glance what pkglint can rewrite for you:

go run ./site -maintainer Jamison -top 500 -out docs

It downloads the AUR metadata dump once a day, fetches package snapshots politely (throttled, cached by LastModified), and scans everything in-process.

Between runs it also remembers each package's source fingerprints (checksums per URL, VCS commit pins, pkgver) in .cache/state.json and flags drift: a checksum changing under an unchanged URL, or a commit pin moving without a version bump — the shape a hijacked upstream release takes. Drifted packages get a warning box on their page, a ⚠ marker on the index, and a drift array in results.json. This is a stateful, cross-scan signal, so it lives in the site generator rather than the per-file linter.

The generated site is checked into docs/ and served at https://jamison.lahman.dev/pkglint/. The Report card site workflow regenerates it nightly and commits any changes.

Roadmap

  • A makepkg shim so AUR helpers lint before building (yay --makepkg pkglint-makepkg, paru [bin] Makepkg)
  • Sandboxed builds: containerized makepkg with the package artifact installed on the host via pacman -U
  • Hermetic builds: two-phase makepkg -o (network) / makepkg -e (--network=none), with these lint rules enforcing the conventions that make that split work

License

GPLv3 — see LICENSE.

Documentation

Overview

pkglint is a security-focused linter for Arch Linux PKGBUILDs.

It statically analyzes PKGBUILDs and install scriptlets — never sourcing them — and reports integrity, hermeticity, and code-execution findings with an overall letter grade per package.

Directories

Path Synopsis
internal
pkgbuild
Package pkgbuild statically parses PKGBUILD and .install files.
Package pkgbuild statically parses PKGBUILD and .install files.
report
Package report aggregates rule findings into graded package reports and renders them for humans and machines.
Package report aggregates rule findings into graded package reports and renders them for humans and machines.
rules
Package rules implements pkglint's security and hygiene checks over statically parsed PKGBUILDs and install scriptlets.
Package rules implements pkglint's security and hygiene checks over statically parsed PKGBUILDs and install scriptlets.
Command site generates the static AUR report-card website.
Command site generates the static AUR report-card website.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL