You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+8-1Lines changed: 8 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,7 +64,9 @@ In July 2025 the AUR packages `firefox-patch-bin`, `librewolf-fix-bin`, and `zen
64
64
65
65
Then in June 2026 the **Atomic Arch** campaign made the point at scale. Attackers adopted **1,500+ orphaned** AUR packages and added a post-install step running `npm install atomic-lockfile`, later `bun install js-digest`, which pulled a Rust credential stealer and, when built as root, an **eBPF rootkit**. Some used git commit forgery to impersonate a trusted maintainer. The package name and history were unchanged. Only the build instructions, and who wrote them, had quietly changed.
66
66
67
-
aurscan is built for exactly this: the unfamiliar trick, not just the one you happen to know. Its prompt and static rules encode both of the signatures above, and the model is there to catch the next one nobody has seen yet.
67
+
Then in August 2026 `xsnow` and `xsnow-bin` moved the payload out of the PKGBUILD entirely. The PKGBUILD was clean. The attack lived in a dot-prefixed **`.xsnow.install`** scriptlet, which `makepkg` never executes — it is embedded in the built package as `.INSTALL` and run by pacman **as root on the installing machine**, on every install and every upgrade. It fetched a binary over Tor into `/usr/local/bin`, persisted it as a systemd unit, harvested `~/.ssh`, `/root/.ssh` and `/home/*/.ssh`, and then pushed **itself** into every AUR repository the victim's key could reach — so each new victim's PKGBUILD looked exactly as clean as the last one's. The only link between the PKGBUILD and the payload was a filename string in `install=`, and the leading dot hid the file from `ls` and from any tool that skips dotfiles.
68
+
69
+
aurscan is built for exactly this: the unfamiliar trick, not just the one you happen to know. Its prompt and static rules encode all three of the signatures above, and the model is there to catch the next one nobody has seen yet.
|`AURSCAN_CACHE_TTL`|`30`| verdict-cache lifetime in **days**; `0` = never expire |
@@ -457,6 +460,10 @@ aurscan --rules-only <pkgname|./dir> # or set AURSCAN_RULES_ONLY=1
457
460
458
461
**Quote-aware — obfuscation does not slip past.** The command, flag and path rules do not match raw text. The `PKGBUILD` and `.install` scripts are parsed with a real shell parser ([`mvdan.cc/sh`](https://github.com/mvdan/sh), pure-Go, vendored, **never executed**) and the rules run against the *deobfuscated* command view. So split-token tricks like `s"ud"o`, `cu""rl … | sh`, `su$'\x64'o` and `${IFS:0:0}sudo` are caught as the commands they actually run, while a `sudo` printed inside an `echo` instruction is correctly ignored instead of false-flagging. The splicing itself is also reported as **`OBF-004` (critical)** — a PKGBUILD has no honest reason to disguise a command name, so any attempt is treated as a strong signal in its own right, even when the disguised command is otherwise harmless.
459
462
463
+
**Install scriptlets are scanned, and a missing one is not a pass.** A `.install` scriptlet is where a package gets root on *your* machine, and it is reachable from the PKGBUILD only through a filename string in `install=`. aurscan resolves every referenced file — `install=`, local `source=()` entries — against the files it actually received, substituting PKGBUILD variables to do it. If a referenced file was **not supplied**, the scan is *incomplete*, which is a different claim from "clean": an `OK` verdict is no longer possible (**`REF-001`/`REF-003`**). A dot-prefixed `install=` is treated as concealment and is fatal on its own (**`REF-002`**). Within a scriptlet, rules cover the root-level behaviour that has no legitimate form there: a remote payload dropped into a system binary directory and made executable, a systemd unit written and enabled, `pacman` pulling in the payload's own dependencies, `.onion` C2 and SOCKS proxying, SSH material read or relocated, AUR push credentials used, and a script that copies **itself** — the signature of a worm, full stop.
464
+
465
+
**A rule hit is a fact; a model verdict is a judgement.** Deterministic findings are folded into the auditor's checklist as first-class checks and derived through the same code path as the model's own answers, so one derivation produces the verdict, severities, confidence and summary. The model can always **escalate**. It has no mechanism to **clear** a finding in the non-overridable set — which is how a scanner ends up reporting "97% confidence, no concerns" on a package whose payload it never read. This is deliberately a narrow floor: an ordinary critical hit in a PKGBUILD is still the model's call to dismiss, and `AURSCAN_STRICT_FLOOR=1` widens it if you would rather it were not.
466
+
460
467
**Build-cache hygiene — your `$HOME` should stay yours.** A `go build`/`go install` without a confined `GOPATH`/`GOMODCACHE` writes the module cache to `~/go/pkg/mod` (read-only files, unless `-modcacherw`); a `cargo build`/`cargo fetch` without `CARGO_HOME` writes registry and git caches to `~/.cargo`. Not malicious — failure by omission — but a scanner that promises "nothing ran yet" should tell you the build will write outside `$srcdir`. Reported as **`BLD-001`/`BLD-002` (info)**. Suppressed when the PKGBUILD exports or inline-prefixes the variable (an export in `prepare()` covers `build()` — same makepkg process) or, for Go, builds vendored with `-mod=vendor`. These checks use the same command-position-aware view: an echo'd `go build` does not fire.
0 commit comments