diff --git a/CHANGELOG.md b/CHANGELOG.md index b4f3c2a..505b292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Fixed +- `parse()` now strips a leading UTF-8 byte order mark (BOM) before `JSON.parse`, so BOM-prefixed SBOMs (common from Windows tooling and some generators) no longer crash with a cryptic "Unexpected token" error + ### Added - Real devDependencies: `typescript`, `vitest`, `@vitest/coverage-v8`, `typescript-eslint`, `@types/node` - `src/types.ts` — Full domain model: `SBOM`, `Component`, `CVEEntry`, `ChangeReport`, `VersionChange`, `SBOMFormat`, `ReportFormat` diff --git a/src/__tests__/parser.test.ts b/src/__tests__/parser.test.ts index e0378fb..ac7498e 100644 --- a/src/__tests__/parser.test.ts +++ b/src/__tests__/parser.test.ts @@ -126,4 +126,21 @@ describe('parse (JSON string input)', () => { const sbom = parse(JSON.stringify(cyclonedxFixture)); expect(sbom.components).toHaveLength(2); }); + + it('strips a leading UTF-8 BOM before parsing', () => { + // Some SBOM generators / Windows tooling emit BOM-prefixed JSON, which is + // valid on disk but makes a naive JSON.parse throw "Unexpected token". + const withBom = '' + JSON.stringify(cyclonedxFixture); + const sbom = parse(withBom); + expect(sbom.format).toBe('cyclonedx'); + expect(sbom.components).toHaveLength(2); + }); + + it('only strips a BOM at the very start, not elsewhere', () => { + // A BOM appearing inside a string value must be preserved verbatim. + const sbom = parse( + JSON.stringify({ bomFormat: 'CycloneDX', components: [{ name: 'ab' }] }), + ); + expect(sbom.components[0].name).toBe('ab'); + }); }); diff --git a/src/parser.ts b/src/parser.ts index 6232fc2..9b0f75b 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -84,7 +84,11 @@ export function parseSPDX(obj: Record): SBOM { * Parse a JSON string or object into an SBOM, auto-detecting format. */ export function parse(input: string | Record): SBOM { - const obj: Record = typeof input === 'string' ? JSON.parse(input) : input; + // Strip a leading UTF-8 byte order mark (U+FEFF) before parsing. Several SBOM + // generators and Windows text tooling emit BOM-prefixed JSON, which is valid + // on disk but makes JSON.parse throw a cryptic "Unexpected token" error. + const obj: Record = + typeof input === 'string' ? JSON.parse(input.replace(/^\uFEFF/, '')) : input; const format = detectFormat(obj); switch (format) {