SrcPackages collapses multiple source versions of one source package, causing false negatives/positives
What did you do?
Scanned and detected on a host where a single source package has binaries
installed at two different source versions at the same time. This is a
normal partial-upgrade / apt-mark hold state.
Example with the curl source (it builds curl, libcurl4t64,
libcurl3t64-gnutls): on Ubuntu 24.04 hold libcurl3t64-gnutls at the GA
version 8.5.0-2ubuntu10 while curl / libcurl4t64 get the security update
8.5.0-2ubuntu10.9. dpkg then reports two source versions for source curl:
$ dpkg-query -W -f='${binary:Package},${db:Status-Abbrev},${Version},${source:Package},${source:Version}\n' | grep curl
curl,ii ,8.5.0-2ubuntu10.9,curl,8.5.0-2ubuntu10.9
libcurl3t64-gnutls:arm64,hi ,8.5.0-2ubuntu10,curl,8.5.0-2ubuntu10
libcurl4t64:arm64,ii ,8.5.0-2ubuntu10.9,curl,8.5.0-2ubuntu10.9What did you expect to happen?
libcurl3t64-gnutls is at 8.5.0-2ubuntu10, which is affected by curl CVEs that
were fixed only in later point releases (e.g. CVE-2024-9681 fixed in
8.5.0-2ubuntu10.5, up to several fixed in 8.5.0-2ubuntu10.9). I expected
those CVEs to be reported against libcurl3t64-gnutls.
What happened instead?
No curl CVEs were reported.
SrcPackages is map[sourceName]SrcPackage with a single scalar Version, so
when one source name has multiple source versions only the last-parsed line
survives; the others are dropped (only BinaryNames are merged). Detection then
matches the dropped binaries against the wrong source version:
- here the collapse keeps
8.5.0-2ubuntu10.9, solibcurl3t64-gnutls(really8.5.0-2ubuntu10) is matched against the fixed version → false negative; - with the opposite dpkg ordering the collapse keeps the old version, so an already-patched binary is matched against the old version → false positive.
- Current Output
Scan result (collapsed — the held-back 8.5.0-2ubuntu10 is gone):
SrcPackages["curl"].version = 8.5.0-2ubuntu10.9 <- collapsed to ONE version
SrcPackages["curl"].binaryNames = [curl, libcurl4t64, libcurl3t64-gnutls]
packages["libcurl3t64-gnutls"].version = 8.5.0-2ubuntu10 <- binary version kept, but unused for source matchingDetection on that scan result:
curl CVEs detected: 0 <- false negativeControl — feeding only libcurl3t64-gnutls at its true source version
8.5.0-2ubuntu10 (no collapse) detects what should have been reported:
libcurl3t64-gnutls: 23 CVEs (CVE-2024-2004 ... CVE-2026-7168)(Re-running vuls scan -debug / vuls report -debug shows the parsed packages;
the relevant signal is the single collapsed SrcPackages["curl"].version above.)
Steps to reproduce the behaviour
A self-contained Docker repro. Dockerfile:
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
ARG VULS_VERSION=0.39.3
# One source (curl) with binaries at two source versions: hold the gnutls
# flavor at the GA 8.5.0-2ubuntu10 while curl/libcurl4t64 get 8.5.0-2ubuntu10.9.
RUN apt-get update -qq && \
apt-get install -y -qq curl ca-certificates iproute2 libcurl3t64-gnutls && \
apt-get install -y -qq --allow-downgrades libcurl3t64-gnutls=8.5.0-2ubuntu10 && \
apt-mark hold libcurl3t64-gnutls && \
rm -rf /var/lib/apt/lists/*
# Official prebuilt vuls release binary.
RUN arch="$(dpkg --print-architecture)" && \
curl -fsSL "https://github.com/future-architect/vuls/releases/download/v${VULS_VERSION}/vuls_${VULS_VERSION}_linux_${arch}.tar.gz" -o /tmp/vuls.tgz && \
mkdir -p /tmp/vulsdl && tar -xzf /tmp/vuls.tgz -C /tmp/vulsdl && \
install -m 0755 /tmp/vulsdl/vuls /usr/local/bin/vuls && \
rm -rf /tmp/vulsdl /tmp/vuls.tgz
WORKDIR /vuls
RUN printf '[servers.local]\nhost = "localhost"\nport = "local"\nscanMode = ["offline"]\n' > config.tomldocker build -t vuls-collapse-repro .
# 1. real divergent state (two source versions for source "curl")
docker run --rm vuls-collapse-repro \
dpkg-query -W -f='${binary:Package},${db:Status-Abbrev},${Version},${source:Package},${source:Version}\n' | grep curl
# 2. scan -> the result JSON has SrcPackages["curl"].version collapsed to one value
docker run --name vrepro vuls-collapse-repro vuls scan -config /vuls/config.toml -results-dir /vuls/results
docker cp vrepro:/vuls/results ./results && docker rm vrepro
# 3. detect with vuls2 (vuls report, or POST the result to `vuls server`)
# -> 0 curl CVEs for libcurl3t64-gnutls, although it is really 8.5.0-2ubuntu10Configuration
Go version (
go version): N/A — reproduced with the official prebuilt release binaryvuls v0.39.3(no local build).Go environment (
go env): N/A (official prebuilt binary).Vuls environment:
Hash : 5ce56238223d505e7f99ab70c25644c6c39b620b
$ vuls -v
vuls-0.39.3-5ce56238223d505e7f99ab70c25644c6c39b620b-2026-06-09T09:08:18Z- config.toml:
[servers.local]
host = "localhost"
port = "local"
scanMode = ["offline"]- command:
vuls scan -config /vuls/config.toml -results-dir /vuls/results
vuls report -config /vuls/config.toml -results-dir /vuls/results # detection (vuls2)Source: future-architect/vuls