From a8bbe74cf39acbf5310b7d3ea16898bf45b06691 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sun, 24 May 2026 09:09:55 -0400 Subject: [PATCH] fix: validate upstream image policy --- protocol/types.go | 16 ++++++++++++++-- protocol/types_test.go | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/protocol/types.go b/protocol/types.go index 44cfe0f..d51ff7c 100644 --- a/protocol/types.go +++ b/protocol/types.go @@ -1059,10 +1059,22 @@ type ProviderUpstreamImagePolicy struct { } func (p ProviderUpstreamImagePolicy) Validate() error { + var errs []error if !p.DigestPinnedImageRequired { - return errors.New("digest_pinned_image_required must be true") + errs = append(errs, errors.New("digest_pinned_image_required must be true")) } - return nil + if !p.OperatorSuppliedImageRequired && strings.TrimSpace(p.RecommendedImageRef) == "" { + errs = append(errs, errors.New("recommended_image_ref is required unless operator_supplied_image_required is true")) + } + if p.RecommendedImageRef != "" && (strings.TrimSpace(p.RecommendedImageRef) == "" || strings.ContainsAny(p.RecommendedImageRef, "\t\r\n\x00")) { + errs = append(errs, errors.New("recommended_image_ref is invalid")) + } + for i, ref := range p.KnownImageRefs { + if strings.TrimSpace(ref) == "" || strings.ContainsAny(ref, "\t\r\n\x00") { + errs = append(errs, fmt.Errorf("known_image_refs[%d] is required", i)) + } + } + return errors.Join(errs...) } func CanonicalHash(value any) string { diff --git a/protocol/types_test.go b/protocol/types_test.go index 4b6a446..b8c01fa 100644 --- a/protocol/types_test.go +++ b/protocol/types_test.go @@ -28,6 +28,25 @@ func TestProviderContractRejectsMalformedConfigSchemaDigest(t *testing.T) { } } +func TestProviderUpstreamImagePolicyRequiresRecommendedImageUnlessOperatorSupplied(t *testing.T) { + policy := protocol.ProviderUpstreamImagePolicy{ + DigestPinnedImageRequired: true, + } + + err := policy.Validate() + if err == nil { + t.Fatal("expected missing recommended image to fail") + } + if !strings.Contains(err.Error(), "recommended_image_ref") { + t.Fatalf("expected recommended_image_ref error, got %v", err) + } + + policy.OperatorSuppliedImageRequired = true + if err := policy.Validate(); err != nil { + t.Fatalf("operator-supplied image should not require recommended image: %v", err) + } +} + func TestProviderRuntimeProfileRejectsReusableResidueWithoutWorkspace(t *testing.T) { contract := validBatchProviderContract() profile := &contract.RuntimeContract.Profiles[0]