On Friday, July 18th, 2025, the Arch Linux team was notified that three AUR packages had been uploaded that contained malware. A few maintainers including myself took care of deleting these packages, removing all traces of the malicious code, and protecting against future malicious uploads.

My fellow maintainer Quentin Michaud already did a nice write-up about how the malware worked, so I won’t go into detail too much about that. If you want to know more about that, read his blog. Instead, I’d like to do a crash course on how these packaging scripts work, and how you would review them yourself.

What is the AUR?

The Arch User Repository is a collection of packaging scripts, PKGBUILD files, created by users. Anyone who creates an account on aur.archlinux.org, can upload an Arch Linux packaging script, granted one doesn’t already exist for the same name. There are of course some rules around what you can submit (e.g. don’t duplicate other official or AUR packages) but generally anything goes.

Each package has one primary maintainer, by default whoever uploaded the packaging script first, but that can change over time, either by that maintainer transferring responsibility themselves, or by moderators removing the maintainer for various reasons. This is not a democracy, or even a meritocracy, but it works, and there is a lot of useful software on there.

Installing from AUR packaging scripts is not quite as simple as installing from the main packaging repos. You can run makepkg on a PKGBUILD and install the resulting package, but this gets more difficult as those PKGBUILDs often depend on other packages only found in the AUR. To make this easier, people often use AUR-helpers, which provide a pacman-like experience to manage them. That does come with some drawbacks, however.

Anyone who wants to do so can upload their PKGBUILD to the AUR. That is great to lower the barrier to entry, and it is how I got my start contributing to Arch Linux. Unfortunately, not everyone has the best intentions, and it has happened that people upload malware. Because of this, it is crucial that you vet the PKGBUILDs that you install.

What do PKGBUILDs look like?

As mentioned before, the AUR does not contain packages, but rather contains build scripts to create packages. Arch Linux PKGBUILDs are simply bash scripts that follow a certain pattern. Take for example the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Maintainer: John Doe <john@example.org>
pkgname=example
pkgver=1.0
pkgrel=1
pkgdesc="An example package"
arch=(x86_64)
url="https://example.org/"
license=('WTFPL')
install="example.install"
source=("https://example.org/$pkgname-$pkgver.tar.gz"
        "$pkgname-$pkgver.patch::https://git.example.org/example/pull/42.patch")
sha256sums=("de4bba005e86b4fbf0399e2cb492b1e941099b951a45137212507c2cbc8fae63"
            "b6dc933311bc2357cc5fc636a4dbe41a01b7a33b583d043a7f870f3440697e27")

prepare() {
    cd "$pkgname-$pkgver"
    patch -p1 -i "$srcdir/$pkgname-$pkgver.patch"
}

build() {
    cd "$pkgname-$pkgver"
    ./configure --prefix=/usr
    make
}

check() {
    cd "$pkgname-$pkgver"
    make -k check
}

package() {
    cd "$pkgname-$pkgver"
    make DESTDIR="$pkgdir/" install
}

It starts with a maintainer-line, which is not used programmatically but helps users of the software contact the maintainer if something doesn’t work. It is good practice to also have the emails of previous maintainers there, to recognise their contributions. Then we come to a few metadata variables.

Metadata

pkgname
defines the package we are building at the moment.
pkgver
signifies which upstream release of the software is being built. This number should only ever increase, as pacman will use this number to determine if an update is necessary.1
pkgrel
is the package release number. This release number is incremented every time an update is done to the PKGBUILD without updating the pkgver, such as when the packaging options change. It resets to 1 every time a new upstream release is done.
pkgdesc
is a short, human-readable description of the package. This is shown in package lists and is used for searching, but doesn’t affect the built software otherwise.
arch
is a list of valid CPU architectures this package can be built for. It can also be the special array ('any'), which does not (only) denote that this package can be built for any architecture, but rather that the resulting package does not contain any architecture-specific code. This is common for packages written in languages such as Python.
url
is just link a package user might go for more information and does not otherwise affect the package.
license
is the licence under which this package is distributed. This used to be an array of all applicable licences for the project, but since the meaning of that is a bit ambiguous, these days the licence array should hold a single item which is an SPDX license expression.
install
is usually not present in a PKGBUILD. If present, it refers to the name of a script (included alongside the package file) with specific bash functions that will get executed when the package is installed, upgraded, or removed. It is run from pacman, and therefore as root.
source
is an array of sources. These can be either downloadable URLs, version control URLs such as git, or simple file names, meaning the file in question in part of the package files.
sha256sums
is another array, of the same length as sources, specifying the expected SHA-256 checksums for the package sources. There are other hashing algorithms available, from crc32sums to b2sums. Multiple sets of digests may be specified but at least one should be. The magic value SKIP may be used to skip verifying checksums for certain files, for example for external checksums, or PGP signatures.

After all that metadata, we get to the actual code that does something: the packaging functions.

Build functions

Building packages is done in 4 stages, each with their own bash function. It starts with prepare(). This function is expected to make all the necessary changes to the source code, such as applying patch files or editing out certain lines with sed. In certain ecosystems, such as Node.js and Rust, it also downloads the dependencies. Ideally, all steps following prepare() no longer need network access.

Then we get to build(), which, as the name suggests, builds the package. This is the place to call configure scripts, compilers, and whatever else produces the binaries involved. This is, generally, the part that takes time.

After packaging, there is usually a check() step. Here we want to check whether the package functions as intended, within the Arch Linux packaging. What to do here varies, but most PKGBUILDs try to run the package’s unit tests. Arch Linux generally has never versions of libraries than the ones upstream projects test with, and such tests form a reasonable smoke test that things did not break in unexpected ways.

Finally, the package() function is called. This is the only function that’s technically required, all the others can be omitted. This function should move the files, generated in the build() step, into their final positions relative to some $pkgdir. Many ecosystems provide (an equivalent of) make install for this, which makes life easier, but often you will simply have to spell this out.

What to look out for

Now that we know what the different parts of a PKGBUILD mean, we can talk about reviewing its contents. I want to go over a few of the items I find most important, but this is by no means an exhaustive list. The fact I’m writing this will probably affect the strategies people use in some way. Nevertheless, let’s fist…

Check the sources array

Regardless of what the PKGBUILD itself says, if the sources it’s going to compile are malicious, nothing else really matters. Make sure that you trust the upstream project, and that files are downloaded from a reasonable place, such as tags in official git repositories, or source releases on the official website. PGP signatures from the authors are generally even better, so you know who created, or at least signed off on the sources.

The Arch Linux project has accepted an RFC on what sources should be used, in order to have the most trustworthy sources for packaging. While such rigour isn’t necessary or, in some cases, even desirable for the AUR, you can use it as inspiration.

Patches are also sources, and malware can hide in them. You should have a look at any patches being applied to the source code, and, if possible where they come from. It’s not unusual to apply merged-but-not-released changes from upstream to deal with newer versions of libraries, but make sure it makes sense.

Check if the build steps make sense

The prepare(), build(), check(), and package() functions are inherently code that will be executed on your machine when you build a package so if any

  • No downloads should happen in build(), check(), or package(), and ideally not in prepare(), though many ecosystems (Go, Node.js, Rust to name a few) require it.

  • The commands run by the script should be obvious packaging commands, and any custom scripts should come from the upstream repos. Often the packaging script matches the upstream “how to install” but often it doesn’t, as package maintainers generally call the build tools directly rather than call wrapper scripts.

  • Build scripts should not run sudo or anything similar. If it does that anyway, it’s wrong. At best, it’s a packaging error, as sudo shouldn’t be expected to work in a non-interactive environment like a build chroot. Sometimes a packager mistakenly tries to move package files into place instead of adding them to the package.

Scrutinise install scripts

Install scripts are, as mentioned above, rarely seen, but if when they are, they need to be scrutinised as their contents will run, as root, when a package is installed, upgraded, or removed. Most software doesn’t need this, as common use cases have safer alternatives.

Similarly, newly installed pacman hooks should also raise an eyebrow. Pacman hooks install to /usr/share/libalpm/hooks (and sometimes to /etc/pacman.d/hooks though that’s incorrect) and can run a command whenever its triggers are hit. The ones included in the main Arch repos are benign and in fact reduce the need for install scripts, by automatically regenerating font- and icon caches as needed, regenerating the initramfs, and many more. An arbitrary PKGBUILD adding one of their own is unusual, and you should pay attention to what that hook is trying to do.

If you don’t understand it, don’t use it

While we try to keep it clean, the AUR is freely accessible to anyone who figures out how to get past the CAPTCHA, and anyone can upload code to it. Even popular, well-intentioned code can have bugs sometimes and do harm to your system. The AUR is, at the best of times, maintained by volunteers, many of which are currently getting their feet wet for the first time while packaging. Mistakes can and will happen, and that’s okay.

When something might be malicious

Now that we have a hunch as to what might be malicious, we can decide what to do with it. The easiest way is to simply ask. The best place for that, in my opinion, is the #archlinux-aur IRC channel on Libera, which is the official venue for AUR discussion. The forums are also a good place to ask. If all fails, there is also the mailing list.

If the package is indeed malicious, one of the Package Maintainers can take it down and block the offending user(s). Should they try to avoid their ban, the measures will also escalate. As a user, that’s where your input ends.

That feels too simple

And it is. The AUR was made for a different kind of internet. More cooperative, less hostile. It’s based on trust that users will generally do sensible things.2 The AUR is old software. It started as “just some FTP server” where people could upload files. Such a system would not be designed in 2026.

There are improvements to be had for sure. The moderation systems are somewhat archaic, the contribution workflow is not what people expect. The licensing of PKGBUILD files, especially when the original author is long gone, is tricky.3 A pull-request like system is often suggested. I think that would be a real improvement. In general, the system needs more love. That’s what you get with open source projects; if no one wants to work on it, it gets no work. And that’s okay.

Right now I believe there is no end in sight for the AUR. We can keep going as-is for a while longer. But if people were to step up to make things better, we can do a lot more.


Acknowledgements

Cover image Cybersecurity palm print by Pete Linforth

More detail about the structure of PKGBUILD files can be found in its man page.

While I am a member of the Arch Linux package maintainer team, this post and all of the advice in it is my opinion and does not (necessarily) reflect that of Arch Linux as a whole.

  1. It’s slightly more complicated than comparing just the pkgver. The pkgrel is used as a tiebreaker, and epoch exists for when the version needs to jump backwards for whatever reason. However ↩︎

  2. It is also built for a world where web scrapers do not try to gobble up as much data as they can. The git history viewer wasn’t made for LLM-crawler abuse. Anubis has helped a little though. ↩︎

  3. In many or even most cases, PKGBUILD files should not be copyrightable, as they are largely mandatory text without creative input. No particular author owns ./configure && make && make install. But it’s not unthinkable that something required sufficiently complex code to build within the Arch Linux ecosystem, and that might qualify. What are the terms by which these files were shared? I don’t know, I’m not a lawyer. ↩︎