From 89ec9fe9b398a29f6592f5870144197b3f425aec Mon Sep 17 00:00:00 2001 From: amarkdotdev Date: Mon, 6 Jul 2026 23:23:25 +0300 Subject: [PATCH 1/2] imagetools: validate descriptor input for create -f Return a clear error when -f contains a manifest or index instead of an OCI content descriptor, and reject descriptors missing a valid digest. Avoids a nil-pointer panic when piping inspect --raw output into imagetools create. Fixes #2091 Signed-off-by: amarkdotdev --- commands/imagetools/create.go | 22 ++++++++++++++++++++++ commands/imagetools/create_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 commands/imagetools/create_test.go diff --git a/commands/imagetools/create.go b/commands/imagetools/create.go index 703083911..7d1af673f 100644 --- a/commands/imagetools/create.go +++ b/commands/imagetools/create.go @@ -357,9 +357,31 @@ func parseSource(in string) (*imagetools.Source, error) { if err := json.Unmarshal([]byte(in), &s.Desc); err != nil { return nil, errors.WithStack(err) } + if err := validateDescriptorJSON(in, s.Desc); err != nil { + return nil, err + } return &s, nil } +func validateDescriptorJSON(raw string, desc ocispecs.Descriptor) error { + var meta struct { + SchemaVersion int `json:"schemaVersion"` + } + if err := json.Unmarshal([]byte(raw), &meta); err != nil { + return errors.WithStack(err) + } + if meta.SchemaVersion != 0 { + return errors.Errorf("expected an OCI content descriptor, got a manifest or index (schemaVersion %d)", meta.SchemaVersion) + } + if desc.Digest == "" { + return errors.Errorf("invalid descriptor: digest is required") + } + if _, err := digest.Parse(desc.Digest.String()); err != nil { + return errors.Wrap(err, "invalid descriptor digest") + } + return nil +} + func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command { var options createOptions diff --git a/commands/imagetools/create_test.go b/commands/imagetools/create_test.go new file mode 100644 index 000000000..81f36c2e6 --- /dev/null +++ b/commands/imagetools/create_test.go @@ -0,0 +1,27 @@ +package commands + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseSourceDescriptorValidation(t *testing.T) { + t.Parallel() + + _, err := parseSource(`{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.list.v2+json","manifests":[]}`) + require.Error(t, err) + require.Contains(t, err.Error(), "expected an OCI content descriptor") + + _, err = parseSource(`{"mediaType":"application/vnd.oci.image.manifest.v1+json"}`) + require.Error(t, err) + require.Contains(t, err.Error(), "digest is required") + + _, err = parseSource(`{"digest":"not-a-digest","mediaType":"application/vnd.oci.image.manifest.v1+json","size":123}`) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid descriptor digest") + + src, err := parseSource(`{"digest":"sha256:0000000000000000000000000000000000000000000000000000000000000000","mediaType":"application/vnd.oci.image.manifest.v1+json","size":123}`) + require.NoError(t, err) + require.Equal(t, "sha256:0000000000000000000000000000000000000000000000000000000000000000", src.Desc.Digest.String()) +} From ff1f2cbff6b0685f2f46e48856157675166070e2 Mon Sep 17 00:00:00 2001 From: amarkdotdev Date: Tue, 7 Jul 2026 07:23:59 +0300 Subject: [PATCH 2/2] imagetools: unmarshal descriptor JSON once in parseSource Parse schemaVersion and descriptor fields in a single pass instead of calling json.Unmarshal twice. Signed-off-by: amarkdotdev --- commands/imagetools/create.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/commands/imagetools/create.go b/commands/imagetools/create.go index 7d1af673f..037c07489 100644 --- a/commands/imagetools/create.go +++ b/commands/imagetools/create.go @@ -353,25 +353,22 @@ func parseSource(in string) (*imagetools.Source, error) { return nil, err } - var s imagetools.Source - if err := json.Unmarshal([]byte(in), &s.Desc); err != nil { + var parsed struct { + SchemaVersion int `json:"schemaVersion"` + ocispecs.Descriptor + } + if err := json.Unmarshal([]byte(in), &parsed); err != nil { return nil, errors.WithStack(err) } - if err := validateDescriptorJSON(in, s.Desc); err != nil { + if err := validateDescriptor(parsed.Descriptor, parsed.SchemaVersion); err != nil { return nil, err } - return &s, nil + return &imagetools.Source{Desc: parsed.Descriptor}, nil } -func validateDescriptorJSON(raw string, desc ocispecs.Descriptor) error { - var meta struct { - SchemaVersion int `json:"schemaVersion"` - } - if err := json.Unmarshal([]byte(raw), &meta); err != nil { - return errors.WithStack(err) - } - if meta.SchemaVersion != 0 { - return errors.Errorf("expected an OCI content descriptor, got a manifest or index (schemaVersion %d)", meta.SchemaVersion) +func validateDescriptor(desc ocispecs.Descriptor, schemaVersion int) error { + if schemaVersion != 0 { + return errors.Errorf("expected an OCI content descriptor, got a manifest or index (schemaVersion %d)", schemaVersion) } if desc.Digest == "" { return errors.Errorf("invalid descriptor: digest is required")