Skip to content

Commit 61dd91e

Browse files
committed
feat(spec1-5): add support for machine learning
Signed-off-by: nscuro <[email protected]>
1 parent f831960 commit 61dd91e

10 files changed

+747
-0
lines changed

convert.go

+6
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ func componentConverter(specVersion SpecVersion) func(*Component) {
137137
}
138138
}
139139

140+
if specVersion < SpecVersion1_5 {
141+
c.ModelCard = nil
142+
c.Data = nil
143+
}
144+
140145
if !specVersion.supportsComponentType(c.Type) {
141146
c.Type = ComponentTypeApplication
142147
}
@@ -382,6 +387,7 @@ func (sv SpecVersion) supportsExternalReferenceType(ert ExternalReferenceType) b
382387
ERTypeDynamicAnalysisReport,
383388
ERTypeExploitabilityStatement,
384389
ERTypeMaturityReport,
390+
ERTypeModelCard,
385391
ERTypePentestReport,
386392
ERTypeQualityMetrics,
387393
ERTypeRiskAssessment,

cyclonedx.go

+129
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,58 @@ type Component struct {
166166
Components *[]Component `json:"components,omitempty" xml:"components>component,omitempty"`
167167
Evidence *Evidence `json:"evidence,omitempty" xml:"evidence,omitempty"`
168168
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
169+
ModelCard *MLModelCard `json:"modelCard,omitempty" xml:"modelCard,omitempty"`
170+
Data *ComponentData `json:"data,omitempty" xml:"data,omitempty"`
169171
}
170172

173+
type ComponentData struct {
174+
BOMRef string `json:"bom-ref,omitempty" xml:"bom-ref,attr,omitempty"`
175+
Type ComponentDataType `json:"type,omitempty" xml:"type,omitempty"`
176+
Name string `json:"name,omitempty" xml:"name,omitempty"`
177+
Contents *ComponentDataContents `json:"contents,omitempty" xml:"contents,omitempty"`
178+
Classification string `json:"classification,omitempty" xml:"classification,omitempty"`
179+
SensitiveData *[]string `json:"sensitiveData,omitempty" xml:"sensitiveData,omitempty"`
180+
Graphics *ComponentDataGraphics `json:"graphics,omitempty" xml:"graphics,omitempty"`
181+
Description string `json:"description,omitempty" xml:"description,omitempty"`
182+
Governance *ComponentDataGovernance `json:"governance,omitempty" xml:"governance,omitempty"`
183+
}
184+
185+
type ComponentDataContents struct {
186+
Attachment *AttachedText `json:"attachment,omitempty" xml:"attachment,omitempty"`
187+
URL string `json:"url,omitempty" xml:"url,omitempty"`
188+
Properties *[]Property `json:"properties,omitempty" xml:"properties,omitempty"`
189+
}
190+
191+
type ComponentDataGovernance struct {
192+
Custodians *[]ComponentDataGovernanceResponsibleParty `json:"custodians,omitempty" xml:"custodians>custodian,omitempty"`
193+
Stewards *[]ComponentDataGovernanceResponsibleParty `json:"stewards,omitempty" xml:"stewards>steward,omitempty"`
194+
Owners *[]ComponentDataGovernanceResponsibleParty `json:"owners,omitempty" xml:"owners>owner,omitempty"`
195+
}
196+
197+
type ComponentDataGovernanceResponsibleParty struct {
198+
Organization *OrganizationalEntity `json:"organization,omitempty" xml:"organization,omitempty"`
199+
Contact *OrganizationalContact `json:"contact,omitempty" xml:"contact,omitempty"`
200+
}
201+
202+
type ComponentDataGraphic struct {
203+
Name string `json:"name,omitempty" xml:"name,omitempty"`
204+
Image *AttachedText `json:"image,omitempty" xml:"image,omitempty"`
205+
}
206+
207+
type ComponentDataGraphics struct {
208+
Description string `json:"description,omitempty" xml:"description,omitempty"`
209+
Collection *[]ComponentDataGraphic `json:"collection,omitempty" xml:"collection>graphic,omitempty"`
210+
}
211+
212+
type ComponentDataType string
213+
214+
const (
215+
ComponentDataTypeConfiguration ComponentDataType = "configuration"
216+
ComponentDataTypeDataset ComponentDataType = "dataset"
217+
ComponentDataTypeOther ComponentDataType = "other"
218+
ComponentDataTypeSourceCode ComponentDataType = "source-code"
219+
)
220+
171221
type Composition struct {
172222
BOMRef string `json:"bom-ref,omitempty" xml:"bom-ref,attr,omitempty"`
173223
Aggregate CompositionAggregate `json:"aggregate" xml:"aggregate"`
@@ -258,6 +308,7 @@ const (
258308
ERTypeLicense ExternalReferenceType = "license"
259309
ERTypeMailingList ExternalReferenceType = "mailing-list"
260310
ERTypeMaturityReport ExternalReferenceType = "maturity-report"
311+
ERTypeModelCard ExternalReferenceType = "model-card"
261312
ERTypeOther ExternalReferenceType = "other"
262313
ERTypePentestReport ExternalReferenceType = "pentest-report"
263314
ERTypeQualityMetrics ExternalReferenceType = "quality-metrics"
@@ -451,6 +502,84 @@ type Metadata struct {
451502
Properties *[]Property `json:"properties,omitempty" xml:"properties>property,omitempty"`
452503
}
453504

505+
type MLDatasetChoice struct {
506+
Ref string `json:"-" xml:"-"`
507+
ComponentData *ComponentData `json:"-" xml:"-"`
508+
}
509+
510+
type MLInputOutputParameters struct {
511+
Format string `json:"format,omitempty" xml:"format,omitempty"`
512+
}
513+
514+
type MLModelCard struct {
515+
BOMRef string `json:"bom-ref,omitempty" xml:"bom-ref,attr,omitempty"`
516+
ModelParameters *MLModelParameters `json:"modelParameters,omitempty" xml:"modelParameters,omitempty"`
517+
QuantitativeAnalysis *MLQuantitativeAnalysis `json:"quantitativeAnalysis,omitempty" xml:"quantitativeAnalysis,omitempty"`
518+
Considerations *MLModelCardConsiderations `json:"considerations,omitempty" xml:"considerations,omitempty"`
519+
}
520+
521+
type MLModelCardConsiderations struct {
522+
Users *[]string `json:"users,omitempty" xml:"users>user,omitempty"`
523+
UseCases *[]string `json:"useCases,omitempty" xml:"useCases>useCase,omitempty"`
524+
TechnicalLimitations *[]string `json:"technicalLimitations,omitempty" xml:"technicalLimitations>technicalLimitation,omitempty"`
525+
PerformanceTradeoffs *[]string `json:"performanceTradeoffs,omitempty" xml:"performanceTradeoffs>performanceTradeoff,omitempty"`
526+
EthicalConsiderations *[]MLModelCardEthicalConsideration `json:"ethicalConsiderations,omitempty" xml:"ethicalConsiderations>ethicalConsideration,omitempty"`
527+
FairnessAssessments *[]MLModelCardFairnessAssessment `json:"fairnessAssessments,omitempty" xml:"fairnessAssessments>fairnessAssessment,omitempty"`
528+
}
529+
530+
type MLModelCardEthicalConsideration struct {
531+
Name string `json:"name,omitempty" xml:"name,omitempty"`
532+
MitigationStrategy string `json:"mitigationStrategy,omitempty" xml:"mitigationStrategy,omitempty"`
533+
}
534+
535+
type MLModelCardFairnessAssessment struct {
536+
GroupAtRisk string `json:"groupAtRisk,omitempty" xml:"groupAtRisk,omitempty"`
537+
Benefits string `json:"benefits,omitempty" xml:"benefits,omitempty"`
538+
Harms string `json:"harms,omitempty" xml:"harms,omitempty"`
539+
MitigationStrategy string `json:"mitigationStrategy,omitempty" xml:"mitigationStrategy,omitempty"`
540+
}
541+
542+
type MLModelParameters struct {
543+
Approach *MLModelParametersApproach `json:"approach,omitempty" xml:"approach,omitempty"`
544+
Task string `json:"task,omitempty" xml:"task,omitempty"`
545+
ArchitectureFamily string `json:"architectureFamily,omitempty" xml:"architectureFamily,omitempty"`
546+
ModelArchitecture string `json:"modelArchitecture,omitempty" xml:"modelArchitecture,omitempty"`
547+
Datasets *[]MLDatasetChoice `json:"datasets,omitempty" xml:"datasets>dataset,omitempty"`
548+
Inputs *[]MLInputOutputParameters `json:"inputs,omitempty" xml:"inputs>input,omitempty"`
549+
Outputs *[]MLInputOutputParameters `json:"outputs,omitempty" xml:"outputs>output,omitempty"`
550+
}
551+
552+
type MLModelParametersApproach struct {
553+
Type MLModelParametersApproachType `json:"type,omitempty" xml:"type,omitempty"`
554+
}
555+
556+
type MLModelParametersApproachType string
557+
558+
const (
559+
MLModelParametersApproachTypeSupervised MLModelParametersApproachType = "supervised"
560+
MLModelParametersApproachTypeUnsupervised MLModelParametersApproachType = "unsupervised"
561+
MLModelParametersApproachTypeReinforcementLearning MLModelParametersApproachType = "reinforcement-learning"
562+
MLModelParametersApproachTypeSemiSupervised MLModelParametersApproachType = "semi-supervised"
563+
MLModelParametersApproachTypeSelfSupervised MLModelParametersApproachType = "self-supervised"
564+
)
565+
566+
type MLQuantitativeAnalysis struct {
567+
PerformanceMetrics *[]MLPerformanceMetric `json:"performanceMetrics,omitempty" xml:"performanceMetrics>performanceMetric,omitempty"`
568+
Graphics *ComponentDataGraphics `json:"graphics,omitempty" xml:"graphics,omitempty"`
569+
}
570+
571+
type MLPerformanceMetric struct {
572+
Type string `json:"type,omitempty" xml:"type,omitempty"`
573+
Value string `json:"value,omitempty" xml:"value,omitempty"`
574+
Slice string `json:"slice,omitempty" xml:"slice,omitempty"`
575+
ConfidenceInterval *MLPerformanceMetricConfidenceInterval `json:"confidenceInterval,omitempty" xml:"confidenceInterval,omitempty"`
576+
}
577+
578+
type MLPerformanceMetricConfidenceInterval struct {
579+
LowerBound string `json:"lowerBound,omitempty" xml:"lowerBound,omitempty"`
580+
UpperBound string `json:"upperBound,omitempty" xml:"upperBound,omitempty"`
581+
}
582+
454583
type Note struct {
455584
Locale string `json:"locale,omitempty" xml:"locale,omitempty"`
456585
Text AttachedText `json:"text" xml:"text"`

cyclonedx_json.go

+39
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,45 @@ import (
2121
"encoding/json"
2222
)
2323

24+
type mlDatasetChoiceRefJSON struct {
25+
Ref string `json:"ref" xml:"-"`
26+
}
27+
28+
func (dc MLDatasetChoice) MarshalJSON() ([]byte, error) {
29+
if dc.Ref != "" {
30+
return json.Marshal(mlDatasetChoiceRefJSON{Ref: dc.Ref})
31+
} else if dc.ComponentData != nil {
32+
return json.Marshal(dc.ComponentData)
33+
}
34+
35+
return []byte("{}"), nil
36+
}
37+
38+
func (dc *MLDatasetChoice) UnmarshalJSON(bytes []byte) error {
39+
var refObj mlDatasetChoiceRefJSON
40+
err := json.Unmarshal(bytes, &refObj)
41+
if err != nil {
42+
return err
43+
}
44+
45+
if refObj.Ref != "" {
46+
dc.Ref = refObj.Ref
47+
return nil
48+
}
49+
50+
var componentData ComponentData
51+
err = json.Unmarshal(bytes, &componentData)
52+
if err != nil {
53+
return err
54+
}
55+
56+
if componentData != (ComponentData{}) {
57+
dc.ComponentData = &componentData
58+
}
59+
60+
return nil
61+
}
62+
2463
func (sv SpecVersion) MarshalJSON() ([]byte, error) {
2564
return json.Marshal(sv.String())
2665
}

cyclonedx_json_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,85 @@
1616
// Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818
package cyclonedx
19+
20+
import (
21+
"encoding/json"
22+
"github.com/stretchr/testify/require"
23+
"testing"
24+
)
25+
26+
func TestMLDatasetChoice_MarshalJSON(t *testing.T) {
27+
t.Run("Empty", func(t *testing.T) {
28+
choice := MLDatasetChoice{}
29+
jsonBytes, err := json.Marshal(choice)
30+
require.NoError(t, err)
31+
require.Equal(t, "{}", string(jsonBytes))
32+
})
33+
34+
t.Run("WithRef", func(t *testing.T) {
35+
choice := MLDatasetChoice{Ref: "foo"}
36+
jsonBytes, err := json.Marshal(choice)
37+
require.NoError(t, err)
38+
require.Equal(t, `{"ref":"foo"}`, string(jsonBytes))
39+
})
40+
41+
t.Run("WithComponentData", func(t *testing.T) {
42+
choice := MLDatasetChoice{
43+
ComponentData: &ComponentData{
44+
BOMRef: "foo",
45+
Name: "bar",
46+
},
47+
}
48+
jsonBytes, err := json.Marshal(choice)
49+
require.NoError(t, err)
50+
require.Equal(t, `{"bom-ref":"foo","name":"bar"}`, string(jsonBytes))
51+
})
52+
53+
t.Run("WithRefAndComponentData", func(t *testing.T) {
54+
choice := MLDatasetChoice{
55+
Ref: "foo",
56+
ComponentData: &ComponentData{
57+
BOMRef: "bar",
58+
Name: "baz",
59+
},
60+
}
61+
jsonBytes, err := json.Marshal(choice)
62+
require.NoError(t, err)
63+
require.Equal(t, `{"ref":"foo"}`, string(jsonBytes))
64+
})
65+
}
66+
67+
func TestMLDatasetChoice_UnmarshalJSON(t *testing.T) {
68+
t.Run("Empty", func(t *testing.T) {
69+
var choice MLDatasetChoice
70+
err := json.Unmarshal([]byte(`{}`), &choice)
71+
require.NoError(t, err)
72+
require.Equal(t, MLDatasetChoice{}, choice)
73+
})
74+
75+
t.Run("WithRef", func(t *testing.T) {
76+
var choice MLDatasetChoice
77+
err := json.Unmarshal([]byte(`{"ref":"foo"}`), &choice)
78+
require.NoError(t, err)
79+
require.Equal(t, "foo", choice.Ref)
80+
require.Nil(t, choice.ComponentData)
81+
})
82+
83+
t.Run("WithComponentData", func(t *testing.T) {
84+
var choice MLDatasetChoice
85+
err := json.Unmarshal([]byte(`{"bom-ref":"foo","name":"bar"}`), &choice)
86+
require.NoError(t, err)
87+
require.Empty(t, choice.Ref)
88+
require.NotNil(t, choice.ComponentData)
89+
require.Equal(t, "foo", choice.ComponentData.BOMRef)
90+
require.Equal(t, "bar", choice.ComponentData.Name)
91+
})
92+
93+
t.Run("WithRefAndComponentData", func(t *testing.T) {
94+
var choice MLDatasetChoice
95+
err := json.Unmarshal([]byte(`{"ref":"foo","bom-ref":"bar","name":"baz"}`), &choice)
96+
require.NoError(t, err)
97+
require.Equal(t, "foo", choice.Ref)
98+
require.Nil(t, choice.ComponentData)
99+
})
100+
}

cyclonedx_xml.go

+38
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,44 @@ func (l *Licenses) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error {
161161
return nil
162162
}
163163

164+
type mlDatasetChoiceRefXML struct {
165+
Ref string `json:"-" xml:"ref"`
166+
}
167+
168+
type mlDatasetChoiceXML struct {
169+
Ref string `json:"-" xml:"ref"`
170+
ComponentData
171+
}
172+
173+
func (dc MLDatasetChoice) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
174+
if dc.Ref != "" {
175+
return e.EncodeElement(mlDatasetChoiceRefXML{Ref: dc.Ref}, start)
176+
} else if dc.ComponentData != nil {
177+
return e.EncodeElement(dc.ComponentData, start)
178+
}
179+
180+
return nil
181+
}
182+
183+
func (dc *MLDatasetChoice) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
184+
var choice mlDatasetChoiceXML
185+
err := d.DecodeElement(&choice, &start)
186+
if err != nil {
187+
return err
188+
}
189+
190+
if choice.Ref != "" {
191+
dc.Ref = choice.Ref
192+
return nil
193+
}
194+
195+
if choice.ComponentData != (ComponentData{}) {
196+
dc.ComponentData = &choice.ComponentData
197+
}
198+
199+
return nil
200+
}
201+
164202
func (sv SpecVersion) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
165203
return e.EncodeElement(sv.String(), start)
166204
}

0 commit comments

Comments
 (0)