Skip to main content

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. --fix applies the safe rewrites, and --unsafe-fix applies 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. A curl | bash with ignore=PB304 above it is still refused. The printed report still honors them.
  • makepkg flags and words that would build or source a different file (-p, -D, --config, BUILDFILE=, PKGDEST=, BUILDDIR=, …) are rejected, and a file argument must be the PKGBUILD itself.
  • The packaging tree is left untouched: archives go to a temporary PKGDEST (use --keep <dir> to keep them), and the PKGBUILD is read-only during the build.
  • Sources are cached in ${XDG_CACHE_HOME:-~/.cache}/pkglint/sources and the build happens under $TMPDIR. Set $SRCDEST or $BUILDDIR to move either.
  • Dependencies are not installed and nothing is signed, so the hook never prompts for a password. Pass -- -s (or --makepkg-arg=-s in pre-commit args:) 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 with pacman -Syu before 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 makepkg on the host, or with --docker/--image, the build runs in a container ($PKGLINT_BUILD_IMAGE, $PKGLINT_BUILD_RUNNER = docker or podman). 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, write pkglint ./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)

Source distribution for pkglint 1.7.1
File Size Uploaded Provenance
pkglint-1.7.1.tar.gz 572.2 kB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for pkglint 1.7.1
File
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.

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