monkeypatchlist 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/NOTICE +61 -0
- package/README.md +183 -2
- package/catalog/catalog.json +552211 -0
- package/catalog/catalog.schema.json +787 -0
- package/catalog/changelog.json +3 -0
- package/dist/bin/monkeypatchlist.d.ts +2 -0
- package/dist/bin/monkeypatchlist.js +3 -0
- package/dist/catalog.d.ts +561 -0
- package/dist/catalog.js +58 -0
- package/dist/cli.d.ts +84 -0
- package/dist/cli.js +470 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lockfile-input.d.ts +9 -0
- package/dist/lockfile-input.js +91 -0
- package/dist/packages/analysis/binding.d.ts +2 -0
- package/dist/packages/analysis/binding.js +5 -0
- package/dist/packages/analysis/index.d.ts +10 -0
- package/dist/packages/analysis/index.js +13 -0
- package/dist/packages/analysis/job.d.ts +33 -0
- package/dist/packages/analysis/job.js +85 -0
- package/dist/packages/analysis/lib/absence-tags.d.ts +34 -0
- package/dist/packages/analysis/lib/absence-tags.js +340 -0
- package/dist/packages/analysis/lib/ast.d.ts +28 -0
- package/dist/packages/analysis/lib/ast.js +64 -0
- package/dist/packages/analysis/lib/binding.d.ts +36 -0
- package/dist/packages/analysis/lib/binding.js +66 -0
- package/dist/packages/analysis/lib/budget.d.ts +80 -0
- package/dist/packages/analysis/lib/budget.js +31 -0
- package/dist/packages/analysis/lib/catch-arms.d.ts +19 -0
- package/dist/packages/analysis/lib/catch-arms.js +351 -0
- package/dist/packages/analysis/lib/chunk-loads.d.ts +160 -0
- package/dist/packages/analysis/lib/chunk-loads.js +882 -0
- package/dist/packages/analysis/lib/chunk-registry.d.ts +45 -0
- package/dist/packages/analysis/lib/chunk-registry.js +334 -0
- package/dist/packages/analysis/lib/code-routes.d.ts +52 -0
- package/dist/packages/analysis/lib/code-routes.js +921 -0
- package/dist/packages/analysis/lib/collisions.d.ts +22 -0
- package/dist/packages/analysis/lib/collisions.js +103 -0
- package/dist/packages/analysis/lib/definitions.d.ts +76 -0
- package/dist/packages/analysis/lib/definitions.js +782 -0
- package/dist/packages/analysis/lib/early-exits.d.ts +39 -0
- package/dist/packages/analysis/lib/early-exits.js +161 -0
- package/dist/packages/analysis/lib/effect.d.ts +138 -0
- package/dist/packages/analysis/lib/effect.js +800 -0
- package/dist/packages/analysis/lib/global-object.d.ts +581 -0
- package/dist/packages/analysis/lib/global-object.js +8240 -0
- package/dist/packages/analysis/lib/keys.d.ts +33 -0
- package/dist/packages/analysis/lib/keys.js +82 -0
- package/dist/packages/analysis/lib/line-starts.d.ts +41 -0
- package/dist/packages/analysis/lib/line-starts.js +79 -0
- package/dist/packages/analysis/lib/link-group.d.ts +51 -0
- package/dist/packages/analysis/lib/link-group.js +487 -0
- package/dist/packages/analysis/lib/module-links.d.ts +117 -0
- package/dist/packages/analysis/lib/module-links.js +1060 -0
- package/dist/packages/analysis/lib/nested-code.d.ts +38 -0
- package/dist/packages/analysis/lib/nested-code.js +200 -0
- package/dist/packages/analysis/lib/numeric.d.ts +16 -0
- package/dist/packages/analysis/lib/numeric.js +216 -0
- package/dist/packages/analysis/lib/once-flags.d.ts +12 -0
- package/dist/packages/analysis/lib/once-flags.js +190 -0
- package/dist/packages/analysis/lib/parameter-arguments.d.ts +12 -0
- package/dist/packages/analysis/lib/parameter-arguments.js +34 -0
- package/dist/packages/analysis/lib/parse.d.ts +10 -0
- package/dist/packages/analysis/lib/parse.js +39 -0
- package/dist/packages/analysis/lib/partial.d.ts +60 -0
- package/dist/packages/analysis/lib/partial.js +518 -0
- package/dist/packages/analysis/lib/print.d.ts +3 -0
- package/dist/packages/analysis/lib/print.js +150 -0
- package/dist/packages/analysis/lib/reachability.d.ts +137 -0
- package/dist/packages/analysis/lib/reachability.js +1295 -0
- package/dist/packages/analysis/lib/resolver.d.ts +13 -0
- package/dist/packages/analysis/lib/resolver.js +2886 -0
- package/dist/packages/analysis/lib/restores.d.ts +42 -0
- package/dist/packages/analysis/lib/restores.js +482 -0
- package/dist/packages/analysis/lib/rewritten-deletes.d.ts +6 -0
- package/dist/packages/analysis/lib/rewritten-deletes.js +142 -0
- package/dist/packages/analysis/lib/scope.d.ts +110 -0
- package/dist/packages/analysis/lib/scope.js +443 -0
- package/dist/packages/analysis/lib/script-loads.d.ts +56 -0
- package/dist/packages/analysis/lib/script-loads.js +359 -0
- package/dist/packages/analysis/lib/summaries.d.ts +177 -0
- package/dist/packages/analysis/lib/summaries.js +498 -0
- package/dist/packages/analysis/lib/types.d.ts +632 -0
- package/dist/packages/analysis/lib/types.js +128 -0
- package/dist/packages/analysis/lib/wrap.d.ts +34 -0
- package/dist/packages/analysis/lib/wrap.js +407 -0
- package/dist/packages/analysis/lib/writes.d.ts +358 -0
- package/dist/packages/analysis/lib/writes.js +1612 -0
- package/dist/packages/analysis/visit-order.d.ts +3 -0
- package/dist/packages/analysis/visit-order.js +23 -0
- package/dist/packages/attribution/index.d.ts +233 -0
- package/dist/packages/attribution/index.js +444 -0
- package/dist/packages/attribution/lib/containing-package.d.ts +76 -0
- package/dist/packages/attribution/lib/containing-package.js +304 -0
- package/dist/packages/attribution/lib/first-party.d.ts +37 -0
- package/dist/packages/attribution/lib/first-party.js +153 -0
- package/dist/packages/attribution/lib/lockfile-graph.d.ts +99 -0
- package/dist/packages/attribution/lib/lockfile-graph.js +158 -0
- package/dist/packages/attribution/lib/lockfile.d.ts +33 -0
- package/dist/packages/attribution/lib/lockfile.js +330 -0
- package/dist/packages/attribution/lib/path-rules.d.ts +29 -0
- package/dist/packages/attribution/lib/path-rules.js +84 -0
- package/dist/packages/attribution/lib/pnpm.d.ts +4 -0
- package/dist/packages/attribution/lib/pnpm.js +133 -0
- package/dist/packages/attribution/lib/remote-url.d.ts +7 -0
- package/dist/packages/attribution/lib/remote-url.js +24 -0
- package/dist/packages/attribution/lib/source-map.d.ts +51 -0
- package/dist/packages/attribution/lib/source-map.js +125 -0
- package/dist/packages/attribution/lib/yaml.d.ts +9 -0
- package/dist/packages/attribution/lib/yaml.js +277 -0
- package/dist/packages/attribution/lib/yarn.d.ts +7 -0
- package/dist/packages/attribution/lib/yarn.js +227 -0
- package/dist/packages/baseline/index.d.ts +45 -0
- package/dist/packages/baseline/index.js +41 -0
- package/dist/packages/baseline/lib/error.d.ts +15 -0
- package/dist/packages/baseline/lib/error.js +25 -0
- package/dist/packages/baseline/lib/format.d.ts +49 -0
- package/dist/packages/baseline/lib/format.js +22 -0
- package/dist/packages/baseline/lib/migrations.d.ts +19 -0
- package/dist/packages/baseline/lib/migrations.js +7 -0
- package/dist/packages/baseline/lib/previous.d.ts +23 -0
- package/dist/packages/baseline/lib/previous.js +64 -0
- package/dist/packages/baseline/lib/read.d.ts +39 -0
- package/dist/packages/baseline/lib/read.js +219 -0
- package/dist/packages/baseline/lib/report.d.ts +25 -0
- package/dist/packages/baseline/lib/report.js +104 -0
- package/dist/packages/baseline/lib/skew.d.ts +51 -0
- package/dist/packages/baseline/lib/skew.js +75 -0
- package/dist/packages/compare/index.d.ts +120 -0
- package/dist/packages/compare/index.js +274 -0
- package/dist/packages/compare/lib/catalog-notes.d.ts +73 -0
- package/dist/packages/compare/lib/catalog-notes.js +224 -0
- package/dist/packages/completeness/index.d.ts +246 -0
- package/dist/packages/completeness/index.js +249 -0
- package/dist/packages/confidence/index.d.ts +54 -0
- package/dist/packages/confidence/index.js +71 -0
- package/dist/packages/config/index.d.ts +107 -0
- package/dist/packages/config/index.js +95 -0
- package/dist/packages/config/lib/command-line.d.ts +23 -0
- package/dist/packages/config/lib/command-line.js +239 -0
- package/dist/packages/config/lib/config-file.d.ts +11 -0
- package/dist/packages/config/lib/config-file.js +119 -0
- package/dist/packages/config/lib/scan-config-id.d.ts +28 -0
- package/dist/packages/config/lib/scan-config-id.js +54 -0
- package/dist/packages/config/lib/schema.d.ts +2 -0
- package/dist/packages/config/lib/schema.js +31 -0
- package/dist/packages/config/lib/types.d.ts +64 -0
- package/dist/packages/config/lib/types.js +6 -0
- package/dist/packages/config/lib/urls.d.ts +6 -0
- package/dist/packages/config/lib/urls.js +9 -0
- package/dist/packages/discovery/index.d.ts +277 -0
- package/dist/packages/discovery/index.js +1950 -0
- package/dist/packages/discovery/lib/import-map.d.ts +68 -0
- package/dist/packages/discovery/lib/import-map.js +180 -0
- package/dist/packages/discovery/lib/requests.d.ts +58 -0
- package/dist/packages/discovery/lib/requests.js +591 -0
- package/dist/packages/discovery/lib/scopes.d.ts +6 -0
- package/dist/packages/discovery/lib/scopes.js +286 -0
- package/dist/packages/discovery/lib/source-maps.d.ts +56 -0
- package/dist/packages/discovery/lib/source-maps.js +209 -0
- package/dist/packages/discovery/lib/swept-code.d.ts +27 -0
- package/dist/packages/discovery/lib/swept-code.js +107 -0
- package/dist/packages/discovery/lib/syntax.d.ts +17 -0
- package/dist/packages/discovery/lib/syntax.js +39 -0
- package/dist/packages/findings/index.d.ts +106 -0
- package/dist/packages/findings/index.js +254 -0
- package/dist/packages/findings/lib/scope.d.ts +38 -0
- package/dist/packages/findings/lib/scope.js +110 -0
- package/dist/packages/parallel/index.d.ts +2 -0
- package/dist/packages/parallel/index.js +5 -0
- package/dist/packages/parallel/lib/protocol.d.ts +15 -0
- package/dist/packages/parallel/lib/protocol.js +2 -0
- package/dist/packages/parallel/lib/runner.d.ts +36 -0
- package/dist/packages/parallel/lib/runner.js +96 -0
- package/dist/packages/parallel/lib/schedule.d.ts +16 -0
- package/dist/packages/parallel/lib/schedule.js +52 -0
- package/dist/packages/parallel/lib/worker.d.ts +1 -0
- package/dist/packages/parallel/lib/worker.js +18 -0
- package/dist/packages/policy/index.d.ts +36 -0
- package/dist/packages/policy/index.js +22 -0
- package/dist/packages/report/explain.d.ts +26 -0
- package/dist/packages/report/explain.js +99 -0
- package/dist/packages/report/index.d.ts +210 -0
- package/dist/packages/report/index.js +388 -0
- package/dist/packages/report/terminal.d.ts +11 -0
- package/dist/packages/report/terminal.js +331 -0
- package/dist/packages/source-store/fixture.d.ts +22 -0
- package/dist/packages/source-store/fixture.js +58 -0
- package/dist/packages/source-store/index.d.ts +278 -0
- package/dist/packages/source-store/index.js +322 -0
- package/dist/packages/source-store/lib/addresses.d.ts +6 -0
- package/dist/packages/source-store/lib/addresses.js +91 -0
- package/dist/packages/source-store/lib/connection.d.ts +15 -0
- package/dist/packages/source-store/lib/connection.js +135 -0
- package/dist/packages/source-store/lib/content-types.d.ts +16 -0
- package/dist/packages/source-store/lib/content-types.js +75 -0
- package/dist/packages/source-store/lib/data-urls.d.ts +10 -0
- package/dist/packages/source-store/lib/data-urls.js +85 -0
- package/dist/packages/source-store/lib/deadline.d.ts +18 -0
- package/dist/packages/source-store/lib/deadline.js +44 -0
- package/dist/packages/source-store/lib/exchange.d.ts +29 -0
- package/dist/packages/source-store/lib/exchange.js +112 -0
- package/dist/packages/source-store/lib/integrity.d.ts +19 -0
- package/dist/packages/source-store/lib/integrity.js +86 -0
- package/dist/packages/source-store/lib/outcomes.d.ts +12 -0
- package/dist/packages/source-store/lib/outcomes.js +22 -0
- package/dist/packages/source-store/lib/proxy.d.ts +11 -0
- package/dist/packages/source-store/lib/proxy.js +53 -0
- package/dist/packages/source-store/lib/session.d.ts +12 -0
- package/dist/packages/source-store/lib/session.js +139 -0
- package/dist/packages/source-store/lib/urls.d.ts +6 -0
- package/dist/packages/source-store/lib/urls.js +15 -0
- package/dist/packages/source-store/resolve.d.ts +59 -0
- package/dist/packages/source-store/resolve.js +45 -0
- package/dist/scan.d.ts +65 -0
- package/dist/scan.js +592 -0
- package/package.json +95 -4
- package/schema/config.schema.json +133 -0
- package/schema/report.schema.json +1905 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Subresource Integrity (§17 Integrity): fetched bytes are checked against the integrity metadata their reference
|
|
2
|
+
// carries, as the SRI spec does it, and bytes that don't match are withheld.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
/** The hash algorithms SRI supports (SRI § Cryptographic hash functions), from the weakest to the strongest. */
|
|
5
|
+
const algorithms = ['sha256', 'sha384', 'sha512'];
|
|
6
|
+
/**
|
|
7
|
+
* The result of fetching `bytes` with the integrity metadata `integrity` (the empty string for none): `ok` with the
|
|
8
|
+
* check, or `sri-mismatch` with it, and without the bytes, which are never analyzed (§17 Integrity).
|
|
9
|
+
*/
|
|
10
|
+
export function verified(bytes, response, integrity) {
|
|
11
|
+
const sri = checkIntegrity(integrity, bytes);
|
|
12
|
+
if (sri === 'none' || sri.result === 'match')
|
|
13
|
+
return { status: 'ok', response, bytes, sri };
|
|
14
|
+
const expected = sri.expected.join(' or ');
|
|
15
|
+
const detail = `the fetched bytes' digest is ${sri.actual}, and the integrity value expects ${expected}`;
|
|
16
|
+
return { status: 'sri-mismatch', detail, response, sri };
|
|
17
|
+
}
|
|
18
|
+
/** Separates the alternatives of an integrity value (`alternativeIntegrity`): not ASCII whitespace, which separates tokens. */
|
|
19
|
+
const separator = '\u001f';
|
|
20
|
+
/**
|
|
21
|
+
* One integrity value that stands for several (§17 Integrity): code sets an element's `integrity` to one of
|
|
22
|
+
* several constants, and the bytes pass when they match any. A single value is returned as it is.
|
|
23
|
+
*/
|
|
24
|
+
export function alternativeIntegrity(values) {
|
|
25
|
+
return values.join(separator);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Checks `bytes` against an integrity value, which is integrity metadata or alternatives of it
|
|
29
|
+
* (`alternativeIntegrity`). The bytes match when they match any alternative, and the check is that alternative's. When
|
|
30
|
+
* none does, the check expects every alternative's digests, and `actual` is the bytes' digest in the strongest
|
|
31
|
+
* algorithm any of them names. An alternative that checks nothing makes the value check nothing (`none`), as in a
|
|
32
|
+
* browser.
|
|
33
|
+
*/
|
|
34
|
+
export function checkIntegrity(integrity, bytes) {
|
|
35
|
+
const checks = [];
|
|
36
|
+
for (const alternative of integrity.split(separator)) {
|
|
37
|
+
const check = checkMetadata(alternative, bytes);
|
|
38
|
+
if (check === 'none')
|
|
39
|
+
return 'none';
|
|
40
|
+
if (check.result === 'match')
|
|
41
|
+
return check;
|
|
42
|
+
checks.push(check);
|
|
43
|
+
}
|
|
44
|
+
if (checks.length === 1)
|
|
45
|
+
return checks[0];
|
|
46
|
+
const algorithm = algorithms.findLast((candidate) => checks.some(({ actual }) => actual.startsWith(`${candidate}-`)));
|
|
47
|
+
const actual = checks.find((check) => check.actual.startsWith(`${algorithm}-`)).actual;
|
|
48
|
+
return { result: 'mismatch', expected: checks.flatMap(({ expected }) => expected), actual };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Checks `bytes` against integrity metadata (SRI § Does response match metadataList?). Only the digests in the
|
|
52
|
+
* strongest algorithm the metadata names are compared, and the bytes match when any of them is their digest in that
|
|
53
|
+
* algorithm, base64-encoded. Metadata that names no supported algorithm, the empty string included, checks nothing
|
|
54
|
+
* (`none`), as in a browser.
|
|
55
|
+
*/
|
|
56
|
+
function checkMetadata(integrity, bytes) {
|
|
57
|
+
const metadata = parseMetadata(integrity);
|
|
58
|
+
if (metadata.length === 0)
|
|
59
|
+
return 'none';
|
|
60
|
+
// The strongest algorithm named (SRI § Get the strongest metadata). Spreading the metadata into `Math.max` would
|
|
61
|
+
// overflow the stack on a long enough integrity value, which a page supplies.
|
|
62
|
+
const named = new Set(metadata.map((item) => item.algorithm));
|
|
63
|
+
const algorithm = algorithms.findLast((candidate) => named.has(candidate));
|
|
64
|
+
const strongest = metadata.filter((item) => item.algorithm === algorithm);
|
|
65
|
+
const digest = createHash(algorithm).update(bytes).digest('base64');
|
|
66
|
+
return {
|
|
67
|
+
result: strongest.some(({ value }) => value === digest) ? 'match' : 'mismatch',
|
|
68
|
+
expected: strongest.map(({ value }) => `${algorithm}-${value}`),
|
|
69
|
+
actual: `${algorithm}-${digest}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parses integrity metadata (SRI § Parse metadata): each token separated by ASCII whitespace is an algorithm and a
|
|
74
|
+
* base64 value, joined by `-`, optionally followed by `?` and options, which are ignored. A token whose algorithm is
|
|
75
|
+
* not a supported one, ignoring ASCII case, is skipped.
|
|
76
|
+
*/
|
|
77
|
+
function parseMetadata(integrity) {
|
|
78
|
+
const metadata = [];
|
|
79
|
+
for (const token of integrity.split(/[\t\n\f\r ]+/)) {
|
|
80
|
+
const [name = '', value = ''] = token.split('?')[0].split('-');
|
|
81
|
+
const algorithm = algorithms.find((supported) => supported === name.replace(/[A-Z]/g, (c) => c.toLowerCase()));
|
|
82
|
+
if (algorithm !== undefined)
|
|
83
|
+
metadata.push({ algorithm, value });
|
|
84
|
+
}
|
|
85
|
+
return metadata;
|
|
86
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FetchedResponse } from '../index.ts';
|
|
2
|
+
/** The safety floor refuses the fetch: a scheme, address, size, count, redirect or content-type refusal. */
|
|
3
|
+
export declare class Refusal extends Error {
|
|
4
|
+
/** The response the refusal was made on, if there was one. */
|
|
5
|
+
readonly response: FetchedResponse | null;
|
|
6
|
+
constructor(detail: string, response?: FetchedResponse | null);
|
|
7
|
+
}
|
|
8
|
+
/** The fetch failed: a timeout, a DNS, connection or TLS error, or a response that can't be read. */
|
|
9
|
+
export declare class Failure extends Error {
|
|
10
|
+
}
|
|
11
|
+
/** An error's code, or its message. */
|
|
12
|
+
export declare function reason(error: unknown): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// How the resolve fetcher's steps report the two ways a fetch can end without bytes (§17 Outcomes). They are thrown
|
|
2
|
+
// inside the fetcher and returned from it as data.
|
|
3
|
+
/** The safety floor refuses the fetch: a scheme, address, size, count, redirect or content-type refusal. */
|
|
4
|
+
export class Refusal extends Error {
|
|
5
|
+
/** The response the refusal was made on, if there was one. */
|
|
6
|
+
response;
|
|
7
|
+
constructor(detail, response = null) {
|
|
8
|
+
super(detail);
|
|
9
|
+
this.response = response;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** The fetch failed: a timeout, a DNS, connection or TLS error, or a response that can't be read. */
|
|
13
|
+
export class Failure extends Error {
|
|
14
|
+
}
|
|
15
|
+
/** An error's code, or its message. */
|
|
16
|
+
export function reason(error) {
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
const code = error.code;
|
|
19
|
+
return code === undefined || error.message.includes(code) ? error.message : `${code}: ${error.message}`;
|
|
20
|
+
}
|
|
21
|
+
return String(error);
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A proxy to tunnel a request through: its URL, and the `Proxy-Authorization` its credentials give. */
|
|
2
|
+
export interface Proxy {
|
|
3
|
+
url: URL;
|
|
4
|
+
authorization: string | null;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The proxy for `url`, from the environment, or null to connect directly. Only `https:` is proxied, through
|
|
8
|
+
* `HTTPS_PROXY` (or `https_proxy`), and not to a host `NO_PROXY` (or `no_proxy`) names. The credentials sent to it are
|
|
9
|
+
* the ones in its URL, and no others.
|
|
10
|
+
*/
|
|
11
|
+
export declare function proxyFor(url: URL, env: Readonly<Record<string, string | undefined>>): Proxy | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// The proxy the floor honours (§17 Safety floor: Proxies): `HTTPS_PROXY`, except for the hosts `NO_PROXY` names.
|
|
2
|
+
import { Failure } from "./outcomes.js";
|
|
3
|
+
import { hostOf, portOf } from "./urls.js";
|
|
4
|
+
/**
|
|
5
|
+
* The proxy for `url`, from the environment, or null to connect directly. Only `https:` is proxied, through
|
|
6
|
+
* `HTTPS_PROXY` (or `https_proxy`), and not to a host `NO_PROXY` (or `no_proxy`) names. The credentials sent to it are
|
|
7
|
+
* the ones in its URL, and no others.
|
|
8
|
+
*/
|
|
9
|
+
export function proxyFor(url, env) {
|
|
10
|
+
if (url.protocol !== 'https:')
|
|
11
|
+
return null;
|
|
12
|
+
const setting = (env.HTTPS_PROXY ?? env.https_proxy ?? '').trim();
|
|
13
|
+
if (setting === '' || bypasses(url, env.NO_PROXY ?? env.no_proxy ?? ''))
|
|
14
|
+
return null;
|
|
15
|
+
const proxy = URL.parse(/^[a-z][a-z\d+.-]*:\/\//i.test(setting) ? setting : `http://${setting}`);
|
|
16
|
+
if (proxy === null || (proxy.protocol !== 'http:' && proxy.protocol !== 'https:')) {
|
|
17
|
+
throw new Failure(`HTTPS_PROXY is not an http: or https: proxy URL: ${setting}`);
|
|
18
|
+
}
|
|
19
|
+
if (proxy.username === '')
|
|
20
|
+
return { url: proxy, authorization: null };
|
|
21
|
+
let credentials;
|
|
22
|
+
try {
|
|
23
|
+
credentials = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Failure("HTTPS_PROXY's credentials are not well-formed percent-encoding");
|
|
27
|
+
}
|
|
28
|
+
return { url: proxy, authorization: `Basic ${Buffer.from(credentials).toString('base64')}` };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Whether `NO_PROXY` names the URL's host: `*`, the host itself, or a domain it is in (`example.com` or `.example.com`
|
|
32
|
+
* for `cdn.example.com`), with or without a port, ignoring case.
|
|
33
|
+
*/
|
|
34
|
+
function bypasses(url, noProxy) {
|
|
35
|
+
const host = hostOf(url).toLowerCase();
|
|
36
|
+
const port = portOf(url);
|
|
37
|
+
return noProxy
|
|
38
|
+
.split(/[\s,]+/)
|
|
39
|
+
.filter((entry) => entry !== '')
|
|
40
|
+
.some((entry) => {
|
|
41
|
+
if (entry === '*')
|
|
42
|
+
return true;
|
|
43
|
+
const lower = entry.toLowerCase();
|
|
44
|
+
// A host, `[IPv6]` or an IPv6 address, then an optional port.
|
|
45
|
+
const bracketed = /^\[(.*)\](?::(\d+))?$/.exec(lower);
|
|
46
|
+
const parts = lower.split(':');
|
|
47
|
+
const [named = '', entryPort] = bracketed !== null ? bracketed.slice(1) : parts.length === 2 ? parts : [lower];
|
|
48
|
+
if (entryPort !== undefined && Number(entryPort) !== port)
|
|
49
|
+
return false;
|
|
50
|
+
const name = named.replace(/^\*?\./, '');
|
|
51
|
+
return name !== '' && (host === name || host.endsWith(`.${name}`));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Fetcher } from '../index.ts';
|
|
2
|
+
import type { Connect, Limits, Lookup } from '../resolve.ts';
|
|
3
|
+
export interface SessionOptions {
|
|
4
|
+
version: string;
|
|
5
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
6
|
+
lookup: Lookup;
|
|
7
|
+
connect: Connect;
|
|
8
|
+
ca: string | string[] | null;
|
|
9
|
+
limits: Limits;
|
|
10
|
+
clock: () => number;
|
|
11
|
+
}
|
|
12
|
+
export declare function createSession(options: SessionOptions): Fetcher;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// One scan's resolve fetcher: the floor's per-fetch rules, and the state its scan-wide limits and its cache keep.
|
|
2
|
+
import { open } from "./connection.js";
|
|
3
|
+
import { blockedTypeRefusal, javascriptTypeRefusal } from "./content-types.js";
|
|
4
|
+
import { Sockets, within } from "./deadline.js";
|
|
5
|
+
import { exchange } from "./exchange.js";
|
|
6
|
+
import { verified } from "./integrity.js";
|
|
7
|
+
import { Failure, Refusal } from "./outcomes.js";
|
|
8
|
+
import { hostOf, withoutFragment } from "./urls.js";
|
|
9
|
+
export function createSession(options) {
|
|
10
|
+
/** What requesting each URL gave, by URL without its fragment: each URL is requested once per scan (§17 Caching). */
|
|
11
|
+
const hops = new Map();
|
|
12
|
+
/** The bytes of every response body read so far, decompressed (§17 Size). */
|
|
13
|
+
let scanned = 0;
|
|
14
|
+
/** How long the scan's requests have taken so far, in all (§17 Time). */
|
|
15
|
+
let spent = 0;
|
|
16
|
+
/** The remote URLs the scan has fetched, without their fragments (§17 Count). A redirect's target isn't one. */
|
|
17
|
+
const fetched = new Set();
|
|
18
|
+
function hop(url) {
|
|
19
|
+
let found = hops.get(url.href);
|
|
20
|
+
if (found === undefined)
|
|
21
|
+
hops.set(url.href, (found = request(url)));
|
|
22
|
+
return found;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Requests `url` once: its connection may take the connect limit, and the whole request the request limit, or what
|
|
26
|
+
* is left of the scan's (§17 Time). Once the scan's is spent, nothing more is requested.
|
|
27
|
+
*/
|
|
28
|
+
async function request(url) {
|
|
29
|
+
const { connectMs, requestMs, scanMs, responseBytes, scanBytes } = options.limits;
|
|
30
|
+
const left = scanMs - spent;
|
|
31
|
+
const outOfTime = () => new Failure(`the scan's fetches took longer than the safety floor's ${scanMs} ms in all`);
|
|
32
|
+
if (left <= 0)
|
|
33
|
+
return failed(outOfTime());
|
|
34
|
+
const sockets = new Sockets();
|
|
35
|
+
const start = options.clock();
|
|
36
|
+
try {
|
|
37
|
+
const connecting = open(url, options, sockets);
|
|
38
|
+
const connection = await within(connecting, Math.min(connectMs, left), () => connectMs <= left
|
|
39
|
+
? new Failure(`connecting to ${hostOf(url)} took longer than the safety floor's ${connectMs} ms`)
|
|
40
|
+
: outOfTime());
|
|
41
|
+
const exchanging = exchange(url, connection, options, { responseBytes, scanBytes, scanned });
|
|
42
|
+
const answer = await within(exchanging, Math.min(requestMs, left) - (options.clock() - start), () => requestMs <= left
|
|
43
|
+
? new Failure(`the request for ${url.href} took longer than the safety floor's ${requestMs} ms`)
|
|
44
|
+
: outOfTime());
|
|
45
|
+
scanned += answer.bytes?.length ?? 0;
|
|
46
|
+
return { kind: 'response', ...answer };
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof Refusal)
|
|
50
|
+
return { kind: 'refused', detail: error.message, response: error.response };
|
|
51
|
+
if (error instanceof Failure)
|
|
52
|
+
return failed(error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
sockets.close();
|
|
57
|
+
spent += options.clock() - start;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
/**
|
|
62
|
+
* Fetches `requested` as `request` asks for it, following redirects. Every hop is checked against the whole floor
|
|
63
|
+
* (§17 Redirects): its scheme here, its address when it is connected to. A fragment is never requested. The bytes
|
|
64
|
+
* are checked against the request's integrity value, if any (§17 Integrity).
|
|
65
|
+
*/
|
|
66
|
+
async fetch(requested, { destination, document, integrity = '' }) {
|
|
67
|
+
let url = withoutFragment(new URL(requested));
|
|
68
|
+
const scheme = schemeRefusal(url, document);
|
|
69
|
+
if (scheme !== null)
|
|
70
|
+
return refused(scheme);
|
|
71
|
+
const { artifacts } = options.limits;
|
|
72
|
+
if (!fetched.has(url.href)) {
|
|
73
|
+
if (fetched.size >= artifacts)
|
|
74
|
+
return refused(`the safety floor fetches at most ${artifacts} remote artifacts`);
|
|
75
|
+
fetched.add(url.href);
|
|
76
|
+
}
|
|
77
|
+
// There is no separate loop check: a loop is refused when it reaches the sixth redirect, as any chain is. Each
|
|
78
|
+
// URL in it is still requested once, since a hop's outcome is cached.
|
|
79
|
+
for (let redirects = 0;; redirects++) {
|
|
80
|
+
const outcome = await hop(url);
|
|
81
|
+
if (outcome.kind === 'failed')
|
|
82
|
+
return failure(outcome.detail);
|
|
83
|
+
if (outcome.kind === 'refused')
|
|
84
|
+
return refused(outcome.detail, outcome.response);
|
|
85
|
+
const { response, status, location, nosniff, bytes } = outcome;
|
|
86
|
+
if (redirectStatuses.has(status) && location !== null) {
|
|
87
|
+
const next = URL.parse(location, url.href);
|
|
88
|
+
if (next === null)
|
|
89
|
+
return failure(`a redirect to ${location}, which isn't a URL`, response);
|
|
90
|
+
url = withoutFragment(next);
|
|
91
|
+
if (redirects === options.limits.redirects) {
|
|
92
|
+
return refused(`the safety floor follows at most ${options.limits.redirects} redirects`);
|
|
93
|
+
}
|
|
94
|
+
const hopScheme = schemeRefusal(url, document);
|
|
95
|
+
if (hopScheme !== null)
|
|
96
|
+
return refused(`${hopScheme}, in a redirect to ${url.href}`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (status < 200 || status > 299 || bytes === null)
|
|
100
|
+
return failure(`HTTP ${status}`, response);
|
|
101
|
+
// The body was read whatever the destination, so a URL fetched as another is still requested once. The checks
|
|
102
|
+
// run in the browser's order (§17 Content types): blocked types and `nosniff`, integrity, then the JavaScript
|
|
103
|
+
// MIME type that modules and workers need.
|
|
104
|
+
const blocked = blockedTypeRefusal(destination, response.contentType, nosniff);
|
|
105
|
+
if (blocked !== null)
|
|
106
|
+
return refused(blocked, response);
|
|
107
|
+
const checked = verified(bytes, response, integrity);
|
|
108
|
+
if (checked.status !== 'ok')
|
|
109
|
+
return checked;
|
|
110
|
+
const type = javascriptTypeRefusal(destination, response.contentType);
|
|
111
|
+
return type === null ? checked : refused(type, response, checked.sri);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function failed(error) {
|
|
117
|
+
return { kind: 'failed', detail: error.message };
|
|
118
|
+
}
|
|
119
|
+
/** The statuses a browser follows a `Location` for (Fetch § HTTP-redirect fetch). */
|
|
120
|
+
const redirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
121
|
+
function refused(detail, response = null, sri = 'none') {
|
|
122
|
+
return { status: 'remote-refused', detail, response, sri };
|
|
123
|
+
}
|
|
124
|
+
function failure(detail, response = null) {
|
|
125
|
+
return { status: 'fetch-failed', detail, response };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Why the floor refuses to fetch `url` for the entry document `document`, or null when it doesn't (§17 Schemes):
|
|
129
|
+
* `https:` is fetched for any document, and `http:` only for an `http:` one.
|
|
130
|
+
*/
|
|
131
|
+
function schemeRefusal(url, document) {
|
|
132
|
+
if (url.protocol === 'https:')
|
|
133
|
+
return null;
|
|
134
|
+
const from = new URL(document).protocol;
|
|
135
|
+
if (url.protocol === 'http:' && from === 'http:')
|
|
136
|
+
return null;
|
|
137
|
+
const allowed = from === 'http:' ? '`https:` and `http:`' : '`https:`';
|
|
138
|
+
return `the safety floor fetches only ${allowed} for an \`${from}\` document, not \`${url.protocol}\``;
|
|
139
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The URL's host, without the brackets of an IPv6 literal. */
|
|
2
|
+
export declare function hostOf(url: URL): string;
|
|
3
|
+
/** The URL's port, or its scheme's default. */
|
|
4
|
+
export declare function portOf(url: URL): number;
|
|
5
|
+
/** The URL without its fragment, which is never part of a request. */
|
|
6
|
+
export declare function withoutFragment(url: URL): URL;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The parts of a URL a request is made from.
|
|
2
|
+
/** The URL's host, without the brackets of an IPv6 literal. */
|
|
3
|
+
export function hostOf(url) {
|
|
4
|
+
return url.hostname.replace(/^\[(.*)\]$/, '$1');
|
|
5
|
+
}
|
|
6
|
+
/** The URL's port, or its scheme's default. */
|
|
7
|
+
export function portOf(url) {
|
|
8
|
+
return url.port === '' ? (url.protocol === 'https:' ? 443 : 80) : Number(url.port);
|
|
9
|
+
}
|
|
10
|
+
/** The URL without its fragment, which is never part of a request. */
|
|
11
|
+
export function withoutFragment(url) {
|
|
12
|
+
const copy = new URL(url);
|
|
13
|
+
copy.hash = '';
|
|
14
|
+
return copy;
|
|
15
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Duplex } from 'node:stream';
|
|
2
|
+
import type { Fetcher } from './index.ts';
|
|
3
|
+
/** The floor's limits (§17 Safety floor). The defaults are §17's; Release 2 makes them configurable (§22). */
|
|
4
|
+
export interface Limits {
|
|
5
|
+
/** The most redirects one fetch follows. */
|
|
6
|
+
redirects: number;
|
|
7
|
+
/** The most bytes one response may have, decompressed. */
|
|
8
|
+
responseBytes: number;
|
|
9
|
+
/** The most bytes all the responses of a scan may have, decompressed. */
|
|
10
|
+
scanBytes: number;
|
|
11
|
+
/** How long a connection may take to open: the lookup, the connect, and any proxy tunnel and TLS handshake. */
|
|
12
|
+
connectMs: number;
|
|
13
|
+
/** How long one request may take, from opening its connection to the end of its body. */
|
|
14
|
+
requestMs: number;
|
|
15
|
+
/** How long all the fetches of a scan may take together. */
|
|
16
|
+
scanMs: number;
|
|
17
|
+
/** The most remote artifacts a scan fetches. */
|
|
18
|
+
artifacts: number;
|
|
19
|
+
}
|
|
20
|
+
export declare const defaultLimits: Readonly<Limits>;
|
|
21
|
+
/** An address a host name resolves to. */
|
|
22
|
+
export interface ResolvedAddress {
|
|
23
|
+
address: string;
|
|
24
|
+
family: 4 | 6;
|
|
25
|
+
}
|
|
26
|
+
/** The DNS hook: every address a host name resolves to. */
|
|
27
|
+
export type Lookup = (hostname: string) => Promise<ResolvedAddress[]>;
|
|
28
|
+
/**
|
|
29
|
+
* The connect hook: opens a TCP connection to an address the floor has checked, resolving once it is connected. When
|
|
30
|
+
* `signal` aborts first, because a time limit ran out, it stops connecting and rejects.
|
|
31
|
+
*/
|
|
32
|
+
export type Connect = (target: ResolvedAddress & {
|
|
33
|
+
port: number;
|
|
34
|
+
signal: AbortSignal;
|
|
35
|
+
}) => Promise<Duplex>;
|
|
36
|
+
export interface ResolveFetcherOptions {
|
|
37
|
+
/** monkeypatchlist's version, which the `User-Agent` names. */
|
|
38
|
+
version: string;
|
|
39
|
+
/** The environment, which `HTTPS_PROXY` and `NO_PROXY` are read from. */
|
|
40
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
41
|
+
/** The DNS hook. The system resolver by default. */
|
|
42
|
+
lookup?: Lookup;
|
|
43
|
+
/** The connect hook. A `net` socket by default. */
|
|
44
|
+
connect?: Connect;
|
|
45
|
+
/**
|
|
46
|
+
* Certificate authorities to trust instead of Node's, for tests. TLS is verified whatever this is: nothing turns
|
|
47
|
+
* verification off.
|
|
48
|
+
*/
|
|
49
|
+
ca?: string | string[];
|
|
50
|
+
/** The floor's limits; §17's by default. */
|
|
51
|
+
limits?: Partial<Limits>;
|
|
52
|
+
/** A monotonic clock in milliseconds, for the scan-wide time limit. */
|
|
53
|
+
clock?: () => number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A resolve fetcher for one scan. Each URL is requested at most once however often it is asked for, and the scan-wide
|
|
57
|
+
* limits (bytes, time and artifacts) count everything it fetches. Nothing it holds outlives it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveFetcher(options: ResolveFetcherOptions): Fetcher;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// The resolve fetcher (§17, §19 Source store and retrieval): fetches remote URLs under §17's fixed safety floor, which
|
|
2
|
+
// it enforces entirely inside itself, and checks their bytes against the integrity value each request carries (§17
|
|
3
|
+
// Integrity). It is made for one scan, and caches nothing beyond it.
|
|
4
|
+
import { lookup as systemLookup } from 'node:dns/promises';
|
|
5
|
+
import { connect as netConnect } from 'node:net';
|
|
6
|
+
import { createSession } from "./lib/session.js";
|
|
7
|
+
export const defaultLimits = {
|
|
8
|
+
redirects: 5,
|
|
9
|
+
responseBytes: 20 * 1024 * 1024,
|
|
10
|
+
scanBytes: 100 * 1024 * 1024,
|
|
11
|
+
connectMs: 10_000,
|
|
12
|
+
requestMs: 30_000,
|
|
13
|
+
scanMs: 5 * 60_000,
|
|
14
|
+
artifacts: 500,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* A resolve fetcher for one scan. Each URL is requested at most once however often it is asked for, and the scan-wide
|
|
18
|
+
* limits (bytes, time and artifacts) count everything it fetches. Nothing it holds outlives it.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveFetcher(options) {
|
|
21
|
+
return createSession({
|
|
22
|
+
version: options.version,
|
|
23
|
+
env: options.env,
|
|
24
|
+
lookup: options.lookup ?? systemLookupAll,
|
|
25
|
+
connect: options.connect ?? tcpConnect,
|
|
26
|
+
ca: options.ca ?? null,
|
|
27
|
+
limits: { ...defaultLimits, ...options.limits },
|
|
28
|
+
clock: options.clock ?? (() => performance.now()),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async function systemLookupAll(hostname) {
|
|
32
|
+
const addresses = await systemLookup(hostname, { all: true, order: 'verbatim' });
|
|
33
|
+
return addresses.map(({ address, family }) => ({ address, family: family === 6 ? 6 : 4 }));
|
|
34
|
+
}
|
|
35
|
+
function tcpConnect({ address, port, signal }) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
// An abort destroys the socket, which then rejects, however far it has got.
|
|
38
|
+
const socket = netConnect({ host: address, port, signal });
|
|
39
|
+
socket.once('connect', () => {
|
|
40
|
+
socket.off('error', reject);
|
|
41
|
+
resolve(socket);
|
|
42
|
+
});
|
|
43
|
+
socket.once('error', reject);
|
|
44
|
+
});
|
|
45
|
+
}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type CatalogChangelog, type SerializedCatalog } from './catalog.ts';
|
|
2
|
+
import { type Budget, type Resolver } from './packages/analysis/index.ts';
|
|
3
|
+
import { type LockfileInput } from './packages/attribution/index.ts';
|
|
4
|
+
import type { Baseline, BaselineNote, PreviousValues } from './packages/baseline/index.ts';
|
|
5
|
+
import type { ScanConfig } from './packages/config/index.ts';
|
|
6
|
+
import { type ResolvedReference } from './packages/discovery/index.ts';
|
|
7
|
+
import { type GroupRunner } from './packages/parallel/index.ts';
|
|
8
|
+
import type { ReportInput } from './packages/report/index.ts';
|
|
9
|
+
import { type SourceStore } from './packages/source-store/index.ts';
|
|
10
|
+
export interface ScanDeps {
|
|
11
|
+
store: SourceStore;
|
|
12
|
+
catalog: SerializedCatalog;
|
|
13
|
+
/** The catalog changelog shipped with the catalog, which `explainedBy` comes from (§4 Catalog versions). */
|
|
14
|
+
changelog: CatalogChangelog;
|
|
15
|
+
resolver: Resolver;
|
|
16
|
+
monkeypatchlistVersion: string;
|
|
17
|
+
/**
|
|
18
|
+
* The baseline to compare with (§11), its path as given, and scan-level notes on it such as version skew (§4), or null
|
|
19
|
+
* for none. `previous` is the previous scan's values the reachability and install-context notes compare with (§11
|
|
20
|
+
* Never `changed`): `--previous`'s, or the baseline's when it is a full report, or null for none.
|
|
21
|
+
*/
|
|
22
|
+
baseline: {
|
|
23
|
+
path: string;
|
|
24
|
+
baseline: Baseline;
|
|
25
|
+
notes: BaselineNote[];
|
|
26
|
+
previous: PreviousValues | null;
|
|
27
|
+
} | null;
|
|
28
|
+
crashGuard: CrashGuard;
|
|
29
|
+
/**
|
|
30
|
+
* The step budget per source byte of a link group, and the caps (§18). Implementation detail, never a user flag: the
|
|
31
|
+
* CLI passes the shipped constants, and the cost benchmark passes them multiplied for its raised-budget rerun.
|
|
32
|
+
*/
|
|
33
|
+
budget: Budget;
|
|
34
|
+
/**
|
|
35
|
+
* What runs the link groups (§18 Parallelism): by default the resolver in process, one group after another. Results
|
|
36
|
+
* merge in group order whatever runs them, so a parallel runner prints the same report.
|
|
37
|
+
*/
|
|
38
|
+
groups?: GroupRunner;
|
|
39
|
+
/**
|
|
40
|
+
* The lockfile `--lockfile` names, read by the CLI (§10 Introducing dependencies), or null when none is given. It only
|
|
41
|
+
* adds evidence: introducers, lockfile versions and workspaces (ADR 0006).
|
|
42
|
+
*/
|
|
43
|
+
lockfile?: LockfileInput | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The wall-clock crash guard (§13, §18). The CLI reads the clock for it, so the pipeline stays free of wall-clock time.
|
|
47
|
+
* It is asked before each link group, never inside one: once it has fired, no further group runs, and the scan is
|
|
48
|
+
* `incomplete`. It never changes results: a scan whose guard fired reports no findings, whichever group it fired
|
|
49
|
+
* before, so every baseline entry is `unconfirmed`.
|
|
50
|
+
*/
|
|
51
|
+
export interface CrashGuard {
|
|
52
|
+
fired(): boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Runs a scan, and returns everything the report needs except the `run` block.
|
|
56
|
+
*
|
|
57
|
+
* The scan runs in rounds (§19 References only the resolver can see): discover, analyze the link groups, take the
|
|
58
|
+
* references only the resolver can see, and discover again with them, until a round's groups find no reference the
|
|
59
|
+
* round didn't start from. Each round discovers and analyzes the whole artifact graph its references give, so the
|
|
60
|
+
* result is the last round's: analyzing, over the final artifact set, every referenced entry's group, then each swept
|
|
61
|
+
* artifact no group links, whatever rounds led there (The result doesn't depend on the rounds). A group whose job is
|
|
62
|
+
* the same as in an earlier round isn't analyzed again: the resolver is a pure function of its job. `seed` gives the
|
|
63
|
+
* first round's references.
|
|
64
|
+
*/
|
|
65
|
+
export declare function scan(config: ScanConfig, deps: ScanDeps, seed?: readonly ResolvedReference[]): Promise<Omit<ReportInput, 'run'>>;
|