Skip to content

Commit b9bc927

Browse files
authored
remove insufficitient license gathering (#32)
see #22 Signed-off-by: Jan Kowalleck <[email protected]>
1 parent ec9a786 commit b9bc927

File tree

10 files changed

+2390
-148
lines changed

10 files changed

+2390
-148
lines changed

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ $ yarn CycloneDX make-sbom
6868
(default: true if the NODE_ENV environment variable is set to "production", otherwise false)
6969
--mc-type #0 Type of the main component.
7070
(choices: "application", "framework", "library", "container", "platform", "device-driver", default: "application")
71-
--licenses Include license information for components in generated SBOM.
72-
License information will always be absent for components that don't specify licenses unambiguously.
7371
--reproducible Whether to go the extra mile and make the output reproducible.
7472
This might result in loss of time- and random-based values.
7573

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
"@yarnpkg/fslib": "^3.0.2",
1616
"clipanion": "^4.0.0-rc.3",
1717
"packageurl-js": "^1.2.1",
18-
"spdx-license-ids": "^3.0.17",
1918
"xmlbuilder2": "^3.1.1"
2019
},
2120
"devDependencies": {

sources/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,6 @@ class SBOMCommand extends BaseCommand {
6969
description: 'Type of the main component.\n(choices: "application", "framework", "library", "container", "platform", "device-driver", default: "application")'
7070
})
7171

72-
licenses = Option.Boolean('--licenses', false, {
73-
description: 'Include license information for components in generated SBOM.\nLicense information will always be absent for components that don\'t specify licenses unambiguously.'
74-
})
75-
7672
reproducible = Option.Boolean('--reproducible', false, {
7773
description: 'Whether to go the extra mile and make the output reproducible.\nThis might result in loss of time- and random-based values.'
7874
})
@@ -101,7 +97,6 @@ class SBOMCommand extends BaseCommand {
10197
outputFormat: parseOutputFormat(this.outputFormat),
10298
outputFile: parseOutputFile(workspace.cwd, this.outputFile),
10399
componentType: parseComponenttype(this.componentType),
104-
licenses: this.licenses,
105100
reproducible: this.reproducible
106101
})
107102
}

sources/sbom.ts

Lines changed: 2 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,12 @@ import {
2222
type Configuration,
2323
type Locator,
2424
type LocatorHash,
25-
type Manifest,
26-
type Package,
2725
type Project,
2826
structUtils,
2927
type Workspace
3028
} from '@yarnpkg/core'
3129
import { type PortablePath, xfs } from '@yarnpkg/fslib'
3230
import { PackageURL } from 'packageurl-js'
33-
import * as ids from 'spdx-license-ids/index.json'
3431

3532
import {
3633
type BuildtimeDependencies,
@@ -40,8 +37,7 @@ import {
4037

4138
const licenseFactory = new CDX.Factories.LicenseFactory()
4239
const npmPurlFactory = new CDX.Factories.PackageUrlFactory('npm')
43-
const externalReferenceFactory =
44-
new CDX.Factories.FromNodePackageJson.ExternalReferenceFactory()
40+
const externalReferenceFactory = new CDX.Factories.FromNodePackageJson.ExternalReferenceFactory()
4541
const componentBuilder = new CDX.Builders.FromNodePackageJson.ComponentBuilder(
4642
externalReferenceFactory,
4743
licenseFactory
@@ -58,8 +54,6 @@ export interface OutputOptions {
5854
/** Output file name. */
5955
outputFile: PortablePath | typeof stdOutOutput
6056
componentType: CDX.Enums.ComponentType
61-
/** If component licenses shall be included. */
62-
licenses: boolean
6357
reproducible: boolean
6458
}
6559

@@ -83,15 +77,13 @@ export async function generateSBOM (
8377
const allDependencies = await traverseWorkspace(
8478
project,
8579
workspace,
86-
config,
87-
outputOptions.licenses
80+
config
8881
)
8982
const componentModels = new Map<LocatorHash, CDX.Models.Component>()
9083
// Build models without their dependencies.
9184
for (const pkgInfo of allDependencies) {
9285
const component = packageInfoToCycloneComponent(
9386
pkgInfo,
94-
outputOptions.licenses,
9587
outputOptions.reproducible
9688
)
9789
componentModels.set(pkgInfo.package.locatorHash, component)
@@ -226,7 +218,6 @@ function getAuthorName (manifestRawAuthor: unknown): string | undefined {
226218
*/
227219
function packageInfoToCycloneComponent (
228220
pkgInfo: PackageInfo,
229-
licenses: boolean,
230221
reproducible: OutputOptions['reproducible']
231222
): CDX.Models.Component {
232223
const manifest = pkgInfo.manifest
@@ -248,12 +239,6 @@ function packageInfoToCycloneComponent (
248239
// @FIXME dont use any `locatorhash` for this purpose - but maybe something that is actually universally reproducible?
249240
// -- like `package-name@version` - which is a discriminated unique value for yarn universe
250241
component.bomRef.value = pkgInfo.package.locatorHash
251-
if (licenses) {
252-
addLicenseInfo(manifest, pkgInfo, component)
253-
} else {
254-
// @FIXME why should this be needed anyway?
255-
component.licenses.clear()
256-
}
257242

258243
const devirtualizedLocator = structUtils.ensureDevirtualizedLocator(
259244
pkgInfo.package
@@ -292,84 +277,3 @@ function gitHubPackagePurl (
292277
}
293278
return undefined
294279
}
295-
296-
/**
297-
* Adds license data to component if available.
298-
* @FIXME remove this license attachment as it isi just wrong
299-
*/
300-
function addLicenseInfo (
301-
manifest: Manifest,
302-
pkgInfo: PackageInfo,
303-
component: CDX.Models.Component
304-
): void {
305-
if (component.licenses.size === 1) {
306-
const license = component.licenses.values().next().value
307-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
308-
if (pkgInfo.licenseFileContent &&
309-
(license instanceof CDX.Models.NamedLicense ||
310-
license instanceof CDX.Models.SpdxLicense)
311-
) {
312-
license.text = new CDX.Models.Attachment(pkgInfo.licenseFileContent)
313-
}
314-
} else if (component.licenses.size === 0) {
315-
attemptFallbackLicense(manifest, pkgInfo.package, component)
316-
}
317-
}
318-
319-
/**
320-
* Attempts to parse bogus but unambigous licenses and augments the component model.
321-
* @FIXME remove this license guessing as iti is incomplete and wrong
322-
*/
323-
function attemptFallbackLicense (
324-
manifest: Manifest,
325-
pkg: Package,
326-
component: CDX.Models.Component
327-
): void {
328-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
329-
if (manifest.raw.license) {
330-
process.stderr.write(
331-
`Package ${structUtils.stringifyLocator(
332-
pkg
333-
)} has invalid "license" property. See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#license\n`
334-
)
335-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
336-
if (ids.includes(manifest.raw.license?.type)) {
337-
process.stderr.write(
338-
`Adding ${
339-
manifest.raw.license?.type
340-
} as fallback for ${structUtils.stringifyLocator(pkg)}\n`
341-
)
342-
component.licenses.add(
343-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
344-
licenseFactory.makeFromString(manifest.raw.license?.type)
345-
)
346-
}
347-
} else
348-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
349-
if (manifest.raw.licenses) {
350-
process.stderr.write(
351-
`Package ${structUtils.stringifyLocator(
352-
pkg
353-
)} has invalid "licenses" property. See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#license\n`
354-
)
355-
if (
356-
Array.isArray(manifest.raw.licenses) &&
357-
manifest.raw.licenses.every((outdatedLicense) =>
358-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
359-
ids.includes(outdatedLicense.type)
360-
)
361-
) {
362-
for (const outdatedLicense of manifest.raw.licenses) {
363-
process.stderr.write(
364-
`Adding ${
365-
outdatedLicense.type
366-
} as fallback for ${structUtils.stringifyLocator(pkg)}\n`
367-
)
368-
component.licenses.add(
369-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
370-
licenseFactory.makeFromString(outdatedLicense.type)
371-
)
372-
}
373-
}
374-
}
375-
}

sources/traverseUtils.ts

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
ThrowReport,
2828
type Workspace
2929
} from '@yarnpkg/core'
30-
import { type FakeFS, type PortablePath, ppath } from '@yarnpkg/fslib'
3130

3231
/**
3332
* Output structure of "yarn info --json"
@@ -44,7 +43,6 @@ export interface PackageInfo {
4443
package: Package
4544
manifest: Manifest
4645
dependencies: Set<LocatorHash>
47-
licenseFileContent?: string
4846
}
4947

5048
// Modelled after traverseWorkspace in https://github.com/yarnpkg/berry/blob/master/packages/plugin-essentials/sources/commands/info.ts#L88
@@ -55,8 +53,7 @@ export interface PackageInfo {
5553
export async function traverseWorkspace (
5654
project: Project,
5755
workspace: Workspace,
58-
config: Configuration,
59-
extractLicenses: boolean
56+
config: Configuration
6057
): Promise<Set<PackageInfo>> {
6158
// Instantiate fetcher to be able to retrieve package manifest. Conversion to CycloneDX model needs this later.
6259
const cache = await Cache.find(config)
@@ -96,38 +93,28 @@ export async function traverseWorkspace (
9693

9794
const fetchResult = await fetcher.fetch(pkg, fetcherOptions)
9895
let manifest: Manifest
99-
let licenseFileContent: string | undefined
10096
try {
10197
manifest = await Manifest.find(fetchResult.prefixPath, {
10298
baseFs: fetchResult.packageFs
10399
})
104-
if (extractLicenses) {
105-
licenseFileContent = readLicenseFile(
106-
fetchResult.prefixPath,
107-
fetchResult.packageFs
108-
)
109-
}
110100
} finally {
111101
fetchResult.releaseFs?.()
112102
}
113103
const packageInfo: PackageInfo = {
114104
package: pkg,
115105
manifest,
116-
dependencies: new Set(),
117-
licenseFileContent
106+
dependencies: new Set()
118107
}
119108
seen.add(hash)
120109
allPackages.add(packageInfo)
121110

122-
// pkg.dependencies has dependencies+peerDependencies for transitve dependencies but not their devDependencies.
111+
// pkg.dependencies has dependencies+peerDependencies for transitive dependencies but not their devDependencies.
123112
for (const dependency of pkg.dependencies.values()) {
124113
const resolution = project.storedResolutions.get(
125114
dependency.descriptorHash
126115
)
127116
if (typeof resolution === 'undefined') {
128-
throw new Error(
129-
'All package descriptor hashes should be resolvable for consistent lockfiles.'
130-
)
117+
throw new Error('All package descriptor hashes should be resolvable for consistent lockfiles.')
131118
}
132119
packageInfo.dependencies.add(resolution)
133120

@@ -139,26 +126,3 @@ export async function traverseWorkspace (
139126

140127
return allPackages
141128
}
142-
143-
const fileNameOptions = ['license', 'licence', 'unlicense', 'unlicence']
144-
const fileNameOptionsStart = fileNameOptions.map((name) => name + '.')
145-
146-
function readLicenseFile (
147-
packageRoot: PortablePath,
148-
packageFs: FakeFS<PortablePath>
149-
): string | undefined {
150-
const files = packageFs.readdirSync(packageRoot).filter((f) => {
151-
const lowerFileName = f.toLocaleLowerCase()
152-
return (
153-
fileNameOptions.includes(lowerFileName) ||
154-
fileNameOptionsStart.some((option) => lowerFileName.startsWith(option))
155-
)
156-
})
157-
for (const licenseFile of files) {
158-
const path = ppath.join(packageRoot, licenseFile)
159-
if (packageFs.existsSync(path)) {
160-
return packageFs.readFileSync(path).toString()
161-
}
162-
}
163-
return undefined
164-
}

0 commit comments

Comments
 (0)