Skip to content

Instantly share code, notes, and snippets.

@AlexZeitler
Last active June 15, 2026 14:45
Show Gist options
  • Select an option

  • Save AlexZeitler/9da08236e5a112b457102c8dd8f5bdcd to your computer and use it in GitHub Desktop.

Select an option

Save AlexZeitler/9da08236e5a112b457102c8dd8f5bdcd to your computer and use it in GitHub Desktop.
Check for infected AUR packages
#!/usr/bin/env bash
#
# check-aur-infected.sh
# Checks the locally installed AUR / foreign packages against the published
# list of compromised AUR packages.
#
# Options:
# -v | --verbose Enable debug output (written to stderr)
# (alternatively: DEBUG=1 as an environment variable)
#
# Exit codes:
# 0 none of your packages are on the list
# 1 at least one affected package is installed
# 2 list could not be fetched (network / HTTP error)
# 3 list looks empty/implausible -> format may have changed, no all-clear
set -uo pipefail
URL="${AUR_LIST_URL:-https://md.archlinux.org/s/SxbqukK6IA}"
MIN_ENTRIES="${AUR_MIN_ENTRIES:-50}" # lower bound for the plausibility check
DEBUG="${DEBUG:-0}"
# Parse flags
for arg in "$@"; do
case "$arg" in
-v|--verbose) DEBUG=1 ;;
-h|--help)
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
exit 0 ;;
*) echo "Unknown option: $arg" >&2; exit 64 ;;
esac
done
# Debug helper: writes to stderr only, and only when DEBUG is enabled
dbg() { [[ "$DEBUG" == 1 ]] && printf '[debug] %s\n' "$*" >&2; return 0; }
dbg "Start $(date '+%F %T')"
dbg "URL = $URL"
dbg "MIN_ENTRIES = $MIN_ENTRIES"
# 1) Fetch the list ---------------------------------------------------------
dbg "Fetching list ..."
if ! raw=$(curl -fsSL --retry 2 "$URL"); then
echo "Error: could not fetch the list from $URL." >&2
exit 2
fi
dbg "Fetched = ${#raw} bytes"
# 2) Extract package names (step by step so each stage is countable) --------
clean=$(printf '%s' "$raw" | sed 's/<[^>]*>//g')
dbg "After sed = ${#clean} bytes (HTML tags removed)"
tokens=$(printf '%s' "$clean" | tr -s '[:space:]' '\n' | grep -c .)
dbg "Tokens (split) = ${tokens} (split on whitespace)"
mapfile -t INFECTED < <(
printf '%s' "$clean" \
| tr -s '[:space:]' '\n' \
| grep -E '^[a-z0-9][a-z0-9_.+-]*[a-z0-9]$' \
| sort -u
)
dbg "Filtered+uniq = ${#INFECTED[@]} valid package names"
if [[ "$DEBUG" == 1 && ${#INFECTED[@]} -gt 0 ]]; then
dbg "First 5 : ${INFECTED[*]:0:5}"
dbg "Last 1 : ${INFECTED[-1]}"
fi
# 3) Plausibility check -----------------------------------------------------
if (( ${#INFECTED[@]} < MIN_ENTRIES )); then
echo "Warning: only ${#INFECTED[@]} entries parsed (expected >= ${MIN_ENTRIES})." >&2
echo "The source format has probably changed. Please verify manually." >&2
exit 3
fi
# 4) Load local foreign packages -------------------------------------------
foreign=$(pacman -Qmq | sort)
foreign_n=$(printf '%s' "$foreign" | grep -c .)
dbg "Foreign pkgs = ${foreign_n} installed locally (pacman -Qmq)"
# 5) Compute the intersection -----------------------------------------------
hits=$(comm -12 \
<(printf '%s\n' "$foreign") \
<(printf '%s\n' "${INFECTED[@]}"))
dbg "Matches = $(printf '%s' "$hits" | grep -c .)"
# 6) Result -----------------------------------------------------------------
if [[ -n "$hits" ]]; then
n=$(printf '%s\n' "$hits" | grep -c .)
echo "WARNING: ${n} affected package(s) installed on this system:"
printf '%s\n' "$hits" | sed 's/^/ - /'
echo
echo "Checked against ${#INFECTED[@]} listed packages from ${URL}"
exit 1
else
echo "OK: none of your installed foreign packages are on the list."
echo "(checked against ${#INFECTED[@]} listed packages)"
exit 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment