BLA
BLA (Blind LLM Agents) is the open-source Blue Defense MCP and CLI built to protect software artifacts against automated AI reverse-engineering workflows.
Static security and hygiene grades for 49,783 PKGBUILDs from the AUR and Arch's official repositories, generated by pkglint without ever sourcing them.
SecurityGo · CSSGitHub @jmelahmanOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Linter for Arch Linux PKGBUILDs
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗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
--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.
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
| 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.
-- 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,
})
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
# 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.
--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.
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:
# 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.PKGDEST (use
--keep <dir> to keep them), and the PKGBUILD is read-only during the build.${XDG_CACHE_HOME:-~/.cache}/pkglint/sources and the build
happens under $TMPDIR. Set $SRCDEST or $BUILDDIR to move either.-- -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.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.build, write pkglint ./build.| 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.
GPLv3. See LICENSE.