From 58b6f5807e627ebf0a71d3df2b72864d0c25e56c Mon Sep 17 00:00:00 2001 From: Christopher Phillips <32073428+spiffcs@users.noreply.github.com> Date: Wed, 27 May 2026 10:45:37 -0400 Subject: [PATCH] fix: support v0.2 Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com> --- syft/pkg/cataloger/ai/cataloger.go | 6 ++--- .../pkg/cataloger/ai/parse_safetensors_oci.go | 15 +++++++++--- .../cataloger/ai/parse_safetensors_test.go | 23 +++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/syft/pkg/cataloger/ai/cataloger.go b/syft/pkg/cataloger/ai/cataloger.go index a5d4c7a69..27adb78c0 100644 --- a/syft/pkg/cataloger/ai/cataloger.go +++ b/syft/pkg/cataloger/ai/cataloger.go @@ -29,12 +29,12 @@ func NewGGUFCataloger() pkg.Cataloger { // covering three discovery paths: // - **/*.safetensors files (single-file models; header-only parse) // - **/model.safetensors.index.json files (sharded models) -// - application/vnd.docker.ai.model.config.v0.1+json OCI layers (Docker Model -// Runner artifacts whose config advertises format=="safetensors") +// - application/vnd.docker.ai.model.config.v0.1+json / v0.2+json OCI layers +// (Docker Model Runner artifacts whose config advertises format=="safetensors") func NewSafeTensorsCataloger() pkg.Cataloger { return generic.NewCataloger(safeTensorsCatalogerName). WithParserByGlobs(parseSafeTensorsFile, "**/*.safetensors"). WithParserByGlobs(parseSafeTensorsIndex, "**/*.safetensors.index.json"). - WithParserByMediaType(parseSafeTensorsOCIConfig, dockerAIModelConfigMediaType). + WithParserByMediaType(parseSafeTensorsOCIConfig, dockerAIModelConfigMediaTypes...). WithProcessors(safeTensorsMergeProcessor) } diff --git a/syft/pkg/cataloger/ai/parse_safetensors_oci.go b/syft/pkg/cataloger/ai/parse_safetensors_oci.go index 499daf944..f0ea1ec04 100644 --- a/syft/pkg/cataloger/ai/parse_safetensors_oci.go +++ b/syft/pkg/cataloger/ai/parse_safetensors_oci.go @@ -18,11 +18,20 @@ import ( // Docker AI OCI media types used by Docker Model Runner artifacts. const ( - dockerAIModelConfigMediaType = "application/vnd.docker.ai.model.config.v0.1+json" - dockerAIModelFileMediaType = "application/vnd.docker.ai.model.file" - dockerAILicenseMediaType = "application/vnd.docker.ai.license" + dockerAIModelFileMediaType = "application/vnd.docker.ai.model.file" + dockerAILicenseMediaType = "application/vnd.docker.ai.license" ) +// dockerAIModelConfigMediaTypes are the model-config schema versions this +// cataloger understands. Versions are enumerated explicitly rather than matched +// with a wildcard so that a future, potentially breaking, config schema is not +// silently consumed; add a new version here only after confirming the fields we +// parse still apply. +var dockerAIModelConfigMediaTypes = []string{ + "application/vnd.docker.ai.model.config.v0.1+json", + "application/vnd.docker.ai.model.config.v0.2+json", +} + // dockerAIModelConfig mirrors the JSON shape of the vnd.docker.ai.model.config // blob written by Docker Model Runner for AI artifacts. Only fields we use are // declared; unknown fields are ignored. diff --git a/syft/pkg/cataloger/ai/parse_safetensors_test.go b/syft/pkg/cataloger/ai/parse_safetensors_test.go index 783edf5a0..5fb04b8a8 100644 --- a/syft/pkg/cataloger/ai/parse_safetensors_test.go +++ b/syft/pkg/cataloger/ai/parse_safetensors_test.go @@ -410,3 +410,26 @@ func TestModelNameFromPath(t *testing.T) { assert.Equal(t, "my-model", modelNameFromIndexPath("/models/my-model/model.safetensors.index.json")) assert.Equal(t, "safetensors-model", modelNameFromIndexPath("model.safetensors.index.json")) } + +func TestDockerAIModelConfigMediaTypes(t *testing.T) { + // supported mirrors how the resolver matches: filepath.Match each registered + // media type against a layer's media type. + supported := func(mt string) bool { + for _, p := range dockerAIModelConfigMediaTypes { + if ok, err := filepath.Match(p, mt); err == nil && ok { + return true + } + } + return false + } + // the known, verified schema versions are consumed + assert.True(t, supported("application/vnd.docker.ai.model.config.v0.1+json")) + assert.True(t, supported("application/vnd.docker.ai.model.config.v0.2+json")) + // unknown/future schema versions are intentionally NOT consumed, to avoid + // silently ingesting a potentially breaking config change + assert.False(t, supported("application/vnd.docker.ai.model.config.v0.3+json")) + assert.False(t, supported("application/vnd.docker.ai.model.config.v9.9+json")) + // sibling layer media types are not matched either + assert.False(t, supported("application/vnd.docker.ai.model.file")) + assert.False(t, supported("application/vnd.docker.ai.gguf.v3")) +}