@@ -104,6 +104,7 @@ func init() {
104104 sbomCmd .AddCommand (sbomProjectCmd )
105105 sbomCmd .AddCommand (sbomHashCmd )
106106 sbomCmd .AddCommand (sbomVerifyCmd )
107+ sbomCmd .AddCommand (sbomValidateCmd )
107108 cmdpkg .RootCmd .AddCommand (sbomCmd )
108109}
109110
@@ -152,14 +153,44 @@ Examples:
152153 RunE : runSBOMVerify ,
153154}
154155
156+ var sbomValidateCmd = & cobra.Command {
157+ Use : "validate <sbom-file>" ,
158+ Short : "Validate SBOM against policy rules" ,
159+ Long : `Validate an SBOM file against defined policy rules.
160+
161+ Policy files are YAML documents that define validation rules for:
162+ - Supply chain security (replace directives, vendoring)
163+ - Security requirements (CGO status, retracted versions)
164+ - Completeness checks (required components, metadata)
165+ - License compliance (allowed/blocked licenses)
166+
167+ Examples:
168+ # Validate with default policy
169+ goenv sbom validate sbom.json --policy=.goenv-policy.yaml
170+
171+ # Validate and fail on warnings
172+ goenv sbom validate sbom.json --policy=policy.yaml --fail-on-warning
173+
174+ # Validate with verbose output
175+ goenv sbom validate sbom.json --policy=policy.yaml --verbose` ,
176+ Args : cobra .ExactArgs (1 ),
177+ RunE : runSBOMValidate ,
178+ }
179+
155180var (
156- hashAlgorithm string
157- verifyDiff bool
181+ hashAlgorithm string
182+ verifyDiff bool
183+ policyFile string
184+ failOnWarning bool
185+ verboseValidate bool
158186)
159187
160188func init () {
161189 sbomHashCmd .Flags ().StringVar (& hashAlgorithm , "algorithm" , "sha256" , "Hash algorithm (sha256, sha512)" )
162190 sbomVerifyCmd .Flags ().BoolVar (& verifyDiff , "diff" , false , "Show detailed differences if SBOMs don't match" )
191+ sbomValidateCmd .Flags ().StringVarP (& policyFile , "policy" , "p" , ".goenv-policy.yaml" , "Path to policy configuration file" )
192+ sbomValidateCmd .Flags ().BoolVar (& failOnWarning , "fail-on-warning" , false , "Treat warnings as failures" )
193+ sbomValidateCmd .Flags ().BoolVar (& verboseValidate , "verbose" , false , "Show detailed validation output" )
163194}
164195
165196func runSBOMHash (cmd * cobra.Command , args []string ) error {
@@ -223,6 +254,91 @@ func runSBOMVerify(cmd *cobra.Command, args []string) error {
223254 return fmt .Errorf ("SBOMs are not reproducibly identical" )
224255}
225256
257+ func runSBOMValidate (cmd * cobra.Command , args []string ) error {
258+ sbomPath := args [0 ]
259+
260+ // Verify SBOM file exists
261+ if ! utils .FileExists (sbomPath ) {
262+ return fmt .Errorf ("SBOM file not found: %s" , sbomPath )
263+ }
264+
265+ // Verify policy file exists
266+ if ! utils .FileExists (policyFile ) {
267+ return fmt .Errorf ("policy file not found: %s (use --policy to specify)" , policyFile )
268+ }
269+
270+ cfg , _ := cmdutil .SetupContext ()
271+
272+ // Load policy engine
273+ engine , err := sbom .NewPolicyEngine (policyFile )
274+ if err != nil {
275+ return errors .FailedTo ("load policy" , err )
276+ }
277+
278+ if cfg .Debug || verboseValidate {
279+ fmt .Fprintf (cmd .ErrOrStderr (), "goenv: Validating %s against policy %s\n " , sbomPath , policyFile )
280+ }
281+
282+ // Run validation
283+ result , err := engine .Validate (sbomPath )
284+ if err != nil {
285+ return errors .FailedTo ("validate SBOM" , err )
286+ }
287+
288+ // Output results
289+ if verboseValidate {
290+ fmt .Fprint (cmd .OutOrStdout (), result .Summary )
291+
292+ // Show detailed violations
293+ if len (result .Violations ) > 0 {
294+ fmt .Fprintf (cmd .OutOrStdout (), "\n Violations:\n " )
295+ for i , v := range result .Violations {
296+ fmt .Fprintf (cmd .OutOrStdout (), "\n %d. %s\n " , i + 1 , v .Rule )
297+ fmt .Fprintf (cmd .OutOrStdout (), " Severity: %s\n " , v .Severity )
298+ fmt .Fprintf (cmd .OutOrStdout (), " Component: %s\n " , v .Component )
299+ fmt .Fprintf (cmd .OutOrStdout (), " Message: %s\n " , v .Message )
300+ if v .Remediation != "" {
301+ fmt .Fprintf (cmd .OutOrStdout (), " Remediation: %s\n " , v .Remediation )
302+ }
303+ }
304+ }
305+
306+ if len (result .Warnings ) > 0 {
307+ fmt .Fprintf (cmd .OutOrStdout (), "\n Warnings:\n " )
308+ for i , w := range result .Warnings {
309+ fmt .Fprintf (cmd .OutOrStdout (), "\n %d. %s\n " , i + 1 , w .Rule )
310+ fmt .Fprintf (cmd .OutOrStdout (), " Severity: %s\n " , w .Severity )
311+ fmt .Fprintf (cmd .OutOrStdout (), " Component: %s\n " , w .Component )
312+ fmt .Fprintf (cmd .OutOrStdout (), " Message: %s\n " , w .Message )
313+ if w .Remediation != "" {
314+ fmt .Fprintf (cmd .OutOrStdout (), " Remediation: %s\n " , w .Remediation )
315+ }
316+ }
317+ }
318+ } else {
319+ // Concise output
320+ if result .Passed {
321+ fmt .Fprintf (cmd .OutOrStdout (), "✓ SBOM validation passed\n " )
322+ } else {
323+ fmt .Fprintf (cmd .ErrOrStderr (), "✗ SBOM validation failed\n " )
324+ fmt .Fprintf (cmd .ErrOrStderr (), " %d violations, %d warnings\n " ,
325+ len (result .Violations ), len (result .Warnings ))
326+ fmt .Fprintf (cmd .ErrOrStderr (), " Run with --verbose for details\n " )
327+ }
328+ }
329+
330+ // Return error if validation failed
331+ if ! result .Passed {
332+ if failOnWarning && len (result .Warnings ) > 0 {
333+ return fmt .Errorf ("validation failed with %d violations and %d warnings" ,
334+ len (result .Violations ), len (result .Warnings ))
335+ }
336+ return fmt .Errorf ("validation failed with %d violations" , len (result .Violations ))
337+ }
338+
339+ return nil
340+ }
341+
226342func runSBOMProject (cmd * cobra.Command , args []string ) error {
227343 cfg , mgr := cmdutil .SetupContext ()
228344
0 commit comments