Skip to main content

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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pkglint-1.0.0.tar.gz (132.0 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pkglint-1.0.0-py3-none-manylinux_2_17_x86_64.whl (2.1 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

pkglint-1.0.0-py3-none-manylinux_2_17_aarch64.whl (1.9 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

pkglint-1.0.0-py3-none-macosx_11_0_arm64.whl (1.9 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

pkglint-1.0.0-py3-none-macosx_10_12_x86_64.whl (2.1 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file pkglint-1.0.0.tar.gz.

File metadata

  • Download URL: pkglint-1.0.0.tar.gz
  • Upload date:
  • Size: 132.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pkglint-1.0.0.tar.gz
Algorithm Hash digest
SHA256 4a3a117b70e8641c6916856d63af7ca3b015bd54358ce3fbc4593b7edc71bc9b
MD5 d04d6c08577e750477d6e39bb01f9f49
BLAKE2b-256 6587a99570065d7c8c695a3dda64c111101f2a8bc728aab67b8cb4cb5757f513

See more details on using hashes here.

File details

Details for the file pkglint-1.0.0-py3-none-manylinux_2_17_x86_64.whl.

File metadata

  • Download URL: pkglint-1.0.0-py3-none-manylinux_2_17_x86_64.whl
  • Upload date:
  • Size: 2.1 MB
  • Tags: Python 3, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pkglint-1.0.0-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 9edf041ba302350d0ef46449d38e830b0c025bc7b25642ae51c7f87ae741d184
MD5 b2841e2e7d7267113fdbd270e94c1e85
BLAKE2b-256 f793c4071b6abf5da68cc53c6d28f0ba210aee0c25669f96ce5f4a86cdc197a1

See more details on using hashes here.

File details

Details for the file pkglint-1.0.0-py3-none-manylinux_2_17_aarch64.whl.

File metadata

  • Download URL: pkglint-1.0.0-py3-none-manylinux_2_17_aarch64.whl
  • Upload date:
  • Size: 1.9 MB
  • Tags: Python 3, manylinux: glibc 2.17+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pkglint-1.0.0-py3-none-manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 12e292a6d6fccec356bfc32ebf9e366c60fb87bf062bf398756656f86c71cfa8
MD5 2ed8dde666a8fb5e1ef1e5975e25656c
BLAKE2b-256 dbaddc03d1edb1a60288b63485d4bf11bbdc12fc21a57477c13760faf91e8a8e

See more details on using hashes here.

File details

Details for the file pkglint-1.0.0-py3-none-macosx_11_0_arm64.whl.

File metadata

  • Download URL: pkglint-1.0.0-py3-none-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 1.9 MB
  • Tags: Python 3, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pkglint-1.0.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3b4afea98f1e30b1014411e0bf57bce5a0630d88ed3cf6c175160cb68c0efa91
MD5 1b11a08468bc985fee0ef8ffc977029d
BLAKE2b-256 14a361bab50fe802bf56e24e4eeaf6b8a01cf7ce489d6a261b0aab9ee17ec4fb

See more details on using hashes here.

File details

Details for the file pkglint-1.0.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

  • Download URL: pkglint-1.0.0-py3-none-macosx_10_12_x86_64.whl
  • Upload date:
  • Size: 2.1 MB
  • Tags: Python 3, macOS 10.12+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pkglint-1.0.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 bce4afb84ee0c58ffcd3c9889d34be081601d53e92d28475b2251afd873f1014
MD5 a71ed372737b950bdcfb498ba1988ad8
BLAKE2b-256 a3db6b2db4b35f9e53e59e26d62f522ed9375f105cab129ed49a013373de7457

See more details on using hashes here.

Release history Release notifications | RSS feed

1.6.2

5 files

1.6.1

5 files

1.6.0

5 files

1.5.3

5 files

1.5.2

5 files

1.5.1

5 files

1.5.0

5 files

1.4.0

5 files

1.3.4

5 files

1.3.3

5 files

1.3.2

5 files

1.3.1

5 files

1.3.0

5 files

1.2.1

5 files

1.2.0

5 files

1.1.3

5 files

1.1.2

5 files

1.1.1

5 files

1.1.0

5 files

1.0.1

5 files

This release

1.0.0 This release

5 files

0.1.0

5 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page