@mitre/hdf-utilities 3.6.0-rc.3 → 3.6.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,11 +21,7 @@ The two languages do not export the same modules, and that is deliberate. This
21
21
  package holds cross-language **policy** — decisions both implementations must
22
22
  agree on — while each language uses its own serializer.
23
23
 
24
- `hash` and `json` have no Go counterpart because Go's standard library already
25
- provides them: `crypto/sha256`, `encoding/json`. Wrapping those in Go would add a
26
- layer with no decision in it. The TypeScript side needs wrappers because
27
- JavaScript ships no equivalent, and the wrapper is where safe defaults get
28
- centralised.
24
+ The plumbing in `hash` and `json` has no Go counterpart because Go's standard library already provides it: `crypto/sha256`, `encoding/json`. Wrapping `parseJSON`, `stringifyJSON` or `generateHash` in Go would add a layer with no decision in it. The TypeScript side needs those wrappers because JavaScript ships no equivalent, and the wrapper is where safe defaults get centralised. The policy in those modules does exist in both: Go's `CanonicalJSON`, `ChecksumJSON` and `SHA256Hex` are the twins of TypeScript's `canonicalJson` and `canonicalChecksum`. The canonical byte form a checksum is taken over (sorted keys, `<`, `>` and `&` escaped, null-valued keys dropped) is a decision both implementations must agree on, so it is pinned against shared vectors rather than left to each serializer.
29
25
 
30
26
  `csv` exists in both, but the two surfaces are deliberately different sizes: the
31
27
  TypeScript module parses and builds CSV, while the Go one carries only the
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/csv/index.ts"],"mappings":";;;;;UAkBiB;EACf;EACA;EACA,mBAAmB,gBAAgB;EACnC;EACA;EACA;;;;;UAMe;EACf;EACA;EACA;EACA;;;;;;;;;iBAwEc,SAAS,IAAI,yBAC3B,aACA,UAAU,QAAQ,mBACjB;;;;;;;;iBAoDa,cACd,aACA,UAAU,QAAQ;;;;;;;;;;;iBAiCJ,SAAS,IAAI,yBAC3B,MAAM,KACN,UAAU,QAAQ;EAAqB;;;;;;;;;;;;iBA0GzB,cACd,kBACA,UAAU,QAAQ;EAAqB;;;;;;;iBA2BzB,WAAW;;;;;;;;;;iBA0BX,iBAAiB;;;;;;;iBAejB,iBAAiB;;;;;;;iBAUjB,kBAAkB,UAAU,yBAC1C,KAAK,IACJ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/csv/index.ts"],"mappings":";;;;;UAkBiB;EACf;EACA;EACA,mBAAmB,gBAAgB;EACnC;EACA;EACA;;;;;UAMe;EACf;EACA;EACA;EACA;;;;;;;;;iBAwEc,SAAS,IAAI,yBAC3B,aACA,UAAU,QAAQ,mBACjB;;;;;;;;iBAoDa,cACd,aACA,UAAU,QAAQ;;;;;;;;;;;iBAiCJ,SAAS,IAAI,yBAC3B,MAAM,KACN,UAAU,QAAQ;EAAqB;;;;;;;;;;;;iBA6GzB,cACd,kBACA,UAAU,QAAQ;EAAqB;;;;;;;iBA4BzB,WAAW;;;;;;;;;;iBA0BX,iBAAiB;;;;;;;iBAejB,iBAAiB;;;;;;;iBAUjB,kBAAkB,UAAU,yBAC1C,KAAK,IACJ"}
package/dist/csv/index.js CHANGED
@@ -153,7 +153,12 @@ function buildCsv(data, options) {
153
153
  * produced, so it cannot fail on unbalanced quotes.
154
154
  */
155
155
  function quoteLeadingWhitespace(csv, dsv, delimiter) {
156
- return dsv.parseRows(csv).map((row) => row.map((field) => requote(field, dsv)).join(delimiter)).join("\n");
156
+ return formatRowsGoCompatible(dsv.parseRows(csv), dsv, delimiter);
157
+ }
158
+ /** The one writer both public builders go through, so a field cannot come out
159
+ * quoted from one and bare from the other. */
160
+ function formatRowsGoCompatible(rows, dsv, delimiter) {
161
+ return rows.map((row) => row.map((field) => requote(field, dsv)).join(delimiter)).join("\n");
157
162
  }
158
163
  /**
159
164
  * Go's encoding/csv fieldNeedsQuotes, minus the conditions d3-dsv already
@@ -204,7 +209,9 @@ function buildCsvArray(data, options) {
204
209
  if (data.length === 0) return "";
205
210
  let processedData = data;
206
211
  if (sanitize) processedData = data.map((row) => sanitizeCsvArray(row));
207
- let result = terminateFinalRecord(dsvFormat(opts.delimiter ?? ",").formatRows(processedData));
212
+ const delimiter = opts.delimiter ?? ",";
213
+ const dsv = dsvFormat(delimiter);
214
+ let result = terminateFinalRecord(formatRowsGoCompatible(processedData, dsv, delimiter));
208
215
  if (opts.newline && opts.newline !== "\n") result = result.replace(/\n/g, opts.newline);
209
216
  return result;
210
217
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/csv/index.ts"],"sourcesContent":["/**\n * CSV parsing and generation utilities using d3-dsv\n *\n * SECURITY WARNING: CSV files opened in Excel/LibreOffice may execute formulas.\n * Use sanitization options when exporting data that may contain user-controlled content.\n */\n\nimport { dsvFormat } from 'd3-dsv';\nimport type { DSV } from 'd3-dsv';\n\nexport {\n extractColumn as extractCsvColumn,\n findRows as findCsvRows,\n} from '../object/index.js';\n\n/**\n * Options for parsing CSV\n */\nexport interface CsvParseOptions {\n header?: boolean;\n skipEmptyLines?: boolean;\n transformHeader?: (header: string, index: number) => string;\n dynamicTyping?: boolean;\n delimiter?: string;\n maxSize?: number;\n}\n\n/**\n * Options for building CSV\n */\nexport interface CsvBuildOptions {\n header?: boolean;\n newline?: string;\n skipEmptyLines?: boolean;\n delimiter?: string;\n}\n\n/**\n * Default options for parsing CSV with headers\n */\nconst DEFAULT_PARSE_OPTIONS: CsvParseOptions = {\n header: true,\n skipEmptyLines: true,\n transformHeader: undefined,\n dynamicTyping: false,\n};\n\n/**\n * Default options for parsing CSV without headers\n */\nconst DEFAULT_PARSE_ARRAY_OPTIONS: CsvParseOptions = {\n header: false,\n skipEmptyLines: true,\n dynamicTyping: false,\n};\n\n/**\n * Default options for building CSV\n */\nconst DEFAULT_BUILD_OPTIONS: CsvBuildOptions = {\n header: true,\n newline: '\\n',\n skipEmptyLines: true,\n};\n\n/**\n * Validate CSV quoting — throws if unclosed quotes are found\n */\nfunction validateQuoting(csv: string): void {\n let inQuotes = false;\n let row = 0;\n for (let i = 0; i < csv.length; i++) {\n if (csv[i] === '\"') {\n if (inQuotes && i + 1 < csv.length && csv[i + 1] === '\"') {\n i++; // skip escaped quote \"\"\n } else {\n inQuotes = !inQuotes;\n }\n } else if (csv[i] === '\\n' && !inQuotes) {\n row++;\n }\n }\n if (inQuotes) {\n throw new Error(`CSV parsing failed: Row ${row}: Unclosed quote`);\n }\n}\n\n/**\n * Convert string value to number or boolean when appropriate\n */\nfunction dynamicTypeConvert(value: string): unknown {\n if (value === '') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n const num = Number(value);\n if (!isNaN(num) && value.trim() !== '') return num;\n return value;\n}\n\n/**\n * Parse CSV string into array of objects with headers as keys\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of objects representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsv<T = Record<string, unknown>>(\n csv: string,\n options?: Partial<CsvParseOptions>\n): T[] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n const parsed = dsv.parse(trimmed);\n const { columns } = parsed;\n\n // Pre-compute transformed headers\n const headers = opts.transformHeader\n ? columns.map((col, i) => opts.transformHeader!(col, i))\n : columns;\n\n let rows: Record<string, unknown>[] = parsed.map((row) => {\n const obj: Record<string, unknown> = {};\n columns.forEach((col, i) => {\n let value: unknown = row[col] ?? '';\n if (opts.dynamicTyping && typeof value === 'string') {\n value = dynamicTypeConvert(value);\n }\n const key = headers[i];\n if (key !== undefined) {\n obj[key] = value;\n }\n });\n return obj;\n });\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) =>\n Object.values(row).some((v) => v !== '' && v !== null && v !== undefined)\n );\n }\n\n return rows as T[];\n}\n\n/**\n * Parse CSV string into array of arrays without treating first row as headers\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of arrays representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsvArray(\n csv: string,\n options?: Partial<CsvParseOptions>\n): string[][] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_ARRAY_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let rows = dsv.parseRows(trimmed);\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) => !(row.length === 1 && row[0] === ''));\n }\n\n return rows;\n}\n\n/**\n * Build CSV string from array of objects\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of objects to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsv<T = Record<string, unknown>>(\n data: T[],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) =>\n sanitizeCsvObject(row as Record<string, unknown>)\n ) as T[];\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result: string;\n\n if (opts.header === false) {\n const rows = processedData.map((row) =>\n Object.values(row as Record<string, unknown>).map(String)\n );\n result = dsv.formatRows(rows);\n } else {\n result = dsv.format(processedData as Record<string, unknown>[]);\n }\n\n result = quoteLeadingWhitespace(result, dsv, opts.delimiter ?? ',');\n result = terminateFinalRecord(result);\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Quote any field Go's encoding/csv would quote and d3-dsv would not.\n *\n * d3-dsv quotes only for the delimiter, a quote, or a newline. Go additionally\n * quotes when the field's FIRST rune is Unicode whitespace — so \" padded \"\n * round-trips through a parser that trims — and quotes the literal field `\\.`,\n * which some dialects treat as end-of-data. Trailing whitespace alone triggers\n * neither language. Without this a TS CSV differs from its Go twin on any such\n * value: the same class of split terminateFinalRecord exists to close.\n *\n * ONE predicate decides, and it is the same one Go uses. An earlier version\n * tested [ \\t] to skip the re-parse cheaply and \\s to decide, so a field led by\n * U+00A0 or U+2003 exited before the decision ever ran.\n *\n * Re-parses rather than intercepting the writer because d3-dsv exposes no\n * per-field hook on format(); the parse is over output this function just\n * produced, so it cannot fail on unbalanced quotes.\n */\nfunction quoteLeadingWhitespace(csv: string, dsv: DSV, delimiter: string): string {\n return dsv\n .parseRows(csv)\n .map((row) => row.map((field) => requote(field, dsv)).join(delimiter))\n .join('\\n');\n}\n\n/**\n * Go's encoding/csv fieldNeedsQuotes, minus the conditions d3-dsv already\n * covers: a leading Unicode space, or the literal `\\.`.\n *\n * The class is Go's unicode.IsSpace, spelled out rather than borrowed from\n * JavaScript's \\s, which is not the same set: \\s omits U+0085 (NEL) and adds\n * U+FEFF, which Unicode 4.0.1 removed from White_Space. Borrowing \\s left a\n * field led by U+0085 unquoted here but quoted in Go, and the reverse for\n * U+FEFF.\n */\nconst GO_LEADING_SPACE = /^[\\t\\n\\v\\f\\r \\u0085\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000]/;\n\nfunction goWouldQuote(field: string): boolean {\n return field === '\\\\.' || GO_LEADING_SPACE.test(field);\n}\n\n/** One field, formatted by d3-dsv, then quoted if Go would quote it and d3-dsv did not. */\nfunction requote(field: string, dsv: DSV): string {\n const formatted = dsv.formatValue(field);\n if (formatted.startsWith('\"') || !goWouldQuote(field)) {\n return formatted;\n }\n return `\"${field.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Terminate the final record with a newline. d3-dsv leaves it off, but Go's\n * encoding/csv (which the Go converters use) always writes it, so without this\n * every TS CSV would differ from its Go twin by exactly one trailing byte.\n * Applied before the newline substitution so a \\r\\n dialect gets it too.\n */\nfunction terminateFinalRecord(csv: string): string {\n return csv.endsWith('\\n') ? csv : `${csv}\\n`;\n}\n\n/**\n * Build CSV string from array of arrays\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of arrays to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsvArray(\n data: string[][],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, header: false, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) => sanitizeCsvArray(row));\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result = terminateFinalRecord(dsv.formatRows(processedData));\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Validate CSV format\n * @param csv - CSV string to validate\n * @returns True if valid CSV, false otherwise\n */\nexport function isValidCsv(csv: string): boolean {\n if (!csv || csv.trim().length === 0) return false;\n\n const trimmed = csv.trim();\n\n // CSV must have at least one delimiter (comma) to be considered CSV structure\n if (!trimmed.includes(',')) return false;\n\n try {\n validateQuoting(trimmed);\n } catch {\n return false;\n }\n\n return true;\n}\n\n/**\n * Sanitize a single CSV value to prevent formula injection\n *\n * Prefixes values starting with =, +, -, @, |, or % with a single quote\n * to prevent Excel/LibreOffice from interpreting them as formulas.\n *\n * @param value - Value to sanitize\n * @returns Sanitized value\n */\nexport function sanitizeCsvValue(value: unknown): string {\n const str = String(value);\n // Check if string starts with formula trigger characters\n if (/^[=+\\-@|%]/.test(str)) {\n return `'${str}`;\n }\n return str;\n}\n\n/**\n * Sanitize all values in an array to prevent formula injection\n *\n * @param values - Array of values to sanitize\n * @returns Array with sanitized values\n */\nexport function sanitizeCsvArray(values: unknown[]): string[] {\n return values.map(sanitizeCsvValue);\n}\n\n/**\n * Sanitize all values in an object to prevent formula injection\n *\n * @param obj - Object with values to sanitize\n * @returns New object with sanitized values\n */\nexport function sanitizeCsvObject<T extends Record<string, unknown>>(\n obj: T\n): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(obj)) {\n sanitized[key] = sanitizeCsvValue(value);\n }\n return sanitized;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/csv/index.ts"],"sourcesContent":["/**\n * CSV parsing and generation utilities using d3-dsv\n *\n * SECURITY WARNING: CSV files opened in Excel/LibreOffice may execute formulas.\n * Use sanitization options when exporting data that may contain user-controlled content.\n */\n\nimport { dsvFormat } from 'd3-dsv';\nimport type { DSV } from 'd3-dsv';\n\nexport {\n extractColumn as extractCsvColumn,\n findRows as findCsvRows,\n} from '../object/index.js';\n\n/**\n * Options for parsing CSV\n */\nexport interface CsvParseOptions {\n header?: boolean;\n skipEmptyLines?: boolean;\n transformHeader?: (header: string, index: number) => string;\n dynamicTyping?: boolean;\n delimiter?: string;\n maxSize?: number;\n}\n\n/**\n * Options for building CSV\n */\nexport interface CsvBuildOptions {\n header?: boolean;\n newline?: string;\n skipEmptyLines?: boolean;\n delimiter?: string;\n}\n\n/**\n * Default options for parsing CSV with headers\n */\nconst DEFAULT_PARSE_OPTIONS: CsvParseOptions = {\n header: true,\n skipEmptyLines: true,\n transformHeader: undefined,\n dynamicTyping: false,\n};\n\n/**\n * Default options for parsing CSV without headers\n */\nconst DEFAULT_PARSE_ARRAY_OPTIONS: CsvParseOptions = {\n header: false,\n skipEmptyLines: true,\n dynamicTyping: false,\n};\n\n/**\n * Default options for building CSV\n */\nconst DEFAULT_BUILD_OPTIONS: CsvBuildOptions = {\n header: true,\n newline: '\\n',\n skipEmptyLines: true,\n};\n\n/**\n * Validate CSV quoting — throws if unclosed quotes are found\n */\nfunction validateQuoting(csv: string): void {\n let inQuotes = false;\n let row = 0;\n for (let i = 0; i < csv.length; i++) {\n if (csv[i] === '\"') {\n if (inQuotes && i + 1 < csv.length && csv[i + 1] === '\"') {\n i++; // skip escaped quote \"\"\n } else {\n inQuotes = !inQuotes;\n }\n } else if (csv[i] === '\\n' && !inQuotes) {\n row++;\n }\n }\n if (inQuotes) {\n throw new Error(`CSV parsing failed: Row ${row}: Unclosed quote`);\n }\n}\n\n/**\n * Convert string value to number or boolean when appropriate\n */\nfunction dynamicTypeConvert(value: string): unknown {\n if (value === '') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n const num = Number(value);\n if (!isNaN(num) && value.trim() !== '') return num;\n return value;\n}\n\n/**\n * Parse CSV string into array of objects with headers as keys\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of objects representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsv<T = Record<string, unknown>>(\n csv: string,\n options?: Partial<CsvParseOptions>\n): T[] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n const parsed = dsv.parse(trimmed);\n const { columns } = parsed;\n\n // Pre-compute transformed headers\n const headers = opts.transformHeader\n ? columns.map((col, i) => opts.transformHeader!(col, i))\n : columns;\n\n let rows: Record<string, unknown>[] = parsed.map((row) => {\n const obj: Record<string, unknown> = {};\n columns.forEach((col, i) => {\n let value: unknown = row[col] ?? '';\n if (opts.dynamicTyping && typeof value === 'string') {\n value = dynamicTypeConvert(value);\n }\n const key = headers[i];\n if (key !== undefined) {\n obj[key] = value;\n }\n });\n return obj;\n });\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) =>\n Object.values(row).some((v) => v !== '' && v !== null && v !== undefined)\n );\n }\n\n return rows as T[];\n}\n\n/**\n * Parse CSV string into array of arrays without treating first row as headers\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of arrays representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsvArray(\n csv: string,\n options?: Partial<CsvParseOptions>\n): string[][] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_ARRAY_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let rows = dsv.parseRows(trimmed);\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) => !(row.length === 1 && row[0] === ''));\n }\n\n return rows;\n}\n\n/**\n * Build CSV string from array of objects\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of objects to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsv<T = Record<string, unknown>>(\n data: T[],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) =>\n sanitizeCsvObject(row as Record<string, unknown>)\n ) as T[];\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result: string;\n\n if (opts.header === false) {\n const rows = processedData.map((row) =>\n Object.values(row as Record<string, unknown>).map(String)\n );\n result = dsv.formatRows(rows);\n } else {\n result = dsv.format(processedData as Record<string, unknown>[]);\n }\n\n result = quoteLeadingWhitespace(result, dsv, opts.delimiter ?? ',');\n result = terminateFinalRecord(result);\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Quote any field Go's encoding/csv would quote and d3-dsv would not.\n *\n * d3-dsv quotes only for the delimiter, a quote, or a newline. Go additionally\n * quotes when the field's FIRST rune is Unicode whitespace — so \" padded \"\n * round-trips through a parser that trims — and quotes the literal field `\\.`,\n * which some dialects treat as end-of-data. Trailing whitespace alone triggers\n * neither language. Without this a TS CSV differs from its Go twin on any such\n * value: the same class of split terminateFinalRecord exists to close.\n *\n * ONE predicate decides, and it is the same one Go uses. An earlier version\n * tested [ \\t] to skip the re-parse cheaply and \\s to decide, so a field led by\n * U+00A0 or U+2003 exited before the decision ever ran.\n *\n * Re-parses rather than intercepting the writer because d3-dsv exposes no\n * per-field hook on format(); the parse is over output this function just\n * produced, so it cannot fail on unbalanced quotes.\n */\nfunction quoteLeadingWhitespace(csv: string, dsv: DSV, delimiter: string): string {\n return formatRowsGoCompatible(dsv.parseRows(csv), dsv, delimiter);\n}\n\n/** The one writer both public builders go through, so a field cannot come out\n * quoted from one and bare from the other. */\nfunction formatRowsGoCompatible(rows: string[][], dsv: DSV, delimiter: string): string {\n return rows.map((row) => row.map((field) => requote(field, dsv)).join(delimiter)).join('\\n');\n}\n\n/**\n * Go's encoding/csv fieldNeedsQuotes, minus the conditions d3-dsv already\n * covers: a leading Unicode space, or the literal `\\.`.\n *\n * The class is Go's unicode.IsSpace, spelled out rather than borrowed from\n * JavaScript's \\s, which is not the same set: \\s omits U+0085 (NEL) and adds\n * U+FEFF, which Unicode 4.0.1 removed from White_Space. Borrowing \\s left a\n * field led by U+0085 unquoted here but quoted in Go, and the reverse for\n * U+FEFF.\n */\nconst GO_LEADING_SPACE = /^[\\t\\n\\v\\f\\r \\u0085\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000]/;\n\nfunction goWouldQuote(field: string): boolean {\n return field === '\\\\.' || GO_LEADING_SPACE.test(field);\n}\n\n/** One field, formatted by d3-dsv, then quoted if Go would quote it and d3-dsv did not. */\nfunction requote(field: string, dsv: DSV): string {\n const formatted = dsv.formatValue(field);\n if (formatted.startsWith('\"') || !goWouldQuote(field)) {\n return formatted;\n }\n return `\"${field.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Terminate the final record with a newline. d3-dsv leaves it off, but Go's\n * encoding/csv (which the Go converters use) always writes it, so without this\n * every TS CSV would differ from its Go twin by exactly one trailing byte.\n * Applied before the newline substitution so a \\r\\n dialect gets it too.\n */\nfunction terminateFinalRecord(csv: string): string {\n return csv.endsWith('\\n') ? csv : `${csv}\\n`;\n}\n\n/**\n * Build CSV string from array of arrays\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of arrays to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsvArray(\n data: string[][],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, header: false, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) => sanitizeCsvArray(row));\n }\n\n const delimiter = opts.delimiter ?? ',';\n const dsv = dsvFormat(delimiter);\n let result = terminateFinalRecord(formatRowsGoCompatible(processedData, dsv, delimiter));\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Validate CSV format\n * @param csv - CSV string to validate\n * @returns True if valid CSV, false otherwise\n */\nexport function isValidCsv(csv: string): boolean {\n if (!csv || csv.trim().length === 0) return false;\n\n const trimmed = csv.trim();\n\n // CSV must have at least one delimiter (comma) to be considered CSV structure\n if (!trimmed.includes(',')) return false;\n\n try {\n validateQuoting(trimmed);\n } catch {\n return false;\n }\n\n return true;\n}\n\n/**\n * Sanitize a single CSV value to prevent formula injection\n *\n * Prefixes values starting with =, +, -, @, |, or % with a single quote\n * to prevent Excel/LibreOffice from interpreting them as formulas.\n *\n * @param value - Value to sanitize\n * @returns Sanitized value\n */\nexport function sanitizeCsvValue(value: unknown): string {\n const str = String(value);\n // Check if string starts with formula trigger characters\n if (/^[=+\\-@|%]/.test(str)) {\n return `'${str}`;\n }\n return str;\n}\n\n/**\n * Sanitize all values in an array to prevent formula injection\n *\n * @param values - Array of values to sanitize\n * @returns Array with sanitized values\n */\nexport function sanitizeCsvArray(values: unknown[]): string[] {\n return values.map(sanitizeCsvValue);\n}\n\n/**\n * Sanitize all values in an object to prevent formula injection\n *\n * @param obj - Object with values to sanitize\n * @returns New object with sanitized values\n */\nexport function sanitizeCsvObject<T extends Record<string, unknown>>(\n obj: T\n): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(obj)) {\n sanitized[key] = sanitizeCsvValue(value);\n }\n return sanitized;\n}\n"],"mappings":"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"}
@@ -58,6 +58,26 @@ declare function stringifyJSON(value: unknown, options?: StringifyOptions): stri
58
58
  * @returns true if input is valid JSON, false otherwise
59
59
  */
60
60
  declare function isValidJSON(input: unknown): boolean;
61
+ /**
62
+ * Render a FINITE JSON number as text the way Go's
63
+ * strconv.FormatFloat(v, 'f', -1, 64) does: shortest round-trip digits,
64
+ * positional always, never exponent notation.
65
+ *
66
+ * JavaScript's own Number-to-string switches to exponent at and above 1e21 and
67
+ * below 1e-6, and drops the sign of negative zero, so a converter that emits
68
+ * String(n) diverges from its Go peer at both magnitude extremes and at -0.
69
+ * Expansion works on the digit string rather than toFixed, which caps at 100
70
+ * fraction digits and so cannot render the smallest denormals at all.
71
+ *
72
+ * Non-finite values are passed through as JavaScript renders them and do NOT
73
+ * match Go: this returns "Infinity"/"-Infinity" where FormatFloat returns
74
+ * "+Inf"/"-Inf" ("NaN" agrees). JSON has no literal for any of them, but
75
+ * JSON.parse manufactures Infinity from an overflow literal such as 1e400, so
76
+ * the case is reachable. The Go peer never renders that value at all -- its
77
+ * ParseFloat fails and it emits the original token text instead -- which is a
78
+ * documented cross-language divergence, not something this helper can close.
79
+ */
80
+ declare function formatJsonNumber(value: number): string;
61
81
  //#endregion
62
- export { canonicalChecksum as a, stringifyJSON as i, isValidJSON as n, canonicalJson as o, parseJSON as r, StringifyOptions as t };
63
- //# sourceMappingURL=index-b6zg3fBr.d.ts.map
82
+ export { stringifyJSON as a, parseJSON as i, formatJsonNumber as n, canonicalChecksum as o, isValidJSON as r, canonicalJson as s, StringifyOptions as t };
83
+ //# sourceMappingURL=index-i4P63h9e.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-b6zg3fBr.d.ts","names":[],"sources":["../src/json/canonical.ts","../src/json/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;iBAkIgB,cAAc;;iBAKR,kBAAkB,iBAAiB;;;;;;UCjIxC;;EAEf;;EAEA;;;;;;;;iBASc,UAAU,aAAa,gBAAgB;;;;;;;;iBAuBvC,cAAc,gBAAgB,UAAS;;;;;;iBAkBvC,YAAY"}
1
+ {"version":3,"file":"index-i4P63h9e.d.ts","names":[],"sources":["../src/json/canonical.ts","../src/json/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;iBAkIgB,cAAc;;iBAKR,kBAAkB,iBAAiB;;;;;;UCjIxC;;EAEf;;EAEA;;;;;;;;iBASc,UAAU,aAAa,gBAAgB;;;;;;;;iBAuBvC,cAAc,gBAAgB,UAAS;;;;;;iBAkBvC,YAAY;;;;;;;;;;;;;;;;;;;;iBAoCZ,iBAAiB"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { extractColumn, findRows, findValuesByKey } from "./object/index.js";
2
2
  import { CsvBuildOptions, CsvParseOptions, buildCsv, buildCsvArray, isValidCsv, parseCsv, parseCsvArray, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue } from "./csv/index.js";
3
3
  import { HashAlgorithm, HashEncoding, HashOptions, generateHash, hashObject, sha256, sha512, verifyHash } from "./hash/index.js";
4
- import { a as canonicalChecksum, i as stringifyJSON, n as isValidJSON, o as canonicalJson, r as parseJSON, t as StringifyOptions } from "./index-b6zg3fBr.js";
4
+ import { a as stringifyJSON, i as parseJSON, n as formatJsonNumber, o as canonicalChecksum, r as isValidJSON, s as canonicalJson, t as StringifyOptions } from "./index-i4P63h9e.js";
5
5
  import { buildXml, extractTextFromXml, isValidXml, isXmlChar, parseXml, parseXmlWithArrays, xmlSafeText } from "./xml/index.js";
6
- import { formatTimestamp, formatTimestampSeconds, normalizeHdfTimestamps, parseTimestamp, replaceHtmlTags, stripHtml, trimUtcFraction } from "./string/index.js";
6
+ import { encodeBase64Utf8, formatTimestamp, formatTimestampSeconds, normalizeHdfTimestamps, parseTimestamp, replaceHtmlTags, stripHtml, trimUtcFraction } from "./string/index.js";
7
7
  //#region src/severity/index.d.ts
8
8
  /**
9
9
  * Severity-to-impact mapping utilities.
@@ -385,5 +385,5 @@ declare const DEFAULT_MAX_INPUT_SIZE: number;
385
385
  */
386
386
  declare function validateInputSize(input: string | Uint8Array, maxSize?: number): void;
387
387
  //#endregion
388
- export { type CpePart, type CsvBuildOptions, type CsvParseOptions, type CvssScore, type CvssValidationResult, DEFAULT_MAX_INPUT_SIZE, type EffectiveStatusInput, type HashAlgorithm, type HashEncoding, type HashOptions, type ParsedCpe, type ParsedCvssVector, type ParsedPurl, STATUS_SEVERITY_ORDER, type StatusOverrideInput, type StatusSeverity, type StringifyOptions, buildCsv, buildCsvArray, buildXml, canonicalChecksum, canonicalJson, computeCvss40Score, computeCvssScore, computeEffectiveStatus, cvss40MacroVector, cvssScoreToSeverity, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, formatTimestamp, formatTimestampSeconds, generateHash, governingOverrideIndex, governingStatusOverride, governingStatusOverrideIndex, hashObject, impactToSeverity, isUnratedSeverity, isValidCsv, isValidJSON, isValidXml, isXmlChar, normalizeHdfTimestamps, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, replaceHtmlTags, roundImpact, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, severityToImpactWithAliases, sha256, sha512, statusRank, stringifyJSON, stripHtml, trimUtcFraction, validateCvssVector, validateInputSize, verifyHash, worstStatus, xmlSafeText };
388
+ export { type CpePart, type CsvBuildOptions, type CsvParseOptions, type CvssScore, type CvssValidationResult, DEFAULT_MAX_INPUT_SIZE, type EffectiveStatusInput, type HashAlgorithm, type HashEncoding, type HashOptions, type ParsedCpe, type ParsedCvssVector, type ParsedPurl, STATUS_SEVERITY_ORDER, type StatusOverrideInput, type StatusSeverity, type StringifyOptions, buildCsv, buildCsvArray, buildXml, canonicalChecksum, canonicalJson, computeCvss40Score, computeCvssScore, computeEffectiveStatus, cvss40MacroVector, cvssScoreToSeverity, encodeBase64Utf8, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, formatJsonNumber, formatTimestamp, formatTimestampSeconds, generateHash, governingOverrideIndex, governingStatusOverride, governingStatusOverrideIndex, hashObject, impactToSeverity, isUnratedSeverity, isValidCsv, isValidJSON, isValidXml, isXmlChar, normalizeHdfTimestamps, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, replaceHtmlTags, roundImpact, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, severityToImpactWithAliases, sha256, sha512, statusRank, stringifyJSON, stripHtml, trimUtcFraction, validateCvssVector, validateInputSize, verifyHash, worstStatus, xmlSafeText };
389
389
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { a as canonicalJson, i as canonicalChecksum, n as parseJSON, r as stringifyJSON, t as isValidJSON } from "./json-DIcA65Nf.js";
1
+ import { a as canonicalChecksum, i as stringifyJSON, n as isValidJSON, o as canonicalJson, r as parseJSON, t as formatJsonNumber } from "./json-DdbPTX2g.js";
2
2
  import { extractColumn, findRows, findValuesByKey } from "./object/index.js";
3
3
  import { generateHash, hashObject, sha256, sha512, verifyHash } from "./hash/index.js";
4
4
  import { buildXml, extractTextFromXml, isValidXml, isXmlChar, parseXml, parseXmlWithArrays, xmlSafeText } from "./xml/index.js";
5
5
  import { buildCsv, buildCsvArray, isValidCsv, parseCsv, parseCsvArray, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue } from "./csv/index.js";
6
- import { formatTimestamp, formatTimestampSeconds, normalizeHdfTimestamps, parseTimestamp, replaceHtmlTags, stripHtml, trimUtcFraction } from "./string/index.js";
6
+ import { encodeBase64Utf8, formatTimestamp, formatTimestampSeconds, normalizeHdfTimestamps, parseTimestamp, replaceHtmlTags, stripHtml, trimUtcFraction } from "./string/index.js";
7
7
  //#region src/severity/index.ts
8
8
  /**
9
9
  * Severity-to-impact mapping utilities.
@@ -1808,10 +1808,37 @@ const DEFAULT_MAX_INPUT_SIZE = 50 * 1024 * 1024;
1808
1808
  */
1809
1809
  function validateInputSize(input, maxSize = 0) {
1810
1810
  const limit = maxSize > 0 ? maxSize : DEFAULT_MAX_INPUT_SIZE;
1811
- const len = typeof input === "string" ? new TextEncoder().encode(input).length : input.length;
1812
- if (len > limit) throw new Error(`input exceeds maximum allowed size of ${limit} bytes (${len} bytes provided)`);
1811
+ if (typeof input !== "string") {
1812
+ if (input.length > limit) throw tooLarge(limit, input.length);
1813
+ return;
1814
+ }
1815
+ if (input.length * 3 <= limit) return;
1816
+ const len = utf8ByteLength(input);
1817
+ if (len > limit) throw tooLarge(limit, len);
1818
+ }
1819
+ function tooLarge(limit, len) {
1820
+ return /* @__PURE__ */ new Error(`input exceeds maximum allowed size of ${limit} bytes (${len} bytes provided)`);
1821
+ }
1822
+ /**
1823
+ * UTF-8 byte length, counted rather than encoded: a TextEncoder pass would
1824
+ * allocate a full copy of the very input this guard exists to reject.
1825
+ * An unpaired surrogate counts as the three-byte replacement character, which
1826
+ * is what TextEncoder emits for one.
1827
+ */
1828
+ function utf8ByteLength(s) {
1829
+ let bytes = 0;
1830
+ for (let i = 0; i < s.length; i++) {
1831
+ const c = s.charCodeAt(i);
1832
+ if (c < 128) bytes += 1;
1833
+ else if (c < 2048) bytes += 2;
1834
+ else if (c >= 55296 && c <= 56319 && (s.charCodeAt(i + 1) & 64512) === 56320) {
1835
+ bytes += 4;
1836
+ i++;
1837
+ } else bytes += 3;
1838
+ }
1839
+ return bytes;
1813
1840
  }
1814
1841
  //#endregion
1815
- export { DEFAULT_MAX_INPUT_SIZE, STATUS_SEVERITY_ORDER, buildCsv, buildCsvArray, buildXml, canonicalChecksum, canonicalJson, computeCvss40Score, computeCvssScore, computeEffectiveStatus, cvss40MacroVector, cvssScoreToSeverity, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, formatTimestamp, formatTimestampSeconds, generateHash, governingOverrideIndex, governingStatusOverride, governingStatusOverrideIndex, hashObject, impactToSeverity, isUnratedSeverity, isValidCsv, isValidJSON, isValidXml, isXmlChar, normalizeHdfTimestamps, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, replaceHtmlTags, roundImpact, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, severityToImpactWithAliases, sha256, sha512, statusRank, stringifyJSON, stripHtml, trimUtcFraction, validateCvssVector, validateInputSize, verifyHash, worstStatus, xmlSafeText };
1842
+ export { DEFAULT_MAX_INPUT_SIZE, STATUS_SEVERITY_ORDER, buildCsv, buildCsvArray, buildXml, canonicalChecksum, canonicalJson, computeCvss40Score, computeCvssScore, computeEffectiveStatus, cvss40MacroVector, cvssScoreToSeverity, encodeBase64Utf8, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, formatJsonNumber, formatTimestamp, formatTimestampSeconds, generateHash, governingOverrideIndex, governingStatusOverride, governingStatusOverrideIndex, hashObject, impactToSeverity, isUnratedSeverity, isValidCsv, isValidJSON, isValidXml, isXmlChar, normalizeHdfTimestamps, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, replaceHtmlTags, roundImpact, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, severityToImpactWithAliases, sha256, sha512, statusRank, stringifyJSON, stripHtml, trimUtcFraction, validateCvssVector, validateInputSize, verifyHash, worstStatus, xmlSafeText };
1816
1843
 
1817
1844
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["cvss40Data"],"sources":["../src/severity/index.ts","../src/status/index.ts","../src/cvss/cvss40-data.json","../src/cvss/index.ts","../src/cpe/index.ts","../src/purl/index.ts","../src/size/index.ts"],"sourcesContent":["/**\n * Severity-to-impact mapping utilities.\n *\n * These define the canonical bidirectional mapping between severity labels\n * (critical, high, medium, low, informational) and HDF impact scores (0.0–1.0),\n * aligned with CVSS 3.x severity bands normalized to 0–1.\n *\n * Threshold ranges (used by impactToSeverity):\n * >=0.9 = critical (CVSS 9.0–10.0)\n * [0.7, 0.9) = high (CVSS 7.0–8.9)\n * [0.4, 0.7) = medium (CVSS 4.0–6.9)\n * (0.0, 0.4) = low (CVSS 0.1–3.9)\n * 0.0 = informational (CVSS 0.0)\n */\n\nconst severityMap: Record<string, number> = {\n critical: 0.9,\n high: 0.7,\n medium: 0.5,\n low: 0.3,\n info: 0.0,\n none: 0.0,\n informational: 0.0,\n information: 0.0,\n};\n\n/**\n * Map a severity string to an impact score.\n *\n * @param severity - Severity level (case-insensitive), or null for no severity assessment\n * @returns Impact score between 0.0 and 1.0; null when input is null; defaults to 0.5 for unrecognized values\n */\nexport function severityToImpact(severity: null): null;\nexport function severityToImpact(severity: string | undefined): number;\nexport function severityToImpact(severity: string | null | undefined): number | null;\nexport function severityToImpact(severity: string | null | undefined): number | null {\n if (severity === null) return null;\n // Absent field: match Go's zero-value semantics (\"\" → the 0.5 default)\n // rather than throwing on .toLowerCase() of undefined.\n const normalized = (severity ?? '').toLowerCase();\n // Own-property guard: a prototype-named token (\"constructor\") must take the\n // default like any unknown word, not return an inherited Object member.\n return Object.prototype.hasOwnProperty.call(severityMap, normalized)\n ? severityMap[normalized]!\n : 0.5;\n}\n\n/**\n * Map severity to impact, checking custom aliases first, then the shared\n * standard map, then the caller default. Case-insensitive. Use for tools with\n * non-standard severity labels (sonarqube BLOCKER, veracode numeric levels,\n * grype critical=0.9). Absent (undefined/null) values take the default —\n * matching the Go peer's zero-value \"\" behavior. TS mirror of Go's\n * SeverityToImpactWithAliases; converters must not hand-roll local severity\n * maps around it (that fork is how Go/TS impact divergence happens).\n */\nexport function severityToImpactWithAliases(\n severity: string | null | undefined,\n aliases: Record<string, number>,\n defaultVal: number,\n): number {\n const lower = (severity ?? '').toLowerCase();\n // Own-property guards: prototype-named tokens (\"constructor\") must take the\n // default like any unknown word, not resolve to an inherited Object member.\n if (Object.prototype.hasOwnProperty.call(aliases, lower)) return aliases[lower]!;\n if (Object.prototype.hasOwnProperty.call(severityMap, lower)) return severityMap[lower]!;\n return defaultVal;\n}\n\n/**\n * Round an HDF impact value to 2 decimal places — its canonical precision —\n * eliminating the representation noise that binary float division leaves behind\n * (e.g. 9.8 / 10 = 0.9800000000000001). Impact is defined on 0.0–1.0 with a\n * natural 0.01 grid (a 1-decimal CVSS score / 10), so this is lossless in\n * intent. Use it wherever impact is COMPUTED (divided or otherwise\n * arithmetically derived), not when assigned from a literal band.\n */\nexport function roundImpact(x: number): number {\n return Math.round(x * 100) / 100;\n}\n\n/**\n * Map an impact score to a severity string.\n *\n * @param impact - Impact score (0.0 to 1.0), or null for no impact assessment\n * @returns Severity level string, or null when input is null\n */\nexport function impactToSeverity(impact: null): null;\nexport function impactToSeverity(impact: number): string;\nexport function impactToSeverity(impact: number | null): string | null;\nexport function impactToSeverity(impact: number | null): string | null {\n if (impact === null) return null;\n if (impact >= 0.9) return 'critical';\n if (impact >= 0.7) return 'high';\n if (impact >= 0.4) return 'medium';\n if (impact > 0.0) return 'low';\n return 'informational';\n}\n\n/**\n * Map a raw CVSS base score (0.0–10.0) to a FIRST qualitative severity band.\n *\n * Bands per FIRST CVSS v3.x / v4.0 spec:\n * none = 0.0\n * low = 0.1 – 3.9\n * medium = 4.0 – 6.9\n * high = 7.0 – 8.9\n * critical = 9.0 – 10.0\n *\n * Out-of-range inputs are clamped: scores < 0.1 → \"none\" (band floor),\n * scores > 10.0 → \"critical\". This matches scanner behavior of treating\n * malformed scores as informational/critical extremes rather than throwing.\n *\n * @param score - CVSS base score\n * @returns FIRST severity band label\n */\nexport function cvssScoreToSeverity(\n score: number,\n): 'none' | 'low' | 'medium' | 'high' | 'critical' {\n if (!Number.isFinite(score) || score < 0.1) return 'none';\n if (score < 4.0) return 'low';\n if (score < 7.0) return 'medium';\n if (score < 9.0) return 'high';\n return 'critical';\n}\n\n// Severity vocabulary values that assert \"no rating was made\" (grype Unknown,\n// Dependency-Track UNASSIGNED, Microsoft Graph unSpecified). Distinct from the\n// zero-impact RATED tier (info/none/informational) and from grype's\n// negligible, which is the lowest rating, not an absent one.\nconst unratedSeverityTokens = new Set(['unknown', 'unassigned', 'unspecified']);\n\n/**\n * Report whether a source severity carries no rating at all: the field is\n * absent/blank, or the token is an explicit no-rating value. Tokens the\n * vocabulary simply doesn't recognize are NOT unrated — an unknown word is not\n * an assertion of unratedness. Converters use this to emit the shared unrated\n * marker so a defaulted impact stays distinguishable from a genuine medium.\n */\nexport function isUnratedSeverity(severity?: string | null): boolean {\n if (severity === undefined || severity === null) return true;\n const s = severity.trim().toLowerCase();\n return s === '' || unratedSeverityTokens.has(s);\n}\n","/**\n * Canonical HDF result-status ordering and effective-status computation.\n *\n * This is the single source of truth for worst-wins roll-up and\n * effective-status selection across hdf-libs (mirrored in Go as\n * hdf-utilities/go status.go). The precedence rules are published in\n * site/docs/architecture/status-determination.md; do not re-implement them.\n */\n\nimport { parseTimestamp } from '../string/index.js';\n\n/** Canonical worst-wins ordering, worst first. */\nexport const STATUS_SEVERITY_ORDER = [\n 'error',\n 'failed',\n 'passed',\n 'notApplicable',\n 'notReviewed',\n] as const;\n\n/** A member of the canonical ordering, as a literal union. */\nexport type StatusSeverity = (typeof STATUS_SEVERITY_ORDER)[number];\n\n/** Widened view for lookups over arbitrary input strings. */\nconst SEVERITY_LOOKUP: readonly string[] = STATUS_SEVERITY_ORDER;\n\n/** Severity rank of a status: higher = worse. Unknown statuses rank -1. */\nexport function statusRank(status: string): number {\n const idx = SEVERITY_LOOKUP.indexOf(status);\n return idx === -1 ? -1 : SEVERITY_LOOKUP.length - 1 - idx;\n}\n\n/**\n * Rolls a list of result statuses up to the worst one. An empty list (or a\n * list of only unknown statuses) rolls up to \"notReviewed\".\n */\nexport function worstStatus(statuses: readonly string[]): string {\n let worst = 'notReviewed';\n let worstRank = -1;\n for (const s of statuses) {\n const r = statusRank(s);\n if (r > worstRank) {\n worstRank = r;\n worst = s;\n }\n }\n return worst;\n}\n\n/**\n * Neutral shape of a status override for effective-status computation.\n * Callers map their concrete types (schema objects or raw JSON) onto it.\n */\nexport interface StatusOverrideInput {\n /** Empty/undefined means the override carries no status and never governs. */\n status?: string;\n /** RFC3339 timestamp; undefined sorts earliest. */\n appliedAt?: string;\n /** RFC3339 timestamp; undefined means the override never expires. */\n expiresAt?: string;\n}\n\nfunction isExpired(override: StatusOverrideInput, ref: Date): boolean {\n if (!override.expiresAt) return false;\n // parseTimestamp normalizes zone-less values to UTC (a raw `new Date` would\n // read them as host-local and make expiry host-timezone-dependent).\n const expires = parseTimestamp(override.expiresAt);\n return expires !== null && expires <= ref;\n}\n\n/**\n * Selects the override that governs a requirement: the most recently applied\n * (by appliedAt) non-expired override that carries a status — matching the\n * schema's definition of disposition (\"the most recent non-expired\n * override\"). Returns undefined when no override governs.\n */\nexport function governingStatusOverride(\n overrides: readonly StatusOverrideInput[],\n referenceTimestamp?: string\n): StatusOverrideInput | undefined {\n const i = governingStatusOverrideIndex(overrides, referenceTimestamp);\n return i >= 0 ? overrides[i] : undefined;\n}\n\n/**\n * Returns the index of the most recently applied (by appliedAt) non-expired\n * override among those for which `eligible` returns true, or -1 when none\n * qualifies. Generalizes governing-override selection to per-field\n * eligibility: the schema defines effectiveStatus, effectiveImpact, and\n * disposition each as \"the most recent non-expired override\" carrying the\n * relevant field, so callers pass the field-presence check as the predicate.\n */\nexport function governingOverrideIndex(\n overrides: readonly StatusOverrideInput[],\n eligible: (index: number) => boolean,\n referenceTimestamp?: string\n): number {\n const ref = refTime(referenceTimestamp);\n let governing = -1;\n for (let i = 0; i < overrides.length; i++) {\n const override = overrides[i];\n if (!override || !eligible(i) || isExpired(override, ref)) continue;\n if (governing === -1 || appliedTime(override) > appliedTime(overrides[governing]!)) {\n governing = i;\n }\n }\n return governing;\n}\n\n/**\n * Index variant of {@link governingStatusOverride}: returns -1 when no\n * override governs. Callers holding richer concrete override types use the\n * index to recover their own object.\n */\nexport function governingStatusOverrideIndex(\n overrides: readonly StatusOverrideInput[],\n referenceTimestamp?: string\n): number {\n return governingOverrideIndex(overrides, (i) => !!overrides[i]?.status, referenceTimestamp);\n}\n\nfunction appliedTime(override: StatusOverrideInput): number {\n if (!override.appliedAt) return Number.NEGATIVE_INFINITY;\n return parseTimestamp(override.appliedAt)?.getTime() ?? Number.NEGATIVE_INFINITY;\n}\n\nfunction refTime(referenceTimestamp?: string): Date {\n const parsed = referenceTimestamp ? parseTimestamp(referenceTimestamp) : null;\n return parsed ?? new Date();\n}\n\n/** Neutral shape of a requirement for effective-status computation. */\nexport interface EffectiveStatusInput {\n impact: number;\n resultStatuses?: readonly string[];\n overrides?: readonly StatusOverrideInput[];\n}\n\n/**\n * Determines a requirement's effective status — the single canonical\n * implementation of the ladder in status-determination.md:\n *\n * 1. the governing (most recent non-expired) status override's status\n * 2. else result roll-up \"error\" → \"error\"\n * 3. else impact === 0 → \"notApplicable\"\n * 4. else worst-wins roll-up of the result statuses (empty → \"notReviewed\")\n *\n * The stored effectiveStatus field is an output cache and is never read: the\n * only sanctioned channel for status to diverge from the results is a\n * governing override.\n */\nexport function computeEffectiveStatus(\n input: EffectiveStatusInput,\n referenceTimestamp?: string\n): string {\n const governing = governingStatusOverride(input.overrides ?? [], referenceTimestamp);\n if (governing?.status) return governing.status;\n const rolledUp = worstStatus(input.resultStatuses ?? []);\n if (rolledUp === 'error') {\n return 'error';\n }\n if (input.impact === 0) {\n return 'notApplicable';\n }\n return rolledUp;\n}\n","","/**\n * CVSS vector parsing and validation utilities.\n *\n * Supports CVSS 2.0 (legacy, no prefix), 3.0, 3.1, and 4.0 vector strings.\n * Parsing is permissive: it splits any well-shaped key:value pair separated\n * by `/`. Validation is strict per the FIRST grammar for each version.\n *\n * References:\n * - CVSS 2.0: https://www.first.org/cvss/v2/guide\n * - CVSS 3.1: https://www.first.org/cvss/v3.1/specification-document\n * - CVSS 4.0: https://www.first.org/cvss/v4.0/specification-document\n */\n\nimport cvss40Data from './cvss40-data.json';\n\nexport interface ParsedCvssVector {\n /** \"2.0\" | \"3.0\" | \"3.1\" | \"4.0\" | \"unknown\" */\n version: string;\n /** Metric name -> metric value (e.g. \"AV\" -> \"N\"). Order-preserving via Map. */\n metrics: Map<string, string>;\n}\n\nexport interface CvssValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Allowed values for each metric, keyed by CVSS major version.\n *\n * Sourced from FIRST specifications (linked above). Only metrics present\n * in a given version's grammar appear in that version's map; unknown metrics\n * encountered during validation are ignored (forward-compat).\n */\nconst METRIC_VALUES: Record<string, Record<string, string[]>> = {\n '2.0': {\n AV: ['L', 'A', 'N'],\n AC: ['H', 'M', 'L'],\n Au: ['M', 'S', 'N'],\n C: ['N', 'P', 'C'],\n I: ['N', 'P', 'C'],\n A: ['N', 'P', 'C'],\n // Temporal\n E: ['U', 'POC', 'F', 'H', 'ND'],\n RL: ['OF', 'TF', 'W', 'U', 'ND'],\n RC: ['UC', 'UR', 'C', 'ND'],\n // Environmental\n CDP: ['N', 'L', 'LM', 'MH', 'H', 'ND'],\n TD: ['N', 'L', 'M', 'H', 'ND'],\n CR: ['L', 'M', 'H', 'ND'],\n IR: ['L', 'M', 'H', 'ND'],\n AR: ['L', 'M', 'H', 'ND'],\n },\n '3.0': {\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'R'],\n S: ['U', 'C'],\n C: ['N', 'L', 'H'],\n I: ['N', 'L', 'H'],\n A: ['N', 'L', 'H'],\n // Temporal\n E: ['X', 'U', 'P', 'F', 'H'],\n RL: ['X', 'O', 'T', 'W', 'U'],\n RC: ['X', 'U', 'R', 'C'],\n // Environmental\n CR: ['X', 'L', 'M', 'H'],\n IR: ['X', 'L', 'M', 'H'],\n AR: ['X', 'L', 'M', 'H'],\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'R'],\n MS: ['X', 'U', 'C'],\n MC: ['X', 'N', 'L', 'H'],\n MI: ['X', 'N', 'L', 'H'],\n MA: ['X', 'N', 'L', 'H'],\n },\n '3.1': {\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'R'],\n S: ['U', 'C'],\n C: ['N', 'L', 'H'],\n I: ['N', 'L', 'H'],\n A: ['N', 'L', 'H'],\n // Temporal\n E: ['X', 'U', 'P', 'F', 'H'],\n RL: ['X', 'O', 'T', 'W', 'U'],\n RC: ['X', 'U', 'R', 'C'],\n // Environmental\n CR: ['X', 'L', 'M', 'H'],\n IR: ['X', 'L', 'M', 'H'],\n AR: ['X', 'L', 'M', 'H'],\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'R'],\n MS: ['X', 'U', 'C'],\n MC: ['X', 'N', 'L', 'H'],\n MI: ['X', 'N', 'L', 'H'],\n MA: ['X', 'N', 'L', 'H'],\n },\n '4.0': {\n // Base\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n AT: ['N', 'P'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'P', 'A'],\n VC: ['H', 'L', 'N'],\n VI: ['H', 'L', 'N'],\n VA: ['H', 'L', 'N'],\n SC: ['H', 'L', 'N'],\n SI: ['H', 'L', 'N'],\n SA: ['H', 'L', 'N'],\n // Threat\n E: ['X', 'A', 'P', 'U'],\n // Environmental (modified base) — same enums as their base counterparts plus X\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MAT: ['X', 'N', 'P'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'P', 'A'],\n MVC: ['X', 'H', 'L', 'N'],\n MVI: ['X', 'H', 'L', 'N'],\n MVA: ['X', 'H', 'L', 'N'],\n MSC: ['X', 'H', 'L', 'N'],\n MSI: ['X', 'S', 'H', 'L', 'N'],\n MSA: ['X', 'S', 'H', 'L', 'N'],\n // Requirements\n CR: ['X', 'H', 'M', 'L'],\n IR: ['X', 'H', 'M', 'L'],\n AR: ['X', 'H', 'M', 'L'],\n // Supplemental\n S: ['X', 'N', 'P'],\n AU: ['X', 'N', 'Y'],\n R: ['X', 'A', 'U', 'I'],\n V: ['X', 'D', 'C'],\n RE: ['X', 'L', 'M', 'H'],\n U: ['X', 'Clear', 'Green', 'Amber', 'Red'],\n },\n};\n\nconst REQUIRED_METRICS: Record<string, string[]> = {\n '2.0': ['AV', 'AC', 'Au', 'C', 'I', 'A'],\n '3.0': ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A'],\n '3.1': ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A'],\n '4.0': ['AV', 'AC', 'AT', 'PR', 'UI', 'VC', 'VI', 'VA', 'SC', 'SI', 'SA'],\n};\n\n/**\n * Parse a CVSS vector string into version + metric map.\n *\n * Version detection rule: a leading `CVSS:X.Y` segment yields version \"X.Y\".\n * Otherwise (legacy v2 vectors), version is \"2.0\". Malformed input yields\n * \"unknown\" with an empty metric map; no exceptions are thrown.\n *\n * @param vector - CVSS vector string (e.g. \"CVSS:3.1/AV:N/AC:L/...\")\n * @returns Parsed version + metric map\n */\nexport function parseCvssVector(vector: string): ParsedCvssVector {\n const metrics = new Map<string, string>();\n if (typeof vector !== 'string' || vector.length === 0) {\n return { version: 'unknown', metrics };\n }\n\n const segments = vector.split('/').filter((s) => s.length > 0);\n if (segments.length === 0) {\n return { version: 'unknown', metrics };\n }\n\n let version = '2.0';\n let start = 0;\n const first = segments[0];\n if (first === undefined) {\n return { version: 'unknown', metrics };\n }\n if (first.startsWith('CVSS:')) {\n version = first.slice(5);\n start = 1;\n } else if (!first.includes(':')) {\n // No prefix AND first segment isn't a key:value pair — bail.\n return { version: 'unknown', metrics };\n }\n\n for (let i = start; i < segments.length; i++) {\n const seg = segments[i];\n if (seg === undefined) {\n continue;\n }\n const colon = seg.indexOf(':');\n if (colon <= 0 || colon === seg.length - 1) {\n // Skip malformed segments (no colon, empty key, or empty value).\n continue;\n }\n const key = seg.slice(0, colon);\n const value = seg.slice(colon + 1);\n metrics.set(key, value);\n }\n\n return { version, metrics };\n}\n\n/**\n * Validate a CVSS vector string against the FIRST grammar for its version.\n *\n * Checks:\n * - Version is one of the supported set (2.0, 3.0, 3.1, 4.0).\n * - All required base metrics for the version are present.\n * - All known metric values are within the allowed enum.\n *\n * Unknown metric keys are tolerated (forward-compat with future minor versions\n * or scanner-specific extensions). To check a v2 vector without the CVSS:\n * prefix, callers may pass version=\"2.0\" explicitly; otherwise the parser\n * defaults to \"2.0\" on prefix absence.\n *\n * @param vector - CVSS vector string\n * @param version - Optional explicit version override (else inferred)\n * @returns `{ valid, errors[] }` — never throws\n */\nexport function validateCvssVector(\n vector: string,\n version?: string,\n): CvssValidationResult {\n const errors: string[] = [];\n\n if (typeof vector !== 'string' || vector.length === 0) {\n return { valid: false, errors: ['vector is empty'] };\n }\n\n const parsed = parseCvssVector(vector);\n const v = version ?? parsed.version;\n\n const grammar = METRIC_VALUES[v];\n const required = REQUIRED_METRICS[v];\n if (!grammar || !required) {\n errors.push(`unsupported CVSS version: ${v}`);\n return { valid: false, errors };\n }\n\n for (const req of required) {\n if (!parsed.metrics.has(req)) {\n errors.push(`missing required metric: ${req}`);\n }\n }\n\n for (const [key, value] of parsed.metrics.entries()) {\n const allowed = grammar[key];\n if (!allowed) {\n // Unknown metric: forward-compat — no error.\n continue;\n }\n if (!allowed.includes(value)) {\n errors.push(`invalid value for metric ${key}: ${value} (allowed: ${allowed.join(',')})`);\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n/** A computed CVSS score. */\nexport interface CvssScore {\n /** CVSS major.minor version the score was computed under (currently always \"3.1\"). */\n version: string;\n /** Base score (0.0–10.0). */\n baseScore: number;\n /**\n * Temporal (Threat) score (0.0–10.0). Equals baseScore when no temporal\n * metrics (E/RL/RC) are present, per the CVSS 3.1 spec.\n */\n temporalScore: number;\n}\n\n// CVSS 3.1 metric weights (FIRST v3.1 specification §7). Privileges Required is\n// scope-dependent, hence two tables.\nconst CVSS31_AV: Record<string, number> = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };\nconst CVSS31_AC: Record<string, number> = { L: 0.77, H: 0.44 };\nconst CVSS31_UI: Record<string, number> = { N: 0.85, R: 0.62 };\nconst CVSS31_CIA: Record<string, number> = { N: 0.0, L: 0.22, H: 0.56 };\nconst CVSS31_PR_UNCHANGED: Record<string, number> = { N: 0.85, L: 0.62, H: 0.27 };\nconst CVSS31_PR_CHANGED: Record<string, number> = { N: 0.85, L: 0.68, H: 0.5 };\nconst CVSS31_E: Record<string, number> = { X: 1.0, H: 1.0, F: 0.97, P: 0.94, U: 0.91 };\nconst CVSS31_RL: Record<string, number> = { X: 1.0, U: 1.0, W: 0.97, T: 0.96, O: 0.95 };\nconst CVSS31_RC: Record<string, number> = { X: 1.0, C: 1.0, R: 0.96, U: 0.92 };\n\n/**\n * CVSS 3.1 Roundup (spec §7.4): the smallest number, to one decimal place, that\n * is >= the input — computed via integer math to avoid the floating-point edge\n * cases 3.0 rounding suffered.\n */\nfunction roundUp31(x: number): number {\n const intInput = Math.round(x * 100000);\n if (intInput % 10000 === 0) {\n return intInput / 100000;\n }\n return (Math.floor(intInput / 10000) + 1) / 10;\n}\n\nfunction cvss31TemporalWeight(\n table: Record<string, number>,\n metrics: Map<string, string>,\n key: string,\n): number {\n const v = metrics.get(key);\n if (v === undefined) {\n return 1.0; // absent → X\n }\n const w = table[v];\n if (w === undefined) {\n throw new Error(`cvss: invalid 3.1 ${key} value \"${v}\"`);\n }\n return w;\n}\n\n/**\n * Compute the CVSS 3.1 Base and Temporal (Threat) scores for a vector string.\n *\n * Only CVSS 3.1 is supported here — CVSS 4.0's MacroVector engine lives\n * separately — so any other version throws rather than returning a wrong number.\n * The peer of the Go `ComputeCvssScore` (kept byte-identical via shared fixtures).\n *\n * @throws if the version is not 3.1, a required base metric is missing, or any\n * metric value is out of the 3.1 enum.\n */\nexport function computeCvssScore(vector: string): CvssScore {\n const { version, metrics } = parseCvssVector(vector);\n if (version !== '3.1') {\n throw new Error(`cvss: score compute supports only CVSS 3.1, got \"${version}\"`);\n }\n for (const k of ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A']) {\n if (!metrics.has(k)) {\n throw new Error(`cvss: missing required 3.1 base metric \"${k}\"`);\n }\n }\n\n const scope = metrics.get('S');\n if (scope !== 'U' && scope !== 'C') {\n throw new Error(`cvss: invalid 3.1 Scope value \"${scope ?? ''}\"`);\n }\n const scopeChanged = scope === 'C';\n\n const prTable = scopeChanged ? CVSS31_PR_CHANGED : CVSS31_PR_UNCHANGED;\n const lookup = (table: Record<string, number>, metric: string): number => {\n const key = metrics.get(metric);\n const w = key === undefined ? undefined : table[key];\n if (w === undefined) {\n throw new Error(`cvss: invalid 3.1 metric value ${metric}:\"${key ?? ''}\"`);\n }\n return w;\n };\n const av = lookup(CVSS31_AV, 'AV');\n const ac = lookup(CVSS31_AC, 'AC');\n const ui = lookup(CVSS31_UI, 'UI');\n const pr = lookup(prTable, 'PR');\n const c = lookup(CVSS31_CIA, 'C');\n const i = lookup(CVSS31_CIA, 'I');\n const a = lookup(CVSS31_CIA, 'A');\n\n const iss = 1 - (1 - c) * (1 - i) * (1 - a);\n const impact = scopeChanged\n ? 7.52 * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15)\n : 6.42 * iss;\n const exploitability = 8.22 * av * ac * pr * ui;\n\n let baseScore: number;\n if (impact <= 0) {\n baseScore = 0.0;\n } else if (scopeChanged) {\n baseScore = roundUp31(Math.min(1.08 * (impact + exploitability), 10));\n } else {\n baseScore = roundUp31(Math.min(impact + exploitability, 10));\n }\n\n const e = cvss31TemporalWeight(CVSS31_E, metrics, 'E');\n const rl = cvss31TemporalWeight(CVSS31_RL, metrics, 'RL');\n const rc = cvss31TemporalWeight(CVSS31_RC, metrics, 'RC');\n const temporalScore = roundUp31(baseScore * e * rl * rc);\n\n return { version: '3.1', baseScore, temporalScore };\n}\n\n// --- CVSS 4.0 scoring (MacroVector engine) ---\n//\n// A faithful port of the FIRST reference calculator (FIRSTdotorg/cvss-v4-calculator:\n// cvss_score.js), driven by the MacroVector lookup + max tables extracted verbatim\n// into cvss40-data.json. The Go peer (go/cvss.go) reads the byte-identical data\n// file and shares the parity fixture, so the two languages score identically.\n\ninterface Cvss40Tables {\n lookup: Record<string, number>;\n maxComposed: {\n eq1: Record<string, string[]>;\n eq2: Record<string, string[]>;\n eq3: Record<string, Record<string, string[]>>;\n eq4: Record<string, string[]>;\n eq5: Record<string, string[]>;\n };\n maxSeverity: {\n eq1: Record<string, number>;\n eq2: Record<string, number>;\n eq3eq6: Record<string, Record<string, number>>;\n eq4: Record<string, number>;\n eq5: Record<string, number>;\n };\n}\nconst CVSS40 = cvss40Data as Cvss40Tables;\n\n// Per-metric severity index tables (FIRST reference cvss_score.js). Lower = worse.\nconst CVSS40_AV: Record<string, number> = { N: 0.0, A: 0.1, L: 0.2, P: 0.3 };\nconst CVSS40_PR: Record<string, number> = { N: 0.0, L: 0.1, H: 0.2 };\nconst CVSS40_UI: Record<string, number> = { N: 0.0, P: 0.1, A: 0.2 };\nconst CVSS40_AC: Record<string, number> = { L: 0.0, H: 0.1 };\nconst CVSS40_AT: Record<string, number> = { N: 0.0, P: 0.1 };\nconst CVSS40_VC: Record<string, number> = { H: 0.0, L: 0.1, N: 0.2 };\nconst CVSS40_SC: Record<string, number> = { H: 0.1, L: 0.2, N: 0.3 };\nconst CVSS40_SI: Record<string, number> = { S: 0.0, H: 0.1, L: 0.2, N: 0.3 };\nconst CVSS40_CR: Record<string, number> = { H: 0.0, M: 0.1, L: 0.2 };\n\n/** Throw on an unexpected missing table entry rather than fabricate a value. */\nfunction req<T>(value: T | undefined, what: string): T {\n if (value === undefined) {\n throw new Error(`cvss: 4.0 data table missing ${what}`);\n }\n return value;\n}\n\n/**\n * Resolve the effective value of a metric, mirroring the reference m(): E:X and\n * unset default to A (worst case); CR/IR/AR:X and unset default to H; a present,\n * non-X Modified (M-prefixed) metric overrides its base.\n */\nfunction cvss40Metric(sel: Map<string, string>, metric: string): string {\n const selected = sel.get(metric) ?? '';\n if (metric === 'E' && (selected === '' || selected === 'X')) {\n return 'A';\n }\n if ((metric === 'CR' || metric === 'IR' || metric === 'AR') && (selected === '' || selected === 'X')) {\n return 'H';\n }\n const modified = sel.get('M' + metric);\n if (modified !== undefined && modified !== 'X') {\n return modified;\n }\n return selected;\n}\n\nfunction cvss40MacroVectorFromSel(sel: Map<string, string>): string {\n const mv = (k: string): string => cvss40Metric(sel, k);\n\n // EQ1 (the \"not all three N\" term of the reference's level-1 clause is\n // redundant here: the level-0 branch already consumes the all-three-N case).\n let eq1 = '2';\n if (mv('AV') === 'N' && mv('PR') === 'N' && mv('UI') === 'N') {\n eq1 = '0';\n } else if ((mv('AV') === 'N' || mv('PR') === 'N' || mv('UI') === 'N') && mv('AV') !== 'P') {\n eq1 = '1';\n }\n\n let eq2 = '1';\n if (mv('AC') === 'L' && mv('AT') === 'N') {\n eq2 = '0';\n }\n\n let eq3 = '2';\n if (mv('VC') === 'H' && mv('VI') === 'H') {\n eq3 = '0';\n } else if (mv('VC') === 'H' || mv('VI') === 'H' || mv('VA') === 'H') {\n eq3 = '1';\n }\n\n let eq4 = '2';\n if (mv('MSI') === 'S' || mv('MSA') === 'S') {\n eq4 = '0';\n } else if (mv('SC') === 'H' || mv('SI') === 'H' || mv('SA') === 'H') {\n eq4 = '1';\n }\n\n let eq5 = '2';\n if (mv('E') === 'A') {\n eq5 = '0';\n } else if (mv('E') === 'P') {\n eq5 = '1';\n }\n\n let eq6 = '1';\n if (\n (mv('CR') === 'H' && mv('VC') === 'H') ||\n (mv('IR') === 'H' && mv('VI') === 'H') ||\n (mv('AR') === 'H' && mv('VA') === 'H')\n ) {\n eq6 = '0';\n }\n\n return eq1 + eq2 + eq3 + eq4 + eq5 + eq6;\n}\n\n/** Parse a CVSS 4.0 vector and return its MacroVector string. */\nexport function cvss40MacroVector(vector: string): string {\n return cvss40MacroVectorFromSel(parseCvssVector(vector).metrics);\n}\n\n/** Pull a metric's value out of a composed max-vector string (reference extractValueMetric). */\nfunction cvss40ExtractMetric(metric: string, s: string): string {\n const idx = s.indexOf(metric);\n if (idx < 0) {\n return '';\n }\n const rest = s.slice(idx + metric.length + 1);\n const slash = rest.indexOf('/');\n return slash >= 0 ? rest.slice(0, slash) : rest;\n}\n\n/** value minus the lower macro's score, or NaN when that lower macro does not exist. */\nfunction cvss40AvailDist(value: number, lowerMacro: string): number {\n const s = CVSS40.lookup[lowerMacro];\n return s === undefined ? NaN : value - s;\n}\n\nfunction cvss40Score(sel: Map<string, string>): number {\n const mv = (k: string): string => cvss40Metric(sel, k);\n\n if (\n mv('VC') === 'N' && mv('VI') === 'N' && mv('VA') === 'N' &&\n mv('SC') === 'N' && mv('SI') === 'N' && mv('SA') === 'N'\n ) {\n return 0.0;\n }\n\n const macro = cvss40MacroVectorFromSel(sel);\n let value = req(CVSS40.lookup[macro], `lookup[${macro}]`);\n\n // Definite per-position digit chars (charAt never yields undefined).\n const c0 = macro.charAt(0);\n const c1 = macro.charAt(1);\n const c2 = macro.charAt(2);\n const c3 = macro.charAt(3);\n const c4 = macro.charAt(4);\n const c5 = macro.charAt(5);\n\n const d = (i: number): number => macro.charCodeAt(i) - 48;\n const eq3 = d(2);\n const eq6 = d(5);\n const rep = (i: number, digit: number): string =>\n macro.slice(0, i) + String(digit) + macro.slice(i + 1);\n\n const availDistEq1 = cvss40AvailDist(value, rep(0, d(0) + 1));\n const availDistEq2 = cvss40AvailDist(value, rep(1, d(1) + 1));\n const availDistEq4 = cvss40AvailDist(value, rep(3, d(3) + 1));\n const availDistEq5 = cvss40AvailDist(value, rep(4, d(4) + 1));\n\n // eq3 and eq6 are coupled.\n let availDistEq3eq6 = NaN;\n if ((eq3 === 1 && eq6 === 1) || (eq3 === 0 && eq6 === 1)) {\n availDistEq3eq6 = cvss40AvailDist(value, rep(2, eq3 + 1));\n } else if (eq3 === 1 && eq6 === 0) {\n availDistEq3eq6 = cvss40AvailDist(value, rep(5, eq6 + 1));\n } else if (eq3 === 0 && eq6 === 0) {\n const left = CVSS40.lookup[rep(5, eq6 + 1)];\n const right = CVSS40.lookup[rep(2, eq3 + 1)];\n if (left !== undefined && right !== undefined) {\n availDistEq3eq6 = value - Math.max(left, right);\n } else if (right !== undefined) {\n availDistEq3eq6 = value - right;\n }\n } else {\n availDistEq3eq6 = cvss40AvailDist(value, macro.slice(0, 2) + String(eq3 + 1) + macro.slice(3, 5) + String(eq6 + 1));\n }\n\n const eq1Maxes = req(CVSS40.maxComposed.eq1[c0], `maxComposed.eq1[${c0}]`);\n const eq2Maxes = req(CVSS40.maxComposed.eq2[c1], `maxComposed.eq2[${c1}]`);\n const eq3eq6Maxes = req(\n req(CVSS40.maxComposed.eq3[c2], `maxComposed.eq3[${c2}]`)[c5],\n `maxComposed.eq3[${c2}][${c5}]`,\n );\n const eq4Maxes = req(CVSS40.maxComposed.eq4[c3], `maxComposed.eq4[${c3}]`);\n const eq5Maxes = req(CVSS40.maxComposed.eq5[c4], `maxComposed.eq5[${c4}]`);\n\n const maxVectors: string[] = [];\n for (const a of eq1Maxes) {\n for (const b of eq2Maxes) {\n for (const c of eq3eq6Maxes) {\n for (const e of eq4Maxes) {\n for (const f of eq5Maxes) {\n maxVectors.push(a + b + c + e + f);\n }\n }\n }\n }\n }\n\n const dist = (table: Record<string, number>, metric: string, maxVector: string): number =>\n req(table[mv(metric)], `level ${metric}:${mv(metric)}`) -\n req(table[cvss40ExtractMetric(metric, maxVector)], `max level ${metric}`);\n\n let sdAV = 0, sdPR = 0, sdUI = 0, sdAC = 0, sdAT = 0, sdVC = 0, sdVI = 0, sdVA = 0;\n let sdSC = 0, sdSI = 0, sdSA = 0, sdCR = 0, sdIR = 0, sdAR = 0;\n for (const maxVector of maxVectors) {\n sdAV = dist(CVSS40_AV, 'AV', maxVector);\n sdPR = dist(CVSS40_PR, 'PR', maxVector);\n sdUI = dist(CVSS40_UI, 'UI', maxVector);\n sdAC = dist(CVSS40_AC, 'AC', maxVector);\n sdAT = dist(CVSS40_AT, 'AT', maxVector);\n sdVC = dist(CVSS40_VC, 'VC', maxVector);\n sdVI = dist(CVSS40_VC, 'VI', maxVector);\n sdVA = dist(CVSS40_VC, 'VA', maxVector);\n sdSC = dist(CVSS40_SC, 'SC', maxVector);\n sdSI = dist(CVSS40_SI, 'SI', maxVector);\n sdSA = dist(CVSS40_SI, 'SA', maxVector);\n sdCR = dist(CVSS40_CR, 'CR', maxVector);\n sdIR = dist(CVSS40_CR, 'IR', maxVector);\n sdAR = dist(CVSS40_CR, 'AR', maxVector);\n if (\n [sdAV, sdPR, sdUI, sdAC, sdAT, sdVC, sdVI, sdVA, sdSC, sdSI, sdSA, sdCR, sdIR, sdAR].some(\n (v) => v < 0,\n )\n ) {\n continue;\n }\n break;\n }\n\n const curEq1 = sdAV + sdPR + sdUI;\n const curEq2 = sdAC + sdAT;\n const curEq3eq6 = sdVC + sdVI + sdVA + sdCR + sdIR + sdAR;\n const curEq4 = sdSC + sdSI + sdSA;\n\n const step = 0.1;\n const maxSevEq1 = req(CVSS40.maxSeverity.eq1[c0], `maxSeverity.eq1[${c0}]`) * step;\n const maxSevEq2 = req(CVSS40.maxSeverity.eq2[c1], `maxSeverity.eq2[${c1}]`) * step;\n const maxSevEq3eq6 =\n req(\n req(CVSS40.maxSeverity.eq3eq6[c2], `maxSeverity.eq3eq6[${c2}]`)[c5],\n `maxSeverity.eq3eq6[${c2}][${c5}]`,\n ) * step;\n const maxSevEq4 = req(CVSS40.maxSeverity.eq4[c3], `maxSeverity.eq4[${c3}]`) * step;\n\n let nExisting = 0;\n let normEq1 = 0, normEq2 = 0, normEq3eq6 = 0, normEq4 = 0;\n const normEq5 = 0; // eq5 proportion is always 0 per the reference\n if (!Number.isNaN(availDistEq1)) {\n nExisting++;\n normEq1 = availDistEq1 * (curEq1 / maxSevEq1);\n }\n if (!Number.isNaN(availDistEq2)) {\n nExisting++;\n normEq2 = availDistEq2 * (curEq2 / maxSevEq2);\n }\n if (!Number.isNaN(availDistEq3eq6)) {\n nExisting++;\n normEq3eq6 = availDistEq3eq6 * (curEq3eq6 / maxSevEq3eq6);\n }\n if (!Number.isNaN(availDistEq4)) {\n nExisting++;\n normEq4 = availDistEq4 * (curEq4 / maxSevEq4);\n }\n if (!Number.isNaN(availDistEq5)) {\n nExisting++;\n }\n\n const meanDistance =\n nExisting === 0 ? 0 : (normEq1 + normEq2 + normEq3eq6 + normEq4 + normEq5) / nExisting;\n\n value -= meanDistance;\n if (value < 0) {\n value = 0.0;\n }\n if (value > 10) {\n value = 10.0;\n }\n return Math.round(value * 10) / 10;\n}\n\n/** Remove the E: segment so the Base score reflects the worst-case default (E:X → E:A). */\nfunction cvss40StripExploitMaturity(vector: string): string {\n return vector\n .split('/')\n .filter((seg) => !seg.startsWith('E:'))\n .join('/');\n}\n\n/**\n * Compute the CVSS 4.0 score for a vector string, split into a Base score\n * (`baseScore`, with Exploit Maturity stripped to its worst-case default E:A) and\n * a Threat score (`temporalScore`, with the E value present in the vector). For a\n * base-only vector the two are equal. The peer of the Go `ComputeCvss40Score`,\n * kept byte-identical via the shared data file and parity fixture.\n *\n * @throws if the version is not 4.0, a required base metric is missing, or any\n * metric value is out of the 4.0 enum.\n */\nexport function computeCvss40Score(vector: string): CvssScore {\n const { version } = parseCvssVector(vector);\n if (version !== '4.0') {\n throw new Error(`cvss: 4.0 score compute supports only CVSS 4.0, got \"${version}\"`);\n }\n const { valid, errors } = validateCvssVector(vector, '4.0');\n if (!valid) {\n throw new Error(`cvss: invalid 4.0 vector: ${errors.join('; ')}`);\n }\n\n const temporalScore = cvss40Score(parseCvssVector(vector).metrics);\n const baseScore = cvss40Score(parseCvssVector(cvss40StripExploitMaturity(vector)).metrics);\n return { version: '4.0', baseScore, temporalScore };\n}\n","/**\n * CPE 2.3 URI parser for HDF converters.\n *\n * Common Platform Enumeration (CPE) 2.3 URIs identify affected products.\n * Container scanners (grype, twistlock, snyk) emit them in vulnerability\n * findings. This module provides an accept-and-warn parser: any input that\n * carries the `cpe:2.3:` prefix is best-effort parsed; deviations from the\n * canonical 13-field form are reported via `warnings` rather than rejected.\n *\n * Canonical form:\n * `cpe:2.3:part:vendor:product:version:update:edition:language:sw_edition:target_sw:target_hw:other`\n *\n * Reference: NIST IR 7695 (https://csrc.nist.gov/publications/detail/nistir/7695/final)\n */\n\n/** Valid CPE 2.3 `part` values. */\nexport type CpePart = 'a' | 'o' | 'h' | '*';\n\n/**\n * Parsed CPE 2.3 URI.\n *\n * `warnings` collects any deviations from the canonical form (truncation,\n * extra fields, unknown `part`). An empty array means the input was a strict\n * 13-field, valid-part CPE.\n */\nexport interface ParsedCpe {\n /** The original input string, unmodified. */\n raw: string;\n /** Application kind. `'a'` (app), `'o'` (OS), `'h'` (hardware), `'*'` (any). */\n part: CpePart | string;\n vendor: string;\n product: string;\n version: string;\n update: string;\n edition: string;\n language: string;\n swEdition: string;\n targetSw: string;\n targetHw: string;\n other: string;\n /** Deviation messages; empty when the input is a canonical 13-field CPE. */\n warnings: string[];\n}\n\nconst CPE_PREFIX = 'cpe:2.3:';\nconst EXPECTED_TOTAL_FIELDS = 13; // \"cpe\", \"2.3\", and 11 product fields = 13 colon-separated tokens\nconst PRODUCT_FIELD_COUNT = 11; // part + 10 attribute fields\nconst VALID_PARTS = new Set(['a', 'o', 'h', '*']);\n\n/**\n * Split a CPE 2.3 body on unescaped `:` separators.\n *\n * Per CPE 2.3 spec section 6.1.2.4, `:` and `\\` inside a field must be\n * escaped as `\\:` and `\\\\`. We respect those escapes during the split and\n * unescape the result.\n */\nfunction splitOnUnescapedColons(body: string): string[] {\n const result: string[] = [];\n let current = '';\n for (let i = 0; i < body.length; i++) {\n const ch = body[i];\n if (ch === '\\\\' && i + 1 < body.length) {\n const next = body[i + 1];\n // Preserve known escapes (\\: and \\\\) as a literal next character.\n if (next === ':' || next === '\\\\') {\n current += next;\n i++;\n continue;\n }\n // Unknown escape — keep the backslash and continue.\n current += ch;\n continue;\n }\n if (ch === ':') {\n result.push(current);\n current = '';\n continue;\n }\n current += ch;\n }\n result.push(current);\n return result;\n}\n\n/**\n * Parse a CPE 2.3 URI.\n *\n * Accept-and-warn semantics:\n * - Input missing the `cpe:2.3:` prefix → returns `null`.\n * - Input with the prefix but fewer than 11 product fields → fields are\n * padded with `\"*\"` and a `truncated:` warning is added.\n * - Input with more than 11 product fields → extras are dropped and an\n * `\"extra fields ignored\"` warning is added.\n * - Unknown `part` value → kept as-is in the result, `\"unknown part: X\"`\n * warning is added.\n * - `\\:` and `\\\\` escapes inside fields are honored during splitting and\n * unescaped in the returned field values.\n *\n * @param cpeUri - CPE 2.3 URI string\n * @returns Parsed CPE with warnings, or `null` if input lacks the prefix\n *\n * @example\n * ```typescript\n * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k:*:*:*:*:*:*:*');\n * // { part: 'a', vendor: 'openssl', product: 'openssl', version: '1.1.1k', ... warnings: [] }\n *\n * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k');\n * // truncated input → padded with '*', warnings: ['truncated: expected 13 colon-separated fields, got 6']\n *\n * parseCpe('openssl:1.1.1k'); // null — no prefix\n * ```\n */\nexport function parseCpe(cpeUri: string): ParsedCpe | null {\n if (!cpeUri.startsWith(CPE_PREFIX)) {\n return null;\n }\n\n const warnings: string[] = [];\n const body = cpeUri.slice(CPE_PREFIX.length);\n\n // Special case: bare prefix `cpe:2.3:` — all fields empty, still warn.\n // This is the \"I have nothing useful here\" form; padding with \"*\" would\n // suggest the parser inferred wildcards, which it didn't.\n const isBarePrefix = body.length === 0;\n\n const bodyParts = isBarePrefix ? [] : splitOnUnescapedColons(body);\n\n // Total colon-separated fields including the prefix \"cpe\" and \"2.3\" markers.\n const totalFields = isBarePrefix ? 2 : 2 + bodyParts.length;\n\n if (bodyParts.length > PRODUCT_FIELD_COUNT) {\n warnings.push('extra fields ignored');\n } else if (bodyParts.length < PRODUCT_FIELD_COUNT) {\n warnings.push(\n `truncated: expected ${EXPECTED_TOTAL_FIELDS} colon-separated fields, got ${totalFields}`,\n );\n }\n\n // Pad missing fields. For a bare prefix, pad with \"\" (no inferred data).\n // For partial input, pad missing trailing fields with \"*\" (wildcard).\n const padValue = isBarePrefix ? '' : '*';\n const fields: string[] = bodyParts.slice(0, PRODUCT_FIELD_COUNT);\n while (fields.length < PRODUCT_FIELD_COUNT) {\n fields.push(padValue);\n }\n\n // fields is padded to PRODUCT_FIELD_COUNT above, so every slot is present;\n // the destructuring defaults only satisfy the compiler's indexed-access check.\n const [\n part = '',\n vendor = '',\n product = '',\n version = '',\n update = '',\n edition = '',\n language = '',\n swEdition = '',\n targetSw = '',\n targetHw = '',\n other = '',\n ] = fields;\n\n if (!VALID_PARTS.has(part)) {\n warnings.push(`unknown part: ${part}`);\n }\n\n return {\n raw: cpeUri,\n part,\n vendor,\n product,\n version,\n update,\n edition,\n language,\n swEdition,\n targetSw,\n targetHw,\n other,\n warnings,\n };\n}\n","/**\n * Package URL (PURL) parser for SBOM and CVE-scanner ingestion.\n *\n * PURL is the canonical package identifier used by CycloneDX, SPDX, OSV,\n * GitHub Advisory Database, and most modern scanners. Spec:\n * https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst\n *\n * Grammar: `pkg:type/namespace/name@version?qualifiers#subpath`\n *\n * This parser is intentionally accept-and-warn. Scanners occasionally emit\n * slightly malformed PURLs (qualifier with no `=`, missing name, etc.) and\n * we'd rather record a warning than fail ingestion.\n */\n\n/**\n * Result of parsing a PURL string.\n */\nexport interface ParsedPurl {\n /** Original input string, unmodified. */\n raw: string;\n /** Package type (e.g., \"npm\", \"rpm\", \"pypi\", \"maven\"). Lowercased. */\n type: string;\n /** Namespace segment (e.g., \"redhat\" for RPM, \"org.apache...\" for Maven). */\n namespace: string | null;\n /** Package name. Empty string (with a warning) if the input lacks one. */\n name: string;\n /** Version after the `@` separator. URL-decoded. */\n version: string | null;\n /** Qualifier key-value pairs from the `?` segment. */\n qualifiers: Map<string, string>;\n /** Subpath from the `#` fragment. */\n subpath: string | null;\n /** Deviations or oddities encountered during parsing. */\n warnings: string[];\n}\n\nconst PURL_PREFIX = 'pkg:';\n\n/**\n * Parse a Package URL string.\n *\n * Returns `null` only when the input is missing the mandatory `pkg:` prefix\n * or the type segment. Other deviations are surfaced via `warnings` on the\n * returned object.\n *\n * @param purlStr - PURL string, e.g. `pkg:npm/lodash@4.17.21`\n * @returns Parsed PURL or `null` if the input is unrecoverably malformed\n *\n * @example\n * ```typescript\n * const r = parsePurl('pkg:rpm/redhat/openssl@1.1.1k-7.el8_4?arch=x86_64');\n * r?.type; // \"rpm\"\n * r?.namespace; // \"redhat\"\n * r?.name; // \"openssl\"\n * r?.qualifiers.get('arch'); // \"x86_64\"\n * ```\n */\nexport function parsePurl(purlStr: string): ParsedPurl | null {\n if (!purlStr || !purlStr.startsWith(PURL_PREFIX)) {\n return null;\n }\n\n const raw = purlStr;\n const warnings: string[] = [];\n\n // Strip prefix. Per spec, scheme is case-insensitive but only `pkg:` is\n // valid; we already matched it.\n let remainder = purlStr.slice(PURL_PREFIX.length);\n\n // Per spec, leading slashes after the scheme are stripped. This also means\n // `pkg:/foo` and `pkg:foo` are equivalent, but `pkg:/` with no following\n // type segment is still invalid.\n while (remainder.startsWith('/')) {\n remainder = remainder.slice(1);\n }\n\n if (remainder.length === 0) {\n return null;\n }\n\n // Extract subpath (fragment). Per spec, the fragment is everything after\n // the first `#`. Splitting before qualifiers keeps `#` inside qualifier\n // values out of scope (which is fine — qualifier values do not legally\n // contain raw `#`; they would be percent-encoded).\n let subpath: string | null = null;\n const hashIdx = remainder.indexOf('#');\n if (hashIdx !== -1) {\n subpath = remainder.slice(hashIdx + 1);\n remainder = remainder.slice(0, hashIdx);\n if (subpath.length === 0) {\n subpath = null;\n }\n }\n\n // Extract qualifiers (query string). Everything after the first `?`.\n const qualifiers = new Map<string, string>();\n const qIdx = remainder.indexOf('?');\n if (qIdx !== -1) {\n const qStr = remainder.slice(qIdx + 1);\n remainder = remainder.slice(0, qIdx);\n parseQualifiers(qStr, qualifiers, warnings);\n }\n\n // Strip a trailing slash from the path portion. Spec allows it; we silently\n // normalize.\n while (remainder.endsWith('/')) {\n remainder = remainder.slice(0, -1);\n }\n\n // Type is everything up to the first `/`.\n const firstSlash = remainder.indexOf('/');\n let type: string;\n let pathPart: string;\n if (firstSlash === -1) {\n type = remainder;\n pathPart = '';\n } else {\n type = remainder.slice(0, firstSlash);\n pathPart = remainder.slice(firstSlash + 1);\n }\n\n if (type.length === 0) {\n return null;\n }\n type = type.toLowerCase();\n\n // Split version off the path part. Per spec, the version separator is the\n // LAST `@` in the path — names may contain `@` (e.g., npm scoped packages\n // when not percent-encoded).\n let version: string | null = null;\n const lastAt = pathPart.lastIndexOf('@');\n let nameAndNs = pathPart;\n if (lastAt !== -1) {\n version = pathPart.slice(lastAt + 1);\n nameAndNs = pathPart.slice(0, lastAt);\n version = safeDecode(version);\n }\n\n // Namespace is everything before the last `/` in the name+namespace\n // section; the final segment is the name.\n let namespace: string | null = null;\n let name: string;\n const lastSlash = nameAndNs.lastIndexOf('/');\n if (lastSlash === -1) {\n name = nameAndNs;\n } else {\n namespace = nameAndNs.slice(0, lastSlash);\n name = nameAndNs.slice(lastSlash + 1);\n }\n\n // Decode percent-encoded segments.\n if (namespace !== null) {\n // Decode each path segment independently to preserve embedded `/`.\n namespace = namespace.split('/').map(safeDecode).join('/');\n }\n name = safeDecode(name);\n\n if (name.length === 0) {\n warnings.push('PURL is missing the name segment');\n }\n\n // Normalize empty namespace to null.\n if (namespace !== null && namespace.length === 0) {\n namespace = null;\n }\n\n // Normalize empty version to null.\n if (version !== null && version.length === 0) {\n version = null;\n }\n\n return {\n raw,\n type,\n namespace,\n name,\n version,\n qualifiers,\n subpath,\n warnings,\n };\n}\n\n/**\n * Parse the `?key=value&key=value` qualifier string. Empty segments are\n * skipped silently; segments with no `=` are recorded with empty value and\n * a warning. Values are URL-decoded.\n */\nfunction parseQualifiers(\n qStr: string,\n out: Map<string, string>,\n warnings: string[],\n): void {\n const parts = qStr.split('&');\n for (const part of parts) {\n if (part.length === 0) {\n continue;\n }\n const eq = part.indexOf('=');\n if (eq === -1) {\n out.set(part, '');\n warnings.push(`qualifier \"${part}\" has no value`);\n continue;\n }\n const key = part.slice(0, eq);\n const value = part.slice(eq + 1);\n out.set(key, safeDecode(value));\n }\n}\n\n/**\n * decodeURIComponent that returns the original string on failure rather than\n * throwing. PURLs in the wild occasionally contain stray `%` characters that\n * aren't legitimate percent-encoding.\n */\nfunction safeDecode(s: string): string {\n try {\n return decodeURIComponent(s);\n } catch {\n return s;\n }\n}\n","// Schema-free input-size guard — the TS peer of hdf-utilities/go/size.go, kept\n// at behavioural parity. The engine loader runs it as its FIRST operation,\n// before any parse, to defend against memory exhaustion on untrusted input.\n\n/** Default maximum input size (50 MB), matching the Go DefaultMaxInputSize. */\nexport const DEFAULT_MAX_INPUT_SIZE = 50 * 1024 * 1024;\n\n/**\n * validateInputSize throws if input exceeds maxSize bytes (maxSize <= 0 uses\n * DEFAULT_MAX_INPUT_SIZE). Byte length is measured on the UTF-8 encoding for a\n * string input, so it matches the Go []byte length for the same document.\n */\nexport function validateInputSize(input: string | Uint8Array, maxSize = 0): void {\n const limit = maxSize > 0 ? maxSize : DEFAULT_MAX_INPUT_SIZE;\n const len = typeof input === 'string' ? new TextEncoder().encode(input).length : input.length;\n if (len > limit) {\n throw new Error(`input exceeds maximum allowed size of ${limit} bytes (${len} bytes provided)`);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["cvss40Data"],"sources":["../src/severity/index.ts","../src/status/index.ts","../src/cvss/cvss40-data.json","../src/cvss/index.ts","../src/cpe/index.ts","../src/purl/index.ts","../src/size/index.ts"],"sourcesContent":["/**\n * Severity-to-impact mapping utilities.\n *\n * These define the canonical bidirectional mapping between severity labels\n * (critical, high, medium, low, informational) and HDF impact scores (0.0–1.0),\n * aligned with CVSS 3.x severity bands normalized to 0–1.\n *\n * Threshold ranges (used by impactToSeverity):\n * >=0.9 = critical (CVSS 9.0–10.0)\n * [0.7, 0.9) = high (CVSS 7.0–8.9)\n * [0.4, 0.7) = medium (CVSS 4.0–6.9)\n * (0.0, 0.4) = low (CVSS 0.1–3.9)\n * 0.0 = informational (CVSS 0.0)\n */\n\nconst severityMap: Record<string, number> = {\n critical: 0.9,\n high: 0.7,\n medium: 0.5,\n low: 0.3,\n info: 0.0,\n none: 0.0,\n informational: 0.0,\n information: 0.0,\n};\n\n/**\n * Map a severity string to an impact score.\n *\n * @param severity - Severity level (case-insensitive), or null for no severity assessment\n * @returns Impact score between 0.0 and 1.0; null when input is null; defaults to 0.5 for unrecognized values\n */\nexport function severityToImpact(severity: null): null;\nexport function severityToImpact(severity: string | undefined): number;\nexport function severityToImpact(severity: string | null | undefined): number | null;\nexport function severityToImpact(severity: string | null | undefined): number | null {\n if (severity === null) return null;\n // Absent field: match Go's zero-value semantics (\"\" → the 0.5 default)\n // rather than throwing on .toLowerCase() of undefined.\n const normalized = (severity ?? '').toLowerCase();\n // Own-property guard: a prototype-named token (\"constructor\") must take the\n // default like any unknown word, not return an inherited Object member.\n return Object.prototype.hasOwnProperty.call(severityMap, normalized)\n ? severityMap[normalized]!\n : 0.5;\n}\n\n/**\n * Map severity to impact, checking custom aliases first, then the shared\n * standard map, then the caller default. Case-insensitive. Use for tools with\n * non-standard severity labels (sonarqube BLOCKER, veracode numeric levels,\n * grype critical=0.9). Absent (undefined/null) values take the default —\n * matching the Go peer's zero-value \"\" behavior. TS mirror of Go's\n * SeverityToImpactWithAliases; converters must not hand-roll local severity\n * maps around it (that fork is how Go/TS impact divergence happens).\n */\nexport function severityToImpactWithAliases(\n severity: string | null | undefined,\n aliases: Record<string, number>,\n defaultVal: number,\n): number {\n const lower = (severity ?? '').toLowerCase();\n // Own-property guards: prototype-named tokens (\"constructor\") must take the\n // default like any unknown word, not resolve to an inherited Object member.\n if (Object.prototype.hasOwnProperty.call(aliases, lower)) return aliases[lower]!;\n if (Object.prototype.hasOwnProperty.call(severityMap, lower)) return severityMap[lower]!;\n return defaultVal;\n}\n\n/**\n * Round an HDF impact value to 2 decimal places — its canonical precision —\n * eliminating the representation noise that binary float division leaves behind\n * (e.g. 9.8 / 10 = 0.9800000000000001). Impact is defined on 0.0–1.0 with a\n * natural 0.01 grid (a 1-decimal CVSS score / 10), so this is lossless in\n * intent. Use it wherever impact is COMPUTED (divided or otherwise\n * arithmetically derived), not when assigned from a literal band.\n */\nexport function roundImpact(x: number): number {\n return Math.round(x * 100) / 100;\n}\n\n/**\n * Map an impact score to a severity string.\n *\n * @param impact - Impact score (0.0 to 1.0), or null for no impact assessment\n * @returns Severity level string, or null when input is null\n */\nexport function impactToSeverity(impact: null): null;\nexport function impactToSeverity(impact: number): string;\nexport function impactToSeverity(impact: number | null): string | null;\nexport function impactToSeverity(impact: number | null): string | null {\n if (impact === null) return null;\n if (impact >= 0.9) return 'critical';\n if (impact >= 0.7) return 'high';\n if (impact >= 0.4) return 'medium';\n if (impact > 0.0) return 'low';\n return 'informational';\n}\n\n/**\n * Map a raw CVSS base score (0.0–10.0) to a FIRST qualitative severity band.\n *\n * Bands per FIRST CVSS v3.x / v4.0 spec:\n * none = 0.0\n * low = 0.1 – 3.9\n * medium = 4.0 – 6.9\n * high = 7.0 – 8.9\n * critical = 9.0 – 10.0\n *\n * Out-of-range inputs are clamped: scores < 0.1 → \"none\" (band floor),\n * scores > 10.0 → \"critical\". This matches scanner behavior of treating\n * malformed scores as informational/critical extremes rather than throwing.\n *\n * @param score - CVSS base score\n * @returns FIRST severity band label\n */\nexport function cvssScoreToSeverity(\n score: number,\n): 'none' | 'low' | 'medium' | 'high' | 'critical' {\n if (!Number.isFinite(score) || score < 0.1) return 'none';\n if (score < 4.0) return 'low';\n if (score < 7.0) return 'medium';\n if (score < 9.0) return 'high';\n return 'critical';\n}\n\n// Severity vocabulary values that assert \"no rating was made\" (grype Unknown,\n// Dependency-Track UNASSIGNED, Microsoft Graph unSpecified). Distinct from the\n// zero-impact RATED tier (info/none/informational) and from grype's\n// negligible, which is the lowest rating, not an absent one.\nconst unratedSeverityTokens = new Set(['unknown', 'unassigned', 'unspecified']);\n\n/**\n * Report whether a source severity carries no rating at all: the field is\n * absent/blank, or the token is an explicit no-rating value. Tokens the\n * vocabulary simply doesn't recognize are NOT unrated — an unknown word is not\n * an assertion of unratedness. Converters use this to emit the shared unrated\n * marker so a defaulted impact stays distinguishable from a genuine medium.\n */\nexport function isUnratedSeverity(severity?: string | null): boolean {\n if (severity === undefined || severity === null) return true;\n const s = severity.trim().toLowerCase();\n return s === '' || unratedSeverityTokens.has(s);\n}\n","/**\n * Canonical HDF result-status ordering and effective-status computation.\n *\n * This is the single source of truth for worst-wins roll-up and\n * effective-status selection across hdf-libs (mirrored in Go as\n * hdf-utilities/go status.go). The precedence rules are published in\n * site/docs/architecture/status-determination.md; do not re-implement them.\n */\n\nimport { parseTimestamp } from '../string/index.js';\n\n/** Canonical worst-wins ordering, worst first. */\nexport const STATUS_SEVERITY_ORDER = [\n 'error',\n 'failed',\n 'passed',\n 'notApplicable',\n 'notReviewed',\n] as const;\n\n/** A member of the canonical ordering, as a literal union. */\nexport type StatusSeverity = (typeof STATUS_SEVERITY_ORDER)[number];\n\n/** Widened view for lookups over arbitrary input strings. */\nconst SEVERITY_LOOKUP: readonly string[] = STATUS_SEVERITY_ORDER;\n\n/** Severity rank of a status: higher = worse. Unknown statuses rank -1. */\nexport function statusRank(status: string): number {\n const idx = SEVERITY_LOOKUP.indexOf(status);\n return idx === -1 ? -1 : SEVERITY_LOOKUP.length - 1 - idx;\n}\n\n/**\n * Rolls a list of result statuses up to the worst one. An empty list (or a\n * list of only unknown statuses) rolls up to \"notReviewed\".\n */\nexport function worstStatus(statuses: readonly string[]): string {\n let worst = 'notReviewed';\n let worstRank = -1;\n for (const s of statuses) {\n const r = statusRank(s);\n if (r > worstRank) {\n worstRank = r;\n worst = s;\n }\n }\n return worst;\n}\n\n/**\n * Neutral shape of a status override for effective-status computation.\n * Callers map their concrete types (schema objects or raw JSON) onto it.\n */\nexport interface StatusOverrideInput {\n /** Empty/undefined means the override carries no status and never governs. */\n status?: string;\n /** RFC3339 timestamp; undefined sorts earliest. */\n appliedAt?: string;\n /** RFC3339 timestamp; undefined means the override never expires. */\n expiresAt?: string;\n}\n\nfunction isExpired(override: StatusOverrideInput, ref: Date): boolean {\n if (!override.expiresAt) return false;\n // parseTimestamp normalizes zone-less values to UTC (a raw `new Date` would\n // read them as host-local and make expiry host-timezone-dependent).\n const expires = parseTimestamp(override.expiresAt);\n return expires !== null && expires <= ref;\n}\n\n/**\n * Selects the override that governs a requirement: the most recently applied\n * (by appliedAt) non-expired override that carries a status — matching the\n * schema's definition of disposition (\"the most recent non-expired\n * override\"). Returns undefined when no override governs.\n */\nexport function governingStatusOverride(\n overrides: readonly StatusOverrideInput[],\n referenceTimestamp?: string\n): StatusOverrideInput | undefined {\n const i = governingStatusOverrideIndex(overrides, referenceTimestamp);\n return i >= 0 ? overrides[i] : undefined;\n}\n\n/**\n * Returns the index of the most recently applied (by appliedAt) non-expired\n * override among those for which `eligible` returns true, or -1 when none\n * qualifies. Generalizes governing-override selection to per-field\n * eligibility: the schema defines effectiveStatus, effectiveImpact, and\n * disposition each as \"the most recent non-expired override\" carrying the\n * relevant field, so callers pass the field-presence check as the predicate.\n */\nexport function governingOverrideIndex(\n overrides: readonly StatusOverrideInput[],\n eligible: (index: number) => boolean,\n referenceTimestamp?: string\n): number {\n const ref = refTime(referenceTimestamp);\n let governing = -1;\n for (let i = 0; i < overrides.length; i++) {\n const override = overrides[i];\n if (!override || !eligible(i) || isExpired(override, ref)) continue;\n if (governing === -1 || appliedTime(override) > appliedTime(overrides[governing]!)) {\n governing = i;\n }\n }\n return governing;\n}\n\n/**\n * Index variant of {@link governingStatusOverride}: returns -1 when no\n * override governs. Callers holding richer concrete override types use the\n * index to recover their own object.\n */\nexport function governingStatusOverrideIndex(\n overrides: readonly StatusOverrideInput[],\n referenceTimestamp?: string\n): number {\n return governingOverrideIndex(overrides, (i) => !!overrides[i]?.status, referenceTimestamp);\n}\n\nfunction appliedTime(override: StatusOverrideInput): number {\n if (!override.appliedAt) return Number.NEGATIVE_INFINITY;\n return parseTimestamp(override.appliedAt)?.getTime() ?? Number.NEGATIVE_INFINITY;\n}\n\nfunction refTime(referenceTimestamp?: string): Date {\n const parsed = referenceTimestamp ? parseTimestamp(referenceTimestamp) : null;\n return parsed ?? new Date();\n}\n\n/** Neutral shape of a requirement for effective-status computation. */\nexport interface EffectiveStatusInput {\n impact: number;\n resultStatuses?: readonly string[];\n overrides?: readonly StatusOverrideInput[];\n}\n\n/**\n * Determines a requirement's effective status — the single canonical\n * implementation of the ladder in status-determination.md:\n *\n * 1. the governing (most recent non-expired) status override's status\n * 2. else result roll-up \"error\" → \"error\"\n * 3. else impact === 0 → \"notApplicable\"\n * 4. else worst-wins roll-up of the result statuses (empty → \"notReviewed\")\n *\n * The stored effectiveStatus field is an output cache and is never read: the\n * only sanctioned channel for status to diverge from the results is a\n * governing override.\n */\nexport function computeEffectiveStatus(\n input: EffectiveStatusInput,\n referenceTimestamp?: string\n): string {\n const governing = governingStatusOverride(input.overrides ?? [], referenceTimestamp);\n if (governing?.status) return governing.status;\n const rolledUp = worstStatus(input.resultStatuses ?? []);\n if (rolledUp === 'error') {\n return 'error';\n }\n if (input.impact === 0) {\n return 'notApplicable';\n }\n return rolledUp;\n}\n","","/**\n * CVSS vector parsing and validation utilities.\n *\n * Supports CVSS 2.0 (legacy, no prefix), 3.0, 3.1, and 4.0 vector strings.\n * Parsing is permissive: it splits any well-shaped key:value pair separated\n * by `/`. Validation is strict per the FIRST grammar for each version.\n *\n * References:\n * - CVSS 2.0: https://www.first.org/cvss/v2/guide\n * - CVSS 3.1: https://www.first.org/cvss/v3.1/specification-document\n * - CVSS 4.0: https://www.first.org/cvss/v4.0/specification-document\n */\n\nimport cvss40Data from './cvss40-data.json';\n\nexport interface ParsedCvssVector {\n /** \"2.0\" | \"3.0\" | \"3.1\" | \"4.0\" | \"unknown\" */\n version: string;\n /** Metric name -> metric value (e.g. \"AV\" -> \"N\"). Order-preserving via Map. */\n metrics: Map<string, string>;\n}\n\nexport interface CvssValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Allowed values for each metric, keyed by CVSS major version.\n *\n * Sourced from FIRST specifications (linked above). Only metrics present\n * in a given version's grammar appear in that version's map; unknown metrics\n * encountered during validation are ignored (forward-compat).\n */\nconst METRIC_VALUES: Record<string, Record<string, string[]>> = {\n '2.0': {\n AV: ['L', 'A', 'N'],\n AC: ['H', 'M', 'L'],\n Au: ['M', 'S', 'N'],\n C: ['N', 'P', 'C'],\n I: ['N', 'P', 'C'],\n A: ['N', 'P', 'C'],\n // Temporal\n E: ['U', 'POC', 'F', 'H', 'ND'],\n RL: ['OF', 'TF', 'W', 'U', 'ND'],\n RC: ['UC', 'UR', 'C', 'ND'],\n // Environmental\n CDP: ['N', 'L', 'LM', 'MH', 'H', 'ND'],\n TD: ['N', 'L', 'M', 'H', 'ND'],\n CR: ['L', 'M', 'H', 'ND'],\n IR: ['L', 'M', 'H', 'ND'],\n AR: ['L', 'M', 'H', 'ND'],\n },\n '3.0': {\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'R'],\n S: ['U', 'C'],\n C: ['N', 'L', 'H'],\n I: ['N', 'L', 'H'],\n A: ['N', 'L', 'H'],\n // Temporal\n E: ['X', 'U', 'P', 'F', 'H'],\n RL: ['X', 'O', 'T', 'W', 'U'],\n RC: ['X', 'U', 'R', 'C'],\n // Environmental\n CR: ['X', 'L', 'M', 'H'],\n IR: ['X', 'L', 'M', 'H'],\n AR: ['X', 'L', 'M', 'H'],\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'R'],\n MS: ['X', 'U', 'C'],\n MC: ['X', 'N', 'L', 'H'],\n MI: ['X', 'N', 'L', 'H'],\n MA: ['X', 'N', 'L', 'H'],\n },\n '3.1': {\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'R'],\n S: ['U', 'C'],\n C: ['N', 'L', 'H'],\n I: ['N', 'L', 'H'],\n A: ['N', 'L', 'H'],\n // Temporal\n E: ['X', 'U', 'P', 'F', 'H'],\n RL: ['X', 'O', 'T', 'W', 'U'],\n RC: ['X', 'U', 'R', 'C'],\n // Environmental\n CR: ['X', 'L', 'M', 'H'],\n IR: ['X', 'L', 'M', 'H'],\n AR: ['X', 'L', 'M', 'H'],\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'R'],\n MS: ['X', 'U', 'C'],\n MC: ['X', 'N', 'L', 'H'],\n MI: ['X', 'N', 'L', 'H'],\n MA: ['X', 'N', 'L', 'H'],\n },\n '4.0': {\n // Base\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n AT: ['N', 'P'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'P', 'A'],\n VC: ['H', 'L', 'N'],\n VI: ['H', 'L', 'N'],\n VA: ['H', 'L', 'N'],\n SC: ['H', 'L', 'N'],\n SI: ['H', 'L', 'N'],\n SA: ['H', 'L', 'N'],\n // Threat\n E: ['X', 'A', 'P', 'U'],\n // Environmental (modified base) — same enums as their base counterparts plus X\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MAT: ['X', 'N', 'P'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'P', 'A'],\n MVC: ['X', 'H', 'L', 'N'],\n MVI: ['X', 'H', 'L', 'N'],\n MVA: ['X', 'H', 'L', 'N'],\n MSC: ['X', 'H', 'L', 'N'],\n MSI: ['X', 'S', 'H', 'L', 'N'],\n MSA: ['X', 'S', 'H', 'L', 'N'],\n // Requirements\n CR: ['X', 'H', 'M', 'L'],\n IR: ['X', 'H', 'M', 'L'],\n AR: ['X', 'H', 'M', 'L'],\n // Supplemental\n S: ['X', 'N', 'P'],\n AU: ['X', 'N', 'Y'],\n R: ['X', 'A', 'U', 'I'],\n V: ['X', 'D', 'C'],\n RE: ['X', 'L', 'M', 'H'],\n U: ['X', 'Clear', 'Green', 'Amber', 'Red'],\n },\n};\n\nconst REQUIRED_METRICS: Record<string, string[]> = {\n '2.0': ['AV', 'AC', 'Au', 'C', 'I', 'A'],\n '3.0': ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A'],\n '3.1': ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A'],\n '4.0': ['AV', 'AC', 'AT', 'PR', 'UI', 'VC', 'VI', 'VA', 'SC', 'SI', 'SA'],\n};\n\n/**\n * Parse a CVSS vector string into version + metric map.\n *\n * Version detection rule: a leading `CVSS:X.Y` segment yields version \"X.Y\".\n * Otherwise (legacy v2 vectors), version is \"2.0\". Malformed input yields\n * \"unknown\" with an empty metric map; no exceptions are thrown.\n *\n * @param vector - CVSS vector string (e.g. \"CVSS:3.1/AV:N/AC:L/...\")\n * @returns Parsed version + metric map\n */\nexport function parseCvssVector(vector: string): ParsedCvssVector {\n const metrics = new Map<string, string>();\n if (typeof vector !== 'string' || vector.length === 0) {\n return { version: 'unknown', metrics };\n }\n\n const segments = vector.split('/').filter((s) => s.length > 0);\n if (segments.length === 0) {\n return { version: 'unknown', metrics };\n }\n\n let version = '2.0';\n let start = 0;\n const first = segments[0];\n if (first === undefined) {\n return { version: 'unknown', metrics };\n }\n if (first.startsWith('CVSS:')) {\n version = first.slice(5);\n start = 1;\n } else if (!first.includes(':')) {\n // No prefix AND first segment isn't a key:value pair — bail.\n return { version: 'unknown', metrics };\n }\n\n for (let i = start; i < segments.length; i++) {\n const seg = segments[i];\n if (seg === undefined) {\n continue;\n }\n const colon = seg.indexOf(':');\n if (colon <= 0 || colon === seg.length - 1) {\n // Skip malformed segments (no colon, empty key, or empty value).\n continue;\n }\n const key = seg.slice(0, colon);\n const value = seg.slice(colon + 1);\n metrics.set(key, value);\n }\n\n return { version, metrics };\n}\n\n/**\n * Validate a CVSS vector string against the FIRST grammar for its version.\n *\n * Checks:\n * - Version is one of the supported set (2.0, 3.0, 3.1, 4.0).\n * - All required base metrics for the version are present.\n * - All known metric values are within the allowed enum.\n *\n * Unknown metric keys are tolerated (forward-compat with future minor versions\n * or scanner-specific extensions). To check a v2 vector without the CVSS:\n * prefix, callers may pass version=\"2.0\" explicitly; otherwise the parser\n * defaults to \"2.0\" on prefix absence.\n *\n * @param vector - CVSS vector string\n * @param version - Optional explicit version override (else inferred)\n * @returns `{ valid, errors[] }` — never throws\n */\nexport function validateCvssVector(\n vector: string,\n version?: string,\n): CvssValidationResult {\n const errors: string[] = [];\n\n if (typeof vector !== 'string' || vector.length === 0) {\n return { valid: false, errors: ['vector is empty'] };\n }\n\n const parsed = parseCvssVector(vector);\n const v = version ?? parsed.version;\n\n const grammar = METRIC_VALUES[v];\n const required = REQUIRED_METRICS[v];\n if (!grammar || !required) {\n errors.push(`unsupported CVSS version: ${v}`);\n return { valid: false, errors };\n }\n\n for (const req of required) {\n if (!parsed.metrics.has(req)) {\n errors.push(`missing required metric: ${req}`);\n }\n }\n\n for (const [key, value] of parsed.metrics.entries()) {\n const allowed = grammar[key];\n if (!allowed) {\n // Unknown metric: forward-compat — no error.\n continue;\n }\n if (!allowed.includes(value)) {\n errors.push(`invalid value for metric ${key}: ${value} (allowed: ${allowed.join(',')})`);\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n/** A computed CVSS score. */\nexport interface CvssScore {\n /** CVSS major.minor version the score was computed under (currently always \"3.1\"). */\n version: string;\n /** Base score (0.0–10.0). */\n baseScore: number;\n /**\n * Temporal (Threat) score (0.0–10.0). Equals baseScore when no temporal\n * metrics (E/RL/RC) are present, per the CVSS 3.1 spec.\n */\n temporalScore: number;\n}\n\n// CVSS 3.1 metric weights (FIRST v3.1 specification §7). Privileges Required is\n// scope-dependent, hence two tables.\nconst CVSS31_AV: Record<string, number> = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };\nconst CVSS31_AC: Record<string, number> = { L: 0.77, H: 0.44 };\nconst CVSS31_UI: Record<string, number> = { N: 0.85, R: 0.62 };\nconst CVSS31_CIA: Record<string, number> = { N: 0.0, L: 0.22, H: 0.56 };\nconst CVSS31_PR_UNCHANGED: Record<string, number> = { N: 0.85, L: 0.62, H: 0.27 };\nconst CVSS31_PR_CHANGED: Record<string, number> = { N: 0.85, L: 0.68, H: 0.5 };\nconst CVSS31_E: Record<string, number> = { X: 1.0, H: 1.0, F: 0.97, P: 0.94, U: 0.91 };\nconst CVSS31_RL: Record<string, number> = { X: 1.0, U: 1.0, W: 0.97, T: 0.96, O: 0.95 };\nconst CVSS31_RC: Record<string, number> = { X: 1.0, C: 1.0, R: 0.96, U: 0.92 };\n\n/**\n * CVSS 3.1 Roundup (spec §7.4): the smallest number, to one decimal place, that\n * is >= the input — computed via integer math to avoid the floating-point edge\n * cases 3.0 rounding suffered.\n */\nfunction roundUp31(x: number): number {\n const intInput = Math.round(x * 100000);\n if (intInput % 10000 === 0) {\n return intInput / 100000;\n }\n return (Math.floor(intInput / 10000) + 1) / 10;\n}\n\nfunction cvss31TemporalWeight(\n table: Record<string, number>,\n metrics: Map<string, string>,\n key: string,\n): number {\n const v = metrics.get(key);\n if (v === undefined) {\n return 1.0; // absent → X\n }\n const w = table[v];\n if (w === undefined) {\n throw new Error(`cvss: invalid 3.1 ${key} value \"${v}\"`);\n }\n return w;\n}\n\n/**\n * Compute the CVSS 3.1 Base and Temporal (Threat) scores for a vector string.\n *\n * Only CVSS 3.1 is supported here — CVSS 4.0's MacroVector engine lives\n * separately — so any other version throws rather than returning a wrong number.\n * The peer of the Go `ComputeCvssScore` (kept byte-identical via shared fixtures).\n *\n * @throws if the version is not 3.1, a required base metric is missing, or any\n * metric value is out of the 3.1 enum.\n */\nexport function computeCvssScore(vector: string): CvssScore {\n const { version, metrics } = parseCvssVector(vector);\n if (version !== '3.1') {\n throw new Error(`cvss: score compute supports only CVSS 3.1, got \"${version}\"`);\n }\n for (const k of ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A']) {\n if (!metrics.has(k)) {\n throw new Error(`cvss: missing required 3.1 base metric \"${k}\"`);\n }\n }\n\n const scope = metrics.get('S');\n if (scope !== 'U' && scope !== 'C') {\n throw new Error(`cvss: invalid 3.1 Scope value \"${scope ?? ''}\"`);\n }\n const scopeChanged = scope === 'C';\n\n const prTable = scopeChanged ? CVSS31_PR_CHANGED : CVSS31_PR_UNCHANGED;\n const lookup = (table: Record<string, number>, metric: string): number => {\n const key = metrics.get(metric);\n const w = key === undefined ? undefined : table[key];\n if (w === undefined) {\n throw new Error(`cvss: invalid 3.1 metric value ${metric}:\"${key ?? ''}\"`);\n }\n return w;\n };\n const av = lookup(CVSS31_AV, 'AV');\n const ac = lookup(CVSS31_AC, 'AC');\n const ui = lookup(CVSS31_UI, 'UI');\n const pr = lookup(prTable, 'PR');\n const c = lookup(CVSS31_CIA, 'C');\n const i = lookup(CVSS31_CIA, 'I');\n const a = lookup(CVSS31_CIA, 'A');\n\n const iss = 1 - (1 - c) * (1 - i) * (1 - a);\n const impact = scopeChanged\n ? 7.52 * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15)\n : 6.42 * iss;\n const exploitability = 8.22 * av * ac * pr * ui;\n\n let baseScore: number;\n if (impact <= 0) {\n baseScore = 0.0;\n } else if (scopeChanged) {\n baseScore = roundUp31(Math.min(1.08 * (impact + exploitability), 10));\n } else {\n baseScore = roundUp31(Math.min(impact + exploitability, 10));\n }\n\n const e = cvss31TemporalWeight(CVSS31_E, metrics, 'E');\n const rl = cvss31TemporalWeight(CVSS31_RL, metrics, 'RL');\n const rc = cvss31TemporalWeight(CVSS31_RC, metrics, 'RC');\n const temporalScore = roundUp31(baseScore * e * rl * rc);\n\n return { version: '3.1', baseScore, temporalScore };\n}\n\n// --- CVSS 4.0 scoring (MacroVector engine) ---\n//\n// A faithful port of the FIRST reference calculator (FIRSTdotorg/cvss-v4-calculator:\n// cvss_score.js), driven by the MacroVector lookup + max tables extracted verbatim\n// into cvss40-data.json. The Go peer (go/cvss.go) reads the byte-identical data\n// file and shares the parity fixture, so the two languages score identically.\n\ninterface Cvss40Tables {\n lookup: Record<string, number>;\n maxComposed: {\n eq1: Record<string, string[]>;\n eq2: Record<string, string[]>;\n eq3: Record<string, Record<string, string[]>>;\n eq4: Record<string, string[]>;\n eq5: Record<string, string[]>;\n };\n maxSeverity: {\n eq1: Record<string, number>;\n eq2: Record<string, number>;\n eq3eq6: Record<string, Record<string, number>>;\n eq4: Record<string, number>;\n eq5: Record<string, number>;\n };\n}\nconst CVSS40 = cvss40Data as Cvss40Tables;\n\n// Per-metric severity index tables (FIRST reference cvss_score.js). Lower = worse.\nconst CVSS40_AV: Record<string, number> = { N: 0.0, A: 0.1, L: 0.2, P: 0.3 };\nconst CVSS40_PR: Record<string, number> = { N: 0.0, L: 0.1, H: 0.2 };\nconst CVSS40_UI: Record<string, number> = { N: 0.0, P: 0.1, A: 0.2 };\nconst CVSS40_AC: Record<string, number> = { L: 0.0, H: 0.1 };\nconst CVSS40_AT: Record<string, number> = { N: 0.0, P: 0.1 };\nconst CVSS40_VC: Record<string, number> = { H: 0.0, L: 0.1, N: 0.2 };\nconst CVSS40_SC: Record<string, number> = { H: 0.1, L: 0.2, N: 0.3 };\nconst CVSS40_SI: Record<string, number> = { S: 0.0, H: 0.1, L: 0.2, N: 0.3 };\nconst CVSS40_CR: Record<string, number> = { H: 0.0, M: 0.1, L: 0.2 };\n\n/** Throw on an unexpected missing table entry rather than fabricate a value. */\nfunction req<T>(value: T | undefined, what: string): T {\n if (value === undefined) {\n throw new Error(`cvss: 4.0 data table missing ${what}`);\n }\n return value;\n}\n\n/**\n * Resolve the effective value of a metric, mirroring the reference m(): E:X and\n * unset default to A (worst case); CR/IR/AR:X and unset default to H; a present,\n * non-X Modified (M-prefixed) metric overrides its base.\n */\nfunction cvss40Metric(sel: Map<string, string>, metric: string): string {\n const selected = sel.get(metric) ?? '';\n if (metric === 'E' && (selected === '' || selected === 'X')) {\n return 'A';\n }\n if ((metric === 'CR' || metric === 'IR' || metric === 'AR') && (selected === '' || selected === 'X')) {\n return 'H';\n }\n const modified = sel.get('M' + metric);\n if (modified !== undefined && modified !== 'X') {\n return modified;\n }\n return selected;\n}\n\nfunction cvss40MacroVectorFromSel(sel: Map<string, string>): string {\n const mv = (k: string): string => cvss40Metric(sel, k);\n\n // EQ1 (the \"not all three N\" term of the reference's level-1 clause is\n // redundant here: the level-0 branch already consumes the all-three-N case).\n let eq1 = '2';\n if (mv('AV') === 'N' && mv('PR') === 'N' && mv('UI') === 'N') {\n eq1 = '0';\n } else if ((mv('AV') === 'N' || mv('PR') === 'N' || mv('UI') === 'N') && mv('AV') !== 'P') {\n eq1 = '1';\n }\n\n let eq2 = '1';\n if (mv('AC') === 'L' && mv('AT') === 'N') {\n eq2 = '0';\n }\n\n let eq3 = '2';\n if (mv('VC') === 'H' && mv('VI') === 'H') {\n eq3 = '0';\n } else if (mv('VC') === 'H' || mv('VI') === 'H' || mv('VA') === 'H') {\n eq3 = '1';\n }\n\n let eq4 = '2';\n if (mv('MSI') === 'S' || mv('MSA') === 'S') {\n eq4 = '0';\n } else if (mv('SC') === 'H' || mv('SI') === 'H' || mv('SA') === 'H') {\n eq4 = '1';\n }\n\n let eq5 = '2';\n if (mv('E') === 'A') {\n eq5 = '0';\n } else if (mv('E') === 'P') {\n eq5 = '1';\n }\n\n let eq6 = '1';\n if (\n (mv('CR') === 'H' && mv('VC') === 'H') ||\n (mv('IR') === 'H' && mv('VI') === 'H') ||\n (mv('AR') === 'H' && mv('VA') === 'H')\n ) {\n eq6 = '0';\n }\n\n return eq1 + eq2 + eq3 + eq4 + eq5 + eq6;\n}\n\n/** Parse a CVSS 4.0 vector and return its MacroVector string. */\nexport function cvss40MacroVector(vector: string): string {\n return cvss40MacroVectorFromSel(parseCvssVector(vector).metrics);\n}\n\n/** Pull a metric's value out of a composed max-vector string (reference extractValueMetric). */\nfunction cvss40ExtractMetric(metric: string, s: string): string {\n const idx = s.indexOf(metric);\n if (idx < 0) {\n return '';\n }\n const rest = s.slice(idx + metric.length + 1);\n const slash = rest.indexOf('/');\n return slash >= 0 ? rest.slice(0, slash) : rest;\n}\n\n/** value minus the lower macro's score, or NaN when that lower macro does not exist. */\nfunction cvss40AvailDist(value: number, lowerMacro: string): number {\n const s = CVSS40.lookup[lowerMacro];\n return s === undefined ? NaN : value - s;\n}\n\nfunction cvss40Score(sel: Map<string, string>): number {\n const mv = (k: string): string => cvss40Metric(sel, k);\n\n if (\n mv('VC') === 'N' && mv('VI') === 'N' && mv('VA') === 'N' &&\n mv('SC') === 'N' && mv('SI') === 'N' && mv('SA') === 'N'\n ) {\n return 0.0;\n }\n\n const macro = cvss40MacroVectorFromSel(sel);\n let value = req(CVSS40.lookup[macro], `lookup[${macro}]`);\n\n // Definite per-position digit chars (charAt never yields undefined).\n const c0 = macro.charAt(0);\n const c1 = macro.charAt(1);\n const c2 = macro.charAt(2);\n const c3 = macro.charAt(3);\n const c4 = macro.charAt(4);\n const c5 = macro.charAt(5);\n\n const d = (i: number): number => macro.charCodeAt(i) - 48;\n const eq3 = d(2);\n const eq6 = d(5);\n const rep = (i: number, digit: number): string =>\n macro.slice(0, i) + String(digit) + macro.slice(i + 1);\n\n const availDistEq1 = cvss40AvailDist(value, rep(0, d(0) + 1));\n const availDistEq2 = cvss40AvailDist(value, rep(1, d(1) + 1));\n const availDistEq4 = cvss40AvailDist(value, rep(3, d(3) + 1));\n const availDistEq5 = cvss40AvailDist(value, rep(4, d(4) + 1));\n\n // eq3 and eq6 are coupled.\n let availDistEq3eq6 = NaN;\n if ((eq3 === 1 && eq6 === 1) || (eq3 === 0 && eq6 === 1)) {\n availDistEq3eq6 = cvss40AvailDist(value, rep(2, eq3 + 1));\n } else if (eq3 === 1 && eq6 === 0) {\n availDistEq3eq6 = cvss40AvailDist(value, rep(5, eq6 + 1));\n } else if (eq3 === 0 && eq6 === 0) {\n const left = CVSS40.lookup[rep(5, eq6 + 1)];\n const right = CVSS40.lookup[rep(2, eq3 + 1)];\n if (left !== undefined && right !== undefined) {\n availDistEq3eq6 = value - Math.max(left, right);\n } else if (right !== undefined) {\n availDistEq3eq6 = value - right;\n }\n } else {\n availDistEq3eq6 = cvss40AvailDist(value, macro.slice(0, 2) + String(eq3 + 1) + macro.slice(3, 5) + String(eq6 + 1));\n }\n\n const eq1Maxes = req(CVSS40.maxComposed.eq1[c0], `maxComposed.eq1[${c0}]`);\n const eq2Maxes = req(CVSS40.maxComposed.eq2[c1], `maxComposed.eq2[${c1}]`);\n const eq3eq6Maxes = req(\n req(CVSS40.maxComposed.eq3[c2], `maxComposed.eq3[${c2}]`)[c5],\n `maxComposed.eq3[${c2}][${c5}]`,\n );\n const eq4Maxes = req(CVSS40.maxComposed.eq4[c3], `maxComposed.eq4[${c3}]`);\n const eq5Maxes = req(CVSS40.maxComposed.eq5[c4], `maxComposed.eq5[${c4}]`);\n\n const maxVectors: string[] = [];\n for (const a of eq1Maxes) {\n for (const b of eq2Maxes) {\n for (const c of eq3eq6Maxes) {\n for (const e of eq4Maxes) {\n for (const f of eq5Maxes) {\n maxVectors.push(a + b + c + e + f);\n }\n }\n }\n }\n }\n\n const dist = (table: Record<string, number>, metric: string, maxVector: string): number =>\n req(table[mv(metric)], `level ${metric}:${mv(metric)}`) -\n req(table[cvss40ExtractMetric(metric, maxVector)], `max level ${metric}`);\n\n let sdAV = 0, sdPR = 0, sdUI = 0, sdAC = 0, sdAT = 0, sdVC = 0, sdVI = 0, sdVA = 0;\n let sdSC = 0, sdSI = 0, sdSA = 0, sdCR = 0, sdIR = 0, sdAR = 0;\n for (const maxVector of maxVectors) {\n sdAV = dist(CVSS40_AV, 'AV', maxVector);\n sdPR = dist(CVSS40_PR, 'PR', maxVector);\n sdUI = dist(CVSS40_UI, 'UI', maxVector);\n sdAC = dist(CVSS40_AC, 'AC', maxVector);\n sdAT = dist(CVSS40_AT, 'AT', maxVector);\n sdVC = dist(CVSS40_VC, 'VC', maxVector);\n sdVI = dist(CVSS40_VC, 'VI', maxVector);\n sdVA = dist(CVSS40_VC, 'VA', maxVector);\n sdSC = dist(CVSS40_SC, 'SC', maxVector);\n sdSI = dist(CVSS40_SI, 'SI', maxVector);\n sdSA = dist(CVSS40_SI, 'SA', maxVector);\n sdCR = dist(CVSS40_CR, 'CR', maxVector);\n sdIR = dist(CVSS40_CR, 'IR', maxVector);\n sdAR = dist(CVSS40_CR, 'AR', maxVector);\n if (\n [sdAV, sdPR, sdUI, sdAC, sdAT, sdVC, sdVI, sdVA, sdSC, sdSI, sdSA, sdCR, sdIR, sdAR].some(\n (v) => v < 0,\n )\n ) {\n continue;\n }\n break;\n }\n\n const curEq1 = sdAV + sdPR + sdUI;\n const curEq2 = sdAC + sdAT;\n const curEq3eq6 = sdVC + sdVI + sdVA + sdCR + sdIR + sdAR;\n const curEq4 = sdSC + sdSI + sdSA;\n\n const step = 0.1;\n const maxSevEq1 = req(CVSS40.maxSeverity.eq1[c0], `maxSeverity.eq1[${c0}]`) * step;\n const maxSevEq2 = req(CVSS40.maxSeverity.eq2[c1], `maxSeverity.eq2[${c1}]`) * step;\n const maxSevEq3eq6 =\n req(\n req(CVSS40.maxSeverity.eq3eq6[c2], `maxSeverity.eq3eq6[${c2}]`)[c5],\n `maxSeverity.eq3eq6[${c2}][${c5}]`,\n ) * step;\n const maxSevEq4 = req(CVSS40.maxSeverity.eq4[c3], `maxSeverity.eq4[${c3}]`) * step;\n\n let nExisting = 0;\n let normEq1 = 0, normEq2 = 0, normEq3eq6 = 0, normEq4 = 0;\n const normEq5 = 0; // eq5 proportion is always 0 per the reference\n if (!Number.isNaN(availDistEq1)) {\n nExisting++;\n normEq1 = availDistEq1 * (curEq1 / maxSevEq1);\n }\n if (!Number.isNaN(availDistEq2)) {\n nExisting++;\n normEq2 = availDistEq2 * (curEq2 / maxSevEq2);\n }\n if (!Number.isNaN(availDistEq3eq6)) {\n nExisting++;\n normEq3eq6 = availDistEq3eq6 * (curEq3eq6 / maxSevEq3eq6);\n }\n if (!Number.isNaN(availDistEq4)) {\n nExisting++;\n normEq4 = availDistEq4 * (curEq4 / maxSevEq4);\n }\n if (!Number.isNaN(availDistEq5)) {\n nExisting++;\n }\n\n const meanDistance =\n nExisting === 0 ? 0 : (normEq1 + normEq2 + normEq3eq6 + normEq4 + normEq5) / nExisting;\n\n value -= meanDistance;\n if (value < 0) {\n value = 0.0;\n }\n if (value > 10) {\n value = 10.0;\n }\n return Math.round(value * 10) / 10;\n}\n\n/** Remove the E: segment so the Base score reflects the worst-case default (E:X → E:A). */\nfunction cvss40StripExploitMaturity(vector: string): string {\n return vector\n .split('/')\n .filter((seg) => !seg.startsWith('E:'))\n .join('/');\n}\n\n/**\n * Compute the CVSS 4.0 score for a vector string, split into a Base score\n * (`baseScore`, with Exploit Maturity stripped to its worst-case default E:A) and\n * a Threat score (`temporalScore`, with the E value present in the vector). For a\n * base-only vector the two are equal. The peer of the Go `ComputeCvss40Score`,\n * kept byte-identical via the shared data file and parity fixture.\n *\n * @throws if the version is not 4.0, a required base metric is missing, or any\n * metric value is out of the 4.0 enum.\n */\nexport function computeCvss40Score(vector: string): CvssScore {\n const { version } = parseCvssVector(vector);\n if (version !== '4.0') {\n throw new Error(`cvss: 4.0 score compute supports only CVSS 4.0, got \"${version}\"`);\n }\n const { valid, errors } = validateCvssVector(vector, '4.0');\n if (!valid) {\n throw new Error(`cvss: invalid 4.0 vector: ${errors.join('; ')}`);\n }\n\n const temporalScore = cvss40Score(parseCvssVector(vector).metrics);\n const baseScore = cvss40Score(parseCvssVector(cvss40StripExploitMaturity(vector)).metrics);\n return { version: '4.0', baseScore, temporalScore };\n}\n","/**\n * CPE 2.3 URI parser for HDF converters.\n *\n * Common Platform Enumeration (CPE) 2.3 URIs identify affected products.\n * Container scanners (grype, twistlock, snyk) emit them in vulnerability\n * findings. This module provides an accept-and-warn parser: any input that\n * carries the `cpe:2.3:` prefix is best-effort parsed; deviations from the\n * canonical 13-field form are reported via `warnings` rather than rejected.\n *\n * Canonical form:\n * `cpe:2.3:part:vendor:product:version:update:edition:language:sw_edition:target_sw:target_hw:other`\n *\n * Reference: NIST IR 7695 (https://csrc.nist.gov/publications/detail/nistir/7695/final)\n */\n\n/** Valid CPE 2.3 `part` values. */\nexport type CpePart = 'a' | 'o' | 'h' | '*';\n\n/**\n * Parsed CPE 2.3 URI.\n *\n * `warnings` collects any deviations from the canonical form (truncation,\n * extra fields, unknown `part`). An empty array means the input was a strict\n * 13-field, valid-part CPE.\n */\nexport interface ParsedCpe {\n /** The original input string, unmodified. */\n raw: string;\n /** Application kind. `'a'` (app), `'o'` (OS), `'h'` (hardware), `'*'` (any). */\n part: CpePart | string;\n vendor: string;\n product: string;\n version: string;\n update: string;\n edition: string;\n language: string;\n swEdition: string;\n targetSw: string;\n targetHw: string;\n other: string;\n /** Deviation messages; empty when the input is a canonical 13-field CPE. */\n warnings: string[];\n}\n\nconst CPE_PREFIX = 'cpe:2.3:';\nconst EXPECTED_TOTAL_FIELDS = 13; // \"cpe\", \"2.3\", and 11 product fields = 13 colon-separated tokens\nconst PRODUCT_FIELD_COUNT = 11; // part + 10 attribute fields\nconst VALID_PARTS = new Set(['a', 'o', 'h', '*']);\n\n/**\n * Split a CPE 2.3 body on unescaped `:` separators.\n *\n * Per CPE 2.3 spec section 6.1.2.4, `:` and `\\` inside a field must be\n * escaped as `\\:` and `\\\\`. We respect those escapes during the split and\n * unescape the result.\n */\nfunction splitOnUnescapedColons(body: string): string[] {\n const result: string[] = [];\n let current = '';\n for (let i = 0; i < body.length; i++) {\n const ch = body[i];\n if (ch === '\\\\' && i + 1 < body.length) {\n const next = body[i + 1];\n // Preserve known escapes (\\: and \\\\) as a literal next character.\n if (next === ':' || next === '\\\\') {\n current += next;\n i++;\n continue;\n }\n // Unknown escape — keep the backslash and continue.\n current += ch;\n continue;\n }\n if (ch === ':') {\n result.push(current);\n current = '';\n continue;\n }\n current += ch;\n }\n result.push(current);\n return result;\n}\n\n/**\n * Parse a CPE 2.3 URI.\n *\n * Accept-and-warn semantics:\n * - Input missing the `cpe:2.3:` prefix → returns `null`.\n * - Input with the prefix but fewer than 11 product fields → fields are\n * padded with `\"*\"` and a `truncated:` warning is added.\n * - Input with more than 11 product fields → extras are dropped and an\n * `\"extra fields ignored\"` warning is added.\n * - Unknown `part` value → kept as-is in the result, `\"unknown part: X\"`\n * warning is added.\n * - `\\:` and `\\\\` escapes inside fields are honored during splitting and\n * unescaped in the returned field values.\n *\n * @param cpeUri - CPE 2.3 URI string\n * @returns Parsed CPE with warnings, or `null` if input lacks the prefix\n *\n * @example\n * ```typescript\n * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k:*:*:*:*:*:*:*');\n * // { part: 'a', vendor: 'openssl', product: 'openssl', version: '1.1.1k', ... warnings: [] }\n *\n * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k');\n * // truncated input → padded with '*', warnings: ['truncated: expected 13 colon-separated fields, got 6']\n *\n * parseCpe('openssl:1.1.1k'); // null — no prefix\n * ```\n */\nexport function parseCpe(cpeUri: string): ParsedCpe | null {\n if (!cpeUri.startsWith(CPE_PREFIX)) {\n return null;\n }\n\n const warnings: string[] = [];\n const body = cpeUri.slice(CPE_PREFIX.length);\n\n // Special case: bare prefix `cpe:2.3:` — all fields empty, still warn.\n // This is the \"I have nothing useful here\" form; padding with \"*\" would\n // suggest the parser inferred wildcards, which it didn't.\n const isBarePrefix = body.length === 0;\n\n const bodyParts = isBarePrefix ? [] : splitOnUnescapedColons(body);\n\n // Total colon-separated fields including the prefix \"cpe\" and \"2.3\" markers.\n const totalFields = isBarePrefix ? 2 : 2 + bodyParts.length;\n\n if (bodyParts.length > PRODUCT_FIELD_COUNT) {\n warnings.push('extra fields ignored');\n } else if (bodyParts.length < PRODUCT_FIELD_COUNT) {\n warnings.push(\n `truncated: expected ${EXPECTED_TOTAL_FIELDS} colon-separated fields, got ${totalFields}`,\n );\n }\n\n // Pad missing fields. For a bare prefix, pad with \"\" (no inferred data).\n // For partial input, pad missing trailing fields with \"*\" (wildcard).\n const padValue = isBarePrefix ? '' : '*';\n const fields: string[] = bodyParts.slice(0, PRODUCT_FIELD_COUNT);\n while (fields.length < PRODUCT_FIELD_COUNT) {\n fields.push(padValue);\n }\n\n // fields is padded to PRODUCT_FIELD_COUNT above, so every slot is present;\n // the destructuring defaults only satisfy the compiler's indexed-access check.\n const [\n part = '',\n vendor = '',\n product = '',\n version = '',\n update = '',\n edition = '',\n language = '',\n swEdition = '',\n targetSw = '',\n targetHw = '',\n other = '',\n ] = fields;\n\n if (!VALID_PARTS.has(part)) {\n warnings.push(`unknown part: ${part}`);\n }\n\n return {\n raw: cpeUri,\n part,\n vendor,\n product,\n version,\n update,\n edition,\n language,\n swEdition,\n targetSw,\n targetHw,\n other,\n warnings,\n };\n}\n","/**\n * Package URL (PURL) parser for SBOM and CVE-scanner ingestion.\n *\n * PURL is the canonical package identifier used by CycloneDX, SPDX, OSV,\n * GitHub Advisory Database, and most modern scanners. Spec:\n * https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst\n *\n * Grammar: `pkg:type/namespace/name@version?qualifiers#subpath`\n *\n * This parser is intentionally accept-and-warn. Scanners occasionally emit\n * slightly malformed PURLs (qualifier with no `=`, missing name, etc.) and\n * we'd rather record a warning than fail ingestion.\n */\n\n/**\n * Result of parsing a PURL string.\n */\nexport interface ParsedPurl {\n /** Original input string, unmodified. */\n raw: string;\n /** Package type (e.g., \"npm\", \"rpm\", \"pypi\", \"maven\"). Lowercased. */\n type: string;\n /** Namespace segment (e.g., \"redhat\" for RPM, \"org.apache...\" for Maven). */\n namespace: string | null;\n /** Package name. Empty string (with a warning) if the input lacks one. */\n name: string;\n /** Version after the `@` separator. URL-decoded. */\n version: string | null;\n /** Qualifier key-value pairs from the `?` segment. */\n qualifiers: Map<string, string>;\n /** Subpath from the `#` fragment. */\n subpath: string | null;\n /** Deviations or oddities encountered during parsing. */\n warnings: string[];\n}\n\nconst PURL_PREFIX = 'pkg:';\n\n/**\n * Parse a Package URL string.\n *\n * Returns `null` only when the input is missing the mandatory `pkg:` prefix\n * or the type segment. Other deviations are surfaced via `warnings` on the\n * returned object.\n *\n * @param purlStr - PURL string, e.g. `pkg:npm/lodash@4.17.21`\n * @returns Parsed PURL or `null` if the input is unrecoverably malformed\n *\n * @example\n * ```typescript\n * const r = parsePurl('pkg:rpm/redhat/openssl@1.1.1k-7.el8_4?arch=x86_64');\n * r?.type; // \"rpm\"\n * r?.namespace; // \"redhat\"\n * r?.name; // \"openssl\"\n * r?.qualifiers.get('arch'); // \"x86_64\"\n * ```\n */\nexport function parsePurl(purlStr: string): ParsedPurl | null {\n if (!purlStr || !purlStr.startsWith(PURL_PREFIX)) {\n return null;\n }\n\n const raw = purlStr;\n const warnings: string[] = [];\n\n // Strip prefix. Per spec, scheme is case-insensitive but only `pkg:` is\n // valid; we already matched it.\n let remainder = purlStr.slice(PURL_PREFIX.length);\n\n // Per spec, leading slashes after the scheme are stripped. This also means\n // `pkg:/foo` and `pkg:foo` are equivalent, but `pkg:/` with no following\n // type segment is still invalid.\n while (remainder.startsWith('/')) {\n remainder = remainder.slice(1);\n }\n\n if (remainder.length === 0) {\n return null;\n }\n\n // Extract subpath (fragment). Per spec, the fragment is everything after\n // the first `#`. Splitting before qualifiers keeps `#` inside qualifier\n // values out of scope (which is fine — qualifier values do not legally\n // contain raw `#`; they would be percent-encoded).\n let subpath: string | null = null;\n const hashIdx = remainder.indexOf('#');\n if (hashIdx !== -1) {\n subpath = remainder.slice(hashIdx + 1);\n remainder = remainder.slice(0, hashIdx);\n if (subpath.length === 0) {\n subpath = null;\n }\n }\n\n // Extract qualifiers (query string). Everything after the first `?`.\n const qualifiers = new Map<string, string>();\n const qIdx = remainder.indexOf('?');\n if (qIdx !== -1) {\n const qStr = remainder.slice(qIdx + 1);\n remainder = remainder.slice(0, qIdx);\n parseQualifiers(qStr, qualifiers, warnings);\n }\n\n // Strip a trailing slash from the path portion. Spec allows it; we silently\n // normalize.\n while (remainder.endsWith('/')) {\n remainder = remainder.slice(0, -1);\n }\n\n // Type is everything up to the first `/`.\n const firstSlash = remainder.indexOf('/');\n let type: string;\n let pathPart: string;\n if (firstSlash === -1) {\n type = remainder;\n pathPart = '';\n } else {\n type = remainder.slice(0, firstSlash);\n pathPart = remainder.slice(firstSlash + 1);\n }\n\n if (type.length === 0) {\n return null;\n }\n type = type.toLowerCase();\n\n // Split version off the path part. Per spec, the version separator is the\n // LAST `@` in the path — names may contain `@` (e.g., npm scoped packages\n // when not percent-encoded).\n let version: string | null = null;\n const lastAt = pathPart.lastIndexOf('@');\n let nameAndNs = pathPart;\n if (lastAt !== -1) {\n version = pathPart.slice(lastAt + 1);\n nameAndNs = pathPart.slice(0, lastAt);\n version = safeDecode(version);\n }\n\n // Namespace is everything before the last `/` in the name+namespace\n // section; the final segment is the name.\n let namespace: string | null = null;\n let name: string;\n const lastSlash = nameAndNs.lastIndexOf('/');\n if (lastSlash === -1) {\n name = nameAndNs;\n } else {\n namespace = nameAndNs.slice(0, lastSlash);\n name = nameAndNs.slice(lastSlash + 1);\n }\n\n // Decode percent-encoded segments.\n if (namespace !== null) {\n // Decode each path segment independently to preserve embedded `/`.\n namespace = namespace.split('/').map(safeDecode).join('/');\n }\n name = safeDecode(name);\n\n if (name.length === 0) {\n warnings.push('PURL is missing the name segment');\n }\n\n // Normalize empty namespace to null.\n if (namespace !== null && namespace.length === 0) {\n namespace = null;\n }\n\n // Normalize empty version to null.\n if (version !== null && version.length === 0) {\n version = null;\n }\n\n return {\n raw,\n type,\n namespace,\n name,\n version,\n qualifiers,\n subpath,\n warnings,\n };\n}\n\n/**\n * Parse the `?key=value&key=value` qualifier string. Empty segments are\n * skipped silently; segments with no `=` are recorded with empty value and\n * a warning. Values are URL-decoded.\n */\nfunction parseQualifiers(\n qStr: string,\n out: Map<string, string>,\n warnings: string[],\n): void {\n const parts = qStr.split('&');\n for (const part of parts) {\n if (part.length === 0) {\n continue;\n }\n const eq = part.indexOf('=');\n if (eq === -1) {\n out.set(part, '');\n warnings.push(`qualifier \"${part}\" has no value`);\n continue;\n }\n const key = part.slice(0, eq);\n const value = part.slice(eq + 1);\n out.set(key, safeDecode(value));\n }\n}\n\n/**\n * decodeURIComponent that returns the original string on failure rather than\n * throwing. PURLs in the wild occasionally contain stray `%` characters that\n * aren't legitimate percent-encoding.\n */\nfunction safeDecode(s: string): string {\n try {\n return decodeURIComponent(s);\n } catch {\n return s;\n }\n}\n","// Schema-free input-size guard — the TS peer of hdf-utilities/go/size.go, kept\n// at behavioural parity. The engine loader runs it as its FIRST operation,\n// before any parse, to defend against memory exhaustion on untrusted input.\n\n/** Default maximum input size (50 MB), matching the Go DefaultMaxInputSize. */\nexport const DEFAULT_MAX_INPUT_SIZE = 50 * 1024 * 1024;\n\n/**\n * validateInputSize throws if input exceeds maxSize bytes (maxSize <= 0 uses\n * DEFAULT_MAX_INPUT_SIZE). Byte length is measured on the UTF-8 encoding for a\n * string input, so it matches the Go []byte length for the same document.\n */\nexport function validateInputSize(input: string | Uint8Array, maxSize = 0): void {\n const limit = maxSize > 0 ? maxSize : DEFAULT_MAX_INPUT_SIZE;\n if (typeof input !== 'string') {\n if (input.length > limit) throw tooLarge(limit, input.length);\n return;\n }\n // UTF-8 never uses fewer than one byte per code unit nor more than three, so\n // the cheap bound settles most inputs before anything reads the string.\n if (input.length * 3 <= limit) return;\n const len = utf8ByteLength(input);\n if (len > limit) throw tooLarge(limit, len);\n}\n\nfunction tooLarge(limit: number, len: number): Error {\n return new Error(`input exceeds maximum allowed size of ${limit} bytes (${len} bytes provided)`);\n}\n\n/**\n * UTF-8 byte length, counted rather than encoded: a TextEncoder pass would\n * allocate a full copy of the very input this guard exists to reject.\n * An unpaired surrogate counts as the three-byte replacement character, which\n * is what TextEncoder emits for one.\n */\nfunction utf8ByteLength(s: string): number {\n let bytes = 0;\n for (let i = 0; i < s.length; i++) {\n const c = s.charCodeAt(i);\n if (c < 0x80) {\n bytes += 1;\n } else if (c < 0x800) {\n bytes += 2;\n } else if (c >= 0xd800 && c <= 0xdbff && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {\n bytes += 4;\n i++;\n } else {\n bytes += 3;\n }\n }\n return bytes;\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import { findValuesByKey } from "../object/index.js";
2
- import { a as canonicalChecksum, i as stringifyJSON, n as isValidJSON, o as canonicalJson, r as parseJSON, t as StringifyOptions } from "../index-b6zg3fBr.js";
3
- export { StringifyOptions, canonicalChecksum, canonicalJson, findValuesByKey as findJsonValues, isValidJSON, parseJSON, stringifyJSON };
2
+ import { a as stringifyJSON, i as parseJSON, n as formatJsonNumber, o as canonicalChecksum, r as isValidJSON, s as canonicalJson, t as StringifyOptions } from "../index-i4P63h9e.js";
3
+ export { StringifyOptions, canonicalChecksum, canonicalJson, findValuesByKey as findJsonValues, formatJsonNumber, isValidJSON, parseJSON, stringifyJSON };
@@ -1,3 +1,3 @@
1
- import { a as canonicalJson, i as canonicalChecksum, n as parseJSON, r as stringifyJSON, t as isValidJSON } from "../json-DIcA65Nf.js";
1
+ import { a as canonicalChecksum, i as stringifyJSON, n as isValidJSON, o as canonicalJson, r as parseJSON, t as formatJsonNumber } from "../json-DdbPTX2g.js";
2
2
  import { findValuesByKey } from "../object/index.js";
3
- export { canonicalChecksum, canonicalJson, findValuesByKey as findJsonValues, isValidJSON, parseJSON, stringifyJSON };
3
+ export { canonicalChecksum, canonicalJson, findValuesByKey as findJsonValues, formatJsonNumber, isValidJSON, parseJSON, stringifyJSON };
@@ -150,7 +150,44 @@ function isValidJSON(input) {
150
150
  return false;
151
151
  }
152
152
  }
153
+ /**
154
+ * Render a FINITE JSON number as text the way Go's
155
+ * strconv.FormatFloat(v, 'f', -1, 64) does: shortest round-trip digits,
156
+ * positional always, never exponent notation.
157
+ *
158
+ * JavaScript's own Number-to-string switches to exponent at and above 1e21 and
159
+ * below 1e-6, and drops the sign of negative zero, so a converter that emits
160
+ * String(n) diverges from its Go peer at both magnitude extremes and at -0.
161
+ * Expansion works on the digit string rather than toFixed, which caps at 100
162
+ * fraction digits and so cannot render the smallest denormals at all.
163
+ *
164
+ * Non-finite values are passed through as JavaScript renders them and do NOT
165
+ * match Go: this returns "Infinity"/"-Infinity" where FormatFloat returns
166
+ * "+Inf"/"-Inf" ("NaN" agrees). JSON has no literal for any of them, but
167
+ * JSON.parse manufactures Infinity from an overflow literal such as 1e400, so
168
+ * the case is reachable. The Go peer never renders that value at all -- its
169
+ * ParseFloat fails and it emits the original token text instead -- which is a
170
+ * documented cross-language divergence, not something this helper can close.
171
+ */
172
+ function formatJsonNumber(value) {
173
+ if (Object.is(value, -0)) return "-0";
174
+ if (!Number.isFinite(value)) return String(value);
175
+ const rendered = String(value);
176
+ const exponent = rendered.indexOf("e");
177
+ if (exponent < 0) return rendered;
178
+ const negative = rendered.startsWith("-");
179
+ const significand = rendered.slice(negative ? 1 : 0, exponent);
180
+ const power = Number(rendered.slice(exponent + 1));
181
+ const point = significand.indexOf(".");
182
+ const digits = point < 0 ? significand : significand.slice(0, point) + significand.slice(point + 1);
183
+ const position = (point < 0 ? significand.length : point) + power;
184
+ let out;
185
+ if (position <= 0) out = `0.${"0".repeat(-position)}${digits}`;
186
+ else if (position >= digits.length) out = digits + "0".repeat(position - digits.length);
187
+ else out = `${digits.slice(0, position)}.${digits.slice(position)}`;
188
+ return negative ? `-${out}` : out;
189
+ }
153
190
  //#endregion
154
- export { canonicalJson as a, canonicalChecksum as i, parseJSON as n, stringifyJSON as r, isValidJSON as t };
191
+ export { canonicalChecksum as a, stringifyJSON as i, isValidJSON as n, canonicalJson as o, parseJSON as r, formatJsonNumber as t };
155
192
 
156
- //# sourceMappingURL=json-DIcA65Nf.js.map
193
+ //# sourceMappingURL=json-DdbPTX2g.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"json-DIcA65Nf.js","names":[],"sources":["../src/json/canonical.ts","../src/json/index.ts"],"sourcesContent":["/**\n * Canonical JSON — the encoding HDF uses for content-addressed checksums, such\n * as the amendment chain's `previousChecksum`.\n *\n * The form is Go's `encoding/json` object encoding of the value's JSON shape:\n *\n * - object keys sorted by UTF-8 byte order;\n * - `<`, `>`, `&`, U+2028 and U+2029 escaped;\n * - keys whose value is `null` removed, so a document that spells an absent\n * optional field as an explicit null hashes the same as one that omits it;\n * - unpaired surrogates replaced with U+FFFD, in keys and string values alike,\n * because Go's `json.Unmarshal` substitutes them at parse time while JS's\n * `JSON.parse` preserves them — without this the two disagree on any string\n * carrying a truncated UTF-16 sequence;\n * - negative zero emitted as `-0`, which `JSON.stringify` renders as `0`;\n * - NaN and Infinity rejected, as Go's encoder rejects them.\n *\n * Every one of those points is a place the two languages would otherwise\n * diverge, and a divergence means a chain written by one is reported as\n * tampered by the other. The Go counterpart is `hdfutil.CanonicalJSON`, and the\n * two are pinned against shared vectors in\n * `hdf-utilities/testdata/canonical-json-vectors.json`.\n */\n\nconst LINE_SEPARATOR = '\\u2028';\nconst PARAGRAPH_SEPARATOR = '\\u2029';\n\n/** Characters Go's encoder escapes that `JSON.stringify` leaves literal. */\nconst GO_ESCAPES: Record<string, string> = {\n '<': '\\\\u003c',\n '>': '\\\\u003e',\n '&': '\\\\u0026',\n [LINE_SEPARATOR]: '\\\\u2028',\n [PARAGRAPH_SEPARATOR]: '\\\\u2029',\n};\n\nconst GO_ESCAPE_PATTERN = /[<>&\\u2028\\u2029]/g;\n\n/** An unpaired high or low surrogate — a truncated UTF-16 sequence. */\nconst LONE_SURROGATE = /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g;\n\nconst utf8 = new TextEncoder();\n\n/**\n * Compares two keys by their UTF-8 bytes, which is how Go orders map keys.\n * `Array.prototype.sort` compares UTF-16 code units instead, and the two\n * disagree for characters outside the BMP.\n */\nfunction compareUtf8(a: string, b: string): number {\n const left = utf8.encode(a);\n const right = utf8.encode(b);\n const shared = Math.min(left.length, right.length);\n for (let i = 0; i < shared; i++) {\n // Indices below the shared length are always present; the fallbacks keep\n // this total for the type checker without asserting.\n const leftByte = left[i] ?? 0;\n const rightByte = right[i] ?? 0;\n if (leftByte !== rightByte) return leftByte - rightByte;\n }\n return left.length - right.length;\n}\n\n/**\n * Replaces unpaired surrogates with U+FFFD, matching what Go's `json.Unmarshal`\n * does when it decodes the same bytes. Applied to keys as well as values, and\n * before sorting, so a key that normalizes onto an existing one collides the\n * same way it would in a Go map (last occurrence wins).\n */\nfunction replaceLoneSurrogates(value: string): string {\n return value.replace(LONE_SURROGATE, '\\uFFFD');\n}\n\n/**\n * Normalizes a value into the shape that gets hashed. This replaces a\n * `JSON.parse(JSON.stringify(...))` round-trip, which would have been simpler\n * but silently destroys negative zero.\n */\nfunction normalize(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n\n if (typeof value === 'object') {\n const withToJson = value as { toJSON?: () => unknown };\n // Dates and anything else that defines toJSON serialize through it, exactly\n // as JSON.stringify would.\n if (typeof withToJson.toJSON === 'function') return normalize(withToJson.toJSON());\n\n if (Array.isArray(value)) {\n // JSON has no undefined: in an array position it becomes null.\n return value.map((element) => (element === undefined ? null : normalize(element)));\n }\n\n const out: Record<string, unknown> = {};\n for (const [key, nested] of Object.entries(value as Record<string, unknown>)) {\n if (nested === null || nested === undefined) continue;\n out[replaceLoneSurrogates(key)] = normalize(nested);\n }\n return out;\n }\n\n if (typeof value === 'string') return replaceLoneSurrogates(value);\n\n if (typeof value === 'number' && !Number.isFinite(value)) {\n throw new TypeError(`value is not JSON-encodable: ${String(value)}`);\n }\n\n return value;\n}\n\n/** Serializes with object keys in UTF-8 byte order at every level. */\nfunction serialize(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map(serialize).join(',')}]`;\n }\n if (value !== null && typeof value === 'object') {\n const entries = Object.keys(value as Record<string, unknown>)\n .sort(compareUtf8)\n .map((key) => `${serialize(key)}:${serialize((value as Record<string, unknown>)[key])}`);\n return `{${entries.join(',')}}`;\n }\n // Go distinguishes negative zero; JSON.stringify renders it as \"0\".\n if (typeof value === 'number' && Object.is(value, -0)) return '-0';\n\n const encoded = JSON.stringify(value);\n if (encoded === undefined) {\n throw new TypeError('value is not JSON-encodable');\n }\n return encoded.replace(GO_ESCAPE_PATTERN, (c) => GO_ESCAPES[c] ?? c);\n}\n\n/** Returns the canonical JSON encoding of a value. */\nexport function canonicalJson(value: unknown): string {\n return serialize(normalize(value ?? null));\n}\n\n/** Returns the hex-encoded SHA-256 of a value's canonical JSON encoding. */\nexport async function canonicalChecksum(value: unknown): Promise<string> {\n const bytes = utf8.encode(canonicalJson(value));\n const digest = await crypto.subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(digest))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","export { canonicalJson, canonicalChecksum } from './canonical.js';\nexport { findValuesByKey as findJsonValues } from '../object/index.js';\n\n/**\n * Options for JSON stringification\n */\nexport interface StringifyOptions {\n /** Enable pretty printing with indentation */\n pretty?: boolean;\n /** Number of spaces for indentation (default: 2) */\n indent?: number;\n}\n\n/**\n * Safely parse a JSON string\n * @param input - JSON string to parse\n * @returns Parsed JSON value\n * @throws Error if input is not valid JSON\n */\nexport function parseJSON<T = unknown>(input: string): T {\n if (typeof input !== 'string') {\n throw new Error('Input must be a string');\n }\n\n if (input.trim() === '') {\n throw new Error('Input cannot be empty');\n }\n\n try {\n return JSON.parse(input) as T;\n } catch (error) {\n throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Safely stringify a value to JSON\n * @param value - Value to stringify\n * @param options - Stringification options\n * @returns JSON string\n * @throws Error if value contains circular references or cannot be stringified\n */\nexport function stringifyJSON(value: unknown, options: StringifyOptions = {}): string {\n const { pretty = false, indent = 2 } = options;\n\n try {\n if (pretty) {\n return JSON.stringify(value, null, indent);\n }\n return JSON.stringify(value);\n } catch (error) {\n throw new Error(`Failed to stringify JSON: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Check if a string is valid JSON\n * @param input - String to validate\n * @returns true if input is valid JSON, false otherwise\n */\nexport function isValidJSON(input: unknown): boolean {\n if (typeof input !== 'string') {\n return false;\n }\n\n if (input.trim() === '') {\n return false;\n }\n\n try {\n JSON.parse(input);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"json-DdbPTX2g.js","names":[],"sources":["../src/json/canonical.ts","../src/json/index.ts"],"sourcesContent":["/**\n * Canonical JSON — the encoding HDF uses for content-addressed checksums, such\n * as the amendment chain's `previousChecksum`.\n *\n * The form is Go's `encoding/json` object encoding of the value's JSON shape:\n *\n * - object keys sorted by UTF-8 byte order;\n * - `<`, `>`, `&`, U+2028 and U+2029 escaped;\n * - keys whose value is `null` removed, so a document that spells an absent\n * optional field as an explicit null hashes the same as one that omits it;\n * - unpaired surrogates replaced with U+FFFD, in keys and string values alike,\n * because Go's `json.Unmarshal` substitutes them at parse time while JS's\n * `JSON.parse` preserves them — without this the two disagree on any string\n * carrying a truncated UTF-16 sequence;\n * - negative zero emitted as `-0`, which `JSON.stringify` renders as `0`;\n * - NaN and Infinity rejected, as Go's encoder rejects them.\n *\n * Every one of those points is a place the two languages would otherwise\n * diverge, and a divergence means a chain written by one is reported as\n * tampered by the other. The Go counterpart is `hdfutil.CanonicalJSON`, and the\n * two are pinned against shared vectors in\n * `hdf-utilities/testdata/canonical-json-vectors.json`.\n */\n\nconst LINE_SEPARATOR = '\\u2028';\nconst PARAGRAPH_SEPARATOR = '\\u2029';\n\n/** Characters Go's encoder escapes that `JSON.stringify` leaves literal. */\nconst GO_ESCAPES: Record<string, string> = {\n '<': '\\\\u003c',\n '>': '\\\\u003e',\n '&': '\\\\u0026',\n [LINE_SEPARATOR]: '\\\\u2028',\n [PARAGRAPH_SEPARATOR]: '\\\\u2029',\n};\n\nconst GO_ESCAPE_PATTERN = /[<>&\\u2028\\u2029]/g;\n\n/** An unpaired high or low surrogate — a truncated UTF-16 sequence. */\nconst LONE_SURROGATE = /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g;\n\nconst utf8 = new TextEncoder();\n\n/**\n * Compares two keys by their UTF-8 bytes, which is how Go orders map keys.\n * `Array.prototype.sort` compares UTF-16 code units instead, and the two\n * disagree for characters outside the BMP.\n */\nfunction compareUtf8(a: string, b: string): number {\n const left = utf8.encode(a);\n const right = utf8.encode(b);\n const shared = Math.min(left.length, right.length);\n for (let i = 0; i < shared; i++) {\n // Indices below the shared length are always present; the fallbacks keep\n // this total for the type checker without asserting.\n const leftByte = left[i] ?? 0;\n const rightByte = right[i] ?? 0;\n if (leftByte !== rightByte) return leftByte - rightByte;\n }\n return left.length - right.length;\n}\n\n/**\n * Replaces unpaired surrogates with U+FFFD, matching what Go's `json.Unmarshal`\n * does when it decodes the same bytes. Applied to keys as well as values, and\n * before sorting, so a key that normalizes onto an existing one collides the\n * same way it would in a Go map (last occurrence wins).\n */\nfunction replaceLoneSurrogates(value: string): string {\n return value.replace(LONE_SURROGATE, '\\uFFFD');\n}\n\n/**\n * Normalizes a value into the shape that gets hashed. This replaces a\n * `JSON.parse(JSON.stringify(...))` round-trip, which would have been simpler\n * but silently destroys negative zero.\n */\nfunction normalize(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n\n if (typeof value === 'object') {\n const withToJson = value as { toJSON?: () => unknown };\n // Dates and anything else that defines toJSON serialize through it, exactly\n // as JSON.stringify would.\n if (typeof withToJson.toJSON === 'function') return normalize(withToJson.toJSON());\n\n if (Array.isArray(value)) {\n // JSON has no undefined: in an array position it becomes null.\n return value.map((element) => (element === undefined ? null : normalize(element)));\n }\n\n const out: Record<string, unknown> = {};\n for (const [key, nested] of Object.entries(value as Record<string, unknown>)) {\n if (nested === null || nested === undefined) continue;\n out[replaceLoneSurrogates(key)] = normalize(nested);\n }\n return out;\n }\n\n if (typeof value === 'string') return replaceLoneSurrogates(value);\n\n if (typeof value === 'number' && !Number.isFinite(value)) {\n throw new TypeError(`value is not JSON-encodable: ${String(value)}`);\n }\n\n return value;\n}\n\n/** Serializes with object keys in UTF-8 byte order at every level. */\nfunction serialize(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map(serialize).join(',')}]`;\n }\n if (value !== null && typeof value === 'object') {\n const entries = Object.keys(value as Record<string, unknown>)\n .sort(compareUtf8)\n .map((key) => `${serialize(key)}:${serialize((value as Record<string, unknown>)[key])}`);\n return `{${entries.join(',')}}`;\n }\n // Go distinguishes negative zero; JSON.stringify renders it as \"0\".\n if (typeof value === 'number' && Object.is(value, -0)) return '-0';\n\n const encoded = JSON.stringify(value);\n if (encoded === undefined) {\n throw new TypeError('value is not JSON-encodable');\n }\n return encoded.replace(GO_ESCAPE_PATTERN, (c) => GO_ESCAPES[c] ?? c);\n}\n\n/** Returns the canonical JSON encoding of a value. */\nexport function canonicalJson(value: unknown): string {\n return serialize(normalize(value ?? null));\n}\n\n/** Returns the hex-encoded SHA-256 of a value's canonical JSON encoding. */\nexport async function canonicalChecksum(value: unknown): Promise<string> {\n const bytes = utf8.encode(canonicalJson(value));\n const digest = await crypto.subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(digest))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","export { canonicalJson, canonicalChecksum } from './canonical.js';\nexport { findValuesByKey as findJsonValues } from '../object/index.js';\n\n/**\n * Options for JSON stringification\n */\nexport interface StringifyOptions {\n /** Enable pretty printing with indentation */\n pretty?: boolean;\n /** Number of spaces for indentation (default: 2) */\n indent?: number;\n}\n\n/**\n * Safely parse a JSON string\n * @param input - JSON string to parse\n * @returns Parsed JSON value\n * @throws Error if input is not valid JSON\n */\nexport function parseJSON<T = unknown>(input: string): T {\n if (typeof input !== 'string') {\n throw new Error('Input must be a string');\n }\n\n if (input.trim() === '') {\n throw new Error('Input cannot be empty');\n }\n\n try {\n return JSON.parse(input) as T;\n } catch (error) {\n throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Safely stringify a value to JSON\n * @param value - Value to stringify\n * @param options - Stringification options\n * @returns JSON string\n * @throws Error if value contains circular references or cannot be stringified\n */\nexport function stringifyJSON(value: unknown, options: StringifyOptions = {}): string {\n const { pretty = false, indent = 2 } = options;\n\n try {\n if (pretty) {\n return JSON.stringify(value, null, indent);\n }\n return JSON.stringify(value);\n } catch (error) {\n throw new Error(`Failed to stringify JSON: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Check if a string is valid JSON\n * @param input - String to validate\n * @returns true if input is valid JSON, false otherwise\n */\nexport function isValidJSON(input: unknown): boolean {\n if (typeof input !== 'string') {\n return false;\n }\n\n if (input.trim() === '') {\n return false;\n }\n\n try {\n JSON.parse(input);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Render a FINITE JSON number as text the way Go's\n * strconv.FormatFloat(v, 'f', -1, 64) does: shortest round-trip digits,\n * positional always, never exponent notation.\n *\n * JavaScript's own Number-to-string switches to exponent at and above 1e21 and\n * below 1e-6, and drops the sign of negative zero, so a converter that emits\n * String(n) diverges from its Go peer at both magnitude extremes and at -0.\n * Expansion works on the digit string rather than toFixed, which caps at 100\n * fraction digits and so cannot render the smallest denormals at all.\n *\n * Non-finite values are passed through as JavaScript renders them and do NOT\n * match Go: this returns \"Infinity\"/\"-Infinity\" where FormatFloat returns\n * \"+Inf\"/\"-Inf\" (\"NaN\" agrees). JSON has no literal for any of them, but\n * JSON.parse manufactures Infinity from an overflow literal such as 1e400, so\n * the case is reachable. The Go peer never renders that value at all -- its\n * ParseFloat fails and it emits the original token text instead -- which is a\n * documented cross-language divergence, not something this helper can close.\n */\nexport function formatJsonNumber(value: number): string {\n if (Object.is(value, -0)) return '-0';\n if (!Number.isFinite(value)) return String(value);\n\n const rendered = String(value);\n const exponent = rendered.indexOf('e');\n if (exponent < 0) return rendered;\n\n const negative = rendered.startsWith('-');\n const significand = rendered.slice(negative ? 1 : 0, exponent);\n const power = Number(rendered.slice(exponent + 1));\n const point = significand.indexOf('.');\n const digits = point < 0 ? significand : significand.slice(0, point) + significand.slice(point + 1);\n // Where the decimal point lands once the exponent is applied, counted from the\n // left of the digit string; <= 0 means the value is entirely below the point.\n const position = (point < 0 ? significand.length : point) + power;\n\n let out: string;\n if (position <= 0) {\n out = `0.${'0'.repeat(-position)}${digits}`;\n } else if (position >= digits.length) {\n out = digits + '0'.repeat(position - digits.length);\n } else {\n out = `${digits.slice(0, position)}.${digits.slice(position)}`;\n }\n return negative ? `-${out}` : out;\n}\n"],"mappings":"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"}
@@ -94,6 +94,12 @@ declare function formatTimestamp(d: Date): string;
94
94
  * formatTimestampSeconds(new Date('2024-11-15T10:30:00.123Z')); // '2024-11-15T10:30:00Z'
95
95
  */
96
96
  declare function formatTimestampSeconds(d: Date): string;
97
+ /**
98
+ * Base64-encode the UTF-8 bytes of a string. Runtime-agnostic (TextEncoder +
99
+ * btoa) so library code stays usable from browser bundles, where Node's
100
+ * `Buffer` does not exist. Equivalent to `Buffer.from(text, 'utf-8').toString('base64')`.
101
+ */
102
+ declare function encodeBase64Utf8(text: string): string;
97
103
  /**
98
104
  * Rewrite zone-less timestamps in a raw HDF document to HDF's canonical
99
105
  * trimmed-UTC RFC3339 form, before the document is parsed.
@@ -110,5 +116,5 @@ declare function formatTimestampSeconds(d: Date): string;
110
116
  */
111
117
  declare function normalizeHdfTimestamps(json: string): string;
112
118
  //#endregion
113
- export { formatTimestamp, formatTimestampSeconds, normalizeHdfTimestamps, parseTimestamp, replaceHtmlTags, stripHtml, trimUtcFraction };
119
+ export { encodeBase64Utf8, formatTimestamp, formatTimestampSeconds, normalizeHdfTimestamps, parseTimestamp, replaceHtmlTags, stripHtml, trimUtcFraction };
114
120
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/string/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BgB,UAAU;;;;;;;;;;;;;;;;;;iBAqBV,gBAAgB,cAAc;;;;;;;;;;;;;;;;;;;;;iBAmD9B,eAAe,YAAY;;;;;;;;;;iBAkC3B,gBAAgB;;;;;;;;;iBAqBhB,gBAAgB,GAAG;;;;;;;;;;iBAanB,uBAAuB,GAAG;;;;;;;;;;;;;;;iBAqB1B,uBAAuB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/string/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BgB,UAAU;;;;;;;;;;;;;;;;;;iBAqBV,gBAAgB,cAAc;;;;;;;;;;;;;;;;;;;;;iBAmD9B,eAAe,YAAY;;;;;;;;;;iBAkC3B,gBAAgB;;;;;;;;;iBAqBhB,gBAAgB,GAAG;;;;;;;;;;iBAanB,uBAAuB,GAAG;;;;;;iBAa1B,iBAAiB;;;;;;;;;;;;;;;iBA0BjB,uBAAuB"}
@@ -138,6 +138,18 @@ function formatTimestamp(d) {
138
138
  function formatTimestampSeconds(d) {
139
139
  return d.toISOString().replace(/\.\d+Z$/, "Z");
140
140
  }
141
+ const BASE64_CHUNK_BYTES = 32768;
142
+ /**
143
+ * Base64-encode the UTF-8 bytes of a string. Runtime-agnostic (TextEncoder +
144
+ * btoa) so library code stays usable from browser bundles, where Node's
145
+ * `Buffer` does not exist. Equivalent to `Buffer.from(text, 'utf-8').toString('base64')`.
146
+ */
147
+ function encodeBase64Utf8(text) {
148
+ const bytes = new TextEncoder().encode(text);
149
+ let binary = "";
150
+ for (let i = 0; i < bytes.length; i += BASE64_CHUNK_BYTES) binary += String.fromCharCode(...bytes.subarray(i, i + BASE64_CHUNK_BYTES));
151
+ return btoa(binary);
152
+ }
141
153
  /** A JSON string whose entire value is an ISO-8601 date-time with no zone designator. */
142
154
  const BARE_ISO_IN_JSON = /"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?)"/g;
143
155
  /**
@@ -162,6 +174,6 @@ function normalizeHdfTimestamps(json) {
162
174
  });
163
175
  }
164
176
  //#endregion
165
- export { formatTimestamp, formatTimestampSeconds, normalizeHdfTimestamps, parseTimestamp, replaceHtmlTags, stripHtml, trimUtcFraction };
177
+ export { encodeBase64Utf8, formatTimestamp, formatTimestampSeconds, normalizeHdfTimestamps, parseTimestamp, replaceHtmlTags, stripHtml, trimUtcFraction };
166
178
 
167
179
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/string/index.ts"],"sourcesContent":["/**\n * String manipulation utilities for HDF converters.\n *\n * These handle common operations on security tool output strings:\n * HTML stripping for STIG descriptions and SonarQube messages,\n * and multi-format timestamp parsing for tool output dates.\n */\n\n/**\n * Remove HTML tags from a string and normalize whitespace.\n *\n * Strips all HTML/XML tags, collapses multiple whitespace characters\n * into single spaces, and trims leading/trailing whitespace.\n *\n * For XML documents, use {@link extractTextFromXml} from the xml module instead.\n * This function is for inline HTML in non-XML strings (STIG descriptions,\n * SonarQube messages, etc.).\n *\n * @param html - String potentially containing HTML tags\n * @returns Plain text with tags removed and whitespace normalized\n *\n * @example\n * ```typescript\n * stripHtml('<p>hello</p> <b>world</b>'); // 'hello world'\n * stripHtml('no tags here'); // 'no tags here'\n * ```\n */\nexport function stripHtml(html: string): string {\n return replaceHtmlTags(html, ' ').replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Replace every `<...>` tag span with `replacement`, in a single linear scan.\n *\n * Matches the historical `replace(/<[^>]*>/g, replacement)` span semantics: a\n * span runs from `<` to the first following `>`; a trailing `<` with no\n * closing `>` stays literal. The manual scan avoids that regex's quadratic\n * backtracking on pathological inputs (e.g. long runs of `<`).\n *\n * `replacement` is inserted literally (no `$`-pattern expansion, unlike\n * `String.replace`) and is expected to be a plain separator such as `' '` or\n * `''`. For such replacements — anything containing no `<` or `>` — the output\n * can never contain a `<...>` pair, so tags cannot be reassembled from\n * fragments.\n *\n * @param html - String potentially containing HTML tags\n * @param replacement - What each tag span becomes (`' '` or `''`)\n */\nexport function replaceHtmlTags(html: string, replacement: string): string {\n let out = '';\n let i = 0;\n while (i < html.length) {\n const lt = html.indexOf('<', i);\n if (lt === -1) {\n out += html.slice(i);\n break;\n }\n const gt = html.indexOf('>', lt + 1);\n if (gt === -1) {\n out += html.slice(i);\n break;\n }\n out += html.slice(i, lt) + replacement;\n i = gt + 1;\n }\n return out;\n}\n\n// A bare ISO-8601 datetime with a time component but no timezone designator.\n// JavaScript's Date constructor interprets such a value as host-LOCAL time,\n// whereas Go's time.Parse (and HDF's canonical model) treats it as UTC. We\n// append 'Z' to force UTC so output is host-timezone-independent.\nconst ISO_DATETIME_NO_ZONE = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}(:\\d{2})?(\\.\\d+)?$/;\n\n// C ctime layout (\"Tue Mar 22 14:54:47 2022\", or \"Mon Jan 2 ...\" for a\n// single-digit day). Go's hdfutil.ParseTimestamp parses this layout as UTC;\n// V8's Date treats it as host-local, so we append a 'GMT' designator to match.\nconst CTIME_NO_ZONE = /^[A-Za-z]{3} [A-Za-z]{3} +\\d{1,2} \\d{2}:\\d{2}:\\d{2} \\d{4}$/;\n\n/**\n * Parse a timestamp string in various common formats into a Date.\n *\n * A zone-less ISO datetime (e.g. `2024-01-15T10:30:00`) is interpreted as\n * UTC — matching Go's `hdfutil.ParseTimestamp` — rather than host-local time,\n * so converter output does not depend on the machine's timezone.\n *\n * Returns `null` if the input is empty or cannot be parsed.\n *\n * @param s - Timestamp string to parse\n * @returns Parsed Date or null if unparseable\n *\n * @example\n * ```typescript\n * parseTimestamp('2024-01-15T10:30:00Z'); // Date object\n * parseTimestamp('2024-01-15T10:30:00'); // Date object (interpreted as UTC)\n * parseTimestamp('not a date'); // null\n * parseTimestamp(''); // null\n * ```\n */\nexport function parseTimestamp(s: string): Date | null {\n if (!s || s.trim().length === 0) {\n return null;\n }\n\n const trimmed = s.trim();\n let normalized = trimmed;\n if (ISO_DATETIME_NO_ZONE.test(trimmed)) {\n normalized = `${trimmed.replace(' ', 'T')}Z`;\n } else if (CTIME_NO_ZONE.test(trimmed)) {\n normalized = `${trimmed} GMT`;\n }\n\n // parseTimestamp IS the sanctioned helper the guard routes callers to: it\n // normalizes zone-less input to UTC above before constructing the Date, so\n // this is not the host-local footgun the guard prevents.\n // eslint-disable-next-line no-restricted-syntax -- sanctioned parse-helper implementation\n const parsed = new Date(normalized);\n if (isNaN(parsed.getTime())) {\n return null;\n }\n\n return parsed;\n}\n\n/**\n * Trim trailing fractional-second zeros from an RFC3339 UTC timestamp string,\n * matching Go's RFC3339Nano marshaling (which drops trailing zeros and the\n * decimal point when the fraction is all zeros).\n *\n * @example\n * trimUtcFraction('2024-11-15T10:30:00.000Z'); // '2024-11-15T10:30:00Z'\n * trimUtcFraction('2024-01-01T00:00:00.120Z'); // '2024-01-01T00:00:00.12Z'\n */\nexport function trimUtcFraction(s: string): string {\n // The digits are located with an unambiguous (linear) pattern; the\n // trailing-zero trim happens on the bounded capture with plain string ops.\n const m = /\\.(\\d*)Z$/.exec(s);\n if (!m) return s;\n const digits = m[1] as string;\n let end = digits.length;\n while (end > 0 && digits[end - 1] === '0') end--;\n if (end === digits.length) return s; // no trailing zeros (or no digits at all)\n const kept = digits.slice(0, end);\n return s.slice(0, m.index) + (kept ? `.${kept}Z` : 'Z');\n}\n\n/**\n * Format a Date as HDF's canonical timestamp: RFC3339 in UTC with trailing\n * fractional-second zeros trimmed. Byte-identical to what the Go converters\n * emit for the same instant (a UTC `time.Time` marshaled as RFC3339Nano).\n *\n * @example\n * formatTimestamp(new Date('2024-11-15T10:30:00Z')); // '2024-11-15T10:30:00Z'\n */\nexport function formatTimestamp(d: Date): string {\n return trimUtcFraction(d.toISOString());\n}\n\n/**\n * Format a Date as RFC3339 in UTC truncated to whole seconds (no fractional\n * part). For exporters whose target format conventionally uses whole-second\n * timestamps (CSAF, OpenVEX, CycloneDX). Use {@link formatTimestamp} for\n * canonical HDF output instead — it preserves sub-second precision.\n *\n * @example\n * formatTimestampSeconds(new Date('2024-11-15T10:30:00.123Z')); // '2024-11-15T10:30:00Z'\n */\nexport function formatTimestampSeconds(d: Date): string {\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n\n/** A JSON string whose entire value is an ISO-8601 date-time with no zone designator. */\nconst BARE_ISO_IN_JSON = /\"(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)\"/g;\n\n/**\n * Rewrite zone-less timestamps in a raw HDF document to HDF's canonical\n * trimmed-UTC RFC3339 form, before the document is parsed.\n *\n * Real HDF carries them — InSpec emits startTime with no offset. Go's schema\n * types decode date-time fields into time.Time, whose decoder demands an offset\n * and hard-fails, so Go rejected documents TypeScript read happily; and where\n * TypeScript did read them, it passed the zone-less value straight through to\n * its output, which is not canonical HDF. Normalizing on ingest in both\n * languages means they accept the same real-world files and emit the same bytes,\n * reading a zone-less value as UTC exactly as parseTimestamp already does.\n *\n * Mirrored by NormalizeHDFTimestamps in hdf-utilities/go/timestamp.go.\n */\nexport function normalizeHdfTimestamps(json: string): string {\n return json.replace(BARE_ISO_IN_JSON, (match, raw: string) => {\n const parsed = parseTimestamp(raw);\n if (!parsed) return match;\n return `\"${trimUtcFraction(parsed.toISOString())}\"`;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,UAAU,MAAsB;CAC9C,OAAO,gBAAgB,MAAM,GAAG,CAAC,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;AAC9D;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,gBAAgB,MAAc,aAA6B;CACzE,IAAI,MAAM;CACV,IAAI,IAAI;CACR,OAAO,IAAI,KAAK,QAAQ;EACtB,MAAM,KAAK,KAAK,QAAQ,KAAK,CAAC;EAC9B,IAAI,OAAO,IAAI;GACb,OAAO,KAAK,MAAM,CAAC;GACnB;EACF;EACA,MAAM,KAAK,KAAK,QAAQ,KAAK,KAAK,CAAC;EACnC,IAAI,OAAO,IAAI;GACb,OAAO,KAAK,MAAM,CAAC;GACnB;EACF;EACA,OAAO,KAAK,MAAM,GAAG,EAAE,IAAI;EAC3B,IAAI,KAAK;CACX;CACA,OAAO;AACT;AAMA,MAAM,uBAAuB;AAK7B,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;AAsBtB,SAAgB,eAAe,GAAwB;CACrD,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,WAAW,GAC5B,OAAO;CAGT,MAAM,UAAU,EAAE,KAAK;CACvB,IAAI,aAAa;CACjB,IAAI,qBAAqB,KAAK,OAAO,GACnC,aAAa,GAAG,QAAQ,QAAQ,KAAK,GAAG,EAAE;MACrC,IAAI,cAAc,KAAK,OAAO,GACnC,aAAa,GAAG,QAAQ;CAO1B,MAAM,SAAS,IAAI,KAAK,UAAU;CAClC,IAAI,MAAM,OAAO,QAAQ,CAAC,GACxB,OAAO;CAGT,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,gBAAgB,GAAmB;CAGjD,MAAM,IAAI,YAAY,KAAK,CAAC;CAC5B,IAAI,CAAC,GAAG,OAAO;CACf,MAAM,SAAS,EAAE;CACjB,IAAI,MAAM,OAAO;CACjB,OAAO,MAAM,KAAK,OAAO,MAAM,OAAO,KAAK;CAC3C,IAAI,QAAQ,OAAO,QAAQ,OAAO;CAClC,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG;CAChC,OAAO,EAAE,MAAM,GAAG,EAAE,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK;AACrD;;;;;;;;;AAUA,SAAgB,gBAAgB,GAAiB;CAC/C,OAAO,gBAAgB,EAAE,YAAY,CAAC;AACxC;;;;;;;;;;AAWA,SAAgB,uBAAuB,GAAiB;CACtD,OAAO,EAAE,YAAY,CAAC,CAAC,QAAQ,WAAW,GAAG;AAC/C;;AAGA,MAAM,mBAAmB;;;;;;;;;;;;;;;AAgBzB,SAAgB,uBAAuB,MAAsB;CAC3D,OAAO,KAAK,QAAQ,mBAAmB,OAAO,QAAgB;EAC5D,MAAM,SAAS,eAAe,GAAG;EACjC,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO,IAAI,gBAAgB,OAAO,YAAY,CAAC,EAAE;CACnD,CAAC;AACH"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/string/index.ts"],"sourcesContent":["/**\n * String manipulation utilities for HDF converters.\n *\n * These handle common operations on security tool output strings:\n * HTML stripping for STIG descriptions and SonarQube messages,\n * and multi-format timestamp parsing for tool output dates.\n */\n\n/**\n * Remove HTML tags from a string and normalize whitespace.\n *\n * Strips all HTML/XML tags, collapses multiple whitespace characters\n * into single spaces, and trims leading/trailing whitespace.\n *\n * For XML documents, use {@link extractTextFromXml} from the xml module instead.\n * This function is for inline HTML in non-XML strings (STIG descriptions,\n * SonarQube messages, etc.).\n *\n * @param html - String potentially containing HTML tags\n * @returns Plain text with tags removed and whitespace normalized\n *\n * @example\n * ```typescript\n * stripHtml('<p>hello</p> <b>world</b>'); // 'hello world'\n * stripHtml('no tags here'); // 'no tags here'\n * ```\n */\nexport function stripHtml(html: string): string {\n return replaceHtmlTags(html, ' ').replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Replace every `<...>` tag span with `replacement`, in a single linear scan.\n *\n * Matches the historical `replace(/<[^>]*>/g, replacement)` span semantics: a\n * span runs from `<` to the first following `>`; a trailing `<` with no\n * closing `>` stays literal. The manual scan avoids that regex's quadratic\n * backtracking on pathological inputs (e.g. long runs of `<`).\n *\n * `replacement` is inserted literally (no `$`-pattern expansion, unlike\n * `String.replace`) and is expected to be a plain separator such as `' '` or\n * `''`. For such replacements — anything containing no `<` or `>` — the output\n * can never contain a `<...>` pair, so tags cannot be reassembled from\n * fragments.\n *\n * @param html - String potentially containing HTML tags\n * @param replacement - What each tag span becomes (`' '` or `''`)\n */\nexport function replaceHtmlTags(html: string, replacement: string): string {\n let out = '';\n let i = 0;\n while (i < html.length) {\n const lt = html.indexOf('<', i);\n if (lt === -1) {\n out += html.slice(i);\n break;\n }\n const gt = html.indexOf('>', lt + 1);\n if (gt === -1) {\n out += html.slice(i);\n break;\n }\n out += html.slice(i, lt) + replacement;\n i = gt + 1;\n }\n return out;\n}\n\n// A bare ISO-8601 datetime with a time component but no timezone designator.\n// JavaScript's Date constructor interprets such a value as host-LOCAL time,\n// whereas Go's time.Parse (and HDF's canonical model) treats it as UTC. We\n// append 'Z' to force UTC so output is host-timezone-independent.\nconst ISO_DATETIME_NO_ZONE = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}(:\\d{2})?(\\.\\d+)?$/;\n\n// C ctime layout (\"Tue Mar 22 14:54:47 2022\", or \"Mon Jan 2 ...\" for a\n// single-digit day). Go's hdfutil.ParseTimestamp parses this layout as UTC;\n// V8's Date treats it as host-local, so we append a 'GMT' designator to match.\nconst CTIME_NO_ZONE = /^[A-Za-z]{3} [A-Za-z]{3} +\\d{1,2} \\d{2}:\\d{2}:\\d{2} \\d{4}$/;\n\n/**\n * Parse a timestamp string in various common formats into a Date.\n *\n * A zone-less ISO datetime (e.g. `2024-01-15T10:30:00`) is interpreted as\n * UTC — matching Go's `hdfutil.ParseTimestamp` — rather than host-local time,\n * so converter output does not depend on the machine's timezone.\n *\n * Returns `null` if the input is empty or cannot be parsed.\n *\n * @param s - Timestamp string to parse\n * @returns Parsed Date or null if unparseable\n *\n * @example\n * ```typescript\n * parseTimestamp('2024-01-15T10:30:00Z'); // Date object\n * parseTimestamp('2024-01-15T10:30:00'); // Date object (interpreted as UTC)\n * parseTimestamp('not a date'); // null\n * parseTimestamp(''); // null\n * ```\n */\nexport function parseTimestamp(s: string): Date | null {\n if (!s || s.trim().length === 0) {\n return null;\n }\n\n const trimmed = s.trim();\n let normalized = trimmed;\n if (ISO_DATETIME_NO_ZONE.test(trimmed)) {\n normalized = `${trimmed.replace(' ', 'T')}Z`;\n } else if (CTIME_NO_ZONE.test(trimmed)) {\n normalized = `${trimmed} GMT`;\n }\n\n // parseTimestamp IS the sanctioned helper the guard routes callers to: it\n // normalizes zone-less input to UTC above before constructing the Date, so\n // this is not the host-local footgun the guard prevents.\n // eslint-disable-next-line no-restricted-syntax -- sanctioned parse-helper implementation\n const parsed = new Date(normalized);\n if (isNaN(parsed.getTime())) {\n return null;\n }\n\n return parsed;\n}\n\n/**\n * Trim trailing fractional-second zeros from an RFC3339 UTC timestamp string,\n * matching Go's RFC3339Nano marshaling (which drops trailing zeros and the\n * decimal point when the fraction is all zeros).\n *\n * @example\n * trimUtcFraction('2024-11-15T10:30:00.000Z'); // '2024-11-15T10:30:00Z'\n * trimUtcFraction('2024-01-01T00:00:00.120Z'); // '2024-01-01T00:00:00.12Z'\n */\nexport function trimUtcFraction(s: string): string {\n // The digits are located with an unambiguous (linear) pattern; the\n // trailing-zero trim happens on the bounded capture with plain string ops.\n const m = /\\.(\\d*)Z$/.exec(s);\n if (!m) return s;\n const digits = m[1] as string;\n let end = digits.length;\n while (end > 0 && digits[end - 1] === '0') end--;\n if (end === digits.length) return s; // no trailing zeros (or no digits at all)\n const kept = digits.slice(0, end);\n return s.slice(0, m.index) + (kept ? `.${kept}Z` : 'Z');\n}\n\n/**\n * Format a Date as HDF's canonical timestamp: RFC3339 in UTC with trailing\n * fractional-second zeros trimmed. Byte-identical to what the Go converters\n * emit for the same instant (a UTC `time.Time` marshaled as RFC3339Nano).\n *\n * @example\n * formatTimestamp(new Date('2024-11-15T10:30:00Z')); // '2024-11-15T10:30:00Z'\n */\nexport function formatTimestamp(d: Date): string {\n return trimUtcFraction(d.toISOString());\n}\n\n/**\n * Format a Date as RFC3339 in UTC truncated to whole seconds (no fractional\n * part). For exporters whose target format conventionally uses whole-second\n * timestamps (CSAF, OpenVEX, CycloneDX). Use {@link formatTimestamp} for\n * canonical HDF output instead — it preserves sub-second precision.\n *\n * @example\n * formatTimestampSeconds(new Date('2024-11-15T10:30:00.123Z')); // '2024-11-15T10:30:00Z'\n */\nexport function formatTimestampSeconds(d: Date): string {\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n\n// btoa takes a \"binary string\" (one char per byte); building it in bounded\n// chunks keeps String.fromCharCode's argument list small on multi-MB input.\nconst BASE64_CHUNK_BYTES = 0x8000;\n\n/**\n * Base64-encode the UTF-8 bytes of a string. Runtime-agnostic (TextEncoder +\n * btoa) so library code stays usable from browser bundles, where Node's\n * `Buffer` does not exist. Equivalent to `Buffer.from(text, 'utf-8').toString('base64')`.\n */\nexport function encodeBase64Utf8(text: string): string {\n const bytes = new TextEncoder().encode(text);\n let binary = '';\n for (let i = 0; i < bytes.length; i += BASE64_CHUNK_BYTES) {\n binary += String.fromCharCode(...bytes.subarray(i, i + BASE64_CHUNK_BYTES));\n }\n return btoa(binary);\n}\n\n/** A JSON string whose entire value is an ISO-8601 date-time with no zone designator. */\nconst BARE_ISO_IN_JSON = /\"(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)\"/g;\n\n/**\n * Rewrite zone-less timestamps in a raw HDF document to HDF's canonical\n * trimmed-UTC RFC3339 form, before the document is parsed.\n *\n * Real HDF carries them — InSpec emits startTime with no offset. Go's schema\n * types decode date-time fields into time.Time, whose decoder demands an offset\n * and hard-fails, so Go rejected documents TypeScript read happily; and where\n * TypeScript did read them, it passed the zone-less value straight through to\n * its output, which is not canonical HDF. Normalizing on ingest in both\n * languages means they accept the same real-world files and emit the same bytes,\n * reading a zone-less value as UTC exactly as parseTimestamp already does.\n *\n * Mirrored by NormalizeHDFTimestamps in hdf-utilities/go/timestamp.go.\n */\nexport function normalizeHdfTimestamps(json: string): string {\n return json.replace(BARE_ISO_IN_JSON, (match, raw: string) => {\n const parsed = parseTimestamp(raw);\n if (!parsed) return match;\n return `\"${trimUtcFraction(parsed.toISOString())}\"`;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,UAAU,MAAsB;CAC9C,OAAO,gBAAgB,MAAM,GAAG,CAAC,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;AAC9D;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,gBAAgB,MAAc,aAA6B;CACzE,IAAI,MAAM;CACV,IAAI,IAAI;CACR,OAAO,IAAI,KAAK,QAAQ;EACtB,MAAM,KAAK,KAAK,QAAQ,KAAK,CAAC;EAC9B,IAAI,OAAO,IAAI;GACb,OAAO,KAAK,MAAM,CAAC;GACnB;EACF;EACA,MAAM,KAAK,KAAK,QAAQ,KAAK,KAAK,CAAC;EACnC,IAAI,OAAO,IAAI;GACb,OAAO,KAAK,MAAM,CAAC;GACnB;EACF;EACA,OAAO,KAAK,MAAM,GAAG,EAAE,IAAI;EAC3B,IAAI,KAAK;CACX;CACA,OAAO;AACT;AAMA,MAAM,uBAAuB;AAK7B,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;AAsBtB,SAAgB,eAAe,GAAwB;CACrD,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,WAAW,GAC5B,OAAO;CAGT,MAAM,UAAU,EAAE,KAAK;CACvB,IAAI,aAAa;CACjB,IAAI,qBAAqB,KAAK,OAAO,GACnC,aAAa,GAAG,QAAQ,QAAQ,KAAK,GAAG,EAAE;MACrC,IAAI,cAAc,KAAK,OAAO,GACnC,aAAa,GAAG,QAAQ;CAO1B,MAAM,SAAS,IAAI,KAAK,UAAU;CAClC,IAAI,MAAM,OAAO,QAAQ,CAAC,GACxB,OAAO;CAGT,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,gBAAgB,GAAmB;CAGjD,MAAM,IAAI,YAAY,KAAK,CAAC;CAC5B,IAAI,CAAC,GAAG,OAAO;CACf,MAAM,SAAS,EAAE;CACjB,IAAI,MAAM,OAAO;CACjB,OAAO,MAAM,KAAK,OAAO,MAAM,OAAO,KAAK;CAC3C,IAAI,QAAQ,OAAO,QAAQ,OAAO;CAClC,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG;CAChC,OAAO,EAAE,MAAM,GAAG,EAAE,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK;AACrD;;;;;;;;;AAUA,SAAgB,gBAAgB,GAAiB;CAC/C,OAAO,gBAAgB,EAAE,YAAY,CAAC;AACxC;;;;;;;;;;AAWA,SAAgB,uBAAuB,GAAiB;CACtD,OAAO,EAAE,YAAY,CAAC,CAAC,QAAQ,WAAW,GAAG;AAC/C;AAIA,MAAM,qBAAqB;;;;;;AAO3B,SAAgB,iBAAiB,MAAsB;CACrD,MAAM,QAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI;CAC3C,IAAI,SAAS;CACb,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,oBACrC,UAAU,OAAO,aAAa,GAAG,MAAM,SAAS,GAAG,IAAI,kBAAkB,CAAC;CAE5E,OAAO,KAAK,MAAM;AACpB;;AAGA,MAAM,mBAAmB;;;;;;;;;;;;;;;AAgBzB,SAAgB,uBAAuB,MAAsB;CAC3D,OAAO,KAAK,QAAQ,mBAAmB,OAAO,QAAgB;EAC5D,MAAM,SAAS,eAAe,GAAG;EACjC,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO,IAAI,gBAAgB,OAAO,YAAY,CAAC,EAAE;CACnD,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mitre/hdf-utilities",
3
- "version": "3.6.0-rc.3",
3
+ "version": "3.6.0-rc.5",
4
4
  "description": "Utility functions for HDF libraries (JSON parsing, validation helpers)",
5
5
  "publishConfig": {
6
6
  "access": "public"