Skip to content

Commit

Permalink
Removed cyclonedx library dependency entirely and replaced internal t…
Browse files Browse the repository at this point in the history
…ypes/classes usage with types built off of the specification

Signed-off-by: Amndeep Singh Mann <[email protected]>
  • Loading branch information
Amndeep7 committed Sep 11, 2024
1 parent 6f4c4de commit d3be8d5
Show file tree
Hide file tree
Showing 8 changed files with 8,035 additions and 507 deletions.
8 changes: 7 additions & 1 deletion libs/hdf-converters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
},
"dependencies": {
"@aws-sdk/client-config-service": "^3.95.0",
"@cyclonedx/cyclonedx-library": "^6.11.0",
"@e965/xlsx": "^0.20.0",
"@mdi/js": "^7.0.96",
"@microsoft/microsoft-graph-types": "^2.40.0",
Expand Down Expand Up @@ -75,6 +74,13 @@
"typedoc": "^0.26.2"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts",
"d.ts",
"node"
],
"moduleNameMapper": {
"axios": "axios/dist/node/axios.cjs"
},
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"platform": {
"name": "Heimdall Tools",
"release": "2.10.14"
"release": "2.10.15"
},
"version": "2.10.14",
"version": "2.10.15",
"statistics": {},
"profiles": [
{
Expand Down Expand Up @@ -90,13 +90,13 @@
{
"status": "failed",
"code_desc": "Component urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar is vulnerable",
"message": "-Component Summary-\n\n- Bom-ref: urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar\n\n- Name: urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar",
"message": "-Component Summary-\n\n- Type: application\n\n- Bom-ref: urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar\n\n- Name: urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar",
"start_time": ""
}
]
}
],
"sha256": "6e7fa4296080be8402cc3a052be4ef033a98f9520959b3ec5dce5c906651160f"
"sha256": "11edd9546478812825f507e52fd640da19bd27f7370f43bfb90b455dbbe03fed"
}
],
"passthrough": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"platform": {
"name": "Heimdall Tools",
"release": "2.10.14"
"release": "2.10.15"
},
"version": "2.10.14",
"version": "2.10.15",
"statistics": {},
"profiles": [
{
Expand Down Expand Up @@ -90,13 +90,13 @@
{
"status": "failed",
"code_desc": "Component urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar is vulnerable",
"message": "-Component Summary-\n\n- Bom-ref: urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar\n\n- Name: urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar",
"message": "-Component Summary-\n\n- Type: application\n\n- Bom-ref: urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar\n\n- Name: urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/[email protected]?type=jar",
"start_time": ""
}
]
}
],
"sha256": "6e7fa4296080be8402cc3a052be4ef033a98f9520959b3ec5dce5c906651160f"
"sha256": "11edd9546478812825f507e52fd640da19bd27f7370f43bfb90b455dbbe03fed"
}
],
"passthrough": {
Expand Down
149 changes: 71 additions & 78 deletions libs/hdf-converters/src/cyclonedx-sbom-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,41 @@ import {BaseConverter, ILookupPath, MappedTransform} from './base-converter';
import {CweNistMapping} from './mappings/CweNistMapping';
import {filterString, getCCIsForNISTTags} from './utils/global';
import {
Credits,
RatingRepository,
Source,
Vulnerability,
VulnerabilityRepository
} from '@cyclonedx/cyclonedx-library/dist.d/models/vulnerability';
import {CweRepository} from '@cyclonedx/cyclonedx-library/dist.d/types';
import {
AnalysisResponseRepository,
Severity
} from '@cyclonedx/cyclonedx-library/dist.d/enums/vulnerability';
import {
Component,
ComponentRepository,
OptionalBomProperties,
OptionalComponentProperties,
ToolRepository
} from '@cyclonedx/cyclonedx-library/dist.d/models';
CycloneDXSoftwareBillOfMaterialSpecification,
CycloneDXSoftwareBillOfMaterialsStandard,
CycloneDXBillOfMaterialsStandardVulnerability,
CycloneDXSoftwareBillOfMaterialsStandardVulnerability,
FluffyCredits,
PurpleCredits,
FluffyRating,
PurpleRating,
MethodEnum,
Response,
CreationToolsLegacyElement,
ToolsTools,
ToolsToolsLegacy,
FluffyTools,
ComponentClass,
ComponentObject
} from '../types/cyclonedx';

const cvssMethods = ["CVSSv2", "CVSSv3", "CVSSv31", "CVSSv4"] as const;
type CVSSMethodEnum = Extract<MethodEnum, typeof cvssMethods[number]>

type IntermediaryComponent = Omit<OptionalComponentProperties, 'components'> & {
type IntermediaryComponent = Omit<ComponentClass | ComponentObject, 'components'> & {
components?: IntermediaryComponent[];
affectingVulnerabilities?: string[];
name: string;
'bom-ref'?: string;
isDummy?: boolean;
};

type IntermediaryVulnerability = Vulnerability & {
type IntermediaryVulnerability = (CycloneDXBillOfMaterialsStandardVulnerability | CycloneDXSoftwareBillOfMaterialsStandardVulnerability) & {
affectedComponents?: number[];
};

type DataStorage = {
components: IntermediaryComponent[];
vulnerabilities: IntermediaryVulnerability[];
raw: OptionalBomProperties;
raw: CycloneDXSoftwareBillOfMaterialSpecification | CycloneDXSoftwareBillOfMaterialsStandard;
};

const CWE_NIST_MAPPING = new CweNistMapping();
Expand All @@ -55,12 +55,12 @@ const IMPACT_MAPPING: Map<string, number> = new Map([
]);

// Convert object type to string[] and prepend `CWE` if used directly for tag display
function formatCWETags(input: CweRepository, addPrefix = true): string[] {
return [...input].map((cwe) => (addPrefix ? `CWE-${cwe}` : `${cwe}`));
function formatCWETags(input: CycloneDXBillOfMaterialsStandardVulnerability['cwes'] | CycloneDXSoftwareBillOfMaterialsStandardVulnerability['cwes'], addPrefix = true): string[] {
return input && Array.isArray(input) ? input.map((cwe) => (addPrefix ? `CWE-${cwe}` : `${cwe}`)) : [];
}

// Convert gathered CWEs to corresponding NIST 800-53s
function getNISTTags(input: CweRepository): string[] {
function getNISTTags(input: CycloneDXBillOfMaterialsStandardVulnerability['cwes'] | CycloneDXSoftwareBillOfMaterialsStandardVulnerability['cwes']): string[] {
return CWE_NIST_MAPPING.nistFilter(
formatCWETags(input, false),
DEFAULT_NIST_TAG
Expand All @@ -69,16 +69,16 @@ function getNISTTags(input: CweRepository): string[] {

// A single SBOM vulnerability can contain multiple security ratings
// Find the max of any existing ratings and then pass to `impact`
function maxImpact(ratings: RatingRepository): number {
return [...ratings]
function maxImpact(ratings: FluffyRating[] | PurpleRating[]): number {
return ratings
.map((rating) =>
rating.score && _.get(rating, 'method') === 'CVSSv31'
rating.score && rating.method && cvssMethods.includes(rating.method as CVSSMethodEnum) // cast required since .includes expects the parameter to be a subtype
? // Prefer to use CVSS-based `score` field when possible
rating.score / 10
: // Else interpret it from `severity` field
(IMPACT_MAPPING.get(
(rating.severity as Severity).toLowerCase()
) as number)
: // Else interpret it from `severity` field, defaulting to medium/0.5
IMPACT_MAPPING.get(
rating.severity?.toLowerCase() ?? ''
) ?? 0.5
)
.reduce(
(maxValue, newValue) =>
Expand Down Expand Up @@ -151,9 +151,7 @@ export class CycloneDXSBOMResults {
// Flatten any arbitrarily nested components list
flattenComponents(data: DataStorage) {
// Pull components from raw data
data.components = [
...(_.cloneDeep(data.raw.components) as ComponentRepository)
] as unknown as IntermediaryComponent[];
data.components = _.cloneDeep(data.raw.components) as IntermediaryComponent[];

// Look through every component at the top level of the list
for (const component of data.components) {
Expand Down Expand Up @@ -198,9 +196,7 @@ export class CycloneDXSBOMResults {
*/
generateIntermediary(data: DataStorage) {
// Pull vulnerabilities from raw data
data.vulnerabilities = [
...(_.cloneDeep(data.raw.vulnerabilities) as VulnerabilityRepository)
] as unknown as IntermediaryVulnerability[];
data.vulnerabilities = _.cloneDeep(data.raw.vulnerabilities) as IntermediaryVulnerability[];

for (const vulnerability of data.vulnerabilities) {
vulnerability.affectedComponents = [];
Expand All @@ -209,9 +205,7 @@ export class CycloneDXSBOMResults {
...Array.from(data.components.entries())
// Find every component that is affected via listed bom-refs
.filter(([_index, component]) =>
[...vulnerability.affects]
.map((id) => id.ref.toString())
.includes(component['bom-ref'] as string)
vulnerability.affects?.map((id) => id.ref.toString()).includes(component['bom-ref'] as string)
)
// Add the index of that affected component to the corresponding vulnerability object
.map(([index, _component]) => index)
Expand All @@ -234,17 +228,18 @@ export class CycloneDXSBOMResults {
formatVEX(data: DataStorage) {
// Pull vulnerabilities from raw data
data.vulnerabilities = [
...(_.cloneDeep(data.raw.vulnerabilities) as VulnerabilityRepository)
...(_.cloneDeep(data.raw.vulnerabilities) as CycloneDXBillOfMaterialsStandardVulnerability[] | CycloneDXSoftwareBillOfMaterialsStandardVulnerability[])
] as unknown as IntermediaryVulnerability[];

for (const vulnerability of data.vulnerabilities) {
vulnerability.affectedComponents = [...vulnerability.affects].map(
vulnerability.affectedComponents = vulnerability.affects?.map(
(id) => {
// Build a dummy component for each bom-ref identified as being affected by the vulnerability
const dummy: IntermediaryComponent = {
name: `${id.ref}`,
'bom-ref': `${id.ref}`,
isDummy: true
isDummy: true,
type: "application" // a type must be provided, and "application" is the default classification
};
// Add that component to the corresponding vulnerability object
data.components.push(dummy);
Expand Down Expand Up @@ -285,14 +280,14 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
{
name: {
path: 'raw.metadata.component',
transformer: (input: Component): string =>
transformer: (input: ComponentClass | ComponentObject): string =>
_.has(input, 'bom-ref')
? `CycloneDX BOM Report: ${input.type}/${input['bom-ref']}`
: 'CycloneDX BOM Report'
},
title: {
path: 'raw.metadata.component',
transformer: (input: Component): string => {
transformer: (input: ComponentClass | ComponentObject): string => {
if (input.name) {
const group = input.group ? `${input.group}/` : '';
return `${group}${input.name} CycloneDX BOM Report`;
Expand All @@ -307,7 +302,7 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
},
maintainer: {
path: 'raw.metadata.component',
transformer: (input: Component): string | undefined => {
transformer: (input: ComponentClass | ComponentObject): string | undefined => {
// Find organization of authors if possible
const manufacturer = _.has(input, 'manufacturer')
? ` (${(input.manufacturer as Record<string, unknown>).name})`
Expand Down Expand Up @@ -336,20 +331,13 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
},
license: {
path: 'raw.metadata.component',
transformer: (input: Component): string | undefined => {
if (input.licenses) {
transformer: (input: ComponentClass | ComponentObject): string | undefined => {
if(!input.licenses) {
return undefined;
}
// Certain license reports only provide the license name in the `name` field
// Check there first and then default to `id`
return [...input.licenses]
.map((license) =>
_.has(license, 'license.name')
? _.get(license, 'license.name')
: _.get(license, 'license.id')
)
.join(', ');
}
// If there are no found licenses, remove field
return undefined;
return input.licenses?.map((license) => license?.license?.name ? license.license.name : license?.license?.id).filter(identifier => identifier).join(', ');
}
},
supports: [],
Expand All @@ -367,7 +355,7 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
},
cci: {
path: 'cwes',
transformer: (input: CweRepository): string[] =>
transformer: (input: CycloneDXBillOfMaterialsStandardVulnerability['cwes'] | CycloneDXSoftwareBillOfMaterialsStandardVulnerability['cwes']): string[] =>
getCCIsForNISTTags(getNISTTags(input))
},
cwe: {path: 'cwes', transformer: formatCWETags},
Expand All @@ -377,12 +365,12 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
},
ratings: {
path: 'ratings',
transformer: (input: RatingRepository): string | undefined =>
transformer: (input: FluffyRating[] | PurpleRating[]): string | undefined =>
input
? [...input]
.map((rating) => {
const ratingSource = (rating.source as Source).name
? `${(rating.source as Source).name} - `
const ratingSource = rating.source?.name
? `${rating.source?.name} - `
: 'Unidentified Source - ';
return `${ratingSource}${rating.severity}`;
})
Expand All @@ -408,17 +396,22 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
},
credits: {
path: 'credits',
transformer: (input: Credits): string | undefined =>
transformer: (input: FluffyCredits | PurpleCredits): string | undefined =>
input
? `${[...input.individuals].map((individual) => individual.name).join(', ')}`
? `${input.individuals?.map((individual) => individual.name).filter(name => name).join(', ')}`
: undefined
},
tools: {
path: 'tools',
transformer: (input: ToolRepository): string | undefined =>
input
? [...input].map((tool) => tool.name).join(', ')
: undefined
transformer: (input: CreationToolsLegacyElement[] | ToolsToolsLegacy[] | ToolsTools | FluffyTools): string | undefined => {
if(!input) {
return undefined;
}
if(Array.isArray(input)) {
return input.map((tool) => tool.name).filter(name => name).join(', ');
}
return [...(input.components?.map(component => component.name) ?? []), ...(input.services?.map(component => component.name) ?? [])].join(', ');
}
},
// Workflow items will not affect `impact`
'analysis.state': {
Expand All @@ -432,10 +425,10 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
'analysis.response': {
path: 'analysis.response',
transformer: (
input: AnalysisResponseRepository
input: Response[]
): string | undefined =>
input && [...input].length > 0
? [...input].join(', ')
input && input.length > 0
? input.join(', ')
: undefined
},
'analysis.detail': {
Expand All @@ -452,7 +445,7 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
}
},
descriptions: {
transformer: (input: Vulnerability) => {
transformer: (input: CycloneDXBillOfMaterialsStandardVulnerability | CycloneDXSoftwareBillOfMaterialsStandardVulnerability) => {
const recommendation = input.recommendation
? `Recommendation: ${input.recommendation}`
: '';
Expand Down Expand Up @@ -496,12 +489,12 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
source_location: {},
title: {
// Give description as title if possible
transformer: (input: Vulnerability): string =>
transformer: (input: CycloneDXBillOfMaterialsStandardVulnerability | CycloneDXSoftwareBillOfMaterialsStandardVulnerability): string =>
input.description ? `${input.description}` : `${input.id}`
},
id: {path: 'id'},
desc: {
transformer: (input: Vulnerability): string | undefined => {
transformer: (input: CycloneDXBillOfMaterialsStandardVulnerability | CycloneDXSoftwareBillOfMaterialsStandardVulnerability): string | undefined => {
const description = input.description
? `Description: ${input.description}`
: '';
Expand All @@ -510,8 +503,8 @@ export class CycloneDXSBOMMapper extends BaseConverter<DataStorage> {
}
},
impact: {
transformer: (input: Vulnerability): number =>
maxImpact(input.ratings)
transformer: (input: CycloneDXBillOfMaterialsStandardVulnerability | CycloneDXSoftwareBillOfMaterialsStandardVulnerability): number =>
maxImpact(input.ratings ?? [])
},
code: {
transformer: (vulnerability: Record<string, unknown>): string =>
Expand Down
Loading

0 comments on commit d3be8d5

Please sign in to comment.