@preventive/triage 1.0.0-alpha.38 → 1.0.0-alpha.39
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/out/{chunk-FU4MTW7F.js → chunk-45WIN3SW.js} +1 -1
- package/out/{chunk-6Z5XLSPJ.js → chunk-6C76GAXK.js} +1 -1
- package/out/{chunk-5FKRIJGF.js → chunk-7ZKDKGAH.js} +1 -1
- package/out/{chunk-BJKRVROW.js → chunk-JVBB4LZX.js} +1 -1
- package/out/{chunk-DCQX2J3C.js → chunk-QSLWGJBQ.js} +52 -52
- package/out/{chunk-JAKWIZWL.js → chunk-XNGEQO7Y.js} +1 -1
- package/out/chunk-XRWAV3DX.js +7 -0
- package/out/{chunk-SD2WJAXN.js → chunk-XYDGU6BM.js} +13 -10
- package/out/client-managed.js +11 -11
- package/out/client-sync.js +1 -1
- package/out/graph.js +1 -1
- package/out/index.html +5 -5
- package/out/view.js +2 -2
- package/package.json +2 -1
- package/risk/files/readability.js +7 -0
- package/risk/files/runs-code.js +31 -2
- package/risk/files/script.js +6 -4
- package/risk/npm/assess.js +1 -1
- package/risk/npm/compare.js +10 -4
- package/risk/npm/install-scripts.js +3 -2
- package/server-managed/bundle-advisories.ts +6 -1
- package/server-managed/github-app.ts +9 -3
- package/server-managed/github-errors.ts +21 -2
- package/server-managed/http.ts +11 -3
- package/server-managed/repository-risk.ts +9 -4
- package/out/chunk-BYUEDJOM.js +0 -7
- /package/out/{chunk-OJ5DGP5E.js → chunk-TINZZKP5.js} +0 -0
package/risk/files/script.js
CHANGED
|
@@ -16,7 +16,8 @@ const MINIFIED_SPACES = .01
|
|
|
16
16
|
// What a regular expression set aside holds.
|
|
17
17
|
const REGEX = '\uE000'
|
|
18
18
|
const JS = {
|
|
19
|
-
// A hashbang, the file's first line (`#!/usr/bin/env node`), a comment
|
|
19
|
+
// A hashbang, the file's first line (`#!/usr/bin/env node`), a comment,
|
|
20
|
+
// after a byte order mark too, which node's ES module loader drops first.
|
|
20
21
|
// A string's or template's escape may be a line continuation (`\` before a
|
|
21
22
|
// line break, CRLF's whole).
|
|
22
23
|
// Linear on any text: a regular expression's context looked back on only
|
|
@@ -25,7 +26,7 @@ const JS = {
|
|
|
25
26
|
// regular expression its line's end, a template or a comment the text's,
|
|
26
27
|
// rather than tried again from each later quote or slash.
|
|
27
28
|
// A regular expression first where a value starts (after `=>`, `...`, an operator or a keyword, not a property: `x.default/2`, `this.#in/2`), so a quote in it (`/["']/`) starts no string.
|
|
28
|
-
stringOrComment: /((?=\/)(?<=(?:^|\.\.\.|[(,=:[!&|?{};>+\-*/%^<~]|(?<![\p{ID_Continue}$.#])(?:await|case|default|delete|do|else|extends|in|instanceof|new|of|return|throw|typeof|void|yield))[ \t]*)\/(?![/*])(?:[^/\\[\n]|\\.|\[(?:[^\]\\\n]|\\.)*(?:\]|(?=\\?$)))+(?:\/[a-z]*|(?=\\?$))|"(?:[^"\\\n]|\\(?:\r\n|[\s\S]))*(?:"|$)|'(?:[^'\\\n]|\\(?:\r\n|[\s\S]))*(?:'|$)|`(?:[^`\\]|\\(?:\r\n|[\s\S]))*(?:`|(?![\s\S])))|(?<![ \t])[ \t]*(?:\/\*[\s\S]*?(?:\*\/|(?![\s\S]))|\/\/.*|(?<![\s\S])
|
|
29
|
+
stringOrComment: /((?=\/)(?<=(?:^|\.\.\.|[(,=:[!&|?{};>+\-*/%^<~]|(?<![\p{ID_Continue}$.#])(?:await|case|default|delete|do|else|extends|in|instanceof|new|of|return|throw|typeof|void|yield))[ \t]*)\/(?![/*])(?:[^/\\[\n]|\\.|\[(?:[^\]\\\n]|\\.)*(?:\]|(?=\\?$)))+(?:\/[a-z]*|(?=\\?$))|"(?:[^"\\\n]|\\(?:\r\n|[\s\S]))*(?:"|$)|'(?:[^'\\\n]|\\(?:\r\n|[\s\S]))*(?:'|$)|`(?:[^`\\]|\\(?:\r\n|[\s\S]))*(?:`|(?![\s\S])))|(?<![ \t])[ \t]*(?:\/\*[\s\S]*?(?:\*\/|(?![\s\S]))|\/\/.*|(?<![\s\S])\uFEFF?#!.*)[ \t]*/gmu,
|
|
29
30
|
// Beside punctuation (`a, b`, `x = 1`), not between two words, as
|
|
30
31
|
// JavaScript tells a word's characters (`return a`, `var π`, `var \u03c0`),
|
|
31
32
|
droppable: /(?<![\p{ID_Continue}$\\\u200C\u200D])[ \t]+|[ \t]+(?![\p{ID_Continue}$\\\u200C\u200D])/gu,
|
|
@@ -76,9 +77,10 @@ export function minifiedAverage(whole, { stringOrComment, droppable, needed }) {
|
|
|
76
77
|
|
|
77
78
|
// JavaScript or TypeScript (a declaration file too, which node runs as any
|
|
78
79
|
// other), or a script run by node by its hashbang (`bin/cli`, with no
|
|
79
|
-
// extension)
|
|
80
|
+
// extension), a byte order mark before it or not, as node runs one after it
|
|
81
|
+
// as an ES module (in a `"type": "module"` package).
|
|
80
82
|
const SCRIPT = /\.[cm]?[jt]sx?$/iu
|
|
81
|
-
const NODE_HASHBANG =
|
|
83
|
+
const NODE_HASHBANG = /^\uFEFF?#!.*\bnode\b/u
|
|
82
84
|
// Whether a file (its path and text) is a script, as SCRIPT and NODE_HASHBANG tell.
|
|
83
85
|
export const npmIsScript = (path, text) => typeof text === 'string' && (SCRIPT.test(path) || NODE_HASHBANG.test(text))
|
|
84
86
|
// How many places a file is told doing a thing, the first ones, at most
|
package/risk/npm/assess.js
CHANGED
|
@@ -17,7 +17,7 @@ import { npmAddedDependency } from './new-dependency.js'
|
|
|
17
17
|
// Which reading of a version an assessment kept (`risk/npm/assessments`) is:
|
|
18
18
|
// one more whenever a heuristic changes what it finds, so one kept by an
|
|
19
19
|
// older reading is read anew.
|
|
20
|
-
const RISK_VERSION =
|
|
20
|
+
const RISK_VERSION = 12
|
|
21
21
|
|
|
22
22
|
// How long an assessment kept may be as JSON, and what risk reads of either
|
|
23
23
|
// version's files: a package past it, whose files name thousands of hosts or
|
package/risk/npm/compare.js
CHANGED
|
@@ -11,7 +11,7 @@ import { bundleFileSizes, bundleFilesAsMap } from '../../common/bundle-sources.j
|
|
|
11
11
|
import { npmVersionFileRisk, npmVersionHosts } from './files.js'
|
|
12
12
|
import { npmHostFinding } from './held.js'
|
|
13
13
|
import { HOST_TAGS, npmHostSeverity, npmHostsTagged } from '../files/hosts.js'
|
|
14
|
-
import { npmBecameUnreadable, npmLargeCode, npmUnreadable } from '../files/readability.js'
|
|
14
|
+
import { npmBecameUnreadable, npmBinaryCode, npmLargeCode, npmUnreadable } from '../files/readability.js'
|
|
15
15
|
import { SEVERITIES } from '../files/stamps.js'
|
|
16
16
|
import { npmFoundAdded, npmRunsCodeFlags, npmRunsCodeWithout } from '../files/runs-code.js'
|
|
17
17
|
import { npmDependencySpecs } from './dependencies.js'
|
|
@@ -27,9 +27,11 @@ import { npmInstallScriptsChange, npmProvenanceLost, npmPublisherChange } from '
|
|
|
27
27
|
// (npmHostSeverity: `exfiltration` and `remote` red, `ip-lookup` orange); a
|
|
28
28
|
// code file of 256 KiB or more it adds (npmLargeCode, `large-code`); a file
|
|
29
29
|
// it changes that had no line over 200 characters and now has one over 2,000
|
|
30
|
-
// (npmBecameUnreadable, `now-unreadable`);
|
|
30
|
+
// (npmBecameUnreadable, `now-unreadable`); a file it adds or changes with
|
|
31
31
|
// an obfuscator's fingerprint where the one compared with had none
|
|
32
|
-
// (`obfuscated`)
|
|
32
|
+
// (`obfuscated`); and a code file it adds or changes that isn't text, where
|
|
33
|
+
// the one compared with's was text, or no code, or none (npmBinaryCode,
|
|
34
|
+
// `binary-code`). Then in orange what any of its files runs beyond itself that
|
|
33
35
|
// `runs-code` doesn't name, kept from the version compared with
|
|
34
36
|
// (`kept-runs-code`). A renamed file by its old path; one renamed alone,
|
|
35
37
|
// unchanged, holds what it held. A file become unreadable calls for less
|
|
@@ -61,6 +63,8 @@ function npmFileFindings(base, other, files, major) {
|
|
|
61
63
|
...rebuiltWithMap(base, other, row.path, row.renamedFrom ?? row.path) ? { rebuilt: true } : {} }))) },
|
|
62
64
|
{ id: 'obfuscated', severity: 'critical', files: touched.filter(row => after(row).readability.obfuscation && !before(row)?.readability.obfuscation)
|
|
63
65
|
.map(row => ({ path: row.path, obfuscation: after(row).readability.obfuscation })) },
|
|
66
|
+
{ id: 'binary-code', severity: 'critical', files: touched.filter(row => npmBinaryCode(row.path, after(row).readability) && !npmBinaryCode(row.renamedFrom ?? row.path, before(row)?.readability))
|
|
67
|
+
.map(row => ({ path: row.path, bytes: sizes.get(row.path) })) },
|
|
64
68
|
{ id: 'kept-runs-code', severity: 'high', files: kept },
|
|
65
69
|
].filter(finding => finding.files.length > 0)
|
|
66
70
|
}
|
|
@@ -159,7 +163,8 @@ function npmNewHosts(base, other, files) {
|
|
|
159
163
|
// files hold that the one compared with's didn't, so where none of them runs
|
|
160
164
|
// code beyond itself, names a service under one of the hosts' tags or a host
|
|
161
165
|
// past ALLOWED_HOSTS, bears an obfuscator's fingerprint, has a line no one
|
|
162
|
-
// writes (npmUnreadable)
|
|
166
|
+
// writes (npmUnreadable), is large code or is code that isn't text
|
|
167
|
+
// (npmBinaryCode), nothing is found of them whatever
|
|
163
168
|
// the one compared with holds. Nor do the one compared with's install scripts
|
|
164
169
|
// differ by a binding.gyp among its files, but where its manifest names
|
|
165
170
|
// neither `preinstall` nor `install`, and the open version has some.
|
|
@@ -170,6 +175,7 @@ export function npmBaseFilesTell(other, manifest) {
|
|
|
170
175
|
return [...bundleFilesAsMap(other).keys()].some(path => {
|
|
171
176
|
const { runsCode, exfiltration, readability, hosts } = npmVersionFileRisk(other, path)
|
|
172
177
|
return Boolean(runsCode || exfiltration || readability.obfuscation) || npmUnreadable(path, readability) || npmLargeCode(path, sizes.get(path))
|
|
178
|
+
|| npmBinaryCode(path, readability)
|
|
173
179
|
|| [...hosts?.keys() ?? []].some(host => !ALLOWED_HOSTS.has(host))
|
|
174
180
|
})
|
|
175
181
|
}
|
|
@@ -23,11 +23,12 @@ export function npmInstallScripts(manifest, paths = []) {
|
|
|
23
23
|
return installScripts(manifest?.installScripts, Boolean(paths && (paths instanceof Set ? paths.has('binding.gyp') : paths.includes('binding.gyp'))))
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// A package.json's install scripts, from its text
|
|
26
|
+
// A package.json's install scripts, from its text, a byte order mark it
|
|
27
|
+
// starts with dropped, as npm drops one reading it; none where it isn't one,
|
|
27
28
|
// or names none.
|
|
28
29
|
export function packageJsonInstallScripts(text, bindingGyp = false) {
|
|
29
30
|
let json = null
|
|
30
|
-
try { json = typeof text === 'string' ? JSON.parse(text) : null } catch {}
|
|
31
|
+
try { json = typeof text === 'string' ? JSON.parse(text.replace(/^\uFEFF/u, '')) : null } catch {}
|
|
31
32
|
const scripts = json && typeof json === 'object' && json.scripts && typeof json.scripts === 'object' ? json.scripts : {}
|
|
32
33
|
return installScripts(scripts, bindingGyp)
|
|
33
34
|
}
|
|
@@ -107,7 +107,12 @@ export async function fetchBundleAdvisories(packages: Package[], signal: AbortSi
|
|
|
107
107
|
const result = await Promise.race([Promise.all(asked.map(({ part, osv }) => part.length === 0 ? Promise.resolve([]) : advisories(part, { ...options, osv }))), deadline])
|
|
108
108
|
return { status: 200, body: result.flat().toSorted((a, b) => order(a.ecosystem, b.ecosystem) || order(a.name, b.name)) }
|
|
109
109
|
} catch (error) {
|
|
110
|
-
|
|
110
|
+
// Answered as upstream's failure alone: logged, its message, or with
|
|
111
|
+
// `debug` the whole error, but for a request its client gave up on. One
|
|
112
|
+
// past its deadline (aborted with a TimeoutError, as AbortSignal.timeout
|
|
113
|
+
// and the bundle route's timer abort) is the server giving up: logged.
|
|
114
|
+
const cancelled = signal.aborted && (signal.reason as { name?: unknown } | undefined)?.name !== 'TimeoutError'
|
|
115
|
+
if (!cancelled) console.warn('managed: advisories failed:', debug || !(error instanceof Error) ? error : error.message)
|
|
111
116
|
return { status: 502, body: { error: 'upstream-unavailable' } }
|
|
112
117
|
} finally {
|
|
113
118
|
if (onAbort) signal.removeEventListener('abort', onAbort)
|
|
@@ -6,7 +6,7 @@ import { Buffer } from 'node:buffer'
|
|
|
6
6
|
import { createHash, createSign } from 'node:crypto'
|
|
7
7
|
import { createClient } from '@preventive/upstream/github.js'
|
|
8
8
|
import type { ManagedConfig } from './config.ts'
|
|
9
|
-
import { GithubApiError, throwGithubResponseError, upstreamGithub } from './github-errors.ts'
|
|
9
|
+
import { GithubApiError, githubUnreachable, throwGithubResponseError, upstreamGithub } from './github-errors.ts'
|
|
10
10
|
|
|
11
11
|
export { GithubApiError } from './github-errors.ts'
|
|
12
12
|
|
|
@@ -71,6 +71,7 @@ export function mergeRepos(...lists: ConnectedRepo[][]): ConnectedRepo[] {
|
|
|
71
71
|
// fold into a GithubApiError (401 passes through for the caller to handle).
|
|
72
72
|
export async function githubJson(url: string, token: string | null, fetchImpl: typeof fetch, method: 'GET' | 'POST' = 'GET'): Promise<unknown> {
|
|
73
73
|
let res: Response
|
|
74
|
+
const started = Date.now()
|
|
74
75
|
try {
|
|
75
76
|
res = await fetchImpl(url, {
|
|
76
77
|
method,
|
|
@@ -81,10 +82,15 @@ export async function githubJson(url: string, token: string | null, fetchImpl: t
|
|
|
81
82
|
'user-agent': USER_AGENT, 'x-github-api-version': API_VERSION,
|
|
82
83
|
},
|
|
83
84
|
})
|
|
84
|
-
} catch { throw
|
|
85
|
+
} catch (error) { throw githubUnreachable(error, started) }
|
|
85
86
|
if (res.status === 401) discardInstallationToken(token, fetchImpl)
|
|
86
87
|
if (!res.ok) await throwGithubResponseError(res)
|
|
87
|
-
|
|
88
|
+
// The answer's body is read within the same timeout: one not read whole is
|
|
89
|
+
// GitHub not reached, as one not answered is; only one that isn't JSON is
|
|
90
|
+
// malformed.
|
|
91
|
+
try { return await res.json() } catch (error) {
|
|
92
|
+
throw error instanceof SyntaxError ? new GithubApiError(502, 'github-malformed') : githubUnreachable(error, started)
|
|
93
|
+
}
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
// Validate one repo object into a ConnectedRepo (or null to skip), tagging it
|
|
@@ -5,14 +5,32 @@ import { HttpError } from '@preventive/upstream/github.js'
|
|
|
5
5
|
export class GithubApiError extends Error {
|
|
6
6
|
status: number
|
|
7
7
|
retryAfter: number | null
|
|
8
|
-
|
|
8
|
+
// What failed, where GitHub wasn't reached (githubUnreachable).
|
|
9
|
+
detail: string | null
|
|
10
|
+
constructor(status: number, message: string, retryAfter: number | null = null, detail: string | null = null) {
|
|
9
11
|
super(message)
|
|
10
12
|
this.name = 'GithubApiError'
|
|
11
13
|
this.status = status
|
|
12
14
|
this.retryAfter = retryAfter
|
|
15
|
+
this.detail = detail
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
// GitHub not reached, or its answer not read whole, `started` ms since the
|
|
20
|
+
// epoch: as `github-unreachable`, with what failed as the error and its causes
|
|
21
|
+
// tell it (a timeout, a connection closed, an answer too large) and after how
|
|
22
|
+
// long, for an admin's page to show (repository-risk.ts). Only the errors'
|
|
23
|
+
// names, codes and messages, never GitHub's text nor a request's headers.
|
|
24
|
+
export function githubUnreachable(error: unknown, started: number): GithubApiError {
|
|
25
|
+
const told: string[] = []
|
|
26
|
+
for (let at = error, depth = 0; at instanceof Error && depth < 4; at = at.cause, depth++) {
|
|
27
|
+
const code = 'code' in at && typeof at.code === 'string' && at.code !== at.name ? ` ${at.code}` : ''
|
|
28
|
+
told.push(`${at.name}${code}${at.message ? `: ${at.message.slice(0, 200)}` : ''}`)
|
|
29
|
+
}
|
|
30
|
+
const failed = told.length > 0 ? told.join('; ') : String(error).slice(0, 200)
|
|
31
|
+
return new GithubApiError(502, 'github-unreachable', null, `${failed}, after ${((Date.now() - started) / 1000).toFixed(1)}s`)
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
export async function throwGithubResponseError(res: Response): Promise<never> {
|
|
17
35
|
if (res.status === 403 || res.status === 429) {
|
|
18
36
|
const body = await res.json().catch(() => null) as { message?: unknown } | null
|
|
@@ -30,6 +48,7 @@ export async function throwGithubResponseError(res: Response): Promise<never> {
|
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
export async function upstreamGithub<T>(request: () => Promise<T>): Promise<T> {
|
|
51
|
+
const started = Date.now()
|
|
33
52
|
try { return await request() } catch (error) {
|
|
34
53
|
if (error instanceof HttpError) {
|
|
35
54
|
// The upstream error includes GitHub's response text. Retain managed
|
|
@@ -44,6 +63,6 @@ export async function upstreamGithub<T>(request: () => Promise<T>): Promise<T> {
|
|
|
44
63
|
if (error instanceof Error && (error.name === 'AssertionError' || error.cause instanceof SyntaxError)) {
|
|
45
64
|
throw new GithubApiError(502, 'github-malformed')
|
|
46
65
|
}
|
|
47
|
-
throw
|
|
66
|
+
throw githubUnreachable(error, started)
|
|
48
67
|
}
|
|
49
68
|
}
|
package/server-managed/http.ts
CHANGED
|
@@ -221,6 +221,13 @@ const NPM_RISK_PATH = '/api/npm/risk'
|
|
|
221
221
|
// having no such package or version, or a tarball unpacking past risk's
|
|
222
222
|
// bounds; npm failing otherwise a 502.
|
|
223
223
|
const NPM_RISK_STATUS: Partial<Record<string, number>> = { 'package-not-found': 404, 'version-not-found': 404, 'package-too-large': 413 }
|
|
224
|
+
// A risk failure answered with its code alone, as risk's status for it (NPM_RISK_STATUS, else 502), its cause
|
|
225
|
+
// logged where it is the server's or npm's, not the reader's (a 5xx), for the logs to say why.
|
|
226
|
+
function sendRiskError(res: ServerResponse, err: NpmRiskError) {
|
|
227
|
+
const status = NPM_RISK_STATUS[err.code] ?? 502
|
|
228
|
+
if (status >= 500) console.error(`managed: npm risk failed (${err.code}):`, err.message)
|
|
229
|
+
sendJson(res, status, { error: err.code })
|
|
230
|
+
}
|
|
224
231
|
const NPM_PATHS = new Set([NPM_PACKAGE_PATH, NPM_VERSIONS_PATH, NPM_DOWNLOAD_PATH, NPM_STATS_PATH, NPM_ADVISORIES_PATH, NPM_TAGS_PATH, NPM_SOCKET_PATH, NPM_PROVENANCE_PATH, NPM_PRETTY_PATH,
|
|
225
232
|
NPM_RENAME_DIFF_PATH, NPM_TYPE_ONLY_PATH, NPM_RISK_PATH])
|
|
226
233
|
// How long an npm response may go unread before its connection is dropped.
|
|
@@ -1548,7 +1555,8 @@ async function handleBundleAdvisories(res: ServerResponse, deps: ManagedHttpDeps
|
|
|
1548
1555
|
const controller = new AbortController()
|
|
1549
1556
|
const onClose = () => { if (!res.writableEnded) controller.abort() }
|
|
1550
1557
|
res.on('close', onClose)
|
|
1551
|
-
|
|
1558
|
+
// Its deadline aborts as AbortSignal.timeout does, for a failure it leaves to be told from the reader's leaving.
|
|
1559
|
+
const timer = setTimeout(() => controller.abort(new DOMException('advisories took too long', 'TimeoutError')), ADVISORIES_TIMEOUT_MS)
|
|
1552
1560
|
try {
|
|
1553
1561
|
if (res.destroyed) return
|
|
1554
1562
|
const needsGithub = inventory.packages.some(pkg => repoAdvisories || pkg.ecosystem === 'github' || pkg.ecosystem === 'soldeer')
|
|
@@ -2804,7 +2812,7 @@ async function handleNpmReviews(req: IncomingMessage, res: ServerResponse, deps:
|
|
|
2804
2812
|
} catch (err) {
|
|
2805
2813
|
if (controller.signal.aborted) return
|
|
2806
2814
|
if (err instanceof NpmPackageError) { sendJson(res, err.status, { error: err.message }); return }
|
|
2807
|
-
if (err instanceof NpmRiskError) {
|
|
2815
|
+
if (err instanceof NpmRiskError) { sendRiskError(res, err); return }
|
|
2808
2816
|
throw err
|
|
2809
2817
|
} finally { res.off('close', onClose) }
|
|
2810
2818
|
}
|
|
@@ -3010,7 +3018,7 @@ async function handleNpm(req: IncomingMessage, res: ServerResponse, deps: Manage
|
|
|
3010
3018
|
writeResponse(res, Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength))
|
|
3011
3019
|
} catch (err) {
|
|
3012
3020
|
if (err instanceof NpmPackageError || err instanceof PrettyError) { if (!res.headersSent && !res.destroyed) sendJson(res, err.status, { error: err.message }); return }
|
|
3013
|
-
if (err instanceof NpmRiskError) { if (!res.headersSent && !res.destroyed)
|
|
3021
|
+
if (err instanceof NpmRiskError) { if (!res.headersSent && !res.destroyed) sendRiskError(res, err); return }
|
|
3014
3022
|
if (controller.signal.aborted) return
|
|
3015
3023
|
throw err
|
|
3016
3024
|
} finally {
|
|
@@ -111,8 +111,11 @@ export type RepositoryRiskLine =
|
|
|
111
111
|
| { type: 'done' }
|
|
112
112
|
|
|
113
113
|
// What an error is known by: a GitHub or npm failure's code, or `failed`;
|
|
114
|
-
//
|
|
115
|
-
|
|
114
|
+
// GitHub not reached with what failed (githubUnreachable), while `doing` what
|
|
115
|
+
// of a repository, and risk's with what it asked of npm and how that failed,
|
|
116
|
+
// where they say.
|
|
117
|
+
function failureOf(error: unknown, doing?: string): RiskFailure {
|
|
118
|
+
if (error instanceof GithubApiError && error.detail) return { error: error.message, detail: doing ? `${doing}: ${error.detail}` : error.detail }
|
|
116
119
|
if (error instanceof GithubApiError || error instanceof NpmPackageError) return { error: error.message }
|
|
117
120
|
if (!(error instanceof NpmRiskError)) return { error: 'failed' }
|
|
118
121
|
return error.message === error.code ? { error: error.code } : { error: error.code, detail: error.message }
|
|
@@ -299,16 +302,18 @@ function assessLater(scan: Scan, packages: LockedPackage[]) {
|
|
|
299
302
|
// for its commit or else from GitHub, those of its `paths` alone; null, with
|
|
300
303
|
// its line telling why, where either failed, or where the scan stopped.
|
|
301
304
|
async function listLockfiles(scan: Scan, repo: RepositoryRiskRepo) {
|
|
305
|
+
let doing = 'opening it'
|
|
302
306
|
try {
|
|
303
307
|
const opened = await scan.source.open(repo)
|
|
304
308
|
if (scan.signal.aborted) return null
|
|
305
309
|
const key = `${repo.repoId}@${opened.commit}`
|
|
306
310
|
let listed = scan.caches.listings.get(key)
|
|
311
|
+
doing = 'listing its lockfiles'
|
|
307
312
|
if (!listed) scan.caches.listings.set(key, listed = await opened.lockfiles())
|
|
308
313
|
const lockfiles = listed.filter(({ path, format }) => NPM_LOCKFILE_FORMATS.has(format) && lockfileOf(path, repo.paths))
|
|
309
314
|
return { opened, lockfiles }
|
|
310
315
|
} catch (error) {
|
|
311
|
-
if (!scan.signal.aborted) scan.lines.add({ type: 'repo', id: repo.repoId, ...failureOf(error) })
|
|
316
|
+
if (!scan.signal.aborted) scan.lines.add({ type: 'repo', id: repo.repoId, ...failureOf(error, doing) })
|
|
312
317
|
return null
|
|
313
318
|
}
|
|
314
319
|
}
|
|
@@ -353,7 +358,7 @@ async function readRepository(scan: Scan, repo: RepositoryRiskRepo) {
|
|
|
353
358
|
lockfiles.push(await readLockfile(scan, repo.repoId, listed.opened, lockfile))
|
|
354
359
|
}
|
|
355
360
|
try { await listed.opened.recheck() } catch (error) {
|
|
356
|
-
if (!scan.signal.aborted) scan.lines.add({ type: 'repo', id: repo.repoId, ...failureOf(error) })
|
|
361
|
+
if (!scan.signal.aborted) scan.lines.add({ type: 'repo', id: repo.repoId, ...failureOf(error, 'checking access to it again') })
|
|
357
362
|
return
|
|
358
363
|
}
|
|
359
364
|
if (scan.signal.aborted) return
|
package/out/chunk-BYUEDJOM.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import{a}from"./chunk-TUII34K2.js";var v="deepview-objstore.v1.put",y="deepview-objstore.v1.delete",h="deepview-objstore.v1.fetch",I="deepview-objstore.v1.fetch-rest",w="deepview-objstore.v1.put-rest",j="deepview-objstore.v1.delete-rest";function l(e){return e==null?"":String(e)}function p(e){return e??""}function x(e,n){return a([v,e.workspaceTag,e.resourceTag,l(e.prevVersion),p(e.prevIncarnation),e.contentHash,String(e.expectedLength),n].join(`
|
|
2
|
-
`))}function O(e,n){return a([y,e.workspaceTag,e.resourceTag,l(e.prevVersion),p(e.prevIncarnation),n].join(`
|
|
3
|
-
`))}function P(e,n,r){return a([h,e,n,r].join(`
|
|
4
|
-
`))}async function i(e,n){return new Uint8Array(await crypto.subtle.sign({name:"Ed25519"},e,n)).toBase64({alphabet:"base64url",omitPadding:!0})}function B(e,n,r){return i(e,x(n,r))}function D(e,n,r){return i(e,O(n,r))}function R(e,n,r,o){return i(e,P(n,r,o))}function A(e,n,r){return a([I,e,n,String(r)].join(`
|
|
5
|
-
`))}function _(e,n,r,o){return i(e,A(n,r,o))}function N(e,n){return a([w,e.workspaceTag,e.resourceTag,l(e.prevVersion),p(e.prevIncarnation),e.contentHash,String(e.expectedLength),String(n)].join(`
|
|
6
|
-
`))}function U(e,n,r){return i(e,N(n,r))}function T(e,n){return a([j,e.workspaceTag,e.resourceTag,l(e.prevVersion),p(e.prevIncarnation),String(n)].join(`
|
|
7
|
-
`))}function L(e,n,r){return i(e,T(n,r))}async function H(e){let n=await crypto.subtle.digest("SHA-256",e);return new Uint8Array(n).toBase64({alphabet:"base64url",omitPadding:!0})}var d="/api/config";var J="http://127.0.0.1:3123/";function m(e){if(typeof e!="string"||!e.trim())return null;try{let n=new URL(e);return!["http:","https:"].includes(n.protocol)||n.username||n.password||n.search||n.hash?null:(n.pathname=n.pathname.replace(/\/+$/u,"")+"/",n.href)}catch{return null}}var b="deepview.sync.serverInfo",g="deepview.sync.standaloneProbe";function $(){try{return localStorage.getItem(g)==="1"}catch{return!1}}function X(){try{localStorage.setItem(g,"1")}catch{}}function f(e){return e==="managed+e2e"||e==="e2e+managed"}function q(e,n){return(e==="managed"||f(e))&&/^\/(?:team|manage)(?:\/|$)/u.test(n)}function Q(e,n=null){return n&&(f(e)||e===n)?n:e==="managed"||e==="managed+e2e"?"managed":"e2e"}function W(e,n){return e&&f(e.mode)&&n.mode==="e2e"?{...e,githubNewIssueLabels:n.githubNewIssueLabels??"",...n.deepviewScanServer?{deepviewScanServer:n.deepviewScanServer}:{}}:n}function S(e){if(e==null||typeof e!="object")return null;let n=e.mode;if(n!=="e2e"&&n!=="managed"&&n!=="managed+e2e"&&n!=="e2e+managed")return null;let r=null,o=e.managed;if(o!=null&&typeof o=="object"){let u=o.loginPath,c=o.cookieName;typeof u=="string"&&typeof c=="string"&&(r={loginPath:u,cookieName:c},o.allowShare===!0&&(r.allowShare=!0))}let t=n==="managed"?null:m(e.deepviewScanServer),s=e.githubNewIssueLabels;return{mode:n,managed:r,...t?{deepviewScanServer:t}:{},...typeof s=="string"&&s.length<=4096?{githubNewIssueLabels:s}:{}}}async function Z(){try{let e=await fetch(d,{credentials:"same-origin",cache:"no-store",headers:{accept:"application/json"}});return e.status===404?"standalone":e.ok?S(await e.json()):null}catch{return null}}async function ee(e,n=3e3){let r;try{return await Promise.race([e,new Promise(o=>{r=setTimeout(()=>o(null),n)})])}catch{return null}finally{clearTimeout(r)}}function ne(){try{let e=localStorage.getItem(b);if(e==null)return null;let n=S(JSON.parse(e));return n?{mode:n.mode,managed:n.managed}:null}catch{return null}}function re(e){try{localStorage.setItem(b,JSON.stringify({mode:e.mode,managed:e.managed})),localStorage.removeItem(g)}catch{}}var E="deepview.objstore-presence.";function M(e){return E+e}function C(e){try{let n=localStorage.getItem(M(e));if(!n)return null;let r=JSON.parse(n);return!r||typeof r!="object"?null:{names:r.names&&typeof r.names=="object"?r.names:{},bundles:r.bundles&&typeof r.bundles=="object"?r.bundles:{},bundleNames:r.bundleNames&&typeof r.bundleNames=="object"?r.bundleNames:{},baselines:k(r.baselines,r.localVersions)}}catch{return null}}function k(e,n){let r={};if(n&&typeof n=="object")for(let[o,t]of Object.entries(n))Number.isSafeInteger(t)&&t>=0&&(r[o]={version:t,incarnation:null,synced:!0,hash:null});if(e&&typeof e=="object")for(let[o,t]of Object.entries(e))!t||typeof t!="object"||!Number.isSafeInteger(t.version)||t.version<0||t.incarnation!=null&&typeof t.incarnation!="string"||(r[o]={version:t.version,incarnation:t.incarnation??null,synced:t.synced===!0,hash:typeof t.hash=="string"?t.hash:null});return r}function oe(e,n,r,o){let t=C(e),s=Object.entries((n==="bundle"?t?.bundles:t?.names)??{}).find(([,c])=>c===r)?.[0],u=t?.baselines?.[s];return{synced:s!==void 0&&(n==="bundle"||u?.synced===!0&&u.hash===o),cached:!0}}export{B as a,D as b,R as c,_ as d,U as e,L as f,H as g,J as h,m as i,$ as j,X as k,f as l,q as m,Q as n,W as o,S as p,Z as q,ee as r,ne as s,re as t,M as u,C as v,oe as w};
|
|
File without changes
|