From 8415183cd40f589573e4b1046eaa46d545796726 Mon Sep 17 00:00:00 2001 From: Adam Acosta Date: Mon, 12 Oct 2026 05:27:26 -0500 Subject: [PATCH] fix: normalize Docker image IDs before export. Addresses https://github.com/neuvector/neuvector/issues/2508. The containerd '/images/get' API does not accept bare checksums without a prepended algorithm identifier for the image ID. The moby client's ImageSave() call fails for Docker >=29.0, which now uses the containerd image store (see https://docs.docker.com/engine/storage/containerd/) instead of the classic storage drivers. This hard-codes 'sha256:' as the prefix, which is *not* future proof. See https://github.com/opencontainers/image-spec/blob/main/descriptor.md\#registered-algorithms, which states compliant implementations of OCI SHOULD use SHA-256, but there are other registered algorithms we may see used in the future. --- cvetools/image.go | 14 +++++++++++++- cvetools/image_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 cvetools/image_test.go diff --git a/cvetools/image.go b/cvetools/image.go index 82b62569f..0d6182272 100644 --- a/cvetools/image.go +++ b/cvetools/image.go @@ -61,6 +61,18 @@ func parseSocketFromRepo(repo string) (string, string) { return "", repo } +const sha256Prefix = "sha256:" + +func dockerImageSaveID(id string) string { + if d, err := goDigest.Parse(id); err == nil { + return d.String() + } + if d, err := goDigest.Parse(sha256Prefix + id); err == nil { + return d.String() + } + + return id +} func (s *ScanTools) GetLocalImageMeta(ctx context.Context, repository, tag string) (*container.ImageMeta, share.ScanErrorCode) { sock, repo := parseSocketFromRepo(repository) if sock == "" { @@ -119,7 +131,7 @@ func (s *ScanTools) LoadLocalImage(ctx context.Context, repository, tag, imgPath return nil, nil, nil, share.ScanErrorCode_ScanErrContainerAPI } - file, err := rt.GetImageFile(meta.ID) + file, err := rt.GetImageFile(dockerImageSaveID(meta.ID)) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Failed to get image") if errdefs.IsNotFound(err) { diff --git a/cvetools/image_test.go b/cvetools/image_test.go new file mode 100644 index 000000000..6934a8ae0 --- /dev/null +++ b/cvetools/image_test.go @@ -0,0 +1,35 @@ +package cvetools + +import "testing" + +func TestDockerImageSaveID(t *testing.T) { + tests := []struct { + name string + id string + want string + }{ + { + name: "bare sha256", + id: "fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40", + want: "sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40", + }, + { + name: "canonical sha256", + id: "sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40", + want: "sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40", + }, + { + name: "short image ID", + id: "fd791d74b689", + want: "fd791d74b689", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := dockerImageSaveID(tt.id); got != tt.want { + t.Fatalf("dockerImageSaveID(%q) = %q, want %q", tt.id, got, tt.want) + } + }) + } +}