Trying to understand sha256 output in syft spdx-json

I have been experimenting with syft output today and I have a question…

To start, I ran the following:

syft scan nginx:latest -o spdx-json | jq > nginx-latest.spdx.json

Looking through the output, I find this containing the pkg:oci value:

    {
      "name": "nginx",
      "SPDXID": "SPDXRef-DocumentRoot-Image-nginx",
      "versionInfo": "sha256:edf555d07d2ddbe6b616d90ee444e2faec1a219310c9a156fa6f6cd0c602881a",
      "supplier": "NOASSERTION",
      "downloadLocation": "NOASSERTION",
      "filesAnalyzed": false,
      "checksums": [
        {
          "algorithm": "SHA256",
          "checksumValue": "edf555d07d2ddbe6b616d90ee444e2faec1a219310c9a156fa6f6cd0c602881a"
        }
      ],
      "licenseConcluded": "NOASSERTION",
      "licenseDeclared": "NOASSERTION",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:oci/nginx@sha256:edf555d07d2ddbe6b616d90ee444e2faec1a219310c9a156fa6f6cd0c602881a?arch=amd64&tag=latest"
        }
      ],
      "primaryPackagePurpose": "CONTAINER"
    }

At first, I was assuming that the sha256 value on the referenceLocator line would be the the sha256 of the image’s digest but I am not finding a match there. If I run this:

docker inspect nginx:latest

This is what I see:

[
    {
        "Id": "sha256:39286ab8a5e14aeaf5fdd6e2fac76e0c8d31a0c07224f0ee5e6be502f12e93f3",
        "RepoTags": [
            "nginx:latest"
        ],
        "RepoDigests": [
            "nginx@sha256:04ba374043ccd2fc5c593885c0eacddebabd5ca375f9323666f28dfd5a9710e3"
        ],
        "Parent": "",
        "Comment": "buildkit.dockerfile.v0",
        "Created": "2024-08-14T21:31:12Z",
        "DockerVersion": "",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "80/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.27.1",
                "NJS_VERSION=0.8.5",
                "NJS_RELEASE=1~bookworm",
                "PKG_RELEASE=1~bookworm",
                "DYNPKG_RELEASE=2~bookworm"
            ],
            "Cmd": [
                "nginx",
                "-g",
                "daemon off;"
            ],
            "ArgsEscaped": true,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": [
                "/docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
            },
            "StopSignal": "SIGQUIT"
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 187706879,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/0c990431620dfdf6f603066e30825b6a1f6d2a67f9175feffd0473a0bc013c6e/diff:/var/lib/docker/overlay2/c6b29ad8645786a168929f4dc84c19d554bc6a926c3f0944c272109ab98f18ad/diff:/var/lib/docker/overlay2/59f5264666519bef102c481f6969a64f6d25e089f32dbb74baaa2583e70460a4/diff:/var/lib/docker/overlay2/8fb654941d0127046f9c8b564630e3bb3f2a98ff430d75c9db63df8b5fbb567e/diff:/var/lib/docker/overlay2/fe30b3093b0d5fc7b361249e92e8533d6ee0e763e5ad1ab240b11bb7cd2125cc/diff:/var/lib/docker/overlay2/53a025185df15b864d7018af627a9cc5810a252c7f4ca81f5b0e68f283c8f5b1/diff",
                "MergedDir": "/var/lib/docker/overlay2/9cfd98a325fc769383e5ed59d04e2f7d636bcf8bd028e73609f123b82891b2df/merged",
                "UpperDir": "/var/lib/docker/overlay2/9cfd98a325fc769383e5ed59d04e2f7d636bcf8bd028e73609f123b82891b2df/diff",
                "WorkDir": "/var/lib/docker/overlay2/9cfd98a325fc769383e5ed59d04e2f7d636bcf8bd028e73609f123b82891b2df/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:8e2ab394fabf557b00041a8f080b10b4e91c7027b7c174f095332c7ebb6501cb",
                "sha256:67796e30ff042303e3c8cbd8478e4f4f6377fd1754a168704f829c4e8a128f40",
                "sha256:eda13eb24d4c7b2c4cf60fbef992e18936613ad9067421ae1b59a413f3393267",
                "sha256:0fc6bb94eec5602c08d4261eedf9f122af7d122983c5ad8d8cf9ab108d9fd7bd",
                "sha256:2bdf51597158f7b0335cb1c082f8449a7ba1af678876158a05747eecca9b7604",
                "sha256:16907864a2d01d70c5c1e740085ace628f9d85b9eb38ced8c623e2c3ae36734d",
                "sha256:11de3d47036d69ed34dcf240a2d82e0ffc8a9dc8b77deeed65fb6380fbc84c8d"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

Now the repo digest is what I see currently for the “index digest” on Docker Hub:

The sha256 from the SPDX output doesn’t match the “manifest digest” either.

Can someone help explain what the sha256 in the referenceLocator represents?

Thanks in advance for the help…

Dwayne

Interesting. I just ran syft scan nginx:latest -o spdx-json | jq > nginx-latest.spdx.json and see syft report the digest in the output as 88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e

In the json, the referenceLocator matches the Manifest Digest on DockerHub

    {
      "name": "nginx",
      "SPDXID": "SPDXRef-DocumentRoot-Image-nginx",
      "versionInfo": "sha256:88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e",
      "supplier": "NOASSERTION",
      "downloadLocation": "NOASSERTION",
      "filesAnalyzed": false,
      "checksums": [
        {
          "algorithm": "SHA256",
          "checksumValue": "88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e"
        }
      ],
      "licenseConcluded": "NOASSERTION",
      "licenseDeclared": "NOASSERTION",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:oci/nginx@sha256:88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e?arch=amd64&tag=latest"
        }
      ],
      "primaryPackagePurpose": "CONTAINER"
    }

If I dig the RepoDigests out of the docker inspect then I see:

$ docker inspect nginx:latest | grep RepoDigests -A 1
        "RepoDigests": [
            "nginx@sha256:04ba374043ccd2fc5c593885c0eacddebabd5ca375f9323666f28dfd5a9710e3"

Which matches the Image Manifest on docker hub.

I’m not sure this fully answers your question, but the digests line up for me, albeit in different places.

Yeah, I just tried it again and I am not seeing what you are seeing… :thinking:

What do you see what you run docker image ls | grep nginx? Here is what I am getting:

❯ docker image ls | grep nginx
nginx      latest     39286ab8a5e1   4 weeks ago     188MB

When I inspect that, here is what I get:

❯ docker inspect 392
[
    {
        "Id": "sha256:39286ab8a5e14aeaf5fdd6e2fac76e0c8d31a0c07224f0ee5e6be502f12e93f3",
        "RepoTags": [
            "nginx:latest"
        ],
        "RepoDigests": [
            "nginx@sha256:04ba374043ccd2fc5c593885c0eacddebabd5ca375f9323666f28dfd5a9710e3"
        ],
        "Parent": "",
        "Comment": "buildkit.dockerfile.v0",
        "Created": "2024-08-14T21:31:12Z",
        "DockerVersion": "",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "80/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.27.1",
                "NJS_VERSION=0.8.5",
                "NJS_RELEASE=1~bookworm",
                "PKG_RELEASE=1~bookworm",
                "DYNPKG_RELEASE=2~bookworm"
            ],
            "Cmd": [
                "nginx",
                "-g",
                "daemon off;"
            ],
            "ArgsEscaped": true,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": [
                "/docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
            },
            "StopSignal": "SIGQUIT"
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 187706879,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/bf98133eb71eaca37db9c056081db63d94ee411db86b2ec1d1616a9fc9fa57
                "MergedDir": "/var/lib/docker/overlay2/7e0fc2022ed85a2f080fa1ff9a73d545c32a1218d37d2c207004d7e9d9c97
                "UpperDir": "/var/lib/docker/overlay2/7e0fc2022ed85a2f080fa1ff9a73d545c32a1218d37d2c207004d7e9d9c9767b/diff",
                "WorkDir": "/var/lib/docker/overlay2/7e0fc2022ed85a2f080fa1ff9a73d545c32a1218d37d2c207004d7e9d9c9767b/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:8e2ab394fabf557b00041a8f080b10b4e91c7027b7c174f095332c7ebb6501cb",
                "sha256:67796e30ff042303e3c8cbd8478e4f4f6377fd1754a168704f829c4e8a128f40",
                "sha256:eda13eb24d4c7b2c4cf60fbef992e18936613ad9067421ae1b59a413f3393267",
                "sha256:0fc6bb94eec5602c08d4261eedf9f122af7d122983c5ad8d8cf9ab108d9fd7bd",
                "sha256:2bdf51597158f7b0335cb1c082f8449a7ba1af678876158a05747eecca9b7604",
                "sha256:16907864a2d01d70c5c1e740085ace628f9d85b9eb38ced8c623e2c3ae36734d",
                "sha256:11de3d47036d69ed34dcf240a2d82e0ffc8a9dc8b77deeed65fb6380fbc84c8d"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

That shows the Repo Digest matching Docker Hub so I’m even more confused… :joy:

Now even some more confusion…

If I take the manifest digest from Docker Hub and do pull with that:

❯ docker pull nginx@sha256:88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e
docker.io/library/nginx@sha256:88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e: Pulling from library/nginx
Digest: sha256:88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e
Status: Downloaded newer image for nginx@sha256:88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e
docker.io/library/nginx@sha256:88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e

And then run docker images --digests | grep nginx, I get this:

❯ docker images --digests | grep nginx
nginx            latest       sha256:04ba374043ccd2fc5c593885c0eacddebabd5ca375f9323666f28dfd5a9710e3   39286ab8a5e1   4 weeks ago     188MB
nginx            latest       sha256:88a0a069d5e9865fcaaf8c1e53ba6bf3d8d987b0fdc5e0135fec8ce8567d673e   39286ab8a5e1   4 weeks ago     188MB

Different digests…same Image Id… :thinking:

Upon further thinking…this one makes sense…

Ok, one more oddity…if I tell syft to go get the image directly from the registry using syft scan registry:nginx:latest, the output gives me the same value as the manifest digest on Docker Hub for the AMD64 version which is what I was originally expecting:

So, now I am wondering what is happening locally???

@popey what version of Docker do you have running on your machine? Here is what I have:

❯ docker version
Client: Docker Engine - Community
 Version:           27.2.1
 API version:       1.47
 Go version:        go1.22.7
 Git commit:        9e34c9b
 Built:             Fri Sep  6 12:08:17 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          27.2.1
  API version:      1.47 (minimum version 1.24)
  Go version:       go1.22.7
  Git commit:       8b539b8
  Built:            Fri Sep  6 12:08:17 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.22
  GitCommit:        7f7fdf5fed64eb6a7caf99b3e12efcf9d60e311c
 runc:
  Version:          1.1.14
  GitCommit:        v1.1.14-0-g2c9f560
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Here’s what I have. Looks like I’m a little outdated here, and using Docker Desktop.

docker version
Client: Docker Engine - Community
 Version:           25.0.2
 API version:       1.44
 Go version:        go1.21.6
 Git commit:        29cf629
 Built:             Thu Feb  1 00:23:03 2024
 OS/Arch:           linux/amd64
 Context:           desktop-linux

Server: Docker Desktop 4.31.0 (153195)
 Engine:
  Version:          26.1.4
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.21.11
  Git commit:       de5c9cf
  Built:            Wed Jun  5 11:29:22 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.33
  GitCommit:        d2d58213f83a351ca8f528a95fbd145f5654e957
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0