Skip to main content

pkglint

A security-focused linter for Arch Linux packages.

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.

Built packages (*.pkg.tar.zst and friends) are first-class inputs too: pkglint inspects the archive the way namcap does — ELF hardening (PIE, RELRO, executable stacks, text relocations, RPATH), stripping and placement, dependencies inferred from linked shared libraries and script shebangs (via pacman's local database), packaged .INSTALL scriptlets, and filesystem hygiene from FHS layout to stale python bytecode — while never executing anything from the package (no ldd, no interpreter launches; ELF files are parsed, not loaded).

$ 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

AUR

yay -S pkglint

PyPi

uv tool install pkglint

Go

go install github.com/jmelahman/pkglint@latest

Usage

pkglint [flags] [path ...]     # paths are package dirs, PKGBUILD files,
                               # or built packages (*.pkg.tar.*) (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–PB114 SKIP/weak/malformed 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–PB504 network access and persistence (crontabs, systemd units, shell profiles, login-capable users) in .install files running as root, unparseable scriptlets, commands pacman hooks already run
Consistency PB601–PB603 PKGBUILD / .SRCINFO drift, network access in pkgver(), provides/replaces/conflicts claims on core system packages
Correctness PB701–PB711 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, VCS sources without their client in makedepends
Built package PB801–PB839 everything namcap checks in a .pkg.tar.*: ELF in any packages and nonstandard paths, executable stacks, text relocations, missing RELRO, non-PIE executables, unstripped binaries, insecure RPATH/RUNPATH, missing/unused library and interpreter dependencies (resolved through pacman's database, statically — no ldd), stale soname declarations, pkg-config requirements, FHS layout, permissions and ownership, empty directories, invalid filenames, cross-directory hardlinks, dangling symlinks, .la/perllocal.pod/info dir/MIME-cache landmines, stale python bytecode, site-packages/tests, systemd/D-Bus units under /etc, missing license and backup files, doc-heavy packages, sphinx caches — plus the full scriptlet analysis over the packaged .INSTALL
Style PB901–PB912 namcap's PKGBUILD conventions: hardcoded architectures instead of $CARCH, custom variables without _ prefix, $startdir, redundant makedepends, pinned SourceForge mirrors, pkgname repeated in pkgdesc, makepkg-internal output helpers, missing Maintainer tag, uppercase package names, missing pkgdesc/url/license, version-only download names, depends duplicated in optdepends

pkglint --rules prints the full documentation for each. A full reference including examples of each rule is available in the documentation.

Relationship to namcap

pkglint covers namcap's rule set — both the PKGBUILD checks and the built-package checks — with a few deliberate differences: nothing from the analyzed package is ever executed (namcap runs ldd -r -u on packaged binaries; pkglint compares dynamic symbol tables instead), findings the lint host cannot actually verify (a library owned by a package that isn't installed here, a declared dependency that isn't installed) are reported informationally instead of as hard errors, and everything is folded into the same graded, suppressible, JSON/SARIF-capable reporting the PKGBUILD rules use. Dependency inference reads pacman's local database directly (/var/lib/pacman/local) and degrades gracefully on non-Arch hosts by skipping just those rules.

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.

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.1.0.tar.gz (220.1 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.1.0-py3-none-manylinux_2_17_x86_64.whl (2.7 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

pkglint-1.1.0-py3-none-manylinux_2_17_aarch64.whl (2.4 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

pkglint-1.1.0-py3-none-macosx_11_0_arm64.whl (2.5 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

pkglint-1.1.0-py3-none-macosx_10_12_x86_64.whl (2.7 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: pkglint-1.1.0.tar.gz
  • Upload date:
  • Size: 220.1 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.1.0.tar.gz
Algorithm Hash digest
SHA256 9765ae63e38b43142613d5b806d6ee8e8572d2d0a2925c5b1c0dfb02d345b597
MD5 842564fa0bf9b4d74780de2d4c1a86a3
BLAKE2b-256 e125563afed2c076fdf530a68d670c0ee740e38e2cf53e9cee12978ea53e6ecb

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pkglint-1.1.0-py3-none-manylinux_2_17_x86_64.whl
  • Upload date:
  • Size: 2.7 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.1.0-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 071a94ebb2dee468e267057764659c2a7838179f78c9f2a36639e49ca61b4489
MD5 94afa3d7efc5249bb0f8de3380b8e3b5
BLAKE2b-256 f47897cf6a616ac5ddf107fa3e145497cbf0329ce03d4306e6104999f0a659d7

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pkglint-1.1.0-py3-none-manylinux_2_17_aarch64.whl
  • Upload date:
  • Size: 2.4 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.1.0-py3-none-manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 5f07dea10d7336136fecf401d9c1b013f8b8935effce209737b2ccbcaa7a333d
MD5 ab906f4c94c85cf26065ddb56cf4f733
BLAKE2b-256 d01cf2c7aafa1da46657eaa8886b5d55167d528f112582c2c063d6448fe21237

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pkglint-1.1.0-py3-none-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 2.5 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.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f2fae06dbeae40a42633a2cc9a20095e69184785b71693028e7e92dc12c471a9
MD5 fcf7e8cc802456678130d6c594bf448c
BLAKE2b-256 e58700ea5dd66df72ca6ad3ff443de03c6809f7b1f5e858a2018def8183c5059

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pkglint-1.1.0-py3-none-macosx_10_12_x86_64.whl
  • Upload date:
  • Size: 2.7 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.1.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 27289ca4e377023af2bfb7f1ea6d22ee1794aa07a5cfe3aa4b5a1ccf63728991
MD5 ae1fa166ecf53af769a96118f30296c8
BLAKE2b-256 a41e2970b7e9fe3b9fb7e8836235645ac69ca85db8f0ec2ab6efca59f0a0c437

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

This release

1.1.0 This release

5 files

1.0.1

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