Naming and Dependencies¶
A name is claimed, or a dependency set changes, in a way that redirects
what gets installed. R074 covers the package's own name impersonating a
popular one; D002 covers the same attack against a dependency name; R116
and D004 cover provides/replaces claiming a name that belongs to
something else.
Every rule here needs the dependency corpus, which is seeded from every
depends entry in the AUR plus every package name and provides alias.
Without the seed they stay silent rather than treating an empty table as
"nothing has ever been seen". Aggregate expansion is counted rather than
named, so it lives in count-based.
See the rule system reference for the field table, the severity weights and the reserved identifier ranges.
R016: New Make/Opt/Check Dependency¶
- Target:
raw_line - Severity: INFO (weight 0); see Note
- Category:
dependency - Pattern:
(?:makedepends|optdepends|checkdepends)\s*= - Scope: All lines
- Description: Fires when a
makedepends=,optdepends=, orcheckdepends=array is added or modified. At INFO it contributes weight 0 and reports context only. Sources a dependency-extraction exclusion: the dependency declaration itself is metadata, not a command, and must not be read for command-position matching.
R074: Package-Name Typosquat¶
- Target: programmatic (package name against seeded candidate list)
- Severity: HIGH (weight 25) - corpus rate 1.12 % (package-name scan)
- Category:
naming - Condition: The package's own name is Damerau-Levenshtein distance ≤2 (or differs only by separator/homoglyph) of an established, far-more-popular package - AND is not an expected variant (
-git,-bin,-debug,-lts, etc.) of that package.
This is the AUR equivalent of the python-sqlite vs pysqlite, electron vs electorn, and trailing-space/separator-swap attacks that have hit every other registry. The AUR has zero typosquat defense; D002 already covers dependency names, but nothing covers the package's own name impersonating a popular one.
The asymmetric gate (the make-or-break):
Symmetric edit-distance is a census generator: foo-git, foo-bin, foo-lts, and every legitimate fork are distance-small from foo. This rule fires ONLY when all hold:
- Similar - Damerau-Levenshtein ≤2 to a candidate
C. - Asymmetric popularity -
Cis observed 10x+ more often (viadependency_observation_count) than this package. A squat impersonates something bigger. - Not a variant - Expected suffixes (
-git,-bin,-debug,-lts,-stable,-beta,-svn,-hg,-bzr,-cvs,-wine,-appimage,-flatpak,-nightly,-devel,-common) are stripped before comparison.
Origin: npm/PyPI/crates typosquat detection - the most exploited supply-chain vector in every other ecosystem. The AUR is defenseless against it.
D001: Novel Dependency Added¶
- Severity: HIGH (weight 25)
- Category:
dependency - Description: A dependency name appears that has never been observed anywhere in the AUR. Either a typo or a package created specifically to be pulled in.
The corpus of known names is seeded from every depends/makedepends/optdepends/checkdepends entry in the AUR, plus every package name and provides alias. Without the latter, a real package that simply nothing else depends on would read as novel.
If the corpus has not been seeded the rule stays silent, rather than treating an empty table as "nothing has ever been seen".
Three classes of name are never considered novel, each an observed false positive:
| Ignored | Example | Why |
|---|---|---|
| Unresolved variables | $_pkgname, ${pkgbase} |
Not a name; the tokenizer could not expand it |
| Sonames | libwlroots-0.21.so |
Satisfied by whichever package provides that ABI |
| Companion split packages | jellyfin-desktop-libcef-bin alongside jellyfin-desktop-git |
Belongs to the same project, so it is expected to be globally unknown |
D002: Typosquatted Dependency¶
- Severity: HIGH (weight 25)
- Category:
dependency - Description: A novel dependency name within one or two edits (Damerau-Levenshtein, so transpositions count) of a popular package:
openss1foropenssl,cur1forcurl.
D002 refines D001: only a name D001 has already found to be globally unknown is compared, and D002 is reported instead of D001 when it matches. That ordering is what makes the check both affordable and correct. A precomputed table of confusable pairs cannot work, because a table built from existing package names can only contain names that must not fire, while the names that should fire do not exist yet.
Popularity is taken from observation_count, so no separate package list is shipped. The distance threshold scales with length: short names sit close to many unrelated real packages, with yay one edit from yak, yam, jay, and may.
D004: Dependency Hijack Via Provides¶
- Severity: HIGH (weight 25)
- Category:
dependency - Description:
provides=orreplaces=declares an established package unrelated to this one.provides=('openssl')orreplaces=('sudo')installs this package in front of the real one, satisfying every dependency on it.
"Established" means present in the official repositories (pacman -Slq), falling back to observation_count when pacman cannot be reached.
Relatedness is what makes this usable, since declaring a variant of yourself is the ordinary pattern. Two forms are accepted:
| Shape | Example | Fires |
|---|---|---|
| One name is a prefix of the other | htop-vim provides htop |
no |
| Shared leading token (siblings) | linux-cachyos provides linux-headers |
no |
| Shared token is a generic ecosystem prefix | python-evil provides python-requests |
yes |
| No relationship | some-pkg provides openssl |
yes |
The ecosystem carve-out matters: thousands of unrelated packages share python-, so treating that as evidence of a common project would suppress exactly the hijack the rule exists to catch.
D003: New Network-Using Makedepends¶
- Severity: MEDIUM (weight 15)
- Category:
dependency - Description:
makedependsgains a network-capable tool (curl,wget,git,python-requests, …) that was not there before, meaning the build can now fetch code that no checksum covers.
MEDIUM because adding git to fetch submodules is legitimate. It is a signal, not a verdict.
R116: Provides/Replaces Scope Expansion¶
- Severity: HIGH (weight 25) for an established package, MEDIUM (weight 15) for a widely-provided one
- Category:
dependency - Condition: A newly claimed
providesorreplacesnames an established package (official repo or fallback observations) or a widely-provided one (observation count at or above[r116] widely_provided_observations, default 25).
Claiming another project's name redirects installs of that name to this package. Relatedness suppresses the obvious false positive: variant, companion and sibling stems of the package's own name never fire. Cold start cannot fire either branch, since neither corpus nor pacman data exists to establish what is established. R116 always runs; the experimental D004 covers the same ground and may double-report when experimental rules are enabled.
Fire rate: 0 of 3246.
R095: Dependency Vendored Into Source¶
- Severity: HIGH (weight 25) for a security-relevant library, MEDIUM (weight 15) otherwise
- Category:
dependency - Condition: A dependency was removed and a new source entry appeared whose project name matches the removed dependency name.
Narrowed to that mechanical case on purpose. Vendoring a library bypasses the
distribution's security updates for it, and [patterns] security_relevant_libraries
is what raises the severity.
R101: Name/Host Consensus Divergence¶
- Severity: MEDIUM (weight 15)
- Category:
source - Condition: An ecosystem-prefixed package (
python-,ruby-,nodejs-, ...) is sourced from neither its ecosystem's canonical hosts nor a known forge.
R110: Name/Repo Divergence¶
- Severity: MEDIUM (weight 15)
- Category:
source - Condition: The package name and the repository it is built from share no meaningful token.