Hi List,

the worst of the June 14th attack now seems to be past us (fingers 
crossed). Unfortunately I guess that it'll not be the last attack the 
AUR will see.

Here's two points:

a) aur-malware-check script

Here's a link to a repo that has been quite popular during the event: 
https://github.com/lenucksi/aur-malware-check

It started out as a collection / summary of the scripts distributed 
throughout the initial gists and then received contributions from others 
for another, fully unrelated "campaign" and the "russian spam" packages 
that were discussed on this list later.

I then turned the initial shell scripts into Python with the intent of 
being as concise as possible, as standard lib-only as possible and as 
reviewable as possible together with the best possible 
grounding+connection in the original sources that are connected to a 
"campaign". Now it has a format that can take in additional malware 
"campaigns" in an easier way if they're sort-of similar to the previous 
ones or can be extended easy enough to fit other malware approaches.

The script has been created with human review (multiple people) and 
LLM-assistance. So for those who hate the existence and/or the use of 
LLMs: Please ignore the script. For the others: Maybe it's something 
that could be of use when the next set of nasty people come around. More 
constructive human review/improvement welcome of course.


b) Enforcing isolated chroot building of packages (or at least promoting 
what already exists)

To my understanding the blast radius of the 14th of June campaign could 
have been limited to a good degree by enforcing the building of the 
PKGBUILD in a chroot/container/nspawn etc that is properly isolated 
against the host system it runs on. Of course, if the resulting package 
is infected past the build process and then does its ill work after / 
when being installed, this is of no help. I did find the devtools 
(https://gitlab.archlinux.org/archlinux/devtools) package, its pkgctl 
tool and of course the more recent AUR helpers such as paru have an 
optional --chroot parameter.

However none of it is default, advertised properly or promoted as a good 
/ best practice even though upon trying those they all worked quite 
well, didn't really take excessive amounts of disk space and didn't 
really extend the time to build and AUR package that much.

I dug around the devtools package and how it is used today and added a 
few tweaks to it that run fine here. Basic point was: See to what degree 
the modern Linux capabilities can be used to isolate the build a bit 
better than what the current chrooted builds do. Also, isolate what the 
AUR builds download in terms of cached packages from the main cache. 
Provide an option to clean said cache after build.

Since the  Archlinux Gitlab is invite only, I have converted the changes 
to patches and attached them here. Maybe they are useful to someone.


Cheers,

Lenucksi


-- 
From 3ad10915cf040956d6537cdc787bc45c9a99def2 Mon Sep 17 00:00:00 2001
From: Lenucksi <[email protected]>
Date: Tue, 7 Jul 2026 15:09:39 +0200
Subject: [PATCH 0/4] arch-nspawn container boundary hardening + AUR cache isolation

This series hardens the systemd-nspawn container boundary in
arch-nspawn and makechrootpkg, and adds AUR cache isolation.

Patches 1-2 add the security infrastructure: capability drops,
seccomp filters, address family restrictions, a config override
mechanism for nspawn flags, noexec on tmpfs, a deterministic
UID range picker, and writable overlay cache support under user
namespaces.

Patch 3 adds AUR cache separation: a dedicated writable cache for
AUR builds when user namespaces are disabled, and a DEVTOOLS_CACHE_CLEAR
flag to control cache persistence.

Patch 4 adds documentation for all new features.

All changes have been tested on Arch Linux with a bespoke fortress
test suite (38/38 PASS) and real-world paru builds in both
user-namespace and non-user-namespace modes.

Lenucksi (4):
  feat: harden arch-nspawn container boundary with security flags
  feat: add user namespace support with writable overlay cache
  feat: separate AUR package cache from host cache
  docs: document container hardening, user namespace, and AUR cache
    isolation

 Makefile                                  |   7 ++
 config/nspawn.conf.d/nspawn-security.conf |  29 +++++
 doc/man/arch-nspawn.1.asciidoc            |  43 +++++++
 doc/man/makechrootpkg.1.asciidoc          |  14 ++-
 src/arch-nspawn.in                        | 130 +++++++++++++++++++++-
 src/makechrootpkg.in                      |   6 +-
 6 files changed, 219 insertions(+), 10 deletions(-)
 create mode 100644 config/nspawn.conf.d/nspawn-security.conf

-- 
2.55.0

From 86fbd1a76e2d1a772591b0b27063350ddbfd325d Mon Sep 17 00:00:00 2001
From: Lenucksi <[email protected]>
Date: Tue, 7 Jul 2026 15:06:09 +0200
Subject: [PATCH 1/4] feat: harden arch-nspawn container boundary with security
 flags

Add a default nspawn_security_flags block to arch-nspawn.in:
- Drop 7 capabilities (CAP_BPF, CAP_NET_ADMIN, CAP_SYS_PTRACE,
  CAP_SYS_MODULE, CAP_MKNOD, CAP_NET_RAW, CAP_SYS_CHROOT)
- Restrict syscalls via seccomp (~@keyring @mount)
- Restrict address families to AF_INET, AF_INET6, AF_UNIX

Add a config override mechanism at
/usr/share/devtools/nspawn.conf.d/nspawn-security.conf, which is
sourced if present. The shipped default enables user namespace
isolation via --private-users=yes --private-users-ownership=map.

Mount /tmp with noexec in makechrootpkg to prevent execution of
binaries placed in /tmp inside the container.

CAP_SYS_ADMIN is retained in the bounding set for pacman Landlock
sandbox compatibility.
---
 Makefile                                  |  7 +++++++
 config/nspawn.conf.d/nspawn-security.conf | 21 +++++++++++++++++++++
 src/arch-nspawn.in                        | 11 +++++++++++
 src/makechrootpkg.in                      |  2 +-
 4 files changed, 40 insertions(+), 1 deletion(-)
 create mode 100644 config/nspawn.conf.d/nspawn-security.conf

diff --git a/Makefile b/Makefile
index 12c9745..100f79a 100644
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@ LIBRARY = $(addprefix $(BUILDDIR)/,$(patsubst src/%,%,$(patsubst %.in,%,$(LIBRAR
 MAKEPKG_CONFIGS=$(wildcard config/makepkg/*)
 PACMAN_CONFIGS=$(wildcard config/pacman/*)
 GIT_CONFIGS = $(wildcard config/git/*)
+NSPAWN_CONFIGS = $(wildcard config/nspawn.conf.d/*)
 SETARCH_ALIASES = $(wildcard config/setarch-aliases.d/*)
 MANS = $(addprefix $(BUILDDIR)/,$(patsubst %.asciidoc,%,$(wildcard doc/man/*.asciidoc)))
 DATA_FILES = $(wildcard data/*)
@@ -112,6 +113,8 @@ conf:
 	@cp -a $(PACMAN_CONFIGS) $(BUILDDIR)/pacman.conf.d
 	@install -d $(BUILDDIR)/git.conf.d
 	@cp -a $(GIT_CONFIGS) $(BUILDDIR)/git.conf.d
+	@install -d $(BUILDDIR)/nspawn.conf.d
+	@cp -a $(NSPAWN_CONFIGS) $(BUILDDIR)/nspawn.conf.d
 
 data:
 	@install -d $(BUILDDIR)/data
@@ -125,11 +128,13 @@ install: all
 	install -dm0755 $(DESTDIR)$(DATADIR)/setarch-aliases.d
 	install -dm0755 $(DESTDIR)$(DATADIR)/makepkg.conf.d
 	install -dm0755 $(DESTDIR)$(DATADIR)/pacman.conf.d
+	install -dm0755 $(DESTDIR)$(DATADIR)/nspawn.conf.d
 	install -m0755 ${BINPROGS} $(DESTDIR)$(PREFIX)/bin
 	install -dm0755 $(DESTDIR)$(DATADIR)/lib
 	install -dm0755 $(DESTDIR)$(DATADIR)/data
 	cp -ra $(BUILDDIR)/lib/* $(DESTDIR)$(DATADIR)/lib
 	cp -a $(BUILDDIR)/git.conf.d -t $(DESTDIR)$(DATADIR)
+	cp -a $(BUILDDIR)/nspawn.conf.d -t $(DESTDIR)$(DATADIR)
 	cp -ra $(BUILDDIR)/makepkg.conf.d -t $(DESTDIR)$(DATADIR)
 	cp -ra $(BUILDDIR)/data -t $(DESTDIR)$(DATADIR)
 	for conf in $(notdir $(PACMAN_CONFIGS)); do install -Dm0644 $(BUILDDIR)/pacman.conf.d/$$conf $(DESTDIR)$(DATADIR)/pacman.conf.d/$${conf##*/}; done
@@ -149,6 +154,7 @@ uninstall:
 	for f in $(notdir $(LIBRARY)); do rm -f $(DESTDIR)$(DATADIR)/lib/$$f; done
 	rm -rf $(DESTDIR)$(DATADIR)/lib
 	rm -rf $(DESTDIR)$(DATADIR)/git.conf.d
+	rm -rf $(DESTDIR)$(DATADIR)/nspawn.conf.d
 	rm -rf $(DESTDIR)$(DATADIR)/makepkg.conf.d
 	rm -rf $(DESTDIR)$(DATADIR)/data
 	for conf in $(notdir $(PACMAN_CONFIGS)); do rm -f $(DESTDIR)$(DATADIR)/pacman.conf.d/$${conf##*/}; done
@@ -163,6 +169,7 @@ uninstall:
 	rmdir --ignore-fail-on-non-empty \
 		$(DESTDIR)$(DATADIR)/setarch-aliases.d \
 		$(DESTDIR)$(DATADIR)/pacman.conf.d \
+		$(DESTDIR)$(DATADIR)/nspawn.conf.d \
 		$(DESTDIR)$(DATADIR)
 
 tag:
diff --git a/config/nspawn.conf.d/nspawn-security.conf b/config/nspawn.conf.d/nspawn-security.conf
new file mode 100644
index 0000000..d664c28
--- /dev/null
+++ b/config/nspawn.conf.d/nspawn-security.conf
@@ -0,0 +1,21 @@
+# This file is sourced by arch-nspawn to extend nspawn_security_flags
+# with user-specific security options.
+#
+# Flags specified here are appended to the defaults in arch-nspawn.in.
+# See systemd-nspawn(1) for available options.
+#
+# === User namespace isolation ===
+#
+# Enable --private-users to map the container's root (UID 0) to an
+# unprivileged host UID, preventing container root from having full
+# host root privileges. This is the single most impactful hardening
+# measure.
+#
+# Replace "yes" with an explicit numeric range if you need a writable
+# overlay cache inside the container, e.g.:
+#   --private-users=524288:65536
+#
+nspawn_security_flags+=(
+    --private-users=yes
+    --private-users-ownership=map
+)
diff --git a/src/arch-nspawn.in b/src/arch-nspawn.in
index fdafe9c..2d5b5bc 100644
--- a/src/arch-nspawn.in
+++ b/src/arch-nspawn.in
@@ -68,6 +68,17 @@ nspawn_args=(
 	--timezone=off
 )
 
+nspawn_security_flags=(
+	--drop-capability=CAP_BPF,CAP_NET_ADMIN,CAP_SYS_PTRACE,CAP_SYS_MODULE,CAP_MKNOD,CAP_NET_RAW,CAP_SYS_CHROOT
+	--system-call-filter="~@keyring @mount"
+	--restrict-address-families="AF_INET AF_INET6 AF_UNIX"
+)
+
+nspawn_conf="@pkgdatadir@/nspawn.conf.d/nspawn-security.conf"
+[[ -f $nspawn_conf ]] && source "$nspawn_conf"
+
+nspawn_args+=("${nspawn_security_flags[@]}")
+
 if (( ${#cache_dirs[@]} == 0 )); then
 	mapfile -t cache_dirs < <(pacman-conf --config "${pac_conf:-$working_dir/etc/pacman.conf}" CacheDir)
 fi
diff --git a/src/makechrootpkg.in b/src/makechrootpkg.in
index 3574ea6..eded65d 100644
--- a/src/makechrootpkg.in
+++ b/src/makechrootpkg.in
@@ -34,7 +34,7 @@ clean_first=0
 run_namcap=0
 run_checkpkg=0
 temp_chroot=0
-tmp_opts="nosuid,nodev,size=50%,nr_inodes=2m"
+tmp_opts="nosuid,nodev,noexec,size=50%,nr_inodes=2m"
 
 inspect=never
 
-- 
2.55.0

From 93cff43e75d733dc68e46548a03c9e796e8009d1 Mon Sep 17 00:00:00 2001
From: Lenucksi <[email protected]>
Date: Tue, 7 Jul 2026 15:07:14 +0200
Subject: [PATCH 2/4] feat: add user namespace support with writable overlay
 cache

Add pick_uid_base(): a deterministic UID allocator that derives a
private-users UID range from the hostname via md5sum. This allows
multiple containers on the same host to each get a stable, non-
overlapping UID range.

Add --private-users=yes resolution: when the config file sets
--private-users=yes, resolve it to a numeric range at runtime so
that overlay mounts receive the correct UID ownership.

Replace read-only bind mounts (--bind-ro) with writable overlay
mounts (--overlay) when --private-users is active, pre-creating and
chowning the upperdir to the mapped container root UID. Cache upperdir
is cleared before and after each nspawn invocation so transient build
dependencies don't accumulate between builds.

Add DEVTOOLS_CACHE_CLEAR=1 (default): gates the upperdir cleanup.
Set to 0 in nspawn-security.conf to preserve the overlay cache.

Add ID-mapped bind mounts (:idmap) for source directories in
makechrootpkg so the build user inside a user namespace can access
source files with their original host UIDs. Strip :idmap from
positional arguments when --private-users is not active.
---
 src/arch-nspawn.in   | 85 +++++++++++++++++++++++++++++++++++++++++---
 src/makechrootpkg.in |  4 +--
 2 files changed, 82 insertions(+), 7 deletions(-)

diff --git a/src/arch-nspawn.in b/src/arch-nspawn.in
index 2d5b5bc..5498311 100644
--- a/src/arch-nspawn.in
+++ b/src/arch-nspawn.in
@@ -13,6 +13,10 @@ source "${_DEVTOOLS_LIBRARY_DIR}"/lib/archroot.sh
 # ensure that sane default is set again
 umask 0022
 
+# When set to 0, the overlay upperdir is not cleared between builds.
+# Set this in nspawn.conf.d/nspawn-security.conf to persist downloaded packages.
+DEVTOOLS_CACHE_CLEAR=${DEVTOOLS_CACHE_CLEAR:-1}
+
 working_dir=''
 
 files=()
@@ -77,6 +81,50 @@ nspawn_security_flags=(
 nspawn_conf="@pkgdatadir@/nspawn.conf.d/nspawn-security.conf"
 [[ -f $nspawn_conf ]] && source "$nspawn_conf"
 
+pick_uid_base() {
+	local name="$1"
+	local min=524288 step=65536
+	local max=1878982656
+	local slots=$(( (max - min) / step ))
+	local hex slot uid
+
+	hex=$(echo -n "$name" | md5sum 2>/dev/null | head -c 8)
+	hex=${hex:-0}
+	slot=$(( (16#${hex}) % slots ))
+	uid=$(( slot * step + min ))
+
+	echo "$uid"
+}
+
+clear_cache_upperdir() {
+	(( DEVTOOLS_CACHE_CLEAR )) || return
+	[[ -z $private_users_uid ]] && return
+	local cache_dir upperdir
+	for cache_dir in "${cache_dirs[@]}"; do
+		upperdir="${cache_dir}.upper"
+		[[ -d $upperdir ]] && find "$upperdir" -mindepth 1 -delete
+	done
+}
+
+private_users_uid=
+for i in "${!nspawn_security_flags[@]}"; do
+    if [[ ${nspawn_security_flags[$i]} == --private-users=* ]]; then
+        private_users_uid=${nspawn_security_flags[$i]#*=}
+        private_users_uid=${private_users_uid%%:*}
+        if ! [[ $private_users_uid =~ ^[0-9]+$ ]]; then
+            uid_base=$(pick_uid_base "$(cat /proc/sys/kernel/hostname 2>/dev/null || echo devtools-container)" 2>/dev/null || true)
+            if [[ -n $uid_base ]]; then
+                private_users_uid="$uid_base"
+                nspawn_security_flags[$i]="--private-users=${uid_base}:65536"
+            else
+                private_users_uid=
+                unset 'nspawn_security_flags[$i]'
+            fi
+        fi
+        break
+    fi
+done
+
 nspawn_args+=("${nspawn_security_flags[@]}")
 
 if (( ${#cache_dirs[@]} == 0 )); then
@@ -107,10 +155,23 @@ while read -r line; do
 	done
 done < <(pacman-conf --config "${pac_conf:-$working_dir/etc/pacman.conf}" --repo-list)
 
-nspawn_args+=(--bind="${cache_dirs[0]//:/\\:}")
-
-for cache_dir in "${cache_dirs[@]:1}"; do
-	nspawn_args+=(--bind-ro="${cache_dir//:/\\:}")
+for cache_dir in "${cache_dirs[@]}"; do
+	cache_dir_escaped="${cache_dir//:/\\:}"
+	if [[ -n $private_users_uid ]]; then
+		upperdir="${cache_dir}.upper"
+		if [[ -d $upperdir ]]; then
+			current_owner=$(stat -c '%u' "$upperdir")
+			if [[ $current_owner != "$private_users_uid" ]]; then
+				chown -R "${private_users_uid}:${private_users_uid}" "$upperdir"
+			fi
+		else
+			mkdir -p "$upperdir"
+			chown "${private_users_uid}:${private_users_uid}" "$upperdir"
+		fi
+		nspawn_args+=(--overlay="${cache_dir_escaped}:${upperdir}:${cache_dir_escaped}")
+	else
+		nspawn_args+=(--bind-ro="${cache_dir_escaped}")
+	fi
 done
 
 # {{{ functions
@@ -163,4 +224,18 @@ else
 	set_arch="${CARCH}"
 fi
 
-exec ${CARCH:+setarch "$set_arch"} systemd-nspawn "${nspawn_args[@]}" "$@"
+clear_cache_upperdir
+
+if [[ -z $private_users_uid ]]; then
+	stripped=()
+	for arg in "$@"; do
+		stripped+=("${arg/:idmap/}")
+	done
+	set -- "${stripped[@]}"
+fi
+
+${CARCH:+setarch "$set_arch"} systemd-nspawn "${nspawn_args[@]}" "$@"
+ret=$?
+
+clear_cache_upperdir
+exit $ret
diff --git a/src/makechrootpkg.in b/src/makechrootpkg.in
index eded65d..b6dc17e 100644
--- a/src/makechrootpkg.in
+++ b/src/makechrootpkg.in
@@ -394,8 +394,8 @@ download_sources
 prepare_chroot
 
 nspawn_build_args=(
-	--bind="${PWD//:/\\:}:/startdir"
-	--bind="${SRCDEST//:/\\:}:/srcdest"
+	--bind="${PWD//:/\\:}:/startdir:idmap"
+	--bind="${SRCDEST//:/\\:}:/srcdest:idmap"
 	--tmpfs="/tmp:${tmp_opts}"
 	"${bindmounts_ro[@]}"
 	"${bindmounts_rw[@]}"
-- 
2.55.0

From c550d701370ce1ef3e0ca604d5cb908583e0b600 Mon Sep 17 00:00:00 2001
From: Lenucksi <[email protected]>
Date: Tue, 7 Jul 2026 15:08:10 +0200
Subject: [PATCH 3/4] feat: separate AUR package cache from host cache

Add AUR cache isolation for builds without --private-users
(non-user-namespace mode):

- A dedicated writable bind mount at /var/cache/aurcache stores
  packages downloaded by AUR helpers, preventing AUR builds from
  polluting the host package cache at /var/cache/pacman/pkg.
- Inject a CacheDir entry into the container's pacman.conf via awk
  (section-aware, placed after the last existing CacheDir in [options]).
- Skip the aurcache path in the cache_dir processing loop to prevent
  systemd-nspawn's open_tree() from failing on feed-forward mounts.
- Remove the stale sed-based CacheDir manipulation from copy_hostconf
  that incorrectly overwrote the container's CacheDir with host paths.
- Add clear_aurcache() gated by DEVTOOLS_CACHE_CLEAR, mirroring
  clear_cache_upperdir() for the non-secconf case.
---
 src/arch-nspawn.in | 65 ++++++++++++++++++++++++++++++++++------------
 1 file changed, 48 insertions(+), 17 deletions(-)

diff --git a/src/arch-nspawn.in b/src/arch-nspawn.in
index 5498311..ed04069 100644
--- a/src/arch-nspawn.in
+++ b/src/arch-nspawn.in
@@ -13,7 +13,7 @@ source "${_DEVTOOLS_LIBRARY_DIR}"/lib/archroot.sh
 # ensure that sane default is set again
 umask 0022
 
-# When set to 0, the overlay upperdir is not cleared between builds.
+# When set to 0, the overlay upperdir and aurcache are not cleared between builds.
 # Set this in nspawn.conf.d/nspawn-security.conf to persist downloaded packages.
 DEVTOOLS_CACHE_CLEAR=${DEVTOOLS_CACHE_CLEAR:-1}
 
@@ -106,6 +106,12 @@ clear_cache_upperdir() {
 	done
 }
 
+clear_aurcache() {
+	(( DEVTOOLS_CACHE_CLEAR )) || return
+	[[ -n $private_users_uid ]] && return
+	[[ -d $aurcache_host ]] && find "$aurcache_host" -mindepth 1 -delete
+}
+
 private_users_uid=
 for i in "${!nspawn_security_flags[@]}"; do
     if [[ ${nspawn_security_flags[$i]} == --private-users=* ]]; then
@@ -155,24 +161,33 @@ while read -r line; do
 	done
 done < <(pacman-conf --config "${pac_conf:-$working_dir/etc/pacman.conf}" --repo-list)
 
-for cache_dir in "${cache_dirs[@]}"; do
-	cache_dir_escaped="${cache_dir//:/\\:}"
-	if [[ -n $private_users_uid ]]; then
-		upperdir="${cache_dir}.upper"
-		if [[ -d $upperdir ]]; then
-			current_owner=$(stat -c '%u' "$upperdir")
-			if [[ $current_owner != "$private_users_uid" ]]; then
-				chown -R "${private_users_uid}:${private_users_uid}" "$upperdir"
+	aurcache_host="/var/cache/pacman/pkg/.aurcache"
+	aurcache_container="/var/cache/aurcache"
+
+	for cache_dir in "${cache_dirs[@]}"; do
+		[[ $cache_dir == "$aurcache_container" ]] && continue
+		cache_dir_escaped="${cache_dir//:/\\:}"
+		if [[ -n $private_users_uid ]]; then
+			upperdir="${cache_dir}.upper"
+			if [[ -d $upperdir ]]; then
+				current_owner=$(stat -c '%u' "$upperdir")
+				if [[ $current_owner != "$private_users_uid" ]]; then
+					chown -R "${private_users_uid}:${private_users_uid}" "$upperdir"
+				fi
+			else
+				mkdir -p "$upperdir"
+				chown "${private_users_uid}:${private_users_uid}" "$upperdir"
 			fi
+			nspawn_args+=(--overlay="${cache_dir_escaped}:${upperdir}:${cache_dir_escaped}")
 		else
-			mkdir -p "$upperdir"
-			chown "${private_users_uid}:${private_users_uid}" "$upperdir"
+			nspawn_args+=(--bind-ro="${cache_dir_escaped}")
 		fi
-		nspawn_args+=(--overlay="${cache_dir_escaped}:${upperdir}:${cache_dir_escaped}")
-	else
-		nspawn_args+=(--bind-ro="${cache_dir_escaped}")
+	done
+
+	if [[ -z $private_users_uid ]]; then
+		mkdir -p "$aurcache_host"
+		nspawn_args+=(--bind="${aurcache_host//:/\\:}:${aurcache_container}")
 	fi
-done
 
 # {{{ functions
 copy_hostconf () {
@@ -199,8 +214,6 @@ copy_hostconf () {
 		mkdir -p "$(dirname "$working_dir$dst")"
 		cp -T "$src" "$working_dir$dst"
 	done
-
-	sed -r "s|^#?\\s*CacheDir.+|CacheDir = ${cache_dirs[*]}|g" -i "$working_dir/etc/pacman.conf"
 }
 # }}}
 
@@ -215,6 +228,22 @@ fi
 
 copy_hostconf
 
+if [[ -z $private_users_uid ]]; then
+	awk -v aur="$aurcache_container" '
+/^\[options\]/ { opts = 1 }
+opts && /^\[/ && $1 != "[options]" { opts = 0 }
+opts && /^CacheDir = / { letzte = NR }
+{ zeilen[NR] = $0 }
+END {
+	for (i = 1; i <= NR; i++) {
+		print zeilen[i]
+		if (i == letzte && aur) print "CacheDir = " aur
+	}
+}
+' "$working_dir/etc/pacman.conf" > "$working_dir/etc/pacman.conf.tmp" \
+	&& mv "$working_dir/etc/pacman.conf.tmp" "$working_dir/etc/pacman.conf"
+fi
+
 eval "$(grep -a '^CARCH=' "$working_dir/etc/makepkg.conf")"
 
 [[ -z $nosetarch ]] || unset CARCH
@@ -225,6 +254,7 @@ else
 fi
 
 clear_cache_upperdir
+clear_aurcache
 
 if [[ -z $private_users_uid ]]; then
 	stripped=()
@@ -238,4 +268,5 @@ ${CARCH:+setarch "$set_arch"} systemd-nspawn "${nspawn_args[@]}" "$@"
 ret=$?
 
 clear_cache_upperdir
+clear_aurcache
 exit $ret
-- 
2.55.0

From 3ad10915cf040956d6537cdc787bc45c9a99def2 Mon Sep 17 00:00:00 2001
From: Lenucksi <[email protected]>
Date: Tue, 7 Jul 2026 15:08:38 +0200
Subject: [PATCH 4/4] docs: document container hardening, user namespace, and
 AUR cache isolation

Add Configuration section to arch-nspawn(1) documenting the
nspawn-security.conf override mechanism, user namespace isolation
via --private-users, and the writable overlay cache.

Add Security sections to both man pages documenting capability
drops, seccomp filters, address family restrictions, noexec on
tmpfs, and ID-mapped source directory mounts.

Document AUR cache separation (/var/cache/aurcache) and the
DEVTOOLS_CACHE_CLEAR flag in both man pages and the config file.
---
 config/nspawn.conf.d/nspawn-security.conf |  8 +++++
 doc/man/arch-nspawn.1.asciidoc            | 43 +++++++++++++++++++++++
 doc/man/makechrootpkg.1.asciidoc          | 14 +++++++-
 src/arch-nspawn.in                        |  1 +
 4 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/config/nspawn.conf.d/nspawn-security.conf b/config/nspawn.conf.d/nspawn-security.conf
index d664c28..d3139e6 100644
--- a/config/nspawn.conf.d/nspawn-security.conf
+++ b/config/nspawn.conf.d/nspawn-security.conf
@@ -19,3 +19,11 @@ nspawn_security_flags+=(
     --private-users=yes
     --private-users-ownership=map
 )
+
+# === Cache persistence ===
+#
+# By default the overlay upperdir and AUR package cache are cleared
+# before and after each build, so every build starts with a fresh cache.
+# Set this to 0 to preserve downloaded packages between builds:
+#
+# DEVTOOLS_CACHE_CLEAR=0
diff --git a/doc/man/arch-nspawn.1.asciidoc b/doc/man/arch-nspawn.1.asciidoc
index be5adc4..2d4c8c5 100644
--- a/doc/man/arch-nspawn.1.asciidoc
+++ b/doc/man/arch-nspawn.1.asciidoc
@@ -39,4 +39,47 @@ Options
 *-h*::
 	Show this usage message
 
+Configuration
+-------------
+
+The file '@pkgdatadir@/nspawn.conf.d/nspawn-security.conf' is sourced
+if present. It may extend nspawn_security_flags with additional
+systemd-nspawn(1) arguments.
+
+The shipped default enables user namespace isolation:
+
+  --private-users=yes
+  --private-users-ownership=map
+
+With this enabled the container root (UID 0) is mapped to an
+unprivileged host UID range, and the package cache is mounted as
+a writable overlay so that pacman can cache downloaded packages.
+The UID range is derived from the hostname via pick_uid_base().
+
+When user namespaces are disabled (no --private-users), the package
+cache is mounted read-only and a separate writable cache is provided
+at /var/cache/aurcache. This prevents AUR builds from polluting the
+host package cache.
+
+The temporary build cache (overlay upperdir and AUR cache) is cleared
+before and after each build by default. Set DEVTOOLS_CACHE_CLEAR=0
+in the config file to preserve cached packages between builds.
+
+Security
+--------
+
+The following hardening is applied to every container:
+
+  *Capabilities dropped*: CAP_BPF, CAP_NET_ADMIN, CAP_SYS_PTRACE,
+   CAP_SYS_MODULE, CAP_MKNOD, CAP_NET_RAW, CAP_SYS_CHROOT.
+   CAP_SYS_ADMIN retained for pacman Landlock sandbox compatibility.
+
+  *System call filter*: The @keyring and @mount seccomp groups are
+   restricted, blocking keyring manipulation and mount/pivot_root
+   inside the container.
+
+  *Address families*: Only AF_INET, AF_INET6 and AF_UNIX permitted.
+   AF_UNIX is required for pacman-key, gpg-agent and systemd-journald
+   communication inside the container.
+
 include::include/footer.asciidoc[]
diff --git a/doc/man/makechrootpkg.1.asciidoc b/doc/man/makechrootpkg.1.asciidoc
index ea7bc05..955397b 100644
--- a/doc/man/makechrootpkg.1.asciidoc
+++ b/doc/man/makechrootpkg.1.asciidoc
@@ -46,7 +46,8 @@ Options
 	Bind directory into build chroot as read-only
 
 *-t* <dir>[:opts]::
-	Mount a tmpfs at 'dir'. See the '--tmpfs' argument in systemd-nspawn(1) for more details.
+	Mount a tmpfs at 'dir' (default: noexec). See the '--tmpfs' argument
+	in systemd-nspawn(1) for more details.
 
 *-u*::
 	Update the working copy of the chroot before building
@@ -79,6 +80,17 @@ Options
 *-x* <when>::
 	Inspect chroot after build, possible modes are 'never' (default), 'always' or 'failure'
 
+Security
+--------
+
+Source directories (/startdir, /srcdest) use ID-mapped bind mounts
+so the build user inside a user namespace can access source files
+with their original host UIDs. The /tmp filesystem is mounted with
+noexec to prevent execution of binaries placed in /tmp.
+
+Without user namespaces, AUR builds use a dedicated writable cache
+at /var/cache/aurcache instead of writing to the host package cache.
+
 See Also
 --------
 
diff --git a/src/arch-nspawn.in b/src/arch-nspawn.in
index ed04069..934dbf3 100644
--- a/src/arch-nspawn.in
+++ b/src/arch-nspawn.in
@@ -214,6 +214,7 @@ copy_hostconf () {
 		mkdir -p "$(dirname "$working_dir$dst")"
 		cp -T "$src" "$working_dir$dst"
 	done
+
 }
 # }}}
 
-- 
2.55.0

Reply via email to