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.

Every page carries its own Open Graph and twitter:card metadata, so a shared link previews as a card rather than a bare URL. The card image is site/assets/og.png, rendered from site/og-card.html — it is a committed PNG because the preview readers will not take the SVG the rest of the site is drawn in. Because Open Graph is read without a document to resolve against, those URLs are absolute and the published origin is the baseURL constant in site/render.go.

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.1.tar.gz (139.6 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.1-py3-none-manylinux_2_17_x86_64.whl (2.1 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

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

Uploaded Python 3manylinux: glibc 2.17+ ARM64

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

Uploaded Python 3macOS 11.0+ ARM64

pkglint-1.0.1-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.1.tar.gz.

File metadata

  • Download URL: pkglint-1.0.1.tar.gz
  • Upload date:
  • Size: 139.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","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.1.tar.gz
Algorithm Hash digest
SHA256 8727801eeefc922ca7e7aa830fe17d1752a927018e286e361e31e316a258ff00
MD5 e7bdc03b5e52aa0906c1d69088efe99d
BLAKE2b-256 d419eaf1c8099d24886202ef0b603498e77185d7e38c1ce94a111dbf7ff656c7

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pkglint-1.0.1-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.7 {"installer":{"name":"uv","version":"0.12.7","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.1-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 c26999e55f1a1883df3fce9bd17fffc4a33cccf9a41f5eeeb0535f8b1a6bbdbf
MD5 3063e585690607303bc0968b32a9ce25
BLAKE2b-256 16f35d4aa941d9f2d964b71a9a63013ce72a96d7fed2e3ae113842e8096a30c5

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pkglint-1.0.1-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.7 {"installer":{"name":"uv","version":"0.12.7","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.1-py3-none-manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 88f003517e93608881a6bf2a25389000a4dcdb05c0769b409b78de9d359e2a70
MD5 8fd06ca7e524bb75bb2d781ead4c7d04
BLAKE2b-256 3caa7a835a2047a3c46266a28d52d59b4049806abfb2f05b19642924a444c943

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pkglint-1.0.1-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.7 {"installer":{"name":"uv","version":"0.12.7","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.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e99ac7d9753ec12be8f2f4e4065f915f7b68a912b75badba9d8d5cafd489c5a6
MD5 4553b317bc1d824de8d788ccb2c0ea21
BLAKE2b-256 f2dce5f6450babc4f3857d60ade21c77a5bda801da722f8b28c36f7e94527655

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pkglint-1.0.1-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.7 {"installer":{"name":"uv","version":"0.12.7","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.1-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 0115c38c1c0673020a936b9877ad64b05868e617b55507493dfc6f510c70df71
MD5 16a464e653604689d4950dcd24d28071
BLAKE2b-256 d6d32ba4b5f5872e389f7771f1b6e6e001d96846ba210473e70dc95e6c1852da

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

This release

1.0.1 This release

5 files

1.0.0

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