pkglint
A security-focused linter for Arch Linux packages.
$ 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:11:1: error [PB104] source "http://..." is fetched over unencrypted http:// with nothing verifying what arrives
1 package linted: 1 with findings
1 finding(s) fixable with --unsafe-fix
- Never runs the package. PKGBUILDs are parsed into a real bash syntax tree, not sourced, so quoting and line-continuation tricks that fool regex scanners don't work. Built packages are inspected without loading or executing anything in them.
- Checks what matters for trust: unverified sources, builds that fetch undeclared code, download-and-execute, writes outside the package, and persistence in install scripts.
- Covers namcap and makepkg too: namcap's PKGBUILD and built-package checks, plus makepkg's own build-breaking metadata errors, all in one report.
- Fixes what it can.
--fixapplies the safe rewrites, and--unsafe-fixapplies the ones that change build behavior.
The AUR Report Card runs pkglint nightly over the official repositories and the AUR's most popular and recently updated packages.
Install
AUR
yay -S pkglint
PyPI
uv tool install pkglint
Go
go install github.com/jmelahman/pkglint@latest
GitHub Releases
Prebuilt binaries for Linux and macOS (amd64 and arm64) are attached to every release.
pre-commit hook
repos:
- repo: https://github.com/jmelahman/pkglint
rev: 18319ffe6c6ea34d19fe0660ac2e99578cb883f0 # frozen: v1.7.0
hooks:
- id: pkglint
Other hook ids and arguments
| Hook id | Runs |
|---|---|
pkglint |
pkglint, built from source |
pkglint-system |
the pkglint already on $PATH |
pkglint-fix |
the safe auto-fixes, in place |
pkglint-build |
a full build, then lints the result (manual stage) |
Pass flags with args:, e.g. args: [--ignore, PB105, --fail-on, critical].
yay hook (yay ≥ 13)
To lint every AUR package before yay builds it, add the snippet below to
~/.config/yay/init.lua. It runs after each PKGBUILD is downloaded, before the
diff/edit menus or any build, and asks before continuing if pkglint finds anything.
init.lua snippet
-- pkglint: lint each AUR PKGBUILD before yay shows menus or builds anything.
-- PKGLINT_FAIL_ON overrides the threshold per run (info|warn|error|critical).
local function shquote(s) return "'" .. s:gsub("'", [['\'']]) .. "'" end
yay.create_autocmd("AURPreInstall", {
desc = "pkglint: refuse packages with static findings at or above the threshold",
callback = function(event)
local fail_on = os.getenv("PKGLINT_FAIL_ON") or "error"
-- --no-inline-ignores: the maintainer's "# pkglint: ignore=" doesn't get to
-- hide findings from the person installing the package.
local status = os.execute("pkglint --no-inline-ignores --fail-on "
.. shquote(fail_on) .. " " .. shquote(event.data.dir))
if status == 0 then return end
io.write(event.match .. ": pkglint reported findings. Continue anyway? [Y/n] ")
io.flush()
-- Enter continues; "n" or no terminal to read from (EOF) refuses.
local answer = io.read("*l")
if answer == nil or answer:match("^%s*[nN]") then
yay.abort("refused by pkglint")
end
end,
})
Usage
pkglint [flags] [path ...] # package dirs, PKGBUILD files, or *.pkg.tar.* (default: .)
--format text|json|sarif # output format (sarif for code scanning)
--fail-on SEVERITY # exit 1 at or above: info, warn (default), error, critical, never
--ignore PB105,PB206 # disable rules
--select PB101,PB304 # check only these rules
--fix # apply safe auto-fixes in place
--unsafe-fix # also apply behavior-changing fixes
--diff # with a fix flag: show changes instead of writing them
--offline # with --fix: skip fixes that need the network
--add-ignores # suppress every current finding with an inline directive
--no-inline-ignores # disregard ignore directives (audit an untrusted package)
--verbose # also list packages with no findings
--color auto|always|never
pkglint explain PB101 # one rule's docs, an example, and how to fix or suppress it
pkglint --rules # every rule
Suppressing findings
# pkglint: ignore=PB204
go build -o "$pkgname" .
A directive covers its own line and the next. To adopt pkglint on an existing package
without fixing everything at once, run --add-ignores once. Directives that stop
matching a finding are flagged and removed by --fix, so a fixed problem can't stay
silenced if it comes back.
Auto-fixing
--fix rewrites what it can and prints every change. Run it with --diff to preview.
Safe fixes keep the build's behavior or restore a security default. Unsafe fixes, such
as upgrading http:// to https:// or adding --locked to cargo, change what the
build does, so review them. A fix that can't see the whole picture is skipped rather
than half-applied. For findings without a mechanical fix, pkglint prints a hint like
updpkgsums or makepkg --printsrcinfo instead.
pkglint --rules and the rule reference
show which rules each flag fixes.
Build and validate
The built-package rules (PB8xx) need a built package:
pkglint build ~/pkgbuilds/somepkg
This lints the PKGBUILD, runs makepkg, and then lints the archive it produces.
pkglint build --fix also fixes PKGBUILD issues found only in the build output, such
as a linked library missing from depends.
This is the only pkglint command that runs PKGBUILD code. It refuses to build when
the static findings reach --fail-on, and always on a critical one. --force
overrides the refusal. Nothing in the PKGBUILD can talk its way past that check.
How the build is isolated, and what you can change:
- The gate ignores the file's own
# pkglint: ignore=directives. Acurl | bashwithignore=PB304above it is still refused. The printed report still honors them. makepkgflags and words that would build or source a different file (-p,-D,--config,BUILDFILE=,PKGDEST=,BUILDDIR=, …) are rejected, and a file argument must be thePKGBUILDitself.- The packaging tree is left untouched: archives go to a temporary
PKGDEST(use--keep <dir>to keep them), and thePKGBUILDis read-only during the build. - Sources are cached in
${XDG_CACHE_HOME:-~/.cache}/pkglint/sourcesand the build happens under$TMPDIR. Set$SRCDESTor$BUILDDIRto move either. - Dependencies are not installed and nothing is signed, so the hook never prompts
for a password. Pass
-- -s(or--makepkg-arg=-sin pre-commitargs:) to install dependencies. In a container, pkglint intercepts-s: makepkg gets no sudo there. pkglint reads the dependency lists from the PKGBUILD statically and installs them as root withpacman -Syubefore the package directory is copied in. It refuses a PKGBUILD whose lists cannot be read without running it, for example lists built by a command substitution,eval,source, or conditional assignment. For those, use an image with the dependencies already installed. - Without
makepkgon the host, or with--docker/--image, the build runs in a container ($PKGLINT_BUILD_IMAGE,$PKGLINT_BUILD_RUNNER=dockerorpodman). The container is a convenience, not a sandbox. The PKGBUILD's code still runs, and you are trusting the image. - To lint a directory named
build, writepkglint ./build.
Rules
| Rules | Category | Asks |
|---|---|---|
| PB101–PB116 | Integrity | Is every source verified, pinned, and fetched over a secure transport? |
| PB201–PB210 | Hermeticity | Does the build download anything it didn't declare and lock? |
| PB301–PB310 | Execution | Does it eval, decode-and-run, or pipe a download into a shell? |
| PB401–PB405 | Filesystem | Does it write outside the package, escalate privileges, or set setuid? |
| PB501–PB505 | Scriptlets | Do the root-run .install scripts reach the network or persist? |
| PB601–PB603 | Consistency | Does .SRCINFO match? Does it claim to replace core packages? |
| PB701–PB711 | Correctness | Would makepkg reject it? |
| PB801–PB842 | Built package | Is the binary hardened and stripped, with all its dependencies declared? |
| PB901–PB984 | Style | Does it follow namcap and the Arch packaging guidelines? |
Run pkglint explain <rule> or see the
rule reference for each rule's details.
License
GPLv3. See LICENSE.
Metadata
Release files for pkglint 1.7.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pkglint-1.7.1.tar.gz | 572.2 kB | Without provenance | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| pkglint-1.7.1-py3-none-manylinux_2_17_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| pkglint-1.7.1-py3-none-manylinux_2_17_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| pkglint-1.7.1-py3-none-macosx_11_0_x86_64.whl | Python 3 | none | macOS 11.0+ x86-64 | Details |
| pkglint-1.7.1-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 19.4 MB
Release files / pkglint-1.7.1.tar.gz
| Download URL | pkglint-1.7.1.tar.gz |
|---|---|
| Size | 572.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fe0c3a21efb3a3a317e2a063775c91840f6d702c6ff0de3efa9b80f8ea51774a
|
|
BLAKE2b-256 checksum How to use checksums |
9e5d449db8a5592694decfce80093c2dc203550db8d8a6b778cbb1999daa2492
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","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}
|
Release files / pkglint-1.7.1-py3-none-manylinux_2_17_x86_64.whl
| Download URL | pkglint-1.7.1-py3-none-manylinux_2_17_x86_64.whl |
|---|---|
| Size | 4.9 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
a67c2f27c0807019b63a5e8dfe346f29a7fd5fdb58f3ae4162d411b111e14066
|
|
BLAKE2b-256 checksum How to use checksums |
fc3a05f29619f64893d9ed7ce54b049d20d399b86a2415437abfa9e1ac70c70b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","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}
|
Release files / pkglint-1.7.1-py3-none-manylinux_2_17_aarch64.whl
| Download URL | pkglint-1.7.1-py3-none-manylinux_2_17_aarch64.whl |
|---|---|
| Size | 4.4 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
86588d7cbd101380e05d02ac755c25113188e6b34991777e76c136393289955c
|
|
BLAKE2b-256 checksum How to use checksums |
cbb6eae084811f47620b824fd2a6fa2cf6169c68795865a939e5ab5a2f65a433
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","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}
|
Release files / pkglint-1.7.1-py3-none-macosx_11_0_x86_64.whl
| Download URL | pkglint-1.7.1-py3-none-macosx_11_0_x86_64.whl |
|---|---|
| Size | 5.0 MB |
| Tags | Python 3 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
0ddc13d4ff16ff4e6afd6575cdb8e2205fa3cbd2fcc03a62e845682889c98863
|
|
BLAKE2b-256 checksum How to use checksums |
bce785e74ea99ca6c2c8bd9900a269fae1156eb127a8796125950217d446d304
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","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}
|
Release files / pkglint-1.7.1-py3-none-macosx_11_0_arm64.whl
| Download URL | pkglint-1.7.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 4.5 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
4e2a60e438e2cd5a4e03b76452afafd0c9d3a8167de44adf78a5fe6c56fe5185
|
|
BLAKE2b-256 checksum How to use checksums |
f83b9c6bf0340670c0248189cb9ce895ed8a5b50201a7400631030652648d239
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","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}
|
Security for pkglint 1.7.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.