Repository navigation
Add Arch Linux package scanning with the Arch security tracker - #9
Conversation
OSV has no Arch Linux ecosystem, so Arch packages were never checked. Host and image scans now read pacman's local database (/var/lib/pacman/local/*/desc) and match it against the Arch Linux Security Tracker. The whole list (~1 MB) is downloaded every 6 hours and matched locally with a port of pacman's vercmp, so package names never leave the machine. pkg:alpm/arch purls and `name:Arch Linux:version` lines work in dependency scans too. - Arch derivatives (Manjaro, EndeavourOS, CachyOS, Garuda, Artix, SteamOS) are matched with a note that their own builds can differ. - An unfixed advisory confirmed only in an older version than the one installed is listed as not checked, not as a finding: the tracker leaves many open long after upstream fixes them. - An unreadable tracker fails the scan, like an OSV outage, so a gate cannot pass with every Arch package unchecked. - Each package database is matched only under its own ecosystem and reports its own path. - Text, Markdown and host-preview output escape control characters and bidi overrides from package databases, images and advisories. - AVG/ASA advisories link to security.archlinux.org in SARIF, CycloneDX, Markdown and the desktop app. - AUR names for Chrome, Edge, Brave and VS Code are checked with NVD.
Fazmin
left a comment
There was a problem hiding this comment.
Thanks Paul, this is a great addition! Arch users had no OS package coverage at all, and doing the matching locally against Arch's own tracker is exactly the right call. I really like the attention to detail: the vercmp port matches libalpm on every tricky case I could think of (epochs, 1.0a vs 1.0, leading zeros, huge numbers), the layer handling and whiteouts look solid, and the row shape means KEV/EPSS, SARIF and history just work. The write-up of the "undecided" decision was really helpful too.
A few things I'd like sorted before merging:
1. rpm -qa is now skipped on non-Arch hosts too (host/linux.rs ~L50). The new !rpm_ecosystems.is_empty() check is meant for Arch, but scan.ecosystems is also empty on any rpm distro OSV doesn't cover (SLES/SLED 15, for example, which hits the rpm_unread path with its NDB database). Before this PR, rpm -qa still ran there, so third_party_apps found Chrome, Edge and VS Code and checked them against NVD. Now they quietly drop out of the scan. Gating on "not Arch" (!scan.ecosystems.iter().any(|e| e == archlinux::ECOSYSTEM)) would match what the comment says.
2. An empty tracker response fails open (clients.rs ~L1100). The only check is is_array(), so a [] (or a schema change, like a renamed packages key) becomes an empty index that's cached for 6 hours. Every Arch package then scans clean and --fail-on passes, which goes against the PR's own "an outage fails the scan" rule. Could we refuse an empty or implausibly small index (the tracker has ~2,400 groups) before caching it? It'd also be worth versioning the cache key (arch:tracker:v1, like kev:catalog:v2), since it stores the derived index and a later format change would otherwise read the old shape.
3. A pkgrel-only rebuild hides a confirmed finding (archlinux.rs ~L260). The undecided test compares the full version-release, so if AVG-xxxx was confirmed in pam 1.7.0-2 and Arch rebuilds it as 1.7.0-3 without a patch (a soname mass rebuild, say), the finding moves to "not checked" and history shows it as removed. Comparing only epoch:pkgver for that test would keep it, and still handle the cases you were after (1.1.1o vs 3.6.5, 5.15 vs 7.2).
4. Use the large-download path for the tracker (clients.rs ~L1099). It goes through get_json, so it gets the normal per-request timeout (30 s, or as low as 5 s with REQUEST_TIMEOUT), and the 10 MB cap is only checked after the whole body is buffered. Now that a tracker outage fails the whole scan, a slow link would fail it too. get_large_bytes (streaming cap, 180 s) is what the other whole-feed downloads use.
5. The image preview still says "checked in OSV as Arch Linux" (image/mod.rs ~L501 and index.html ~L1968). The host preview got the new wording, this one didn't.
Smaller things, none blocking:
image::preview_text(scan image --list) doesn't go throughclean_text, so the image name,PRETTY_NAME, the new derivative note and the reasons are printed raw. The PR says image metadata is now escaped.- The tracker's AVG
nameand ASA IDs aren't checked withis_advisory_id(CVE IDs are), and the AVG name becomesvuln_idand ends up in URLs and Markdown links. It would need a compromised tracker, but it's an easy filter. tokio::join!inscan_inventorywaits for the whole OSV batch even when the tracker has already failed.try_join!would fail fast.--strictwill exit 1 on almost every current Arch host, because the stale "Vulnerable" groups land innot_checked. You flagged this already. I wonder if a separate "undecided" bucket would keep--strictmeaning "couldn't check". Happy to leave it as a follow-up.- For pasted or SBOM input only: a versionless
pkg:alpm/arch/xcompares as older than everything, so every group ever filed for that package shows up as a finding, fixed ones included. And a version without pkgrel compares equal to a pkgrel-only fix (a false negative). Host scans aren't affected, sincedescalways has a pkgrel. - IDs are sorted as strings, so AVG-1000 comes before AVG-999.
- I'm not 100% sure, but I think SteamOS 3 keeps its database under
/usr/lib/holo/pacmandb. If so, nothing gets read there and there's no not-checked row, even though the README lists SteamOS. - The README still says "lists all 40 sources" in one place, and there's a "1 packages" plural.
- Not from this PR, but worth knowing: the MCP tools
check_package_vulnsandfind_fixed_versionwith ecosystem "Arch Linux" still query OSV and come back with 0, which looks clean. That could be a nice follow-up now that the tracker is wired in.
Since #8 and #5 have landed, the next push will run CI against the current main as well. There are no conflicts, it just needs a fresh run.
Really nice work. Looking forward to the next round!
- Refuse a tracker list with under 1,000 usable groups before caching it, so an empty or renamed-field response fails the scan instead of passing every package. Version the cache key (arch:tracker:v1). - Download the tracker through the streaming large-feed path (16 MB cap, long timeout) instead of get_json. - Keep a finding when only the pkgrel moved past the confirmed version; only a newer pkgver is undecided. - Pasted or SBOM input: a version without a pkgrel counts as older than a fix in a later release of that pkgver, and a package with no version is listed as not checked instead of matching every group. - Accept only AVG/ASA-shaped IDs from the tracker; sort IDs by number. - Fail fast with try_join! when either OSV or the tracker fails. - Run rpm -qa again on rpm hosts OSV does not cover (SLES), skipping it only on Arch, so third-party apps are still found there. - Read SteamOS's pacman database (/usr/lib/holo/pacmandb/local) on hosts and in images; list an Arch-family OS with no database as not checked. - Image preview: tracker wording for Arch, escape all dynamic text. - Fix "1 packages" and the stale source count in the README.
|
Thanks for the careful review! All five are fixed in 13414dd, along with most of the smaller notes.
Smaller ones: I left the |
|
Merged, thanks Paul! That was a really quick and thorough turnaround. Every point is covered and there are tests for all of it. The lua51 one you found on your own machine is a nice proof that the rebuild change was worth making, and thanks for checking the SteamOS path too. Arch users finally have proper OS package coverage now, which is great to have. The Looking forward to the OAuth PR for the CLI! |
Why
Host and image scans read dpkg, apk and rpm databases and match them through OSV.dev. OSV has no Arch Linux ecosystem, so an Arch machine or an
archlinuximage got no OS package coverage at all.Approach
Arch packages are matched against the Arch Linux Security Tracker, the distribution's own advisory source. The whole list (~1 MB, ~2,400 advisory groups) is downloaded every 6 hours and matched on the machine, so no package names are sent anywhere. Version order follows pacman exactly:
vercmpis a port of libalpm'salpm_pkg_vercmp/rpmvercmp, checked against the realvercmpbinary on 34 cases.Decisions worth reviewing:
arch-audit(every unfixed group, at any version) produced 27 findings on a current Arch desktop, nearly all out of date. An unfixed group now counts as a finding only up to the version the tracker confirmed it in. Past that it is listed as not checked, with the advisory IDs, so nothing is hidden. One consequence:--strictcounts those as gaps.--fail-onwould pass with every Arch package unchecked.%NAME%), not by pkgbase, because that is how the tracker files advisories (poppler-glib,libcurl-gnutls,kube-apiserver).How it works
ospkg.rsreadsvar/lib/pacman/local/*/desc, using libalpm's section rules, for host scans and for image layers (image/layers.rskeeps only thedescfiles, and whiteouts of upgraded package dirs apply). Each package database now keeps its own packages and path. pacman packages are matched only underArch Linux, and dpkg/apk/rpm packages only under OSV ecosystems. A database that fits neither is listed as not checked.archlinux.rsholdsvercmp, the tracker index and the matcher.scan_inventorysends Arch packages to it while the OSV batch runs, and its rows have the same shape as OSV rows, so findings, ranking, KEV/EPSS enrichment, SARIF and history work unchanged. Findings carrysources: ["arch_tracker"].pkg:alpm/arch/...purls (not MSYS2's) andname:Arch Linux:versionlines are mapped to the Arch ecosystem.format::advisory_urlsends CVEs to NVD, AVG/ASA IDs to security.archlinux.org and everything else to OSV. SARIF, CycloneDX, Markdown, attack chains and the desktop app use it.format::clean_text, moved out oftls.rs.arch-securityis added to the source catalog (now 41 sources). README and SECURITY.md describe the behaviour and the data flow.Testing:
cargo fmt --check, clippy with-D warnings, 283 core tests plus the server tests, andcargo check -p cve-intel-desktop.vercmp, the tracker index and matcher, the undecided rule, a tracker failure, pacmandescparsing, per-database matching, an Arch image with an upgraded package, purls, and terminal escaping. The escaping tests fail with escaping disabled.scan hostread all 2,042 installed packages.pam:Arch Linux:1.7.0-2gives CVE-2025-6020, linked tosecurity.archlinux.org/AVG-2901in SARIF.archlinuximage (only the unit test above), and the desktop GUI was not opened.Links
/issues/all.json)lib/libalpm/version.cin pacman