From c4e7cc47d6a4ce2691795c2c0c7f151e8f7c2811 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Tue, 10 Feb 2026 13:08:31 +0100 Subject: [PATCH 1/6] introduce no strict validation Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 3 + app/cli/documentation/cli-reference.mdx | 1 + app/cli/internal/policydevel/eval.go | 2 +- app/cli/pkg/action/attestation_add.go | 5 ++ pkg/attestation/crafter/crafter.go | 14 +++- .../crafter/materials/cyclonedxjson.go | 54 ++++++++++++--- .../crafter/materials/cyclonedxjson_test.go | 66 +++++++++++++++++++ .../crafter/materials/materials.go | 13 +++- .../crafter/materials/materials_test.go | 2 +- .../sbom.cyclonedx-invalid-schema.json | 7 ++ 10 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 pkg/attestation/crafter/materials/testdata/sbom.cyclonedx-invalid-schema.json diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index e4e377db4..4208409fa 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -38,6 +38,7 @@ func newAttestationAddCmd() *cobra.Command { var name, value, kind string var artifactCASConn *grpc.ClientConn var annotationsFlag []string + var noStrictValidation bool // OCI registry credentials can be passed as flags or environment variables var registryServer, registryUsername, registryPassword string @@ -76,6 +77,7 @@ func newAttestationAddCmd() *cobra.Command { RegistryUsername: registryUsername, RegistryPassword: registryPassword, LocalStatePath: attestationLocalStatePath, + NoStrictValidation: noStrictValidation, }, ) if err != nil { @@ -142,6 +144,7 @@ func newAttestationAddCmd() *cobra.Command { cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value") flagAttestationID(cmd) cmd.Flags().StringVar(&kind, "kind", "", fmt.Sprintf("kind of the material to be recorded: %q", schemaapi.ListAvailableMaterialKind())) + cmd.Flags().BoolVar(&noStrictValidation, "no-strict-validation", false, "skip strict schema validation for SBOM files (CycloneDX/SPDX)") // Optional OCI registry credentials cmd.Flags().StringVar(®istryServer, "registry-server", "", fmt.Sprintf("OCI repository server, ($%s)", registryServerEnvVarName)) diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index 4d3b1763b..9c3daa27d 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -202,6 +202,7 @@ Options -h, --help help for add --kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] --name string name of the material as shown in the contract +--no-strict-validation skip strict schema validation for SBOM files (CycloneDX/SPDX) --registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD) --registry-server string OCI repository server, ($CHAINLOOP_REGISTRY_SERVER) --registry-username string registry username, ($CHAINLOOP_REGISTRY_USERNAME) diff --git a/app/cli/internal/policydevel/eval.go b/app/cli/internal/policydevel/eval.go index 8f1ab3100..998845897 100644 --- a/app/cli/internal/policydevel/eval.go +++ b/app/cli/internal/policydevel/eval.go @@ -199,7 +199,7 @@ func craft(materialPath string, kind v1.CraftingSchema_Material_MaterialType, na Name: name, } - m, err := materials.Craft(context.Background(), materialSchema, materialPath, backend, nil, logger) + m, err := materials.Craft(context.Background(), materialSchema, materialPath, backend, nil, logger, nil) if err != nil { return nil, fmt.Errorf("failed to craft material (kind=%s): %w", kind.String(), err) } diff --git a/app/cli/pkg/action/attestation_add.go b/app/cli/pkg/action/attestation_add.go index eb1c278b0..082db2322 100644 --- a/app/cli/pkg/action/attestation_add.go +++ b/app/cli/pkg/action/attestation_add.go @@ -36,6 +36,8 @@ type AttestationAddOpts struct { // OCI registry credentials used for CONTAINER_IMAGE material type RegistryServer, RegistryUsername, RegistryPassword string LocalStatePath string + // NoStrictValidation skips strict schema validation + NoStrictValidation bool } type newCrafterOpts struct { @@ -59,6 +61,9 @@ func NewAttestationAdd(cfg *AttestationAddOpts) (*AttestationAdd, error) { cfg.Logger.Debug().Str("server", cfg.RegistryServer).Str("username", cfg.RegistryUsername).Msg("using OCI registry credentials") opts = append(opts, crafter.WithOCIAuth(cfg.RegistryServer, cfg.RegistryUsername, cfg.RegistryPassword)) } + if cfg.NoStrictValidation { + opts = append(opts, crafter.WithNoStrictValidation(cfg.NoStrictValidation)) + } return &AttestationAdd{ ActionsOpts: cfg.ActionsOpts, diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index cbe85aba3..444a13ac0 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -73,6 +73,9 @@ type Crafter struct { // attestation client is used to load chainloop policies attClient v1.AttestationServiceClient + + // noStrictValidation skips strict schema validation + noStrictValidation bool } type VersionedCraftingState struct { @@ -118,6 +121,13 @@ func WithOCIAuth(server, username, password string) NewOpt { } } +func WithNoStrictValidation(noStrict bool) NewOpt { + return func(c *Crafter) error { + c.noStrictValidation = noStrict + return nil + } +} + // Create a completely new crafter func NewCrafter(stateManager StateManager, attClient v1.AttestationServiceClient, opts ...NewOpt) (*Crafter, error) { noopLogger := zerolog.Nop() @@ -671,7 +681,9 @@ func (c *Crafter) AddMaterialContactFreeWithAutoDetectedKind(ctx context.Context // addMaterials adds the incoming material m to the crafting state func (c *Crafter) addMaterial(ctx context.Context, m *schemaapi.CraftingSchema_Material, attestationID, value string, casBackend *casclient.CASBackend, runtimeAnnotations map[string]string) (*api.Attestation_Material, error) { // 3- Craft resulting material - mt, err := materials.Craft(context.Background(), m, value, casBackend, c.ociRegistryAuth, c.Logger) + mt, err := materials.Craft(context.Background(), m, value, casBackend, c.ociRegistryAuth, c.Logger, &materials.CraftingOpts{ + NoStrictValidation: c.noStrictValidation, + }) if err != nil { return nil, err } diff --git a/pkg/attestation/crafter/materials/cyclonedxjson.go b/pkg/attestation/crafter/materials/cyclonedxjson.go index 658d516cb..a72058f51 100644 --- a/pkg/attestation/crafter/materials/cyclonedxjson.go +++ b/pkg/attestation/crafter/materials/cyclonedxjson.go @@ -39,10 +39,28 @@ const ( ) type CyclonedxJSONCrafter struct { - backend *casclient.CASBackend + backend *casclient.CASBackend + noStrictValidation bool *crafterCommon } +// CycloneDXCraftOpt is a functional option for CyclonedxJSONCrafter +type CycloneDXCraftOpt func(*CyclonedxJSONCrafter) + +// WithCycloneDXNoStrictValidation sets the noStrictValidation option +func WithCycloneDXNoStrictValidation(noStrict bool) CycloneDXCraftOpt { + return func(c *CyclonedxJSONCrafter) { + c.noStrictValidation = noStrict + } +} + +// cyclonedxRequiredFields checks the three required top-level fields per CycloneDX spec +type cyclonedxRequiredFields struct { + BOMFormat string `json:"bomFormat"` + SpecVersion string `json:"specVersion"` + Version int `json:"version"` +} + // cyclonedxDoc internal struct to unmarshall the incoming CycloneDX JSON type cyclonedxDoc struct { SpecVersion string `json:"specVersion"` @@ -78,15 +96,21 @@ type cyclonedxMetadataV15 struct { Component cyclonedxComponent `json:"component"` } -func NewCyclonedxJSONCrafter(materialSchema *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, l *zerolog.Logger) (*CyclonedxJSONCrafter, error) { +func NewCyclonedxJSONCrafter(materialSchema *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, l *zerolog.Logger, opts ...CycloneDXCraftOpt) (*CyclonedxJSONCrafter, error) { if materialSchema.Type != schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON { return nil, fmt.Errorf("material type is not cyclonedx json") } - return &CyclonedxJSONCrafter{ + c := &CyclonedxJSONCrafter{ backend: backend, crafterCommon: &crafterCommon{logger: l, input: materialSchema}, - }, nil + } + + for _, opt := range opts { + opt(c) + } + + return c, nil } func (i *CyclonedxJSONCrafter) Craft(ctx context.Context, filePath string) (*api.Attestation_Material, error) { @@ -95,6 +119,17 @@ func (i *CyclonedxJSONCrafter) Craft(ctx context.Context, filePath string) (*api return nil, fmt.Errorf("can't open the file: %w", err) } + var required cyclonedxRequiredFields + if err := json.Unmarshal(f, &required); err != nil { + i.logger.Debug().Err(err).Msg("error decoding file") + return nil, fmt.Errorf("invalid cyclonedx sbom file: %w", ErrInvalidMaterialType) + } + + if required.BOMFormat != "CycloneDX" || required.SpecVersion == "" || required.Version < 1 { + i.logger.Debug().Str("bomFormat", required.BOMFormat).Str("specVersion", required.SpecVersion).Int("version", required.Version).Msg("missing required CycloneDX fields") + return nil, fmt.Errorf("invalid cyclonedx sbom file: %w", ErrInvalidMaterialType) + } + var v interface{} if err := json.Unmarshal(f, &v); err != nil { i.logger.Debug().Err(err).Msg("error decoding file") @@ -102,10 +137,13 @@ func (i *CyclonedxJSONCrafter) Craft(ctx context.Context, filePath string) (*api } // Setting the version to empty string to validate against the latest version of the schema - err = schemavalidators.ValidateCycloneDX(v, "") - if err != nil { - i.logger.Debug().Err(err).Msgf("error decoding file: %#v", err) - return nil, fmt.Errorf("invalid cyclonedx sbom file: %w", ErrInvalidMaterialType) + if err := schemavalidators.ValidateCycloneDX(v, ""); err != nil { + if i.noStrictValidation { + i.logger.Warn().Err(err).Msg("error decoding file, strict validation disabled, continuing") + } else { + i.logger.Debug().Err(err).Msg("error decoding file") + return nil, fmt.Errorf("invalid cyclonedx sbom file: %w", ErrInvalidMaterialType) + } } m, err := uploadAndCraft(ctx, i.input, i.backend, filePath, i.logger) diff --git a/pkg/attestation/crafter/materials/cyclonedxjson_test.go b/pkg/attestation/crafter/materials/cyclonedxjson_test.go index 912f0f919..a4e354275 100644 --- a/pkg/attestation/crafter/materials/cyclonedxjson_test.go +++ b/pkg/attestation/crafter/materials/cyclonedxjson_test.go @@ -225,6 +225,72 @@ func TestCyclonedxJSONCraft(t *testing.T) { } } +func TestCycloneDXJSONCraftNoStrictValidation(t *testing.T) { + testCases := []struct { + name string + filePath string + noStrictValidation bool + wantErr string + }{ + { + name: "invalid schema without skip flag fails", + filePath: "./testdata/sbom.cyclonedx-invalid-schema.json", + noStrictValidation: false, + wantErr: "invalid cyclonedx sbom file", + }, + { + name: "invalid schema with skip flag succeeds", + filePath: "./testdata/sbom.cyclonedx-invalid-schema.json", + noStrictValidation: true, + wantErr: "", + }, + { + name: "non-cyclonedx file fails even with skip flag", + filePath: "./testdata/random.json", + noStrictValidation: true, + wantErr: "invalid cyclonedx sbom file", + }, + { + name: "valid file works without skip flag", + filePath: "./testdata/sbom.cyclonedx.json", + noStrictValidation: false, + wantErr: "", + }, + } + + schema := &contractAPI.CraftingSchema_Material{ + Name: "test", + Type: contractAPI.CraftingSchema_Material_SBOM_CYCLONEDX_JSON, + } + l := zerolog.Nop() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ast := assert.New(t) + // Mock uploader + uploader := mUploader.NewUploader(t) + if tc.wantErr == "" { + uploader.On("UploadFile", context.TODO(), tc.filePath). + Return(&casclient.UpDownStatus{}, nil) + } + + backend := &casclient.CASBackend{Uploader: uploader} + crafter, err := materials.NewCyclonedxJSONCrafter(schema, backend, &l, + materials.WithCycloneDXNoStrictValidation(tc.noStrictValidation)) + require.NoError(t, err) + + got, err := crafter.Craft(context.TODO(), tc.filePath) + if tc.wantErr != "" { + ast.ErrorContains(err, tc.wantErr) + return + } + + require.NoError(t, err) + ast.Equal(contractAPI.CraftingSchema_Material_SBOM_CYCLONEDX_JSON.String(), got.MaterialType.String()) + }) + } +} + func TestCycloneDXJSONCraft_SkipUpload(t *testing.T) { testCases := []struct { name string diff --git a/pkg/attestation/crafter/materials/materials.go b/pkg/attestation/crafter/materials/materials.go index 123cbacd9..0b399096c 100644 --- a/pkg/attestation/crafter/materials/materials.go +++ b/pkg/attestation/crafter/materials/materials.go @@ -205,7 +205,12 @@ type Craftable interface { Craft(ctx context.Context, value string) (*api.Attestation_Material, error) } -func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Material, value string, casBackend *casclient.CASBackend, ociAuth authn.Keychain, logger *zerolog.Logger) (*api.Attestation_Material, error) { +// CraftingOpts contains options for crafting materials +type CraftingOpts struct { + NoStrictValidation bool +} + +func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Material, value string, casBackend *casclient.CASBackend, ociAuth authn.Keychain, logger *zerolog.Logger, opts *CraftingOpts) (*api.Attestation_Material, error) { var crafter Craftable var err error @@ -218,6 +223,10 @@ func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Materia return nil, fmt.Errorf("validating material: %w", err) } + if opts == nil { + opts = &CraftingOpts{} + } + switch materialSchema.Type { case schemaapi.CraftingSchema_Material_STRING: crafter, err = NewStringCrafter(materialSchema) @@ -226,7 +235,7 @@ func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Materia case schemaapi.CraftingSchema_Material_ARTIFACT: crafter, err = NewArtifactCrafter(materialSchema, casBackend, logger) case schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON: - crafter, err = NewCyclonedxJSONCrafter(materialSchema, casBackend, logger) + crafter, err = NewCyclonedxJSONCrafter(materialSchema, casBackend, logger, WithCycloneDXNoStrictValidation(opts.NoStrictValidation)) case schemaapi.CraftingSchema_Material_SBOM_SPDX_JSON: crafter, err = NewSPDXJSONCrafter(materialSchema, casBackend, logger) case schemaapi.CraftingSchema_Material_JUNIT_XML: diff --git a/pkg/attestation/crafter/materials/materials_test.go b/pkg/attestation/crafter/materials/materials_test.go index 7d323bb64..f7aaafdab 100644 --- a/pkg/attestation/crafter/materials/materials_test.go +++ b/pkg/attestation/crafter/materials/materials_test.go @@ -40,7 +40,7 @@ func TestCraft(t *testing.T) { }, } - got, err := materials.Craft(context.TODO(), schema, "test-value", nil, nil, nil) + got, err := materials.Craft(context.TODO(), schema, "test-value", nil, nil, nil, nil) require.NoError(t, err) assert.Equal(contractAPI.CraftingSchema_Material_STRING, got.MaterialType) assert.False(got.UploadedToCas) diff --git a/pkg/attestation/crafter/materials/testdata/sbom.cyclonedx-invalid-schema.json b/pkg/attestation/crafter/materials/testdata/sbom.cyclonedx-invalid-schema.json new file mode 100644 index 000000000..b0f7011bf --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/sbom.cyclonedx-invalid-schema.json @@ -0,0 +1,7 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "invalidField": "this field is not valid according to the schema", + "components": "should be an array but is a string" +} From a1676f33d6e76936a647f8afd8aeb66bbbe37fa1 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Tue, 10 Feb 2026 13:31:58 +0100 Subject: [PATCH 2/6] lint Signed-off-by: Sylwester Piskozub --- .../crafter/materials/cyclonedxjson_test.go | 30 +++++++++---------- .../crafter/materials/materials.go | 1 + 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pkg/attestation/crafter/materials/cyclonedxjson_test.go b/pkg/attestation/crafter/materials/cyclonedxjson_test.go index a4e354275..5112407ea 100644 --- a/pkg/attestation/crafter/materials/cyclonedxjson_test.go +++ b/pkg/attestation/crafter/materials/cyclonedxjson_test.go @@ -227,34 +227,34 @@ func TestCyclonedxJSONCraft(t *testing.T) { func TestCycloneDXJSONCraftNoStrictValidation(t *testing.T) { testCases := []struct { - name string - filePath string + name string + filePath string noStrictValidation bool - wantErr string + wantErr string }{ { - name: "invalid schema without skip flag fails", - filePath: "./testdata/sbom.cyclonedx-invalid-schema.json", + name: "invalid schema without skip flag fails", + filePath: "./testdata/sbom.cyclonedx-invalid-schema.json", noStrictValidation: false, - wantErr: "invalid cyclonedx sbom file", + wantErr: "invalid cyclonedx sbom file", }, { - name: "invalid schema with skip flag succeeds", - filePath: "./testdata/sbom.cyclonedx-invalid-schema.json", + name: "invalid schema with skip flag succeeds", + filePath: "./testdata/sbom.cyclonedx-invalid-schema.json", noStrictValidation: true, - wantErr: "", + wantErr: "", }, { - name: "non-cyclonedx file fails even with skip flag", - filePath: "./testdata/random.json", + name: "non-cyclonedx file fails even with skip flag", + filePath: "./testdata/random.json", noStrictValidation: true, - wantErr: "invalid cyclonedx sbom file", + wantErr: "invalid cyclonedx sbom file", }, { - name: "valid file works without skip flag", - filePath: "./testdata/sbom.cyclonedx.json", + name: "valid file works without skip flag", + filePath: "./testdata/sbom.cyclonedx.json", noStrictValidation: false, - wantErr: "", + wantErr: "", }, } diff --git a/pkg/attestation/crafter/materials/materials.go b/pkg/attestation/crafter/materials/materials.go index 0b399096c..a8c92a090 100644 --- a/pkg/attestation/crafter/materials/materials.go +++ b/pkg/attestation/crafter/materials/materials.go @@ -210,6 +210,7 @@ type CraftingOpts struct { NoStrictValidation bool } +//nolint:gocyclo func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Material, value string, casBackend *casclient.CASBackend, ociAuth authn.Keychain, logger *zerolog.Logger, opts *CraftingOpts) (*api.Attestation_Material, error) { var crafter Craftable var err error From 5c2a47e2c9ce830aa7c9afab09593316ab43c935 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Tue, 10 Feb 2026 13:34:23 +0100 Subject: [PATCH 3/6] rename param; change flag desc Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 2 +- app/cli/documentation/cli-reference.mdx | 2 +- pkg/attestation/crafter/crafter.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index 4208409fa..8acaf0130 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -144,7 +144,7 @@ func newAttestationAddCmd() *cobra.Command { cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value") flagAttestationID(cmd) cmd.Flags().StringVar(&kind, "kind", "", fmt.Sprintf("kind of the material to be recorded: %q", schemaapi.ListAvailableMaterialKind())) - cmd.Flags().BoolVar(&noStrictValidation, "no-strict-validation", false, "skip strict schema validation for SBOM files (CycloneDX/SPDX)") + cmd.Flags().BoolVar(&noStrictValidation, "no-strict-validation", false, "skip strict schema validation") // Optional OCI registry credentials cmd.Flags().StringVar(®istryServer, "registry-server", "", fmt.Sprintf("OCI repository server, ($%s)", registryServerEnvVarName)) diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index 9c3daa27d..2f031d72a 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -202,7 +202,7 @@ Options -h, --help help for add --kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] --name string name of the material as shown in the contract ---no-strict-validation skip strict schema validation for SBOM files (CycloneDX/SPDX) +--no-strict-validation skip strict schema validation --registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD) --registry-server string OCI repository server, ($CHAINLOOP_REGISTRY_SERVER) --registry-username string registry username, ($CHAINLOOP_REGISTRY_USERNAME) diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index 444a13ac0..7d845ea74 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -121,9 +121,9 @@ func WithOCIAuth(server, username, password string) NewOpt { } } -func WithNoStrictValidation(noStrict bool) NewOpt { +func WithNoStrictValidation(noStrictValidation bool) NewOpt { return func(c *Crafter) error { - c.noStrictValidation = noStrict + c.noStrictValidation = noStrictValidation return nil } } From 8a9bc2c391aedb5f26295a1e8b0bf5898958bafe Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Tue, 10 Feb 2026 14:06:59 +0100 Subject: [PATCH 4/6] update flag desc Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 2 +- app/cli/documentation/cli-reference.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index 8acaf0130..68db329f9 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -144,7 +144,7 @@ func newAttestationAddCmd() *cobra.Command { cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value") flagAttestationID(cmd) cmd.Flags().StringVar(&kind, "kind", "", fmt.Sprintf("kind of the material to be recorded: %q", schemaapi.ListAvailableMaterialKind())) - cmd.Flags().BoolVar(&noStrictValidation, "no-strict-validation", false, "skip strict schema validation") + cmd.Flags().BoolVar(&noStrictValidation, "no-strict-validation", false, "skip strict schema validation for CycloneDX SBOM files") // Optional OCI registry credentials cmd.Flags().StringVar(®istryServer, "registry-server", "", fmt.Sprintf("OCI repository server, ($%s)", registryServerEnvVarName)) diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index 2f031d72a..e41b80aba 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -202,7 +202,7 @@ Options -h, --help help for add --kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] --name string name of the material as shown in the contract ---no-strict-validation skip strict schema validation +--no-strict-validation skip strict schema validation for CycloneDX SBOM files --registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD) --registry-server string OCI repository server, ($CHAINLOOP_REGISTRY_SERVER) --registry-username string registry username, ($CHAINLOOP_REGISTRY_USERNAME) From 380c031b29d409be10ed9f248f64ee55f2835a1a Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Tue, 10 Feb 2026 14:08:29 +0100 Subject: [PATCH 5/6] change flag desc Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 2 +- app/cli/documentation/cli-reference.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index 68db329f9..c8b1720cb 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -144,7 +144,7 @@ func newAttestationAddCmd() *cobra.Command { cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value") flagAttestationID(cmd) cmd.Flags().StringVar(&kind, "kind", "", fmt.Sprintf("kind of the material to be recorded: %q", schemaapi.ListAvailableMaterialKind())) - cmd.Flags().BoolVar(&noStrictValidation, "no-strict-validation", false, "skip strict schema validation for CycloneDX SBOM files") + cmd.Flags().BoolVar(&noStrictValidation, "no-strict-validation", false, "skip strict schema validation for SBOM_CYCLONEDX_JSON materials") // Optional OCI registry credentials cmd.Flags().StringVar(®istryServer, "registry-server", "", fmt.Sprintf("OCI repository server, ($%s)", registryServerEnvVarName)) diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index e41b80aba..591785762 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -202,7 +202,7 @@ Options -h, --help help for add --kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] --name string name of the material as shown in the contract ---no-strict-validation skip strict schema validation for CycloneDX SBOM files +--no-strict-validation skip strict schema validation for SBOM_CYCLONEDX_JSON materials --registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD) --registry-server string OCI repository server, ($CHAINLOOP_REGISTRY_SERVER) --registry-username string registry username, ($CHAINLOOP_REGISTRY_USERNAME) From d818883853ca3477c94e4e4c40982d3d293f0067 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Tue, 10 Feb 2026 14:28:32 +0100 Subject: [PATCH 6/6] log hint when schema validation fails Signed-off-by: Sylwester Piskozub --- pkg/attestation/crafter/materials/cyclonedxjson.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/attestation/crafter/materials/cyclonedxjson.go b/pkg/attestation/crafter/materials/cyclonedxjson.go index a72058f51..102f062f1 100644 --- a/pkg/attestation/crafter/materials/cyclonedxjson.go +++ b/pkg/attestation/crafter/materials/cyclonedxjson.go @@ -142,6 +142,7 @@ func (i *CyclonedxJSONCrafter) Craft(ctx context.Context, filePath string) (*api i.logger.Warn().Err(err).Msg("error decoding file, strict validation disabled, continuing") } else { i.logger.Debug().Err(err).Msg("error decoding file") + i.logger.Info().Msg("you can disable strict validation to skip schema validation") return nil, fmt.Errorf("invalid cyclonedx sbom file: %w", ErrInvalidMaterialType) } }