Files
attest/src/sbom.ts
T
Brian DeHamer 7d7ff4475a ESM Conversion (#347)
* initial esm conversion

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* esm'ify jest tests

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* lint issues

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* debug mock

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* glob updated

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* async all file functions

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* update @actions/github

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* update @actions/attest

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* rebuild package-lock.json

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* use experimental flag for jest in ci

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* remove stray istanbul ignore

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* Optimize getSubjectFromPath to avoid concurrent stat calls

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* Fix boundary condition for MAX_SUBJECT_COUNT check

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* Improve error message clarity for subject count limit

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* Update test to match new error message format

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* rebuild dist

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* Fix parseSBOMFromPath to check file size before reading

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* Build package with updated changes

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

---------

Signed-off-by: Brian DeHamer <bdehamer@github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>
2026-02-18 08:52:30 -08:00

97 lines
2.3 KiB
TypeScript

import fs from 'fs/promises'
import type { Predicate } from '@actions/attest'
export type SBOM = {
type: 'spdx' | 'cyclonedx'
object: object
}
// SBOMs cannot exceed 16MB.
const MAX_SBOM_SIZE_BYTES = 16 * 1024 * 1024
export const parseSBOMFromPath = async (filePath: string): Promise<SBOM> => {
let stats
try {
stats = await fs.stat(filePath)
} catch (error) {
const err = error as NodeJS.ErrnoException
if (err.code === 'ENOENT') {
throw new Error('SBOM file not found')
}
throw error
}
if (stats.size > MAX_SBOM_SIZE_BYTES) {
throw new Error(
`SBOM file exceeds maximum allowed size: ${MAX_SBOM_SIZE_BYTES} bytes`
)
}
const fileContent = await fs.readFile(filePath, 'utf8')
const sbom = JSON.parse(fileContent) as object
if (checkIsSPDX(sbom)) {
return { type: 'spdx', object: sbom }
} else if (checkIsCycloneDX(sbom)) {
return { type: 'cyclonedx', object: sbom }
}
throw new Error(
'Unsupported SBOM format. Must be valid SPDX or CycloneDX JSON.'
)
}
const checkIsSPDX = (sbomObject: {
spdxVersion?: string
SPDXID?: string
}): boolean => {
return !!(sbomObject?.spdxVersion && sbomObject?.SPDXID)
}
const checkIsCycloneDX = (sbomObject: {
bomFormat?: string
serialNumber?: string
specVersion?: string
}): boolean => {
return !!(
sbomObject?.bomFormat &&
sbomObject?.serialNumber &&
sbomObject?.specVersion
)
}
export const generateSBOMPredicate = (sbom: SBOM): Predicate => {
switch (sbom.type) {
case 'spdx':
return generateSPDXPredicate(sbom.object)
case 'cyclonedx':
return generateCycloneDXPredicate(sbom.object)
default:
throw new Error('Unsupported SBOM format')
}
}
// ref: https://github.com/in-toto/attestation/blob/main/spec/predicates/spdx.md
const generateSPDXPredicate = (sbom: object): Predicate => {
const spdxVersion = (sbom as { spdxVersion?: string })?.['spdxVersion']
if (!spdxVersion) {
throw new Error('Cannot find spdxVersion in the SBOM')
}
const version = spdxVersion.split('-')[1]
return {
type: `https://spdx.dev/Document/v${version}`,
params: sbom
}
}
// ref: https://github.com/in-toto/attestation/blob/main/spec/predicates/cyclonedx.md
const generateCycloneDXPredicate = (sbom: object): Predicate => {
return {
type: 'https://cyclonedx.org/bom',
params: sbom
}
}