@ulisesgascon/openjs-cna-tools 1.0.0-alpha.1 → 1.0.0-alpha.4
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 +137 -1
- package/components/publish.js +135 -30
- package/components/review.js +67 -20
- package/lib/batch.js +141 -0
- package/lib/convert/ghsa.js +30 -121
- package/lib/cvss.js +113 -0
- package/lib/hackerone/index.js +42 -0
- package/lib/hackerone/weaknesses.json +6550 -0
- package/lib/preview.js +15 -14
- package/lib/prompt.js +12 -7
- package/lib/record.js +173 -0
- package/lib/review/rules.js +72 -21
- package/package.json +7 -3
package/lib/preview.js
CHANGED
|
@@ -20,22 +20,23 @@ function getDescription (c) {
|
|
|
20
20
|
''
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Metric blocks the record format accepts, in display preference order.
|
|
24
|
+
const CVSS_BLOCKS = [
|
|
25
|
+
{ key: 'cvssV3_1', version: '3.1' },
|
|
26
|
+
{ key: 'cvssV4_0', version: '4.0' },
|
|
27
|
+
{ key: 'cvssV3_0', version: '3.0' }
|
|
28
|
+
]
|
|
29
|
+
|
|
23
30
|
function getCvss (c) {
|
|
24
31
|
for (const m of c?.metrics ?? []) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (m?.cvssV4_0?.vectorString) {
|
|
34
|
-
return {
|
|
35
|
-
version: '4.0',
|
|
36
|
-
score: m.cvssV4_0.baseScore,
|
|
37
|
-
severity: m.cvssV4_0.baseSeverity,
|
|
38
|
-
vector: m.cvssV4_0.vectorString
|
|
32
|
+
for (const { key, version } of CVSS_BLOCKS) {
|
|
33
|
+
if (m?.[key]?.vectorString) {
|
|
34
|
+
return {
|
|
35
|
+
version,
|
|
36
|
+
score: m[key].baseScore,
|
|
37
|
+
severity: m[key].baseSeverity,
|
|
38
|
+
vector: m[key].vectorString
|
|
39
|
+
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
}
|
package/lib/prompt.js
CHANGED
|
@@ -43,15 +43,20 @@ function summarizeAffected (cna) {
|
|
|
43
43
|
})
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// Metric blocks the record format accepts, in display preference order.
|
|
47
|
+
const CVSS_BLOCKS = [
|
|
48
|
+
{ key: 'cvssV3_1', label: 'CVSS 3.1' },
|
|
49
|
+
{ key: 'cvssV4_0', label: 'CVSS 4.0' },
|
|
50
|
+
{ key: 'cvssV3_0', label: 'CVSS 3.0' }
|
|
51
|
+
]
|
|
52
|
+
|
|
46
53
|
function summarizeCvss (cna) {
|
|
47
54
|
for (const m of cna?.metrics ?? []) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const { vectorString, baseScore, baseSeverity } = m.cvssV4_0
|
|
54
|
-
return { label: 'CVSS 4.0', body: `${baseScore ?? '?'} ${baseSeverity ?? ''} (${vectorString})`.replace(/\s+/g, ' ').trim() }
|
|
55
|
+
for (const { key, label } of CVSS_BLOCKS) {
|
|
56
|
+
if (m?.[key]?.vectorString) {
|
|
57
|
+
const { vectorString, baseScore, baseSeverity } = m[key]
|
|
58
|
+
return { label, body: `${baseScore ?? '?'} ${baseSeverity ?? ''} (${vectorString})`.replace(/\s+/g, ' ').trim() }
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
return null
|
package/lib/record.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Helpers for the parts of a v5.2 CVE Record that are about the record
|
|
2
|
+
// format rather than about where the data came from: the description, the
|
|
3
|
+
// CVSS metric block, the problem type and the record envelope. Converters
|
|
4
|
+
// (GHSA here, release trackers in downstream projects) build the container
|
|
5
|
+
// fields they know about and use these so the format-level details are
|
|
6
|
+
// written once.
|
|
7
|
+
|
|
8
|
+
import { USER_AGENT } from './client.js'
|
|
9
|
+
import { calculateBaseScore, severityFromScore } from './cvss.js'
|
|
10
|
+
import { OPENJS_CNA_ORG_ID } from './review/schema.js'
|
|
11
|
+
|
|
12
|
+
// MITRE schema version we emit on every Record. Matches what every CVE
|
|
13
|
+
// published by the OpenJS CNA carries today.
|
|
14
|
+
export const DATA_VERSION = '5.2'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Strip Markdown formatting down to readable plain text. Generic: headers
|
|
18
|
+
* become "Label:" lines (the published OpenJS CVE convention), emphasis,
|
|
19
|
+
* code spans and links collapse to their text, list bullets and blockquote
|
|
20
|
+
* markers drop, line endings normalise to `\n`.
|
|
21
|
+
*/
|
|
22
|
+
export function stripMarkdown (md) {
|
|
23
|
+
if (typeof md !== 'string') return ''
|
|
24
|
+
let text = md.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
25
|
+
// Headers → "Label:" (any ATX heading, level 1–6). [ \t]* rather than
|
|
26
|
+
// \s* so the blank lines around the heading survive.
|
|
27
|
+
text = text.replace(/^[ \t]*#{1,6}[ \t]+(.+?)[ \t]*$/gm, '$1:')
|
|
28
|
+
// **bold** / __bold__ / *italic* / _italic_ → inner text
|
|
29
|
+
text = text.replace(/\*\*(.+?)\*\*/g, '$1')
|
|
30
|
+
text = text.replace(/__(.+?)__/g, '$1')
|
|
31
|
+
text = text.replace(/(?<![*\w])\*(?!\s)(.+?)(?<!\s)\*(?![*\w])/g, '$1')
|
|
32
|
+
text = text.replace(/(?<![_\w])_(?!\s)(.+?)(?<!\s)_(?![_\w])/g, '$1')
|
|
33
|
+
// `inline code` → inner text
|
|
34
|
+
text = text.replace(/`(.+?)`/g, '$1')
|
|
35
|
+
// [link](url) → link
|
|
36
|
+
text = text.replace(/\[(.+?)\]\([^)]+\)/g, '$1')
|
|
37
|
+
// Bullet / ordered-list markers at line start → strip the marker
|
|
38
|
+
text = text.replace(/^\s*(?:[-*+]|\d+\.)\s+/gm, '')
|
|
39
|
+
// Blockquote markers
|
|
40
|
+
text = text.replace(/^\s*>\s?/gm, '')
|
|
41
|
+
// Collapse 3+ blank lines to a single blank line.
|
|
42
|
+
text = text.replace(/\n{3,}/g, '\n\n')
|
|
43
|
+
return text.trim()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build one `descriptions[]` entry from text that may carry Markdown.
|
|
48
|
+
*
|
|
49
|
+
* MITRE's `value` is plain text (NVD and cve.org show it verbatim), so the
|
|
50
|
+
* Markdown is stripped for `value` and the original is kept in
|
|
51
|
+
* `supportingMedia` as `text/markdown`, only when the two differ. Returns
|
|
52
|
+
* `null` for empty input so callers can leave `descriptions` out.
|
|
53
|
+
*/
|
|
54
|
+
export function buildDescription (text, { lang = 'en' } = {}) {
|
|
55
|
+
const raw = String(text ?? '').replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim()
|
|
56
|
+
if (!raw) return null
|
|
57
|
+
const plain = stripMarkdown(raw) || raw
|
|
58
|
+
const description = { lang, value: plain }
|
|
59
|
+
if (raw !== plain) {
|
|
60
|
+
description.supportingMedia = [{ type: 'text/markdown', base64: false, value: raw }]
|
|
61
|
+
}
|
|
62
|
+
return description
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Metric block key per CVSS version prefix. v2 is not emitted by anything
|
|
66
|
+
// the foundation publishes, so it is left out on purpose.
|
|
67
|
+
const METRIC_KEYS = new Map([
|
|
68
|
+
['3.0', 'cvssV3_0'],
|
|
69
|
+
['3.1', 'cvssV3_1'],
|
|
70
|
+
['4.0', 'cvssV4_0']
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build one `metrics[]` entry from a CVSS vector string.
|
|
75
|
+
*
|
|
76
|
+
* The block key (`cvssV3_0`, `cvssV3_1`, `cvssV4_0`) follows the vector's
|
|
77
|
+
* `CVSS:<version>/` prefix, which is what MITRE validates each block
|
|
78
|
+
* against. `baseScore` is taken from `options.baseScore` when given and
|
|
79
|
+
* computed from the vector otherwise (v3.x only; v4.0 has no bundled
|
|
80
|
+
* calculator). `baseSeverity` is always derived from the final score.
|
|
81
|
+
*
|
|
82
|
+
* A vector with an unknown prefix or one the calculator rejects is passed
|
|
83
|
+
* through under the closest key with no score, so the reviewer can report
|
|
84
|
+
* it instead of this helper guessing.
|
|
85
|
+
*/
|
|
86
|
+
export function buildCvssMetric (vectorString, { baseScore } = {}) {
|
|
87
|
+
const vector = String(vectorString ?? '').trim()
|
|
88
|
+
const version = vector.match(/^CVSS:(\d\.\d)\//)?.[1]
|
|
89
|
+
const key = METRIC_KEYS.get(version) ?? 'cvssV3_1'
|
|
90
|
+
|
|
91
|
+
let score = typeof baseScore === 'number' ? baseScore : undefined
|
|
92
|
+
if (score === undefined && (version === '3.0' || version === '3.1')) {
|
|
93
|
+
try {
|
|
94
|
+
score = calculateBaseScore(vector).baseScore
|
|
95
|
+
} catch {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
format: 'CVSS',
|
|
100
|
+
[key]: {
|
|
101
|
+
version: version ?? '3.1',
|
|
102
|
+
vectorString: vector,
|
|
103
|
+
...(score !== undefined ? { baseScore: score, baseSeverity: severityFromScore(score) } : {})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Build one `problemTypes[]` entry from one or more CWE references.
|
|
110
|
+
*
|
|
111
|
+
* Accepts a single `{ cweId, name? }` or an array of them. `cweId` may be
|
|
112
|
+
* given as `400`, `CWE-400` or `cwe-400` and is normalised to the
|
|
113
|
+
* `CWE-<number>` form MITRE's pattern requires; anything else throws so
|
|
114
|
+
* the mistake surfaces at build time. `description` is required by MITRE,
|
|
115
|
+
* so it is the CWE name when known and the id otherwise.
|
|
116
|
+
*/
|
|
117
|
+
export function buildProblemType (cwes) {
|
|
118
|
+
const list = Array.isArray(cwes) ? cwes : [cwes]
|
|
119
|
+
if (list.length === 0) {
|
|
120
|
+
throw new TypeError('buildProblemType: at least one CWE reference is required')
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
descriptions: list.map(({ cweId, name } = {}) => {
|
|
124
|
+
const id = String(cweId ?? '').trim().match(/^(?:CWE-)?([1-9]\d*)$/i)?.[1]
|
|
125
|
+
if (!id) {
|
|
126
|
+
throw new TypeError(`buildProblemType: '${cweId}' is not a CWE id (expected CWE-<number>)`)
|
|
127
|
+
}
|
|
128
|
+
const text = String(name ?? '').trim()
|
|
129
|
+
return {
|
|
130
|
+
lang: 'en',
|
|
131
|
+
type: 'CWE',
|
|
132
|
+
cweId: `CWE-${id}`,
|
|
133
|
+
description: text || `CWE-${id}`
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Wrap a CNA container in the v5.2 Record envelope.
|
|
141
|
+
*
|
|
142
|
+
* Sets `providerMetadata.orgId` and `x_generator.engine` on the container
|
|
143
|
+
* when the caller has not, so no consumer needs to know the OpenJS CNA org
|
|
144
|
+
* id or the generator string. `cveMetadata` is emitted only when `cveId` is
|
|
145
|
+
* given; without it the publisher's CLI argument is the sole source of
|
|
146
|
+
* truth for the id.
|
|
147
|
+
*/
|
|
148
|
+
export function buildRecordEnvelope ({
|
|
149
|
+
cna,
|
|
150
|
+
cveId,
|
|
151
|
+
assignerOrgId = OPENJS_CNA_ORG_ID,
|
|
152
|
+
engineId = USER_AGENT,
|
|
153
|
+
dataVersion = DATA_VERSION
|
|
154
|
+
} = {}) {
|
|
155
|
+
if (!cna || typeof cna !== 'object') {
|
|
156
|
+
throw new TypeError('buildRecordEnvelope: `cna` must be the CNA container object')
|
|
157
|
+
}
|
|
158
|
+
const container = {
|
|
159
|
+
providerMetadata: { orgId: assignerOrgId },
|
|
160
|
+
...cna,
|
|
161
|
+
// x_ prefixes are MITRE's extension namespace. x_generator is the
|
|
162
|
+
// canonical place to surface "this container was produced by …".
|
|
163
|
+
x_generator: cna.x_generator ?? { engine: engineId }
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
dataType: 'CVE_RECORD',
|
|
167
|
+
dataVersion,
|
|
168
|
+
...(cveId
|
|
169
|
+
? { cveMetadata: { cveId, assignerOrgId, state: 'PUBLISHED' } }
|
|
170
|
+
: {}),
|
|
171
|
+
containers: { cna: container }
|
|
172
|
+
}
|
|
173
|
+
}
|
package/lib/review/rules.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
// See docs/mitre-schema-summary.md for the constraint inventory the tiers
|
|
17
17
|
// are derived from.
|
|
18
18
|
|
|
19
|
+
import { calculateBaseScore } from '../cvss.js'
|
|
20
|
+
|
|
19
21
|
const isString = (v) => typeof v === 'string'
|
|
20
22
|
const isNonEmptyArray = (v) => Array.isArray(v) && v.length > 0
|
|
21
23
|
|
|
@@ -26,13 +28,30 @@ function getDescription (c) {
|
|
|
26
28
|
c?.descriptions?.[0]
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
// CVSS v3 blocks the record format accepts, newest first. v3.0 and v3.1
|
|
32
|
+
// share the same metrics and vector grammar; only the `CVSS:<version>/`
|
|
33
|
+
// prefix differs, and MITRE validates each block against its own schema.
|
|
34
|
+
const CVSS_V3_KEYS = [
|
|
35
|
+
{ key: 'cvssV3_1', version: '3.1' },
|
|
36
|
+
{ key: 'cvssV3_0', version: '3.0' }
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
// First CVSS v3 block (3.1 or 3.0) with a vector, plus the key it lives
|
|
40
|
+
// under so issue paths and prefix checks can name the right version.
|
|
41
|
+
function getCvssV3 (c) {
|
|
42
|
+
for (const m of c?.metrics ?? []) {
|
|
43
|
+
for (const { key, version } of CVSS_V3_KEYS) {
|
|
44
|
+
if (m?.[key]?.vectorString) return { key, version, block: m[key] }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null
|
|
31
48
|
}
|
|
32
49
|
|
|
33
50
|
function getCvssScore (c) {
|
|
34
51
|
for (const m of c?.metrics ?? []) {
|
|
35
|
-
|
|
52
|
+
for (const { key } of CVSS_V3_KEYS) {
|
|
53
|
+
if (typeof m?.[key]?.baseScore === 'number') return m[key].baseScore
|
|
54
|
+
}
|
|
36
55
|
}
|
|
37
56
|
return null
|
|
38
57
|
}
|
|
@@ -196,38 +215,42 @@ export const RULES = [
|
|
|
196
215
|
id: 'metrics-present',
|
|
197
216
|
severity: 'warning',
|
|
198
217
|
// MITRE doesn't require a metrics block, but every published OpenJS
|
|
199
|
-
// CVE carries at least one (v3.1 or v4.0). Without metrics the
|
|
218
|
+
// CVE carries at least one (v3.0, v3.1 or v4.0). Without metrics the
|
|
200
219
|
// record looks half-finished to consumers parsing NVD/OSV.
|
|
201
220
|
tier: 'openjs-extra',
|
|
202
221
|
check (c) {
|
|
203
222
|
const metrics = c?.metrics ?? []
|
|
204
|
-
const hasCvss = metrics.some(m => m?.
|
|
223
|
+
const hasCvss = getCvssV3(c) != null || metrics.some(m => m?.cvssV4_0?.vectorString)
|
|
205
224
|
if (hasCvss) return null
|
|
206
225
|
return {
|
|
207
226
|
path: 'metrics',
|
|
208
|
-
message: 'No CVSS metric present. Add at least one cvssV3_1 or cvssV4_0 block.'
|
|
227
|
+
message: 'No CVSS metric present. Add at least one cvssV3_1, cvssV3_0 or cvssV4_0 block.'
|
|
209
228
|
}
|
|
210
229
|
}
|
|
211
230
|
},
|
|
212
231
|
{
|
|
213
232
|
id: 'cvss-vector-parseable',
|
|
214
233
|
severity: 'blocker',
|
|
215
|
-
// MITRE imports the FIRST.org CVSS v3.
|
|
216
|
-
// `vectorString` with a pattern that requires the `CVSS:3.1/`
|
|
217
|
-
//
|
|
234
|
+
// MITRE imports the FIRST.org CVSS v3.x schemas, which constrain
|
|
235
|
+
// `vectorString` with a pattern that requires the `CVSS:3.1/` (or
|
|
236
|
+
// `CVSS:3.0/` in a cvssV3_0 block) prefix and validates every KEY:VAL
|
|
237
|
+
// component. Same failure as this rule.
|
|
218
238
|
tier: 'mitre-common',
|
|
219
239
|
check (c) {
|
|
220
|
-
const
|
|
221
|
-
if (!
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
const cvss = getCvssV3(c)
|
|
241
|
+
if (!cvss) return null
|
|
242
|
+
const vec = cvss.block.vectorString
|
|
243
|
+
const prefix = `CVSS:${cvss.version}/`
|
|
244
|
+
const path = `metrics[].${cvss.key}.vectorString`
|
|
245
|
+
// Minimal CVSS v3 sanity check. Full parsing would need an external
|
|
246
|
+
// library; we just verify the prefix matches the block's version and
|
|
247
|
+
// that every component looks like KEY:VAL pairs.
|
|
248
|
+
if (!vec.startsWith(prefix)) {
|
|
249
|
+
return { path, message: `CVSS vector does not start with ${prefix}` }
|
|
227
250
|
}
|
|
228
|
-
const parts = vec.slice(
|
|
251
|
+
const parts = vec.slice(prefix.length).split('/')
|
|
229
252
|
if (parts.some(p => !/^[A-Z]+:[A-Z]+$/.test(p))) {
|
|
230
|
-
return { path
|
|
253
|
+
return { path, message: 'CVSS vector has malformed components' }
|
|
231
254
|
}
|
|
232
255
|
return null
|
|
233
256
|
}
|
|
@@ -235,24 +258,52 @@ export const RULES = [
|
|
|
235
258
|
{
|
|
236
259
|
id: 'cvss-score-matches-band',
|
|
237
260
|
severity: 'warning',
|
|
238
|
-
// MITRE's bundled CVSS v3.
|
|
261
|
+
// MITRE's bundled CVSS v3.x schemas have an `anyOf` that pairs each score
|
|
239
262
|
// band (none/low/medium/high/critical) with the matching baseSeverity
|
|
240
263
|
// enum, so a band mismatch is a schema violation upstream too.
|
|
241
264
|
tier: 'mitre-common',
|
|
242
265
|
check (c) {
|
|
243
266
|
const score = getCvssScore(c)
|
|
244
|
-
const
|
|
267
|
+
const cvss = getCvssV3(c)
|
|
268
|
+
const band = cvss?.block.baseSeverity
|
|
245
269
|
if (score == null || !band) return null
|
|
246
270
|
const expected = expectedSeverityBand(score)
|
|
247
271
|
if (expected !== band) {
|
|
248
272
|
return {
|
|
249
|
-
path:
|
|
273
|
+
path: `metrics[].${cvss.key}`,
|
|
250
274
|
message: `baseSeverity is '${band}' but the score ${score} falls in band '${expected}'`
|
|
251
275
|
}
|
|
252
276
|
}
|
|
253
277
|
return null
|
|
254
278
|
}
|
|
255
279
|
},
|
|
280
|
+
{
|
|
281
|
+
id: 'cvss-score-matches-vector',
|
|
282
|
+
severity: 'blocker',
|
|
283
|
+
// MITRE does not recompute scores, so a baseScore that disagrees with
|
|
284
|
+
// its vector is accepted upstream and then contradicts every consumer
|
|
285
|
+
// that scores the vector itself (NVD, OSV, scanners). The vector is the
|
|
286
|
+
// source of truth; the score must be the one FIRST.org's v3.x formula
|
|
287
|
+
// yields for it. Unparseable vectors are left to cvss-vector-parseable.
|
|
288
|
+
tier: 'openjs-extra',
|
|
289
|
+
check (c) {
|
|
290
|
+
const cvss = getCvssV3(c)
|
|
291
|
+
if (!cvss || typeof cvss.block.baseScore !== 'number') return null
|
|
292
|
+
let computed
|
|
293
|
+
try {
|
|
294
|
+
computed = calculateBaseScore(cvss.block.vectorString)
|
|
295
|
+
} catch {
|
|
296
|
+
return null
|
|
297
|
+
}
|
|
298
|
+
if (computed.baseScore !== cvss.block.baseScore) {
|
|
299
|
+
return {
|
|
300
|
+
path: `metrics[].${cvss.key}.baseScore`,
|
|
301
|
+
message: `baseScore is ${cvss.block.baseScore} but the vector scores ${computed.baseScore}`
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return null
|
|
305
|
+
}
|
|
306
|
+
},
|
|
256
307
|
|
|
257
308
|
// CWE
|
|
258
309
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ulisesgascon/openjs-cna-tools",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.4",
|
|
4
4
|
"description": "EXPERIMENTAL fork of the OpenJS Foundation CNA tools: CLI and library for the OpenJS CNA API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,14 +11,18 @@
|
|
|
11
11
|
"node": ">=22.0.0"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|
|
14
|
-
"openjs-cna-experimental": "
|
|
14
|
+
"openjs-cna-experimental": "bin/openjs-cna.js"
|
|
15
15
|
},
|
|
16
16
|
"main": "./lib/index.js",
|
|
17
17
|
"exports": {
|
|
18
18
|
".": "./lib/index.js",
|
|
19
19
|
"./client": "./lib/client.js",
|
|
20
20
|
"./review": "./lib/review/index.js",
|
|
21
|
-
"./convert": "./lib/convert/ghsa.js"
|
|
21
|
+
"./convert": "./lib/convert/ghsa.js",
|
|
22
|
+
"./cvss": "./lib/cvss.js",
|
|
23
|
+
"./record": "./lib/record.js",
|
|
24
|
+
"./batch": "./lib/batch.js",
|
|
25
|
+
"./hackerone": "./lib/hackerone/index.js"
|
|
22
26
|
},
|
|
23
27
|
"files": [
|
|
24
28
|
"bin/",
|