bun-scan 1.1.0 → 1.1.1-beta.1
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 +91 -0
- package/dist/cli.js +184 -24
- package/dist/index.d.ts +86 -10
- package/dist/index.js +192 -28
- package/package.json +5 -5
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Bun-Scan
|
|
2
|
+
|
|
3
|
+
A security scanner for [Bun](https://bun.sh/) that checks packages for known vulnerabilities during installation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Real-time Scanning**: Checks packages against configured sources (OSV, npm, or both) during installation
|
|
8
|
+
- **Whitelists**: Specific warnings can be ignored
|
|
9
|
+
- **Fail-safe**: Can configure non-critical advisories to not prevent installations
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Install as a dev dependency
|
|
15
|
+
bun add -d bun-scan
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Add to your `bunfig.toml`:
|
|
19
|
+
|
|
20
|
+
```toml
|
|
21
|
+
[install.security]
|
|
22
|
+
scanner = "bun-scan"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Select your source from `npm`, `osv` (default), or run checks against `both` by setting up your config in `.bun-scan.config.json`
|
|
26
|
+
|
|
27
|
+
Note to set the schema version in the URL to the correct version:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"$schema": "https://raw.githubusercontent.com/rawtoast/bun-scan/v1.1.0/schema/bun-scan.schema.json",
|
|
32
|
+
"source": "npm"
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Ignoring Vulnerabilities
|
|
37
|
+
|
|
38
|
+
A package may have a vulnerability, but your project is not affected. In this scenario, you would
|
|
39
|
+
not want installations to be prevented. To work around this, the vulnerability can be flagged as ignored in your `.bun-scan.config.json`
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"$schema": "https://raw.githubusercontent.com/rawtoast/bun-scan/v1.1.0/schema/bun-scan.schema.json",
|
|
44
|
+
"source": "npm",
|
|
45
|
+
"packages": {
|
|
46
|
+
"hono": {
|
|
47
|
+
"vulnerabilities": ["CVE-2026-22818"],
|
|
48
|
+
"reason": "Project does not use JWT from hono, verify again in June",
|
|
49
|
+
"until": "2026-06-01"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Note that `bunReportWarnings` can be set `false` to print warning-level advisories without triggering Bun's install prompt:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"bunReportWarnings": false
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Advisory Levels
|
|
64
|
+
|
|
65
|
+
#### Fatal (Installation Blocked)
|
|
66
|
+
|
|
67
|
+
- **CVSS Score**: ≥ 7.0 (High/Critical)
|
|
68
|
+
- **Database Severity**: CRITICAL or HIGH
|
|
69
|
+
- **Action**: Installation is immediately blocked
|
|
70
|
+
|
|
71
|
+
#### Warning (User Prompted)
|
|
72
|
+
|
|
73
|
+
- **CVSS Score**: < 7.0 (Medium/Low)
|
|
74
|
+
- **Database Severity**: MEDIUM, LOW, or unspecified
|
|
75
|
+
- **Action**: User is prompted to continue or cancel
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
80
|
+
|
|
81
|
+
## Acknowledgments
|
|
82
|
+
|
|
83
|
+
- **maloma7**: For the original implementation of the Bun OSV Scanner
|
|
84
|
+
|
|
85
|
+
## Related Projects
|
|
86
|
+
|
|
87
|
+
- [Bun Security Scanner API](https://bun.com/docs/install/security-scanner-api)
|
|
88
|
+
- [OSV.dev](https://osv.dev/)
|
|
89
|
+
- [GitHub advisories](https://github.com/advisories)
|
|
90
|
+
- [Bun OSV Scanner](https://github.com/bun-security-scanner/osv)
|
|
91
|
+
- [Bun NPM Scanner](https://github.com/bun-security-scanner/npm)
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,8 @@ var ENV = {
|
|
|
29
29
|
LOG_LEVEL: "BUN_SCAN_LOG_LEVEL",
|
|
30
30
|
API_BASE_URL: "OSV_API_BASE_URL",
|
|
31
31
|
TIMEOUT_MS: "OSV_TIMEOUT_MS",
|
|
32
|
-
DISABLE_BATCH: "OSV_DISABLE_BATCH"
|
|
32
|
+
DISABLE_BATCH: "OSV_DISABLE_BATCH",
|
|
33
|
+
FAIL_ON_SCANNER_ERROR: "BUN_SCAN_FAIL_ON_SCANNER_ERROR"
|
|
33
34
|
};
|
|
34
35
|
// ../core/src/logger.ts
|
|
35
36
|
var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
@@ -118,6 +119,7 @@ import { z } from "zod";
|
|
|
118
119
|
var CONFIG_DEFAULTS = {
|
|
119
120
|
logLevel: "info",
|
|
120
121
|
bunReportWarnings: true,
|
|
122
|
+
failOnScannerError: false,
|
|
121
123
|
osv: {
|
|
122
124
|
apiBaseUrl: OSV_API.BASE_URL,
|
|
123
125
|
timeoutMs: OSV_API.TIMEOUT_MS,
|
|
@@ -152,6 +154,7 @@ var ConfigSchema = z.object({
|
|
|
152
154
|
packages: z.record(z.string(), IgnorePackageRuleSchema).optional(),
|
|
153
155
|
logLevel: z.enum(["debug", "info", "warn", "error"]).optional(),
|
|
154
156
|
bunReportWarnings: z.boolean().optional(),
|
|
157
|
+
failOnScannerError: z.boolean().optional(),
|
|
155
158
|
osv: OsvConfigSchema.optional(),
|
|
156
159
|
npm: NpmConfigSchema.optional()
|
|
157
160
|
});
|
|
@@ -198,6 +201,7 @@ function parseEnvLogLevel(envVar) {
|
|
|
198
201
|
function buildEnvConfig() {
|
|
199
202
|
return {
|
|
200
203
|
logLevel: parseEnvLogLevel(ENV.LOG_LEVEL),
|
|
204
|
+
failOnScannerError: parseEnvBoolean(ENV.FAIL_ON_SCANNER_ERROR),
|
|
201
205
|
osv: {
|
|
202
206
|
apiBaseUrl: Bun.env[ENV.API_BASE_URL] || undefined,
|
|
203
207
|
timeoutMs: parseEnvNumber(ENV.TIMEOUT_MS),
|
|
@@ -215,11 +219,14 @@ function mergeConfig(fileConfig) {
|
|
|
215
219
|
source: DEFAULT_SOURCE,
|
|
216
220
|
logLevel: CONFIG_DEFAULTS.logLevel,
|
|
217
221
|
bunReportWarnings: CONFIG_DEFAULTS.bunReportWarnings,
|
|
222
|
+
failOnScannerError: CONFIG_DEFAULTS.failOnScannerError,
|
|
218
223
|
osv: { ...CONFIG_DEFAULTS.osv },
|
|
219
224
|
npm: { ...CONFIG_DEFAULTS.npm }
|
|
220
225
|
};
|
|
221
226
|
if (envConfig.logLevel !== undefined)
|
|
222
227
|
merged.logLevel = envConfig.logLevel;
|
|
228
|
+
if (envConfig.failOnScannerError !== undefined)
|
|
229
|
+
merged.failOnScannerError = envConfig.failOnScannerError;
|
|
223
230
|
if (envConfig.osv?.apiBaseUrl !== undefined)
|
|
224
231
|
merged.osv.apiBaseUrl = envConfig.osv.apiBaseUrl;
|
|
225
232
|
if (envConfig.osv?.timeoutMs !== undefined)
|
|
@@ -241,6 +248,8 @@ function mergeConfig(fileConfig) {
|
|
|
241
248
|
merged.logLevel = fileConfig.logLevel;
|
|
242
249
|
if (fileConfig.bunReportWarnings !== undefined)
|
|
243
250
|
merged.bunReportWarnings = fileConfig.bunReportWarnings;
|
|
251
|
+
if (fileConfig.failOnScannerError !== undefined)
|
|
252
|
+
merged.failOnScannerError = fileConfig.failOnScannerError;
|
|
244
253
|
if (fileConfig.osv?.apiBaseUrl !== undefined)
|
|
245
254
|
merged.osv.apiBaseUrl = fileConfig.osv.apiBaseUrl;
|
|
246
255
|
if (fileConfig.osv?.timeoutMs !== undefined)
|
|
@@ -252,18 +261,22 @@ function mergeConfig(fileConfig) {
|
|
|
252
261
|
if (fileConfig.npm?.timeoutMs !== undefined)
|
|
253
262
|
merged.npm.timeoutMs = fileConfig.npm.timeoutMs;
|
|
254
263
|
}
|
|
264
|
+
if (envConfig.failOnScannerError !== undefined) {
|
|
265
|
+
merged.failOnScannerError = envConfig.failOnScannerError;
|
|
266
|
+
}
|
|
255
267
|
return merged;
|
|
256
268
|
}
|
|
257
269
|
async function loadConfig() {
|
|
270
|
+
const strictBootstrap = parseEnvBoolean(ENV.FAIL_ON_SCANNER_ERROR) === true;
|
|
258
271
|
for (const filename of CONFIG_FILES) {
|
|
259
|
-
const config = await tryLoadConfigFile(filename);
|
|
272
|
+
const config = await tryLoadConfigFile(filename, { fatalOnError: strictBootstrap });
|
|
260
273
|
if (config) {
|
|
261
274
|
return mergeConfig(config);
|
|
262
275
|
}
|
|
263
276
|
}
|
|
264
277
|
return mergeConfig(null);
|
|
265
278
|
}
|
|
266
|
-
async function tryLoadConfigFile(filename) {
|
|
279
|
+
async function tryLoadConfigFile(filename, options) {
|
|
267
280
|
try {
|
|
268
281
|
const file = Bun.file(filename);
|
|
269
282
|
const exists = await file.exists();
|
|
@@ -276,6 +289,18 @@ async function tryLoadConfigFile(filename) {
|
|
|
276
289
|
logConfigStats(parsed);
|
|
277
290
|
return parsed;
|
|
278
291
|
} catch (error) {
|
|
292
|
+
const isENOENT = error instanceof Error && ("code" in error ? error.code === "ENOENT" : error.message.includes("No such file"));
|
|
293
|
+
if (options?.fatalOnError) {
|
|
294
|
+
if (isENOENT) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
logger.error(`Failed to read config file ${filename}`, {
|
|
298
|
+
error: error instanceof Error ? error.message : String(error),
|
|
299
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
300
|
+
});
|
|
301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
302
|
+
throw new Error(`bun-scan: failed to load config file ${filename}: ${message}. ` + `BUN_SCAN_FAIL_ON_SCANNER_ERROR=true makes config errors fatal.`);
|
|
303
|
+
}
|
|
279
304
|
if (error instanceof z.ZodError) {
|
|
280
305
|
logger.warn(`Invalid config in ${filename}`, {
|
|
281
306
|
errors: error.issues.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
@@ -284,6 +309,13 @@ async function tryLoadConfigFile(filename) {
|
|
|
284
309
|
logger.warn(`Failed to parse ${filename} as JSON`, {
|
|
285
310
|
error: error.message
|
|
286
311
|
});
|
|
312
|
+
} else if (isENOENT) {
|
|
313
|
+
logger.debug(`Config file ${filename} not found (race condition handled)`);
|
|
314
|
+
} else {
|
|
315
|
+
logger.warn(`Failed to read config file ${filename}`, {
|
|
316
|
+
error: error instanceof Error ? error.message : String(error),
|
|
317
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
318
|
+
});
|
|
287
319
|
}
|
|
288
320
|
return null;
|
|
289
321
|
}
|
|
@@ -408,6 +440,7 @@ function createOSVClient(options = {}) {
|
|
|
408
440
|
const baseUrl = osvConfig.apiBaseUrl ?? OSV_API.BASE_URL;
|
|
409
441
|
const timeout = osvConfig.timeoutMs ?? OSV_API.TIMEOUT_MS;
|
|
410
442
|
const useBatch = !(osvConfig.disableBatch ?? false);
|
|
443
|
+
const failOnError = options.failOnScannerError ?? false;
|
|
411
444
|
function deduplicatePackages(packages) {
|
|
412
445
|
const packageMap = new Map;
|
|
413
446
|
for (const pkg of packages) {
|
|
@@ -466,8 +499,15 @@ function createOSVClient(options = {}) {
|
|
|
466
499
|
return OSVVulnerabilitySchema.parse(data);
|
|
467
500
|
}, `Get vulnerability ${id}`);
|
|
468
501
|
} catch (error) {
|
|
502
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
503
|
+
if (failOnError) {
|
|
504
|
+
logger.error(`Failed to fetch vulnerability ${id} (strict mode)`, {
|
|
505
|
+
error: message
|
|
506
|
+
});
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
469
509
|
logger.warn(`Failed to fetch vulnerability ${id}`, {
|
|
470
|
-
error:
|
|
510
|
+
error: message
|
|
471
511
|
});
|
|
472
512
|
return null;
|
|
473
513
|
}
|
|
@@ -482,6 +522,18 @@ function createOSVClient(options = {}) {
|
|
|
482
522
|
for (let i = 0;i < uniqueIds.length; i += chunkSize) {
|
|
483
523
|
const chunk = uniqueIds.slice(i, i + chunkSize);
|
|
484
524
|
const chunkResults = await Promise.allSettled(chunk.map((id) => fetchSingleVulnerability(id)));
|
|
525
|
+
if (failOnError) {
|
|
526
|
+
const rejections = chunkResults.filter((r) => r.status === "rejected");
|
|
527
|
+
if (rejections.length > 0) {
|
|
528
|
+
const firstError = rejections[0].reason;
|
|
529
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
530
|
+
logger.error(`Failed to fetch vulnerability details (strict mode)`, {
|
|
531
|
+
error: message,
|
|
532
|
+
failedCount: rejections.length
|
|
533
|
+
});
|
|
534
|
+
throw firstError;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
485
537
|
for (const result of chunkResults) {
|
|
486
538
|
if (result.status === "fulfilled" && result.value) {
|
|
487
539
|
vulnerabilities.push(result.value);
|
|
@@ -499,8 +551,16 @@ function createOSVClient(options = {}) {
|
|
|
499
551
|
const batchIds = await executeBatchQuery(batchQueries);
|
|
500
552
|
vulnerabilityIds.push(...batchIds);
|
|
501
553
|
} catch (error) {
|
|
554
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
555
|
+
if (failOnError) {
|
|
556
|
+
logger.error(`Batch query failed for ${batchQueries.length} packages (strict mode)`, {
|
|
557
|
+
error: message,
|
|
558
|
+
startIndex: i
|
|
559
|
+
});
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
502
562
|
logger.error(`Batch query failed for ${batchQueries.length} packages`, {
|
|
503
|
-
error:
|
|
563
|
+
error: message,
|
|
504
564
|
startIndex: i
|
|
505
565
|
});
|
|
506
566
|
}
|
|
@@ -538,8 +598,15 @@ function createOSVClient(options = {}) {
|
|
|
538
598
|
break;
|
|
539
599
|
}
|
|
540
600
|
} catch (error) {
|
|
601
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
602
|
+
if (failOnError) {
|
|
603
|
+
logger.error(`Query failed for ${query.package?.name || "unknown"}@${query.version || "unknown"} (strict mode)`, {
|
|
604
|
+
error: message
|
|
605
|
+
});
|
|
606
|
+
throw error;
|
|
607
|
+
}
|
|
541
608
|
logger.warn(`Query failed for ${query.package?.name || "unknown"}@${query.version || "unknown"}`, {
|
|
542
|
-
error:
|
|
609
|
+
error: message
|
|
543
610
|
});
|
|
544
611
|
break;
|
|
545
612
|
}
|
|
@@ -548,6 +615,18 @@ function createOSVClient(options = {}) {
|
|
|
548
615
|
}
|
|
549
616
|
async function queryIndividually(queries) {
|
|
550
617
|
const responses = await Promise.allSettled(queries.map((query) => querySinglePackage(query)));
|
|
618
|
+
if (failOnError) {
|
|
619
|
+
const rejections = responses.filter((r) => r.status === "rejected");
|
|
620
|
+
if (rejections.length > 0) {
|
|
621
|
+
const firstError = rejections[0].reason;
|
|
622
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
623
|
+
logger.error(`Individual query failed (strict mode)`, {
|
|
624
|
+
error: message,
|
|
625
|
+
failedCount: rejections.length
|
|
626
|
+
});
|
|
627
|
+
throw firstError;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
551
630
|
const vulnerabilities = [];
|
|
552
631
|
let successCount = 0;
|
|
553
632
|
for (const response of responses) {
|
|
@@ -846,6 +925,8 @@ function createVulnerabilityProcessor(ignoreConfig = {}) {
|
|
|
846
925
|
function isNewOptionsFormat(options) {
|
|
847
926
|
if ("osv" in options)
|
|
848
927
|
return true;
|
|
928
|
+
if ("failOnScannerError" in options)
|
|
929
|
+
return true;
|
|
849
930
|
if ("ignore" in options && options.ignore && typeof options.ignore === "object" && !Array.isArray(options.ignore)) {
|
|
850
931
|
return true;
|
|
851
932
|
}
|
|
@@ -854,14 +935,32 @@ function isNewOptionsFormat(options) {
|
|
|
854
935
|
function createOSVSource(options = {}) {
|
|
855
936
|
let ignoreConfig;
|
|
856
937
|
let osvConfig;
|
|
938
|
+
let failOnScannerError;
|
|
857
939
|
if (isNewOptionsFormat(options)) {
|
|
858
|
-
|
|
940
|
+
const ignoreFromOptions = options.ignore;
|
|
941
|
+
const packagesFromOptions = options.packages;
|
|
942
|
+
if (ignoreFromOptions !== undefined) {
|
|
943
|
+
if (Array.isArray(ignoreFromOptions)) {
|
|
944
|
+
ignoreConfig = { ignore: ignoreFromOptions, packages: packagesFromOptions };
|
|
945
|
+
} else {
|
|
946
|
+
ignoreConfig = {
|
|
947
|
+
ignore: ignoreFromOptions.ignore,
|
|
948
|
+
packages: ignoreFromOptions.packages ?? packagesFromOptions
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
} else if (packagesFromOptions !== undefined) {
|
|
952
|
+
ignoreConfig = { ignore: undefined, packages: packagesFromOptions };
|
|
953
|
+
} else {
|
|
954
|
+
ignoreConfig = {};
|
|
955
|
+
}
|
|
859
956
|
osvConfig = options.osv;
|
|
957
|
+
failOnScannerError = options.failOnScannerError;
|
|
860
958
|
} else {
|
|
861
959
|
ignoreConfig = options;
|
|
862
960
|
osvConfig = undefined;
|
|
961
|
+
failOnScannerError = undefined;
|
|
863
962
|
}
|
|
864
|
-
const client = createOSVClient({ osv: osvConfig });
|
|
963
|
+
const client = createOSVClient({ osv: osvConfig, failOnScannerError });
|
|
865
964
|
const processor = createVulnerabilityProcessor(ignoreConfig);
|
|
866
965
|
return {
|
|
867
966
|
name: "osv",
|
|
@@ -934,6 +1033,7 @@ function createNpmAuditClient(options = {}) {
|
|
|
934
1033
|
const npmConfig = options.npm ?? CONFIG_DEFAULTS.npm;
|
|
935
1034
|
const registryUrl = npmConfig.registryUrl ?? NPM_AUDIT_API.REGISTRY_URL;
|
|
936
1035
|
const timeout = npmConfig.timeoutMs ?? NPM_AUDIT_API.TIMEOUT_MS;
|
|
1036
|
+
const failOnError = options.failOnScannerError ?? false;
|
|
937
1037
|
function deduplicatePackages(packages) {
|
|
938
1038
|
const packageMap = new Map;
|
|
939
1039
|
for (const pkg of packages) {
|
|
@@ -1012,8 +1112,16 @@ function createNpmAuditClient(options = {}) {
|
|
|
1012
1112
|
const batchAdvisories = await executeBulkQuery(payload);
|
|
1013
1113
|
advisories.push(...batchAdvisories);
|
|
1014
1114
|
} catch (error) {
|
|
1115
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1116
|
+
if (failOnError) {
|
|
1117
|
+
logger.error(`Batch query failed for ${batch.length} packages (strict mode)`, {
|
|
1118
|
+
error: message,
|
|
1119
|
+
startIndex: i
|
|
1120
|
+
});
|
|
1121
|
+
throw error;
|
|
1122
|
+
}
|
|
1015
1123
|
logger.error(`Batch query failed for ${batch.length} packages`, {
|
|
1016
|
-
error:
|
|
1124
|
+
error: message,
|
|
1017
1125
|
startIndex: i
|
|
1018
1126
|
});
|
|
1019
1127
|
}
|
|
@@ -1030,7 +1138,23 @@ function createNpmAuditClient(options = {}) {
|
|
|
1030
1138
|
if (uniquePackages.length > NPM_AUDIT_API.MAX_PACKAGES_PER_REQUEST) {
|
|
1031
1139
|
return await queryInBatches(uniquePackages);
|
|
1032
1140
|
}
|
|
1033
|
-
|
|
1141
|
+
try {
|
|
1142
|
+
return await executeBulkQuery(requestPayload);
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1145
|
+
if (failOnError) {
|
|
1146
|
+
logger.error(`Bulk query failed (strict mode)`, {
|
|
1147
|
+
error: message,
|
|
1148
|
+
packageCount: uniquePackages.length
|
|
1149
|
+
});
|
|
1150
|
+
throw error;
|
|
1151
|
+
}
|
|
1152
|
+
logger.error(`Bulk query failed`, {
|
|
1153
|
+
error: message,
|
|
1154
|
+
packageCount: uniquePackages.length
|
|
1155
|
+
});
|
|
1156
|
+
return [];
|
|
1157
|
+
}
|
|
1034
1158
|
}
|
|
1035
1159
|
return {
|
|
1036
1160
|
queryVulnerabilities
|
|
@@ -1176,6 +1300,8 @@ function createAdvisoryProcessor(ignoreConfig = {}) {
|
|
|
1176
1300
|
function isNewOptionsFormat2(options) {
|
|
1177
1301
|
if ("npm" in options)
|
|
1178
1302
|
return true;
|
|
1303
|
+
if ("failOnScannerError" in options)
|
|
1304
|
+
return true;
|
|
1179
1305
|
if ("ignore" in options && options.ignore && typeof options.ignore === "object" && !Array.isArray(options.ignore)) {
|
|
1180
1306
|
return true;
|
|
1181
1307
|
}
|
|
@@ -1184,14 +1310,32 @@ function isNewOptionsFormat2(options) {
|
|
|
1184
1310
|
function createNpmSource(options = {}) {
|
|
1185
1311
|
let ignoreConfig;
|
|
1186
1312
|
let npmConfig;
|
|
1313
|
+
let failOnScannerError;
|
|
1187
1314
|
if (isNewOptionsFormat2(options)) {
|
|
1188
|
-
|
|
1315
|
+
const ignoreFromOptions = options.ignore;
|
|
1316
|
+
const packagesFromOptions = options.packages;
|
|
1317
|
+
if (ignoreFromOptions !== undefined) {
|
|
1318
|
+
if (Array.isArray(ignoreFromOptions)) {
|
|
1319
|
+
ignoreConfig = { ignore: ignoreFromOptions, packages: packagesFromOptions };
|
|
1320
|
+
} else {
|
|
1321
|
+
ignoreConfig = {
|
|
1322
|
+
ignore: ignoreFromOptions.ignore,
|
|
1323
|
+
packages: ignoreFromOptions.packages ?? packagesFromOptions
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
} else if (packagesFromOptions !== undefined) {
|
|
1327
|
+
ignoreConfig = { ignore: undefined, packages: packagesFromOptions };
|
|
1328
|
+
} else {
|
|
1329
|
+
ignoreConfig = {};
|
|
1330
|
+
}
|
|
1189
1331
|
npmConfig = options.npm;
|
|
1332
|
+
failOnScannerError = options.failOnScannerError;
|
|
1190
1333
|
} else {
|
|
1191
1334
|
ignoreConfig = options;
|
|
1192
1335
|
npmConfig = undefined;
|
|
1336
|
+
failOnScannerError = undefined;
|
|
1193
1337
|
}
|
|
1194
|
-
const client = createNpmAuditClient({ npm: npmConfig });
|
|
1338
|
+
const client = createNpmAuditClient({ npm: npmConfig, failOnScannerError });
|
|
1195
1339
|
const processor = createAdvisoryProcessor(ignoreConfig);
|
|
1196
1340
|
return {
|
|
1197
1341
|
name: "npm",
|
|
@@ -1211,17 +1355,17 @@ function createNpmSource(options = {}) {
|
|
|
1211
1355
|
function extractIgnoreConfig(config) {
|
|
1212
1356
|
return { ignore: config.ignore, packages: config.packages };
|
|
1213
1357
|
}
|
|
1214
|
-
function createSources(type, config) {
|
|
1358
|
+
function createSources(type, config, failOnScannerError) {
|
|
1215
1359
|
const ignoreConfig = extractIgnoreConfig(config);
|
|
1216
1360
|
switch (type) {
|
|
1217
1361
|
case "osv":
|
|
1218
|
-
return [createOSVSource({ ignore: ignoreConfig, osv: config.osv })];
|
|
1362
|
+
return [createOSVSource({ ignore: ignoreConfig, osv: config.osv, failOnScannerError })];
|
|
1219
1363
|
case "npm":
|
|
1220
|
-
return [createNpmSource({ ignore: ignoreConfig, npm: config.npm })];
|
|
1364
|
+
return [createNpmSource({ ignore: ignoreConfig, npm: config.npm, failOnScannerError })];
|
|
1221
1365
|
case "both":
|
|
1222
1366
|
return [
|
|
1223
|
-
createOSVSource({ ignore: ignoreConfig, osv: config.osv }),
|
|
1224
|
-
createNpmSource({ ignore: ignoreConfig, npm: config.npm })
|
|
1367
|
+
createOSVSource({ ignore: ignoreConfig, osv: config.osv, failOnScannerError }),
|
|
1368
|
+
createNpmSource({ ignore: ignoreConfig, npm: config.npm, failOnScannerError })
|
|
1225
1369
|
];
|
|
1226
1370
|
default:
|
|
1227
1371
|
throw new Error(`Unknown source type: ${type}`);
|
|
@@ -1229,10 +1373,11 @@ function createSources(type, config) {
|
|
|
1229
1373
|
}
|
|
1230
1374
|
|
|
1231
1375
|
// src/sources/multi.ts
|
|
1232
|
-
function createMultiSourceScanner(sources) {
|
|
1376
|
+
function createMultiSourceScanner(sources, options) {
|
|
1233
1377
|
if (sources.length === 0) {
|
|
1234
1378
|
throw new Error("MultiSourceScanner requires at least one source");
|
|
1235
1379
|
}
|
|
1380
|
+
const failOnError = options?.failOnScannerError ?? false;
|
|
1236
1381
|
function isHigherSeverity(a, b) {
|
|
1237
1382
|
const priority = { fatal: 2, warn: 1 };
|
|
1238
1383
|
return (priority[a] ?? 0) > (priority[b] ?? 0);
|
|
@@ -1269,8 +1414,9 @@ function createMultiSourceScanner(sources) {
|
|
|
1269
1414
|
async function scan(packages) {
|
|
1270
1415
|
const sourceNames = sources.map((s) => s.name).join(", ");
|
|
1271
1416
|
logger.info(`Scanning with sources: ${sourceNames}`);
|
|
1272
|
-
const results = await Promise.allSettled(sources.map((source) => source.scan(packages)));
|
|
1417
|
+
const results = await Promise.allSettled(sources.map((source) => Promise.resolve().then(() => source.scan(packages))));
|
|
1273
1418
|
const allAdvisories = [];
|
|
1419
|
+
const failures = [];
|
|
1274
1420
|
for (const [i, result] of results.entries()) {
|
|
1275
1421
|
const source = sources[i];
|
|
1276
1422
|
if (!source)
|
|
@@ -1279,11 +1425,15 @@ function createMultiSourceScanner(sources) {
|
|
|
1279
1425
|
logger.debug(`[${source.name}] Found ${result.value.length} advisories`);
|
|
1280
1426
|
allAdvisories.push(...result.value);
|
|
1281
1427
|
} else {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
});
|
|
1428
|
+
const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
1429
|
+
logger.error(`[${source.name}] Scan failed`, { error: errorMessage });
|
|
1430
|
+
failures.push({ name: source.name, error: errorMessage });
|
|
1285
1431
|
}
|
|
1286
1432
|
}
|
|
1433
|
+
if (failOnError && failures.length > 0) {
|
|
1434
|
+
const details = failures.map((f) => `${f.name}: ${f.error}`).join("; ");
|
|
1435
|
+
throw new Error(`bun-scan: scan failed for ${failures.length === 1 ? "source" : "sources"} ` + `${failures.map((f) => `"${f.name}"`).join(", ")}. ` + `Details: ${details}. ` + `failOnScannerError=true requires all configured sources to succeed.`);
|
|
1436
|
+
}
|
|
1287
1437
|
return deduplicateAdvisories(allAdvisories);
|
|
1288
1438
|
}
|
|
1289
1439
|
return {
|
|
@@ -1295,12 +1445,16 @@ function createMultiSourceScanner(sources) {
|
|
|
1295
1445
|
var scanner = {
|
|
1296
1446
|
version: "1",
|
|
1297
1447
|
async scan({ packages }) {
|
|
1448
|
+
let failOnScannerError = parseEnvBoolean(ENV.FAIL_ON_SCANNER_ERROR) === true;
|
|
1298
1449
|
try {
|
|
1299
1450
|
logger.debug(`Starting vulnerability scan for ${packages.length} packages`);
|
|
1300
1451
|
const config = await loadConfig();
|
|
1301
1452
|
const bunReportWarnings = config.bunReportWarnings ?? CONFIG_DEFAULTS.bunReportWarnings;
|
|
1302
|
-
|
|
1303
|
-
|
|
1453
|
+
if (parseEnvBoolean(ENV.FAIL_ON_SCANNER_ERROR) === undefined) {
|
|
1454
|
+
failOnScannerError = config.failOnScannerError ?? failOnScannerError;
|
|
1455
|
+
}
|
|
1456
|
+
const sources = createSources(config.source ?? "osv", config, failOnScannerError);
|
|
1457
|
+
const multiScanner = createMultiSourceScanner(sources, { failOnScannerError });
|
|
1304
1458
|
const advisories = await multiScanner.scan(packages);
|
|
1305
1459
|
logger.info(`Scan completed: ${advisories.length} advisories found for ${packages.length} packages`);
|
|
1306
1460
|
if (!bunReportWarnings) {
|
|
@@ -1318,6 +1472,12 @@ var scanner = {
|
|
|
1318
1472
|
return advisories;
|
|
1319
1473
|
} catch (error) {
|
|
1320
1474
|
const message = error instanceof Error ? error.message : String(error);
|
|
1475
|
+
if (failOnScannerError) {
|
|
1476
|
+
logger.error("Scanner error in strict mode \u2014 failing scan", {
|
|
1477
|
+
error: message
|
|
1478
|
+
});
|
|
1479
|
+
throw error;
|
|
1480
|
+
}
|
|
1321
1481
|
logger.error("Scanner encountered an unexpected error", {
|
|
1322
1482
|
error: message
|
|
1323
1483
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ export declare const DEFAULT_RETRY_CONFIG: RetryConfig
|
|
|
64
64
|
export declare function withRetry<T>(
|
|
65
65
|
operation: () => Promise<T>,
|
|
66
66
|
operationName: string,
|
|
67
|
-
config?:
|
|
67
|
+
config?: RetryConfig,
|
|
68
68
|
): Promise<T>
|
|
69
69
|
|
|
70
70
|
// ============================================================================
|
|
@@ -86,6 +86,12 @@ export interface Config {
|
|
|
86
86
|
source?: SourceType
|
|
87
87
|
ignore?: string[]
|
|
88
88
|
packages?: Record<string, IgnorePackageRule>
|
|
89
|
+
logLevel?: "debug" | "info" | "warn" | "error"
|
|
90
|
+
bunReportWarnings?: boolean
|
|
91
|
+
/** Fail on scanner errors (block install). Env var overrides config file (escape hatch). */
|
|
92
|
+
failOnScannerError?: boolean
|
|
93
|
+
osv?: OsvConfig
|
|
94
|
+
npm?: NpmConfig
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
export interface OsvConfig {
|
|
@@ -94,9 +100,29 @@ export interface OsvConfig {
|
|
|
94
100
|
disableBatch?: boolean
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
export interface NpmConfig {
|
|
104
|
+
registryUrl?: string
|
|
105
|
+
timeoutMs?: number
|
|
106
|
+
}
|
|
107
|
+
|
|
97
108
|
export interface CreateOSVSourceOptions {
|
|
98
|
-
|
|
109
|
+
/** Legacy array form or new object form */
|
|
110
|
+
ignore?: IgnoreConfig | string[]
|
|
111
|
+
/** Legacy packages config (when ignore is not an object) */
|
|
112
|
+
packages?: Record<string, IgnorePackageRule>
|
|
99
113
|
osv?: OsvConfig
|
|
114
|
+
/** When true, throw on internal errors (batch/query failures) instead of continuing with partial results */
|
|
115
|
+
failOnScannerError?: boolean
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface CreateNpmSourceOptions {
|
|
119
|
+
/** Legacy array form or new object form */
|
|
120
|
+
ignore?: IgnoreConfig | string[]
|
|
121
|
+
/** Legacy packages config (when ignore is not an object) */
|
|
122
|
+
packages?: Record<string, IgnorePackageRule>
|
|
123
|
+
npm?: NpmConfig
|
|
124
|
+
/** When true, throw on internal errors (batch/query failures) instead of continuing with partial results */
|
|
125
|
+
failOnScannerError?: boolean
|
|
100
126
|
}
|
|
101
127
|
|
|
102
128
|
export interface CompiledPackageRule {
|
|
@@ -110,6 +136,24 @@ export interface CompiledIgnoreConfig {
|
|
|
110
136
|
packages: Map<string, CompiledPackageRule>
|
|
111
137
|
}
|
|
112
138
|
|
|
139
|
+
export interface ConfigDefaults {
|
|
140
|
+
readonly logLevel: "debug" | "info" | "warn" | "error"
|
|
141
|
+
readonly bunReportWarnings: boolean
|
|
142
|
+
readonly failOnScannerError: boolean
|
|
143
|
+
readonly osv: {
|
|
144
|
+
readonly apiBaseUrl: string
|
|
145
|
+
readonly timeoutMs: number
|
|
146
|
+
readonly disableBatch: boolean
|
|
147
|
+
}
|
|
148
|
+
readonly npm: {
|
|
149
|
+
readonly registryUrl: string
|
|
150
|
+
readonly timeoutMs: number
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Default configuration values */
|
|
155
|
+
export declare const CONFIG_DEFAULTS: ConfigDefaults
|
|
156
|
+
|
|
113
157
|
/** Zod schema for ignore config validation */
|
|
114
158
|
export declare const IgnoreConfigSchema: z.ZodObject<{
|
|
115
159
|
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>
|
|
@@ -139,6 +183,22 @@ export declare const ConfigSchema: z.ZodObject<{
|
|
|
139
183
|
}>
|
|
140
184
|
>
|
|
141
185
|
>
|
|
186
|
+
logLevel: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>
|
|
187
|
+
bunReportWarnings: z.ZodOptional<z.ZodBoolean>
|
|
188
|
+
failOnScannerError: z.ZodOptional<z.ZodBoolean>
|
|
189
|
+
osv: z.ZodOptional<
|
|
190
|
+
z.ZodObject<{
|
|
191
|
+
apiBaseUrl: z.ZodOptional<z.ZodString>
|
|
192
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>
|
|
193
|
+
disableBatch: z.ZodOptional<z.ZodBoolean>
|
|
194
|
+
}>
|
|
195
|
+
>
|
|
196
|
+
npm: z.ZodOptional<
|
|
197
|
+
z.ZodObject<{
|
|
198
|
+
registryUrl: z.ZodOptional<z.ZodString>
|
|
199
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>
|
|
200
|
+
}>
|
|
201
|
+
>
|
|
142
202
|
}>
|
|
143
203
|
|
|
144
204
|
/** Load config from .bun-scan.json or .bun-scan.config.json */
|
|
@@ -149,11 +209,11 @@ export declare function compileIgnoreConfig(config: IgnoreConfig): CompiledIgnor
|
|
|
149
209
|
|
|
150
210
|
/** Check if a vulnerability should be ignored */
|
|
151
211
|
export declare function shouldIgnoreVulnerability(
|
|
152
|
-
compiledConfig: CompiledIgnoreConfig,
|
|
153
212
|
vulnId: string,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
213
|
+
vulnAliases: string[] | undefined,
|
|
214
|
+
packageName: string | undefined,
|
|
215
|
+
config: CompiledIgnoreConfig,
|
|
216
|
+
): { ignored: boolean; reason?: string }
|
|
157
217
|
|
|
158
218
|
// ============================================================================
|
|
159
219
|
// Source Factories
|
|
@@ -165,15 +225,22 @@ export declare function createOSVSource(
|
|
|
165
225
|
): VulnerabilitySource
|
|
166
226
|
|
|
167
227
|
/** Create an npm audit vulnerability source */
|
|
168
|
-
export declare function createNpmSource(
|
|
228
|
+
export declare function createNpmSource(
|
|
229
|
+
options?: CreateNpmSourceOptions | IgnoreConfig,
|
|
230
|
+
): VulnerabilitySource
|
|
169
231
|
|
|
170
232
|
/** Create a vulnerability source by type */
|
|
171
|
-
export declare function createSource(
|
|
233
|
+
export declare function createSource(
|
|
234
|
+
type: "osv" | "npm",
|
|
235
|
+
config: Config,
|
|
236
|
+
failOnScannerError?: boolean,
|
|
237
|
+
): VulnerabilitySource
|
|
172
238
|
|
|
173
239
|
/** Create all sources for a given type (both = OSV + npm) */
|
|
174
240
|
export declare function createSources(
|
|
175
241
|
type: SourceType,
|
|
176
|
-
config
|
|
242
|
+
config: Config,
|
|
243
|
+
failOnScannerError?: boolean,
|
|
177
244
|
): VulnerabilitySource[]
|
|
178
245
|
|
|
179
246
|
// ============================================================================
|
|
@@ -184,8 +251,17 @@ export interface MultiSourceScanner {
|
|
|
184
251
|
scan(packages: Bun.Security.Package[]): Promise<Bun.Security.Advisory[]>
|
|
185
252
|
}
|
|
186
253
|
|
|
254
|
+
/** Options for multi-source scanner */
|
|
255
|
+
export interface MultiSourceScannerOptions {
|
|
256
|
+
/** When true, throw if any configured source fails */
|
|
257
|
+
failOnScannerError?: boolean
|
|
258
|
+
}
|
|
259
|
+
|
|
187
260
|
/** Create a scanner that queries multiple sources in parallel */
|
|
188
|
-
export declare function createMultiSourceScanner(
|
|
261
|
+
export declare function createMultiSourceScanner(
|
|
262
|
+
sources: VulnerabilitySource[],
|
|
263
|
+
options?: MultiSourceScannerOptions,
|
|
264
|
+
): MultiSourceScanner
|
|
189
265
|
|
|
190
266
|
// ============================================================================
|
|
191
267
|
// Main Scanner Export
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, {
|
|
6
10
|
get: all[name],
|
|
7
11
|
enumerable: true,
|
|
8
12
|
configurable: true,
|
|
9
|
-
set: (
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
10
14
|
});
|
|
11
15
|
};
|
|
12
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -44,7 +48,8 @@ var init_constants = __esm(() => {
|
|
|
44
48
|
LOG_LEVEL: "BUN_SCAN_LOG_LEVEL",
|
|
45
49
|
API_BASE_URL: "OSV_API_BASE_URL",
|
|
46
50
|
TIMEOUT_MS: "OSV_TIMEOUT_MS",
|
|
47
|
-
DISABLE_BATCH: "OSV_DISABLE_BATCH"
|
|
51
|
+
DISABLE_BATCH: "OSV_DISABLE_BATCH",
|
|
52
|
+
FAIL_ON_SCANNER_ERROR: "BUN_SCAN_FAIL_ON_SCANNER_ERROR"
|
|
48
53
|
};
|
|
49
54
|
});
|
|
50
55
|
|
|
@@ -179,6 +184,7 @@ function parseEnvLogLevel(envVar) {
|
|
|
179
184
|
function buildEnvConfig() {
|
|
180
185
|
return {
|
|
181
186
|
logLevel: parseEnvLogLevel(ENV.LOG_LEVEL),
|
|
187
|
+
failOnScannerError: parseEnvBoolean(ENV.FAIL_ON_SCANNER_ERROR),
|
|
182
188
|
osv: {
|
|
183
189
|
apiBaseUrl: Bun.env[ENV.API_BASE_URL] || undefined,
|
|
184
190
|
timeoutMs: parseEnvNumber(ENV.TIMEOUT_MS),
|
|
@@ -196,11 +202,14 @@ function mergeConfig(fileConfig) {
|
|
|
196
202
|
source: DEFAULT_SOURCE,
|
|
197
203
|
logLevel: CONFIG_DEFAULTS.logLevel,
|
|
198
204
|
bunReportWarnings: CONFIG_DEFAULTS.bunReportWarnings,
|
|
205
|
+
failOnScannerError: CONFIG_DEFAULTS.failOnScannerError,
|
|
199
206
|
osv: { ...CONFIG_DEFAULTS.osv },
|
|
200
207
|
npm: { ...CONFIG_DEFAULTS.npm }
|
|
201
208
|
};
|
|
202
209
|
if (envConfig.logLevel !== undefined)
|
|
203
210
|
merged.logLevel = envConfig.logLevel;
|
|
211
|
+
if (envConfig.failOnScannerError !== undefined)
|
|
212
|
+
merged.failOnScannerError = envConfig.failOnScannerError;
|
|
204
213
|
if (envConfig.osv?.apiBaseUrl !== undefined)
|
|
205
214
|
merged.osv.apiBaseUrl = envConfig.osv.apiBaseUrl;
|
|
206
215
|
if (envConfig.osv?.timeoutMs !== undefined)
|
|
@@ -222,6 +231,8 @@ function mergeConfig(fileConfig) {
|
|
|
222
231
|
merged.logLevel = fileConfig.logLevel;
|
|
223
232
|
if (fileConfig.bunReportWarnings !== undefined)
|
|
224
233
|
merged.bunReportWarnings = fileConfig.bunReportWarnings;
|
|
234
|
+
if (fileConfig.failOnScannerError !== undefined)
|
|
235
|
+
merged.failOnScannerError = fileConfig.failOnScannerError;
|
|
225
236
|
if (fileConfig.osv?.apiBaseUrl !== undefined)
|
|
226
237
|
merged.osv.apiBaseUrl = fileConfig.osv.apiBaseUrl;
|
|
227
238
|
if (fileConfig.osv?.timeoutMs !== undefined)
|
|
@@ -233,18 +244,22 @@ function mergeConfig(fileConfig) {
|
|
|
233
244
|
if (fileConfig.npm?.timeoutMs !== undefined)
|
|
234
245
|
merged.npm.timeoutMs = fileConfig.npm.timeoutMs;
|
|
235
246
|
}
|
|
247
|
+
if (envConfig.failOnScannerError !== undefined) {
|
|
248
|
+
merged.failOnScannerError = envConfig.failOnScannerError;
|
|
249
|
+
}
|
|
236
250
|
return merged;
|
|
237
251
|
}
|
|
238
252
|
async function loadConfig() {
|
|
253
|
+
const strictBootstrap = parseEnvBoolean(ENV.FAIL_ON_SCANNER_ERROR) === true;
|
|
239
254
|
for (const filename of CONFIG_FILES) {
|
|
240
|
-
const config = await tryLoadConfigFile(filename);
|
|
255
|
+
const config = await tryLoadConfigFile(filename, { fatalOnError: strictBootstrap });
|
|
241
256
|
if (config) {
|
|
242
257
|
return mergeConfig(config);
|
|
243
258
|
}
|
|
244
259
|
}
|
|
245
260
|
return mergeConfig(null);
|
|
246
261
|
}
|
|
247
|
-
async function tryLoadConfigFile(filename) {
|
|
262
|
+
async function tryLoadConfigFile(filename, options) {
|
|
248
263
|
try {
|
|
249
264
|
const file = Bun.file(filename);
|
|
250
265
|
const exists = await file.exists();
|
|
@@ -257,6 +272,18 @@ async function tryLoadConfigFile(filename) {
|
|
|
257
272
|
logConfigStats(parsed);
|
|
258
273
|
return parsed;
|
|
259
274
|
} catch (error) {
|
|
275
|
+
const isENOENT = error instanceof Error && ("code" in error ? error.code === "ENOENT" : error.message.includes("No such file"));
|
|
276
|
+
if (options?.fatalOnError) {
|
|
277
|
+
if (isENOENT) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
logger.error(`Failed to read config file ${filename}`, {
|
|
281
|
+
error: error instanceof Error ? error.message : String(error),
|
|
282
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
283
|
+
});
|
|
284
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
285
|
+
throw new Error(`bun-scan: failed to load config file ${filename}: ${message}. ` + `BUN_SCAN_FAIL_ON_SCANNER_ERROR=true makes config errors fatal.`);
|
|
286
|
+
}
|
|
260
287
|
if (error instanceof z.ZodError) {
|
|
261
288
|
logger.warn(`Invalid config in ${filename}`, {
|
|
262
289
|
errors: error.issues.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
@@ -265,6 +292,13 @@ async function tryLoadConfigFile(filename) {
|
|
|
265
292
|
logger.warn(`Failed to parse ${filename} as JSON`, {
|
|
266
293
|
error: error.message
|
|
267
294
|
});
|
|
295
|
+
} else if (isENOENT) {
|
|
296
|
+
logger.debug(`Config file ${filename} not found (race condition handled)`);
|
|
297
|
+
} else {
|
|
298
|
+
logger.warn(`Failed to read config file ${filename}`, {
|
|
299
|
+
error: error instanceof Error ? error.message : String(error),
|
|
300
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
301
|
+
});
|
|
268
302
|
}
|
|
269
303
|
return null;
|
|
270
304
|
}
|
|
@@ -315,6 +349,7 @@ var init_config = __esm(() => {
|
|
|
315
349
|
CONFIG_DEFAULTS = {
|
|
316
350
|
logLevel: "info",
|
|
317
351
|
bunReportWarnings: true,
|
|
352
|
+
failOnScannerError: false,
|
|
318
353
|
osv: {
|
|
319
354
|
apiBaseUrl: OSV_API.BASE_URL,
|
|
320
355
|
timeoutMs: OSV_API.TIMEOUT_MS,
|
|
@@ -349,6 +384,7 @@ var init_config = __esm(() => {
|
|
|
349
384
|
packages: z.record(z.string(), IgnorePackageRuleSchema).optional(),
|
|
350
385
|
logLevel: z.enum(["debug", "info", "warn", "error"]).optional(),
|
|
351
386
|
bunReportWarnings: z.boolean().optional(),
|
|
387
|
+
failOnScannerError: z.boolean().optional(),
|
|
352
388
|
osv: OsvConfigSchema.optional(),
|
|
353
389
|
npm: NpmConfigSchema.optional()
|
|
354
390
|
});
|
|
@@ -447,6 +483,7 @@ function createOSVClient(options = {}) {
|
|
|
447
483
|
const baseUrl = osvConfig.apiBaseUrl ?? OSV_API.BASE_URL;
|
|
448
484
|
const timeout = osvConfig.timeoutMs ?? OSV_API.TIMEOUT_MS;
|
|
449
485
|
const useBatch = !(osvConfig.disableBatch ?? false);
|
|
486
|
+
const failOnError = options.failOnScannerError ?? false;
|
|
450
487
|
function deduplicatePackages(packages) {
|
|
451
488
|
const packageMap = new Map;
|
|
452
489
|
for (const pkg of packages) {
|
|
@@ -505,8 +542,15 @@ function createOSVClient(options = {}) {
|
|
|
505
542
|
return OSVVulnerabilitySchema.parse(data);
|
|
506
543
|
}, `Get vulnerability ${id}`);
|
|
507
544
|
} catch (error) {
|
|
545
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
546
|
+
if (failOnError) {
|
|
547
|
+
logger.error(`Failed to fetch vulnerability ${id} (strict mode)`, {
|
|
548
|
+
error: message
|
|
549
|
+
});
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
508
552
|
logger.warn(`Failed to fetch vulnerability ${id}`, {
|
|
509
|
-
error:
|
|
553
|
+
error: message
|
|
510
554
|
});
|
|
511
555
|
return null;
|
|
512
556
|
}
|
|
@@ -521,6 +565,18 @@ function createOSVClient(options = {}) {
|
|
|
521
565
|
for (let i = 0;i < uniqueIds.length; i += chunkSize) {
|
|
522
566
|
const chunk = uniqueIds.slice(i, i + chunkSize);
|
|
523
567
|
const chunkResults = await Promise.allSettled(chunk.map((id) => fetchSingleVulnerability(id)));
|
|
568
|
+
if (failOnError) {
|
|
569
|
+
const rejections = chunkResults.filter((r) => r.status === "rejected");
|
|
570
|
+
if (rejections.length > 0) {
|
|
571
|
+
const firstError = rejections[0].reason;
|
|
572
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
573
|
+
logger.error(`Failed to fetch vulnerability details (strict mode)`, {
|
|
574
|
+
error: message,
|
|
575
|
+
failedCount: rejections.length
|
|
576
|
+
});
|
|
577
|
+
throw firstError;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
524
580
|
for (const result of chunkResults) {
|
|
525
581
|
if (result.status === "fulfilled" && result.value) {
|
|
526
582
|
vulnerabilities.push(result.value);
|
|
@@ -538,8 +594,16 @@ function createOSVClient(options = {}) {
|
|
|
538
594
|
const batchIds = await executeBatchQuery(batchQueries);
|
|
539
595
|
vulnerabilityIds.push(...batchIds);
|
|
540
596
|
} catch (error) {
|
|
597
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
598
|
+
if (failOnError) {
|
|
599
|
+
logger.error(`Batch query failed for ${batchQueries.length} packages (strict mode)`, {
|
|
600
|
+
error: message,
|
|
601
|
+
startIndex: i
|
|
602
|
+
});
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
541
605
|
logger.error(`Batch query failed for ${batchQueries.length} packages`, {
|
|
542
|
-
error:
|
|
606
|
+
error: message,
|
|
543
607
|
startIndex: i
|
|
544
608
|
});
|
|
545
609
|
}
|
|
@@ -577,8 +641,15 @@ function createOSVClient(options = {}) {
|
|
|
577
641
|
break;
|
|
578
642
|
}
|
|
579
643
|
} catch (error) {
|
|
644
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
645
|
+
if (failOnError) {
|
|
646
|
+
logger.error(`Query failed for ${query.package?.name || "unknown"}@${query.version || "unknown"} (strict mode)`, {
|
|
647
|
+
error: message
|
|
648
|
+
});
|
|
649
|
+
throw error;
|
|
650
|
+
}
|
|
580
651
|
logger.warn(`Query failed for ${query.package?.name || "unknown"}@${query.version || "unknown"}`, {
|
|
581
|
-
error:
|
|
652
|
+
error: message
|
|
582
653
|
});
|
|
583
654
|
break;
|
|
584
655
|
}
|
|
@@ -587,6 +658,18 @@ function createOSVClient(options = {}) {
|
|
|
587
658
|
}
|
|
588
659
|
async function queryIndividually(queries) {
|
|
589
660
|
const responses = await Promise.allSettled(queries.map((query) => querySinglePackage(query)));
|
|
661
|
+
if (failOnError) {
|
|
662
|
+
const rejections = responses.filter((r) => r.status === "rejected");
|
|
663
|
+
if (rejections.length > 0) {
|
|
664
|
+
const firstError = rejections[0].reason;
|
|
665
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
666
|
+
logger.error(`Individual query failed (strict mode)`, {
|
|
667
|
+
error: message,
|
|
668
|
+
failedCount: rejections.length
|
|
669
|
+
});
|
|
670
|
+
throw firstError;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
590
673
|
const vulnerabilities = [];
|
|
591
674
|
let successCount = 0;
|
|
592
675
|
for (const response of responses) {
|
|
@@ -901,6 +984,8 @@ var init_processor = __esm(() => {
|
|
|
901
984
|
function isNewOptionsFormat(options) {
|
|
902
985
|
if ("osv" in options)
|
|
903
986
|
return true;
|
|
987
|
+
if ("failOnScannerError" in options)
|
|
988
|
+
return true;
|
|
904
989
|
if ("ignore" in options && options.ignore && typeof options.ignore === "object" && !Array.isArray(options.ignore)) {
|
|
905
990
|
return true;
|
|
906
991
|
}
|
|
@@ -909,14 +994,32 @@ function isNewOptionsFormat(options) {
|
|
|
909
994
|
function createOSVSource(options = {}) {
|
|
910
995
|
let ignoreConfig;
|
|
911
996
|
let osvConfig;
|
|
997
|
+
let failOnScannerError;
|
|
912
998
|
if (isNewOptionsFormat(options)) {
|
|
913
|
-
|
|
999
|
+
const ignoreFromOptions = options.ignore;
|
|
1000
|
+
const packagesFromOptions = options.packages;
|
|
1001
|
+
if (ignoreFromOptions !== undefined) {
|
|
1002
|
+
if (Array.isArray(ignoreFromOptions)) {
|
|
1003
|
+
ignoreConfig = { ignore: ignoreFromOptions, packages: packagesFromOptions };
|
|
1004
|
+
} else {
|
|
1005
|
+
ignoreConfig = {
|
|
1006
|
+
ignore: ignoreFromOptions.ignore,
|
|
1007
|
+
packages: ignoreFromOptions.packages ?? packagesFromOptions
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
} else if (packagesFromOptions !== undefined) {
|
|
1011
|
+
ignoreConfig = { ignore: undefined, packages: packagesFromOptions };
|
|
1012
|
+
} else {
|
|
1013
|
+
ignoreConfig = {};
|
|
1014
|
+
}
|
|
914
1015
|
osvConfig = options.osv;
|
|
1016
|
+
failOnScannerError = options.failOnScannerError;
|
|
915
1017
|
} else {
|
|
916
1018
|
ignoreConfig = options;
|
|
917
1019
|
osvConfig = undefined;
|
|
1020
|
+
failOnScannerError = undefined;
|
|
918
1021
|
}
|
|
919
|
-
const client = createOSVClient({ osv: osvConfig });
|
|
1022
|
+
const client = createOSVClient({ osv: osvConfig, failOnScannerError });
|
|
920
1023
|
const processor = createVulnerabilityProcessor(ignoreConfig);
|
|
921
1024
|
return {
|
|
922
1025
|
name: "osv",
|
|
@@ -1005,6 +1108,7 @@ function createNpmAuditClient(options = {}) {
|
|
|
1005
1108
|
const npmConfig = options.npm ?? CONFIG_DEFAULTS.npm;
|
|
1006
1109
|
const registryUrl = npmConfig.registryUrl ?? NPM_AUDIT_API.REGISTRY_URL;
|
|
1007
1110
|
const timeout = npmConfig.timeoutMs ?? NPM_AUDIT_API.TIMEOUT_MS;
|
|
1111
|
+
const failOnError = options.failOnScannerError ?? false;
|
|
1008
1112
|
function deduplicatePackages(packages) {
|
|
1009
1113
|
const packageMap = new Map;
|
|
1010
1114
|
for (const pkg of packages) {
|
|
@@ -1083,8 +1187,16 @@ function createNpmAuditClient(options = {}) {
|
|
|
1083
1187
|
const batchAdvisories = await executeBulkQuery(payload);
|
|
1084
1188
|
advisories.push(...batchAdvisories);
|
|
1085
1189
|
} catch (error) {
|
|
1190
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1191
|
+
if (failOnError) {
|
|
1192
|
+
logger.error(`Batch query failed for ${batch.length} packages (strict mode)`, {
|
|
1193
|
+
error: message,
|
|
1194
|
+
startIndex: i
|
|
1195
|
+
});
|
|
1196
|
+
throw error;
|
|
1197
|
+
}
|
|
1086
1198
|
logger.error(`Batch query failed for ${batch.length} packages`, {
|
|
1087
|
-
error:
|
|
1199
|
+
error: message,
|
|
1088
1200
|
startIndex: i
|
|
1089
1201
|
});
|
|
1090
1202
|
}
|
|
@@ -1101,7 +1213,23 @@ function createNpmAuditClient(options = {}) {
|
|
|
1101
1213
|
if (uniquePackages.length > NPM_AUDIT_API.MAX_PACKAGES_PER_REQUEST) {
|
|
1102
1214
|
return await queryInBatches(uniquePackages);
|
|
1103
1215
|
}
|
|
1104
|
-
|
|
1216
|
+
try {
|
|
1217
|
+
return await executeBulkQuery(requestPayload);
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1220
|
+
if (failOnError) {
|
|
1221
|
+
logger.error(`Bulk query failed (strict mode)`, {
|
|
1222
|
+
error: message,
|
|
1223
|
+
packageCount: uniquePackages.length
|
|
1224
|
+
});
|
|
1225
|
+
throw error;
|
|
1226
|
+
}
|
|
1227
|
+
logger.error(`Bulk query failed`, {
|
|
1228
|
+
error: message,
|
|
1229
|
+
packageCount: uniquePackages.length
|
|
1230
|
+
});
|
|
1231
|
+
return [];
|
|
1232
|
+
}
|
|
1105
1233
|
}
|
|
1106
1234
|
return {
|
|
1107
1235
|
queryVulnerabilities
|
|
@@ -1261,6 +1389,8 @@ var init_processor2 = __esm(() => {
|
|
|
1261
1389
|
function isNewOptionsFormat2(options) {
|
|
1262
1390
|
if ("npm" in options)
|
|
1263
1391
|
return true;
|
|
1392
|
+
if ("failOnScannerError" in options)
|
|
1393
|
+
return true;
|
|
1264
1394
|
if ("ignore" in options && options.ignore && typeof options.ignore === "object" && !Array.isArray(options.ignore)) {
|
|
1265
1395
|
return true;
|
|
1266
1396
|
}
|
|
@@ -1269,14 +1399,32 @@ function isNewOptionsFormat2(options) {
|
|
|
1269
1399
|
function createNpmSource(options = {}) {
|
|
1270
1400
|
let ignoreConfig;
|
|
1271
1401
|
let npmConfig;
|
|
1402
|
+
let failOnScannerError;
|
|
1272
1403
|
if (isNewOptionsFormat2(options)) {
|
|
1273
|
-
|
|
1404
|
+
const ignoreFromOptions = options.ignore;
|
|
1405
|
+
const packagesFromOptions = options.packages;
|
|
1406
|
+
if (ignoreFromOptions !== undefined) {
|
|
1407
|
+
if (Array.isArray(ignoreFromOptions)) {
|
|
1408
|
+
ignoreConfig = { ignore: ignoreFromOptions, packages: packagesFromOptions };
|
|
1409
|
+
} else {
|
|
1410
|
+
ignoreConfig = {
|
|
1411
|
+
ignore: ignoreFromOptions.ignore,
|
|
1412
|
+
packages: ignoreFromOptions.packages ?? packagesFromOptions
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
} else if (packagesFromOptions !== undefined) {
|
|
1416
|
+
ignoreConfig = { ignore: undefined, packages: packagesFromOptions };
|
|
1417
|
+
} else {
|
|
1418
|
+
ignoreConfig = {};
|
|
1419
|
+
}
|
|
1274
1420
|
npmConfig = options.npm;
|
|
1421
|
+
failOnScannerError = options.failOnScannerError;
|
|
1275
1422
|
} else {
|
|
1276
1423
|
ignoreConfig = options;
|
|
1277
1424
|
npmConfig = undefined;
|
|
1425
|
+
failOnScannerError = undefined;
|
|
1278
1426
|
}
|
|
1279
|
-
const client = createNpmAuditClient({ npm: npmConfig });
|
|
1427
|
+
const client = createNpmAuditClient({ npm: npmConfig, failOnScannerError });
|
|
1280
1428
|
const processor = createAdvisoryProcessor(ignoreConfig);
|
|
1281
1429
|
return {
|
|
1282
1430
|
name: "npm",
|
|
@@ -1306,28 +1454,28 @@ var init_src3 = __esm(() => {
|
|
|
1306
1454
|
function extractIgnoreConfig(config) {
|
|
1307
1455
|
return { ignore: config.ignore, packages: config.packages };
|
|
1308
1456
|
}
|
|
1309
|
-
function createSource(type, config) {
|
|
1457
|
+
function createSource(type, config, failOnScannerError) {
|
|
1310
1458
|
const ignoreConfig = extractIgnoreConfig(config);
|
|
1311
1459
|
switch (type) {
|
|
1312
1460
|
case "osv":
|
|
1313
|
-
return createOSVSource({ ignore: ignoreConfig, osv: config.osv });
|
|
1461
|
+
return createOSVSource({ ignore: ignoreConfig, osv: config.osv, failOnScannerError });
|
|
1314
1462
|
case "npm":
|
|
1315
|
-
return createNpmSource({ ignore: ignoreConfig, npm: config.npm });
|
|
1463
|
+
return createNpmSource({ ignore: ignoreConfig, npm: config.npm, failOnScannerError });
|
|
1316
1464
|
default:
|
|
1317
1465
|
throw new Error(`Unknown source type: ${type}`);
|
|
1318
1466
|
}
|
|
1319
1467
|
}
|
|
1320
|
-
function createSources(type, config) {
|
|
1468
|
+
function createSources(type, config, failOnScannerError) {
|
|
1321
1469
|
const ignoreConfig = extractIgnoreConfig(config);
|
|
1322
1470
|
switch (type) {
|
|
1323
1471
|
case "osv":
|
|
1324
|
-
return [createOSVSource({ ignore: ignoreConfig, osv: config.osv })];
|
|
1472
|
+
return [createOSVSource({ ignore: ignoreConfig, osv: config.osv, failOnScannerError })];
|
|
1325
1473
|
case "npm":
|
|
1326
|
-
return [createNpmSource({ ignore: ignoreConfig, npm: config.npm })];
|
|
1474
|
+
return [createNpmSource({ ignore: ignoreConfig, npm: config.npm, failOnScannerError })];
|
|
1327
1475
|
case "both":
|
|
1328
1476
|
return [
|
|
1329
|
-
createOSVSource({ ignore: ignoreConfig, osv: config.osv }),
|
|
1330
|
-
createNpmSource({ ignore: ignoreConfig, npm: config.npm })
|
|
1477
|
+
createOSVSource({ ignore: ignoreConfig, osv: config.osv, failOnScannerError }),
|
|
1478
|
+
createNpmSource({ ignore: ignoreConfig, npm: config.npm, failOnScannerError })
|
|
1331
1479
|
];
|
|
1332
1480
|
default:
|
|
1333
1481
|
throw new Error(`Unknown source type: ${type}`);
|
|
@@ -1339,10 +1487,11 @@ var init_factory = __esm(() => {
|
|
|
1339
1487
|
});
|
|
1340
1488
|
|
|
1341
1489
|
// src/sources/multi.ts
|
|
1342
|
-
function createMultiSourceScanner(sources) {
|
|
1490
|
+
function createMultiSourceScanner(sources, options) {
|
|
1343
1491
|
if (sources.length === 0) {
|
|
1344
1492
|
throw new Error("MultiSourceScanner requires at least one source");
|
|
1345
1493
|
}
|
|
1494
|
+
const failOnError = options?.failOnScannerError ?? false;
|
|
1346
1495
|
function isHigherSeverity(a, b) {
|
|
1347
1496
|
const priority = { fatal: 2, warn: 1 };
|
|
1348
1497
|
return (priority[a] ?? 0) > (priority[b] ?? 0);
|
|
@@ -1379,8 +1528,9 @@ function createMultiSourceScanner(sources) {
|
|
|
1379
1528
|
async function scan(packages) {
|
|
1380
1529
|
const sourceNames = sources.map((s) => s.name).join(", ");
|
|
1381
1530
|
logger.info(`Scanning with sources: ${sourceNames}`);
|
|
1382
|
-
const results = await Promise.allSettled(sources.map((source) => source.scan(packages)));
|
|
1531
|
+
const results = await Promise.allSettled(sources.map((source) => Promise.resolve().then(() => source.scan(packages))));
|
|
1383
1532
|
const allAdvisories = [];
|
|
1533
|
+
const failures = [];
|
|
1384
1534
|
for (const [i, result] of results.entries()) {
|
|
1385
1535
|
const source = sources[i];
|
|
1386
1536
|
if (!source)
|
|
@@ -1389,11 +1539,15 @@ function createMultiSourceScanner(sources) {
|
|
|
1389
1539
|
logger.debug(`[${source.name}] Found ${result.value.length} advisories`);
|
|
1390
1540
|
allAdvisories.push(...result.value);
|
|
1391
1541
|
} else {
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
});
|
|
1542
|
+
const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
1543
|
+
logger.error(`[${source.name}] Scan failed`, { error: errorMessage });
|
|
1544
|
+
failures.push({ name: source.name, error: errorMessage });
|
|
1395
1545
|
}
|
|
1396
1546
|
}
|
|
1547
|
+
if (failOnError && failures.length > 0) {
|
|
1548
|
+
const details = failures.map((f) => `${f.name}: ${f.error}`).join("; ");
|
|
1549
|
+
throw new Error(`bun-scan: scan failed for ${failures.length === 1 ? "source" : "sources"} ` + `${failures.map((f) => `"${f.name}"`).join(", ")}. ` + `Details: ${details}. ` + `failOnScannerError=true requires all configured sources to succeed.`);
|
|
1550
|
+
}
|
|
1397
1551
|
return deduplicateAdvisories(allAdvisories);
|
|
1398
1552
|
}
|
|
1399
1553
|
return {
|
|
@@ -1567,12 +1721,16 @@ var init_src4 = __esm(async () => {
|
|
|
1567
1721
|
scanner = {
|
|
1568
1722
|
version: "1",
|
|
1569
1723
|
async scan({ packages }) {
|
|
1724
|
+
let failOnScannerError = parseEnvBoolean(ENV.FAIL_ON_SCANNER_ERROR) === true;
|
|
1570
1725
|
try {
|
|
1571
1726
|
logger.debug(`Starting vulnerability scan for ${packages.length} packages`);
|
|
1572
1727
|
const config = await loadConfig();
|
|
1573
1728
|
const bunReportWarnings = config.bunReportWarnings ?? CONFIG_DEFAULTS.bunReportWarnings;
|
|
1574
|
-
|
|
1575
|
-
|
|
1729
|
+
if (parseEnvBoolean(ENV.FAIL_ON_SCANNER_ERROR) === undefined) {
|
|
1730
|
+
failOnScannerError = config.failOnScannerError ?? failOnScannerError;
|
|
1731
|
+
}
|
|
1732
|
+
const sources = createSources(config.source ?? "osv", config, failOnScannerError);
|
|
1733
|
+
const multiScanner = createMultiSourceScanner(sources, { failOnScannerError });
|
|
1576
1734
|
const advisories = await multiScanner.scan(packages);
|
|
1577
1735
|
logger.info(`Scan completed: ${advisories.length} advisories found for ${packages.length} packages`);
|
|
1578
1736
|
if (!bunReportWarnings) {
|
|
@@ -1590,6 +1748,12 @@ var init_src4 = __esm(async () => {
|
|
|
1590
1748
|
return advisories;
|
|
1591
1749
|
} catch (error) {
|
|
1592
1750
|
const message = error instanceof Error ? error.message : String(error);
|
|
1751
|
+
if (failOnScannerError) {
|
|
1752
|
+
logger.error("Scanner error in strict mode \u2014 failing scan", {
|
|
1753
|
+
error: message
|
|
1754
|
+
});
|
|
1755
|
+
throw error;
|
|
1756
|
+
}
|
|
1593
1757
|
logger.error("Scanner encountered an unexpected error", {
|
|
1594
1758
|
error: message
|
|
1595
1759
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bun-scan",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1-beta.1",
|
|
4
4
|
"description": "Vulnerability scanner for Bun projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"audit",
|
|
@@ -49,15 +49,15 @@
|
|
|
49
49
|
"lint": "oxlint check"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"zod": "4.3.
|
|
52
|
+
"zod": "4.3.6"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@repo/core": "workspace:*",
|
|
56
56
|
"@repo/source-npm": "workspace:*",
|
|
57
57
|
"@repo/source-osv": "workspace:*",
|
|
58
|
-
"@types/bun": "1.3.
|
|
59
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
60
|
-
"bun-types": "1.3.
|
|
58
|
+
"@types/bun": "1.3.8",
|
|
59
|
+
"@typescript/native-preview": "7.0.0-dev.20260131.1",
|
|
60
|
+
"bun-types": "1.3.8"
|
|
61
61
|
},
|
|
62
62
|
"engines": {
|
|
63
63
|
"bun": ">=1.0.0"
|