#2578·vuls

SrcPackages collapses multiple source versions of one source package, causing false negatives/positives

Author: knqyf263Created Jun 18, 2026Updated Jun 22, 2026

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:

bash
$ 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.9

What 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, so libcurl3t64-gnutls (really 8.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 matching

Detection on that scan result:

bash
curl CVEs detected: 0          <- false negative

Control — 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:

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.toml
bash
docker 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-2ubuntu10

Configuration

  • Go version (go version): N/A — reproduced with the official prebuilt release binary vuls 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:
toml
[servers.local]
host = "localhost"
port = "local"
scanMode = ["offline"]
  • command:
bash
vuls scan  -config /vuls/config.toml -results-dir /vuls/results
vuls report -config /vuls/config.toml -results-dir /vuls/results   # detection (vuls2)