@vltpkg/security-archive 1.4.0 → 1.6.0
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 +14 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +51 -6
- package/dist/update-expired.d.ts +2 -0
- package/dist/update-expired.js +6 -4
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -23,7 +23,9 @@ const graph = actual.load({
|
|
|
23
23
|
projectRoot: process.cwd(),
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
const archive = await SecurityArchive.start({
|
|
26
|
+
const archive = await SecurityArchive.start({
|
|
27
|
+
nodes: [...graph.nodes.values()],
|
|
28
|
+
})
|
|
27
29
|
|
|
28
30
|
if (archive.ok) {
|
|
29
31
|
for (const node of graph.nodes.values()) {
|
|
@@ -36,3 +38,14 @@ if (archive.ok) {
|
|
|
36
38
|
console.warn('Failed to start the SecurityArchive')
|
|
37
39
|
}
|
|
38
40
|
```
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
- `path`: sqlite db location. Defaults to the vlt XDG cache dir.
|
|
45
|
+
- `retries`: retry attempts on failed API requests. Defaults to `3`.
|
|
46
|
+
- `timeout`: max ms to wait for the API, retries included. Defaults to
|
|
47
|
+
`VLT_SECURITY_ARCHIVE_TIMEOUT` env or `30000`. Must be a positive
|
|
48
|
+
integer, other values are ignored. On timeout a warning is printed,
|
|
49
|
+
missing packages stay unscanned, `ok` is `false`, `timedOut` is
|
|
50
|
+
`true` and nothing is cached.
|
|
51
|
+
- `ttl`: ms to cache newly fetched entries for. Defaults to 3 hours.
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,14 @@ export type SecurityArchiveOptions = LRUCache.OptionsBase<DepID, PackageReportDa
|
|
|
24
24
|
* Number of retries attempts to reach the remote security API.
|
|
25
25
|
*/
|
|
26
26
|
retries?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Max ms to wait for the remote security API, incl. retries.
|
|
29
|
+
* Defaults to `VLT_SECURITY_ARCHIVE_TIMEOUT` env or
|
|
30
|
+
* {@link SecurityArchive.defaultTimeout}. Positive integers only,
|
|
31
|
+
* other values are ignored. On timeout, missing packages are left
|
|
32
|
+
* unscanned and a warning is printed.
|
|
33
|
+
*/
|
|
34
|
+
timeout?: number;
|
|
27
35
|
};
|
|
28
36
|
/**
|
|
29
37
|
* A database of security information for given packages from a list of nodes.
|
|
@@ -31,7 +39,8 @@ export type SecurityArchiveOptions = LRUCache.OptionsBase<DepID, PackageReportDa
|
|
|
31
39
|
* Using the SecurityArchive.refresh() method will update the local cache
|
|
32
40
|
* with information from the socket.dev APIs or load from the local storage
|
|
33
41
|
* if available. Information about package security is then available
|
|
34
|
-
* using the SecurityArchive.get() method.
|
|
42
|
+
* using the SecurityArchive.get() method. Remote requests are bounded
|
|
43
|
+
* by the `timeout` option.
|
|
35
44
|
*/
|
|
36
45
|
export declare class SecurityArchive extends LRUCache<DepID, PackageReportData> implements SecurityArchiveLike {
|
|
37
46
|
#private;
|
|
@@ -40,6 +49,10 @@ export declare class SecurityArchive extends LRUCache<DepID, PackageReportData>
|
|
|
40
49
|
* for all public registry packages from the initial list of nodes.
|
|
41
50
|
*/
|
|
42
51
|
ok: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* True if the last refresh() timed out waiting for the remote API.
|
|
54
|
+
*/
|
|
55
|
+
timedOut: boolean;
|
|
43
56
|
/**
|
|
44
57
|
* Creates a new security archive instance and starts the refresh process.
|
|
45
58
|
*/
|
|
@@ -52,6 +65,10 @@ export declare class SecurityArchive extends LRUCache<DepID, PackageReportData>
|
|
|
52
65
|
* By default, entries are cached for 3 hours.
|
|
53
66
|
*/
|
|
54
67
|
static get defaultTtl(): number;
|
|
68
|
+
/**
|
|
69
|
+
* By default, remote requests time out after 30 seconds.
|
|
70
|
+
*/
|
|
71
|
+
static get defaultTimeout(): number;
|
|
55
72
|
constructor(options?: SecurityArchiveOptions);
|
|
56
73
|
/**
|
|
57
74
|
* Get the report data for a node, keyed by its base {@link DepID}.
|
package/dist/index.js
CHANGED
|
@@ -16,18 +16,27 @@ export * from "./types.js";
|
|
|
16
16
|
export { npmRegistryURL, usesNpmRegistry } from "./browser.js";
|
|
17
17
|
const SOCKET_API_V0_URL = 'https://api.socket.dev/v0/purl?alerts=true';
|
|
18
18
|
const SOCKET_PUBLIC_API_TOKEN = 'sktsec_t_--RAN5U4ivauy4w37-6aoKyYPDt5ZbaT5JBVMqiwKo_api';
|
|
19
|
+
// AbortSignal.timeout throws on <0/NaN and overflows to 1ms above 2^31-1
|
|
20
|
+
const asTimeout = (raw) => {
|
|
21
|
+
const n = Number(raw);
|
|
22
|
+
return Number.isInteger(n) && n > 0 && n <= 2_147_483_647 ?
|
|
23
|
+
n
|
|
24
|
+
: undefined;
|
|
25
|
+
};
|
|
19
26
|
/**
|
|
20
27
|
* A database of security information for given packages from a list of nodes.
|
|
21
28
|
*
|
|
22
29
|
* Using the SecurityArchive.refresh() method will update the local cache
|
|
23
30
|
* with information from the socket.dev APIs or load from the local storage
|
|
24
31
|
* if available. Information about package security is then available
|
|
25
|
-
* using the SecurityArchive.get() method.
|
|
32
|
+
* using the SecurityArchive.get() method. Remote requests are bounded
|
|
33
|
+
* by the `timeout` option.
|
|
26
34
|
*/
|
|
27
35
|
export class SecurityArchive extends LRUCache {
|
|
28
36
|
#expired = new Set();
|
|
29
37
|
#path;
|
|
30
38
|
#retries;
|
|
39
|
+
#timeout;
|
|
31
40
|
#nodesByName = new Map();
|
|
32
41
|
#nodesByID = new Map();
|
|
33
42
|
/**
|
|
@@ -35,6 +44,10 @@ export class SecurityArchive extends LRUCache {
|
|
|
35
44
|
* for all public registry packages from the initial list of nodes.
|
|
36
45
|
*/
|
|
37
46
|
ok = false;
|
|
47
|
+
/**
|
|
48
|
+
* True if the last refresh() timed out waiting for the remote API.
|
|
49
|
+
*/
|
|
50
|
+
timedOut = false;
|
|
38
51
|
/**
|
|
39
52
|
* Creates a new security archive instance and starts the refresh process.
|
|
40
53
|
*/
|
|
@@ -55,6 +68,12 @@ export class SecurityArchive extends LRUCache {
|
|
|
55
68
|
static get defaultTtl() {
|
|
56
69
|
return 1000 * 60 * 60 * 3;
|
|
57
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* By default, remote requests time out after 30 seconds.
|
|
73
|
+
*/
|
|
74
|
+
static get defaultTimeout() {
|
|
75
|
+
return 30_000;
|
|
76
|
+
}
|
|
58
77
|
constructor(options = {}) {
|
|
59
78
|
super({
|
|
60
79
|
max: _a.defaultMax,
|
|
@@ -65,6 +84,10 @@ export class SecurityArchive extends LRUCache {
|
|
|
65
84
|
this.#path =
|
|
66
85
|
options.path ?? new XDG('vlt').cache('security-archive.db');
|
|
67
86
|
this.#retries = options.retries ?? 3;
|
|
87
|
+
this.#timeout =
|
|
88
|
+
asTimeout(options.timeout) ??
|
|
89
|
+
asTimeout(process.env.VLT_SECURITY_ARCHIVE_TIMEOUT) ??
|
|
90
|
+
_a.defaultTimeout;
|
|
68
91
|
}
|
|
69
92
|
/**
|
|
70
93
|
* Get the report data for a node, keyed by its base {@link DepID}.
|
|
@@ -187,6 +210,7 @@ export class SecurityArchive extends LRUCache {
|
|
|
187
210
|
const payload = {
|
|
188
211
|
dbPath: this.#path,
|
|
189
212
|
retries: this.#retries,
|
|
213
|
+
timeout: this.#timeout,
|
|
190
214
|
ttl: _a.defaultTtl,
|
|
191
215
|
expired,
|
|
192
216
|
};
|
|
@@ -235,7 +259,7 @@ export class SecurityArchive extends LRUCache {
|
|
|
235
259
|
* Fetches security information from the socket.dev API.
|
|
236
260
|
* Returns a string of NDJSON data.
|
|
237
261
|
*/
|
|
238
|
-
async #retrieveRemoteData(queue) {
|
|
262
|
+
async #retrieveRemoteData(queue, signal) {
|
|
239
263
|
// fetch information from the socket.dev API
|
|
240
264
|
const req = await fetch(SOCKET_API_V0_URL, {
|
|
241
265
|
method: 'POST',
|
|
@@ -246,6 +270,7 @@ export class SecurityArchive extends LRUCache {
|
|
|
246
270
|
body: JSON.stringify({
|
|
247
271
|
components: Array.from(queue),
|
|
248
272
|
}),
|
|
273
|
+
signal,
|
|
249
274
|
});
|
|
250
275
|
// on missing valid auth or API, it should abort the retry logic
|
|
251
276
|
if (req.status === 404) {
|
|
@@ -258,6 +283,23 @@ export class SecurityArchive extends LRUCache {
|
|
|
258
283
|
const str = await req.text();
|
|
259
284
|
return str.trim() + '\n';
|
|
260
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Fetches remote data within the configured timeout, retries included.
|
|
288
|
+
* Returns `undefined` on timeout so that queued packages stay unscanned.
|
|
289
|
+
*/
|
|
290
|
+
async #fetchRemoteData(queue) {
|
|
291
|
+
const signal = AbortSignal.timeout(this.#timeout);
|
|
292
|
+
try {
|
|
293
|
+
return await pRetry(() => this.#retrieveRemoteData(queue, signal), { retries: this.#retries, signal });
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
if (!signal.aborted)
|
|
297
|
+
throw err;
|
|
298
|
+
this.timedOut = true;
|
|
299
|
+
// eslint-disable-next-line no-console
|
|
300
|
+
console.warn(`security-archive: timed out after ${this.#timeout}ms fetching security data; ${queue.size} package(s) left unscanned (use :scanned to exclude them). Set VLT_SECURITY_ARCHIVE_TIMEOUT=<ms> to wait longer.`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
261
303
|
/**
|
|
262
304
|
* Parses and load NDJSON data into the in-memory cache.
|
|
263
305
|
* Returns a set of dep ids that were successfully loaded.
|
|
@@ -345,6 +387,7 @@ export class SecurityArchive extends LRUCache {
|
|
|
345
387
|
async refresh({ nodes }) {
|
|
346
388
|
// should start by clearing the current in-memory cache
|
|
347
389
|
this.clear();
|
|
390
|
+
this.timedOut = false;
|
|
348
391
|
const db = this.#openDatabase();
|
|
349
392
|
// indexes nodes by name and id for quick lookup
|
|
350
393
|
this.#nodesByName.clear();
|
|
@@ -362,10 +405,12 @@ export class SecurityArchive extends LRUCache {
|
|
|
362
405
|
const queue = this.#queueUpRequiredPackages();
|
|
363
406
|
// only reach for the remote API if there are packages queued up
|
|
364
407
|
if (queue.size > 0) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
408
|
+
const res = await this.#fetchRemoteData(queue);
|
|
409
|
+
// on timeout nothing is loaded nor cached
|
|
410
|
+
if (res !== undefined) {
|
|
411
|
+
const ids = this.#loadFromNDJSON(res);
|
|
412
|
+
this.#storeNewItemsToDatabase(db, ids);
|
|
413
|
+
}
|
|
369
414
|
}
|
|
370
415
|
// validates the refresh process was successful
|
|
371
416
|
this.#validateReportData();
|
package/dist/update-expired.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type UpdateExpiredPayload = {
|
|
|
9
9
|
dbPath: string;
|
|
10
10
|
/** Number of retries for fetching remote data */
|
|
11
11
|
retries: number;
|
|
12
|
+
/** Max ms for the remote request, incl. retries */
|
|
13
|
+
timeout: number;
|
|
12
14
|
/** TTL in ms to use for new entries */
|
|
13
15
|
ttl: number;
|
|
14
16
|
/** Expired entries to revalidate */
|
package/dist/update-expired.js
CHANGED
|
@@ -17,7 +17,8 @@ const isMain = (path) => path === __CODE_SPLIT_SCRIPT_NAME ||
|
|
|
17
17
|
/**
|
|
18
18
|
* Fetches security information from the socket.dev API.
|
|
19
19
|
*/
|
|
20
|
-
const retrieveRemoteData = async (queue, retries) => {
|
|
20
|
+
const retrieveRemoteData = async (queue, retries, timeout) => {
|
|
21
|
+
const signal = AbortSignal.timeout(timeout);
|
|
21
22
|
return pRetry(async () => {
|
|
22
23
|
const req = await fetch(SOCKET_API_V0_URL, {
|
|
23
24
|
method: 'POST',
|
|
@@ -28,6 +29,7 @@ const retrieveRemoteData = async (queue, retries) => {
|
|
|
28
29
|
body: JSON.stringify({
|
|
29
30
|
components: Array.from(queue),
|
|
30
31
|
}),
|
|
32
|
+
signal,
|
|
31
33
|
});
|
|
32
34
|
if (req.status === 404) {
|
|
33
35
|
throw new AbortError('Missing API');
|
|
@@ -39,7 +41,7 @@ const retrieveRemoteData = async (queue, retries) => {
|
|
|
39
41
|
}
|
|
40
42
|
const str = await req.text();
|
|
41
43
|
return str.trim() + '\n';
|
|
42
|
-
}, { retries });
|
|
44
|
+
}, { retries, signal });
|
|
43
45
|
};
|
|
44
46
|
/**
|
|
45
47
|
* Main entrypoint for the detached process.
|
|
@@ -68,7 +70,7 @@ export const main = async (input = process.stdin) => {
|
|
|
68
70
|
/* c8 ignore next */
|
|
69
71
|
input.on('error', reject);
|
|
70
72
|
});
|
|
71
|
-
const { dbPath, retries, ttl, expired } = payload;
|
|
73
|
+
const { dbPath, retries, timeout, ttl, expired } = payload;
|
|
72
74
|
if (!expired.length) {
|
|
73
75
|
return false;
|
|
74
76
|
}
|
|
@@ -82,7 +84,7 @@ export const main = async (input = process.stdin) => {
|
|
|
82
84
|
entryByNameVersion.set(`${entry.name}@${entry.version}`, entry);
|
|
83
85
|
}
|
|
84
86
|
// Fetch updated data
|
|
85
|
-
const res = await retrieveRemoteData(expiredQueue, retries);
|
|
87
|
+
const res = await retrieveRemoteData(expiredQueue, retries, timeout);
|
|
86
88
|
// Parse NDJSON response and collect results
|
|
87
89
|
const now = Date.now();
|
|
88
90
|
const results = [];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vltpkg/security-archive",
|
|
3
3
|
"description": "A database of security information of packages",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
"url": "http://vlt.sh"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@vltpkg/dep-id": "1.
|
|
17
|
-
"@vltpkg/error-cause": "1.
|
|
18
|
-
"@vltpkg/spec": "1.
|
|
19
|
-
"@vltpkg/types": "1.
|
|
20
|
-
"@vltpkg/user-agent": "1.
|
|
21
|
-
"@vltpkg/xdg": "1.
|
|
16
|
+
"@vltpkg/dep-id": "1.6.0",
|
|
17
|
+
"@vltpkg/error-cause": "1.6.0",
|
|
18
|
+
"@vltpkg/spec": "1.6.0",
|
|
19
|
+
"@vltpkg/types": "1.6.0",
|
|
20
|
+
"@vltpkg/user-agent": "1.6.0",
|
|
21
|
+
"@vltpkg/xdg": "1.6.0",
|
|
22
22
|
"lru-cache": "^11.2.4",
|
|
23
23
|
"p-retry": "^7.1.1"
|
|
24
24
|
},
|