Skip to content

When scanning an image, syft only reports one file per set of hardlinks, leading to wrong SPDX packageVerificationCode #5019

Description

@chmeliik

What happened:

We recently switched from scanning container images as actual container images to scanning the mounted container filesystem as a dir: (it appears to improve the problems reported in #3800).

After this switch, syft produces more entries in the files array for SPDX (or more components with type: file for CycloneDX). For SPDX, this also results in different packageVerificationCodeValue for the exact same packages.

What you expected to happen:

Same files reported when scanning the image as an image and when scanning it as a dir:, same packageVerificationCodes

Steps to reproduce the issue:

# 1. scan an image that has packages that contain hardlinked files
$ syft scan registry.fedoraproject.org/fedora-minimal:43@sha256:d028a3d86b3b68081c6610d0e77fa08a0853bb9488f522d864192c84af5b1a44 \
    -o spdx-json@2.3 | jq >/tmp/fedora-minimal-image.json

# 2. scan the same image as a dir
# (if you don't have buildah, other ways to export the filesystem will work too)
$ buildah unshare -- bash -c '
set -x
container=$(buildah from registry.fedoraproject.org/fedora-minimal:43@sha256:d028a3d86b3b68081c6610d0e77fa08a0853bb9488f522d864192c84af5b1a44)
trap "buildah rm $container" EXIT
rootfs=$(buildah mount $container)
syft scan dir:$rootfs --override-default-catalogers=image -o spdx-json@2.3 | jq >/tmp/fedora-minimal-dir.json
'

# 3. see that the packageVerificationCode for e.g. glibc is different
$ jq -c < /tmp/fedora-minimal-image.json '.packages[] | select(.name == "glibc").packageVerificationCode'
{"packageVerificationCodeValue":"d601c6bf99eaa51af6783e6bd8706502c5390f0d"}

$ jq -c < /tmp/fedora-minimal-dir.json '.packages[] | select(.name == "glibc").packageVerificationCode'
{"packageVerificationCodeValue":"a3e94cf048079551f077c29a799c3522521a3acf"}

# 4. compare the files reported for glibc
$ get_glibc_files() {
    local sbom=$1
    jq -r '
        reduce .files[] as $f ({}; .[$f.SPDXID] = $f.fileName) as $files |
        (.packages[] | select(.name == "glibc")) as $glibc |
        .relationships[] |
        select(.spdxElementId == $glibc.SPDXID and .relationshipType == "CONTAINS") |
        $files[.relatedSpdxElement]
    ' "$sbom" | sort
}

$ diff -u2 <(get_glibc_files /tmp/fedora-minimal-image.json) <(get_glibc_files /tmp/fedora-minimal-dir.json)

--- /proc/self/fd/12	2026-06-25 10:49:51.706356089 +0200
+++ /proc/self/fd/13	2026-06-25 10:49:51.707356075 +0200
@@ -31,4 +31,6 @@
 usr/lib64/libutil.so.1
 usr/libexec/getconf/POSIX_V6_LP64_OFF64
+usr/libexec/getconf/POSIX_V7_LP64_OFF64
+usr/libexec/getconf/XBS5_LP64_OFF64
 usr/share/licenses/glibc/COPYING
 usr/share/licenses/glibc/COPYING.LIB

# 5. see that the files reported only for dir and not for image are hardlinks
$ podman run --rm -ti registry.fedoraproject.org/fedora-minimal:43@sha256:d028a3d86b3b68081c6610d0e77fa08a0853bb9488f522d864192c84af5b1a44 \
    bash -c 'stat /usr/libexec/getconf/* | grep -E "File:|Device:"'

  File: /usr/libexec/getconf/POSIX_V6_LP64_OFF64
Device: 0,111	Inode: 78554226    Links: 3
  File: /usr/libexec/getconf/POSIX_V7_LP64_OFF64
Device: 0,111	Inode: 78554226    Links: 3
  File: /usr/libexec/getconf/XBS5_LP64_OFF64
Device: 0,111	Inode: 78554226    Links: 3

Anything else we need to know?:

The root cause seems to be that when scanning a container image, syft will only report one file per set of hardlinks pointing to the same file. When scanning a dir:, Syft reports all of the hardlinks. Because the package verification code is computed from all the files belonging to a package, this also changes the verification code value. The SPDX spec doesn't say anything about hardlinks, but the dir: approach of including hardlinks in the computation is likely more correct.

Environment:

  • Output of syft version:
    Application:   syft
    Version:       1.45.1
    BuildDate:     2026-06-05T14:21:36Z
    GitCommit:     d4496b05aab2d489a5c2ec606755b657229b8925
    GitDescription: v1.45.1
    Platform:      linux/amd64
    GoVersion:     go1.26.3
    Compiler:      gc
    SchemaVersion: 16.1.3
  • OS (e.g: cat /etc/os-release or similar):
    NAME="Fedora Linux"
    VERSION="43 (Workstation Edition)"
    RELEASE_TYPE=stable
    ID=fedora
    VERSION_ID=43
    VERSION_CODENAME=""
    PRETTY_NAME="Fedora Linux 43 (Workstation Edition)"
    ANSI_COLOR="0;38;2;60;110;180"
    LOGO=fedora-logo-icon
    CPE_NAME="cpe:/o:fedoraproject:fedora:43"
    DEFAULT_HOSTNAME="fedora"
    HOME_URL="https://fedoraproject.org/"
    DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f43/"
    SUPPORT_URL="https://ask.fedoraproject.org/"
    BUG_REPORT_URL="https://bugzilla.redhat.com/"
    REDHAT_BUGZILLA_PRODUCT="Fedora"
    REDHAT_BUGZILLA_PRODUCT_VERSION=43
    REDHAT_SUPPORT_PRODUCT="Fedora"
    REDHAT_SUPPORT_PRODUCT_VERSION=43
    SUPPORT_END=2026-12-02
    VARIANT="Workstation Edition"
    VARIANT_ID=workstation

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions