@4da/mcp-server 6.2.3 → 6.2.5
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/CHANGELOG.md +520 -0
- package/README.md +22 -6
- package/dist/db.d.ts +38 -0
- package/dist/db.js +78 -23
- package/dist/doctor.js +6 -3
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.js +16 -14
- package/dist/index.js +73 -103
- package/dist/live/advisory-groups.d.ts +68 -0
- package/dist/live/advisory-groups.js +131 -0
- package/dist/live/cache.js +11 -3
- package/dist/live/cargo-platform.d.ts +6 -0
- package/dist/live/cargo-platform.js +8 -2
- package/dist/live/changelog.js +38 -6
- package/dist/live/dependency-set.d.ts +9 -1
- package/dist/live/dependency-set.js +10 -2
- package/dist/live/fix-paths.d.ts +289 -16
- package/dist/live/fix-paths.js +876 -93
- package/dist/live/github-releases.js +3 -0
- package/dist/live/held-dependencies.d.ts +93 -0
- package/dist/live/held-dependencies.js +212 -0
- package/dist/live/held-targets.d.ts +30 -0
- package/dist/live/held-targets.js +107 -0
- package/dist/live/http-utils.js +13 -3
- package/dist/live/index.d.ts +7 -4
- package/dist/live/index.js +24 -19
- package/dist/live/install-state.js +6 -2
- package/dist/live/js-lockfile-readers.d.ts +8 -1
- package/dist/live/js-lockfile-readers.js +43 -6
- package/dist/live/lockfile-parents.d.ts +15 -0
- package/dist/live/lockfile-parents.js +85 -10
- package/dist/live/lockfile-parsers-pygo.d.ts +47 -2
- package/dist/live/lockfile-parsers-pygo.js +230 -12
- package/dist/live/lockfile-parsers-rbphp.d.ts +38 -0
- package/dist/live/lockfile-parsers-rbphp.js +123 -0
- package/dist/live/lockfile-parsers.d.ts +6 -0
- package/dist/live/lockfile-parsers.js +116 -4
- package/dist/live/lockfile-types.d.ts +28 -1
- package/dist/live/maintenance.d.ts +20 -4
- package/dist/live/maintenance.js +31 -2
- package/dist/live/malware.d.ts +20 -0
- package/dist/live/malware.js +31 -0
- package/dist/live/npm-packument.d.ts +4 -1
- package/dist/live/npm-packument.js +5 -2
- package/dist/live/offered-release.d.ts +53 -0
- package/dist/live/offered-release.js +117 -0
- package/dist/live/osv-ranges.d.ts +111 -0
- package/dist/live/osv-ranges.js +173 -14
- package/dist/live/osv-scanner.d.ts +87 -5
- package/dist/live/osv-scanner.js +310 -59
- package/dist/live/pinned-advisories.d.ts +2 -2
- package/dist/live/pinned-advisories.js +3 -3
- package/dist/live/pypi-advice.d.ts +36 -0
- package/dist/live/pypi-advice.js +158 -0
- package/dist/live/range-satisfies.d.ts +4 -2
- package/dist/live/range-satisfies.js +7 -5
- package/dist/live/rate-limiter.d.ts +7 -0
- package/dist/live/rate-limiter.js +22 -1
- package/dist/live/refresh-rule.d.ts +36 -0
- package/dist/live/refresh-rule.js +49 -0
- package/dist/live/release-metadata.d.ts +5 -0
- package/dist/live/resolution.d.ts +3 -0
- package/dist/live/resolution.js +6 -3
- package/dist/live/scan-coverage.d.ts +26 -0
- package/dist/live/scan-coverage.js +70 -0
- package/dist/live/semver-utils.d.ts +8 -0
- package/dist/live/semver-utils.js +26 -3
- package/dist/live/severity-scope.d.ts +26 -1
- package/dist/live/severity-scope.js +32 -0
- package/dist/live/shell-safe.d.ts +19 -0
- package/dist/live/shell-safe.js +36 -0
- package/dist/live/types.d.ts +134 -8
- package/dist/live/version-compare.d.ts +51 -0
- package/dist/live/version-compare.js +152 -1
- package/dist/live/withdrawn-releases.d.ts +50 -0
- package/dist/live/withdrawn-releases.js +125 -0
- package/dist/project-manifests.d.ts +7 -0
- package/dist/project-manifests.js +45 -5
- package/dist/project-scanner.js +6 -4
- package/dist/project-tree.d.ts +56 -0
- package/dist/project-tree.js +174 -5
- package/dist/schema-registry.d.ts +47 -4
- package/dist/schema-registry.js +76 -9
- package/dist/server-instructions.js +4 -2
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +21 -2
- package/dist/tool-dispatch.js +3 -3
- package/dist/tools/agent-memory.d.ts +5 -0
- package/dist/tools/agent-memory.js +9 -0
- package/dist/tools/briefing-task-scope.js +5 -2
- package/dist/tools/decision-memory.js +2 -0
- package/dist/tools/dependency-check-held.d.ts +44 -0
- package/dist/tools/dependency-check-held.js +98 -0
- package/dist/tools/dependency-check-signals.d.ts +19 -2
- package/dist/tools/dependency-check-signals.js +84 -13
- package/dist/tools/dependency-check-versions.d.ts +37 -4
- package/dist/tools/dependency-check-versions.js +48 -13
- package/dist/tools/dependency-check.d.ts +11 -1
- package/dist/tools/dependency-check.js +81 -29
- package/dist/tools/dependency-health.js +209 -45
- package/dist/tools/get-context.d.ts +3 -0
- package/dist/tools/get-context.js +4 -0
- package/dist/tools/project-scope.d.ts +8 -0
- package/dist/tools/project-scope.js +21 -0
- package/dist/tools/upgrade-impact-features.d.ts +37 -0
- package/dist/tools/upgrade-impact-features.js +176 -0
- package/dist/tools/upgrade-impact-literals.js +26 -4
- package/dist/tools/upgrade-impact-report.d.ts +5 -1
- package/dist/tools/upgrade-impact-report.js +26 -5
- package/dist/tools/upgrade-impact.js +10 -3
- package/dist/tools/upgrade-planner-fixpaths.d.ts +164 -5
- package/dist/tools/upgrade-planner-fixpaths.js +0 -0
- package/dist/tools/upgrade-planner.d.ts +70 -7
- package/dist/tools/upgrade-planner.js +237 -61
- package/dist/tools/vulnerability-scan-concise.d.ts +47 -2
- package/dist/tools/vulnerability-scan-concise.js +37 -3
- package/dist/tools/vulnerability-scan-format.d.ts +126 -24
- package/dist/tools/vulnerability-scan-format.js +193 -26
- package/dist/tools/vulnerability-scan.d.ts +224 -63
- package/dist/tools/vulnerability-scan.js +50 -15
- package/dist/tools/what-should-i-know.d.ts +6 -1
- package/dist/tools/what-should-i-know.js +72 -15
- package/package.json +2 -2
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* One advisory, many records. OSV publishes the same issue as a GHSA record
|
|
4
|
+
* and as an ecosystem record (PYSEC, RUSTSEC, GO), each naming the other in
|
|
5
|
+
* `aliases`. Counting records counted one issue two or three times:
|
|
6
|
+
* - tokio 1.18.0 showed "6 advisories" for 3 (audit A9);
|
|
7
|
+
* - pygoat's django "96 known vulnerabilities" for 51 (readiness audit M1, 2026-10-16).
|
|
8
|
+
* Every count of advisories goes through here.
|
|
9
|
+
*/
|
|
10
|
+
import { isMaintenanceNotice } from "./maintenance.js";
|
|
11
|
+
import { isMalware } from "./malware.js";
|
|
12
|
+
const RANK = { unknown: 0, low: 1, medium: 2, high: 3, critical: 4 };
|
|
13
|
+
/**
|
|
14
|
+
* Group the records of ONE installed copy by shared ids. The grouping is transitive: a record
|
|
15
|
+
* naming ids of two groups joins them.
|
|
16
|
+
*/
|
|
17
|
+
export function groupAdvisories(entries) {
|
|
18
|
+
let groups = [];
|
|
19
|
+
for (const v of entries ?? []) {
|
|
20
|
+
const ids = [v.vulnId, ...(v.aliases ?? [])];
|
|
21
|
+
const hit = groups.filter((g) => ids.some((id) => g.ids.has(id)));
|
|
22
|
+
const merged = { ids: new Set(ids), best: v, members: [v] };
|
|
23
|
+
for (const g of hit) {
|
|
24
|
+
for (const id of g.ids)
|
|
25
|
+
merged.ids.add(id);
|
|
26
|
+
merged.members.push(...g.members);
|
|
27
|
+
if ((RANK[g.best.severity] ?? 0) > (RANK[merged.best.severity] ?? 0))
|
|
28
|
+
merged.best = g.best;
|
|
29
|
+
}
|
|
30
|
+
groups = [...groups.filter((g) => !hit.includes(g)), merged];
|
|
31
|
+
}
|
|
32
|
+
return groups;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* THE grade of one alias group, shared with the 4DA app (`fourda_depengine::severity`, vectors
|
|
36
|
+
* severity.jsonl sv-001..sv-019): the MOST severe OWN grade among the group's records; only when no
|
|
37
|
+
* record has one, the highest grade among their GHSA aliases that can be read (`aliasGrades`, null =
|
|
38
|
+
* held but ungraded), the smallest id on a tie. An unmaintained-package notice is never graded
|
|
39
|
+
* from an alias.
|
|
40
|
+
*/
|
|
41
|
+
export function gradeGroup(records, aliasGrades) {
|
|
42
|
+
let own = null;
|
|
43
|
+
for (const r of records) {
|
|
44
|
+
if (r.own === "unknown")
|
|
45
|
+
continue;
|
|
46
|
+
if (!own || RANK[r.own] > RANK[own.grade])
|
|
47
|
+
own = { id: r.id, grade: r.own };
|
|
48
|
+
}
|
|
49
|
+
let severity = null;
|
|
50
|
+
let inheritedFrom = null;
|
|
51
|
+
if (own) {
|
|
52
|
+
severity = own.grade;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const ids = new Set(records.map((r) => r.id));
|
|
56
|
+
const ghsas = [...new Set(records.filter((r) => !r.maintenance).flatMap((r) => r.aliases.filter((a) => /^GHSA-/i.test(a) && !ids.has(a))))].sort();
|
|
57
|
+
let best = null;
|
|
58
|
+
for (const id of ghsas) {
|
|
59
|
+
const g = aliasGrades.get(id);
|
|
60
|
+
if (!g || g === "unknown")
|
|
61
|
+
continue;
|
|
62
|
+
if (!best || RANK[g] > RANK[best.grade])
|
|
63
|
+
best = { id, grade: g };
|
|
64
|
+
}
|
|
65
|
+
if (best) {
|
|
66
|
+
severity = best.grade;
|
|
67
|
+
inheritedFrom = best.id;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// A malicious-package record (malware.ts) is critical (the 4DA lead, 2026-10-18; sv-010 read high).
|
|
71
|
+
const malicious = records.some((r) => isMalware(r.id, r.aliases, r.cweIds ?? [], r.summary ?? ""));
|
|
72
|
+
const urgency = malicious
|
|
73
|
+
? "critical"
|
|
74
|
+
: severity === "critical" || severity === "high" || severity === "medium"
|
|
75
|
+
? severity
|
|
76
|
+
: severity === "low"
|
|
77
|
+
? "watch"
|
|
78
|
+
: records.every((r) => r.maintenance)
|
|
79
|
+
? "watch"
|
|
80
|
+
: "medium";
|
|
81
|
+
return { severity, inheritedFrom, ownFrom: own?.id ?? null, urgency };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* One severity per advisory, in every tool (the rule shared with the 4DA app, gradeGroup): within
|
|
85
|
+
* one alias group of one installed copy, every record carries the group's grade. vulnerability_scan
|
|
86
|
+
* kept the "richest" record's grade while dependency_check, dependency_health and upgrade_planner
|
|
87
|
+
* kept the most severe, so CVE-2026-4277 (GHSA-pwjp-ccjc-ghwg rated LOW, PYSEC-2026-52 CVSS 9.8) was
|
|
88
|
+
* low in one tool and critical in the others (final release audit of 6.2.5, 2026-10-18). A record
|
|
89
|
+
* that does not carry the grade itself names where it came from (`severityFrom`: the group record,
|
|
90
|
+
* or the GHSA alias of an inherited grade). A grade a record took from its alias
|
|
91
|
+
* (osv-scanner.ts rateFromAliases) never overrides an own grade in its group.
|
|
92
|
+
*/
|
|
93
|
+
export function gradeAliasGroups(entries) {
|
|
94
|
+
const byCopy = new Map();
|
|
95
|
+
for (const v of entries) {
|
|
96
|
+
const key = `${v.ecosystem}\u0000${v.package}\u0000${v.currentVersion}\u0000${v.installDriftOf ?? ""}`;
|
|
97
|
+
byCopy.set(key, [...(byCopy.get(key) ?? []), v]);
|
|
98
|
+
}
|
|
99
|
+
const regraded = new Map();
|
|
100
|
+
for (const copy of byCopy.values()) {
|
|
101
|
+
for (const group of groupAdvisories(copy)) {
|
|
102
|
+
if (group.members.length < 2)
|
|
103
|
+
continue;
|
|
104
|
+
const inherited = (m) => Boolean(m.severityFrom) && m.severityFrom !== m.vulnId;
|
|
105
|
+
const aliasGrades = new Map();
|
|
106
|
+
for (const m of group.members)
|
|
107
|
+
if (inherited(m))
|
|
108
|
+
aliasGrades.set(m.severityFrom, m.severity);
|
|
109
|
+
const grade = gradeGroup(group.members.map((m) => ({ id: m.vulnId, own: inherited(m) ? "unknown" : m.severity, aliases: [...m.aliases, ...(inherited(m) ? [m.severityFrom] : [])], maintenance: isMaintenanceNotice(m) })), aliasGrades);
|
|
110
|
+
const severity = grade.severity ?? "unknown";
|
|
111
|
+
const from = grade.inheritedFrom ?? grade.ownFrom;
|
|
112
|
+
for (const m of group.members) {
|
|
113
|
+
if (isMaintenanceNotice(m) && grade.inheritedFrom)
|
|
114
|
+
continue;
|
|
115
|
+
if (m.severity === severity && (!inherited(m) || m.severityFrom === from))
|
|
116
|
+
continue;
|
|
117
|
+
const next = { ...m, severity };
|
|
118
|
+
if (from && from !== m.vulnId)
|
|
119
|
+
next.severityFrom = from;
|
|
120
|
+
else
|
|
121
|
+
delete next.severityFrom;
|
|
122
|
+
regraded.set(m, next);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return regraded.size === 0 ? [...entries] : entries.map((e) => regraded.get(e) ?? e);
|
|
127
|
+
}
|
|
128
|
+
/** Distinct advisories among the records of one installed copy. */
|
|
129
|
+
export function distinctAdvisoryCount(entries) {
|
|
130
|
+
return groupAdvisories(entries).length;
|
|
131
|
+
}
|
package/dist/live/cache.js
CHANGED
|
@@ -37,6 +37,14 @@ export function closeServerCacheDb() {
|
|
|
37
37
|
serverCache.close();
|
|
38
38
|
serverCache = null;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* A row fetched "in the future" was written under a clock that was ahead, or the clock has
|
|
42
|
+
* since gone back (an RTC reset). Its expires_at came from that clock too, so it could keep a
|
|
43
|
+
* year-old "no advisories" answer live: scan 0 vulnerable, dependency_check proceed,
|
|
44
|
+
* what_should_i_know safe_to_delegate (audit 6.2.5 B-3). Such a row is never an answer, fresh or
|
|
45
|
+
* stale, and is purged at start. Five minutes of skew between processes is tolerated.
|
|
46
|
+
*/
|
|
47
|
+
const SANE_FETCHED_AT = "fetched_at <= datetime('now', '+5 minutes')";
|
|
40
48
|
/**
|
|
41
49
|
* Registry, OSV and changelog responses with a TTL.
|
|
42
50
|
*
|
|
@@ -82,7 +90,7 @@ export class LiveCache {
|
|
|
82
90
|
const hit = this.memory.get(key);
|
|
83
91
|
return hit && this.live(hit) ? JSON.parse(hit.data) : null;
|
|
84
92
|
}
|
|
85
|
-
const row = this.db.prepare(
|
|
93
|
+
const row = this.db.prepare(`SELECT data FROM live_cache WHERE cache_key = ? AND expires_at > datetime('now') AND ${SANE_FETCHED_AT}`).get(key);
|
|
86
94
|
if (!row)
|
|
87
95
|
return null;
|
|
88
96
|
try {
|
|
@@ -97,7 +105,7 @@ export class LiveCache {
|
|
|
97
105
|
const hit = this.memory.get(key);
|
|
98
106
|
return hit ? { data: JSON.parse(hit.data), fetchedAt: hit.fetched_at } : null;
|
|
99
107
|
}
|
|
100
|
-
const row = this.db.prepare(
|
|
108
|
+
const row = this.db.prepare(`SELECT data, fetched_at FROM live_cache WHERE cache_key = ? AND ${SANE_FETCHED_AT}`).get(key);
|
|
101
109
|
if (!row)
|
|
102
110
|
return null;
|
|
103
111
|
try {
|
|
@@ -150,7 +158,7 @@ export class LiveCache {
|
|
|
150
158
|
purged++;
|
|
151
159
|
return purged;
|
|
152
160
|
}
|
|
153
|
-
const result = this.db.prepare(
|
|
161
|
+
const result = this.db.prepare(`DELETE FROM live_cache WHERE expires_at <= datetime('now') OR NOT (${SANE_FETCHED_AT})`).run();
|
|
154
162
|
return result.changes;
|
|
155
163
|
}
|
|
156
164
|
invalidateSource(source) {
|
|
@@ -22,6 +22,12 @@ export declare function activeCratesForHost(dir: string): Set<string> | null;
|
|
|
22
22
|
* untrusted input. `CARGO_TARGET_DIR` points at the same scratch directory so
|
|
23
23
|
* cargo's metadata writes never land in the user's tree either.
|
|
24
24
|
*
|
|
25
|
+
* The scratch directory lives in the server's per-user data directory, not
|
|
26
|
+
* the system temp directory. On a shared machine `/tmp` is writable by every
|
|
27
|
+
* user, so a `/tmp/.cargo/config.toml` or a pre-created
|
|
28
|
+
* `/tmp/4da-mcp-cargo-resolve/.cargo/config.toml` planted by another account
|
|
29
|
+
* could set `build.rustc-wrapper` and run code as this user (audit 6.2.5 C-2).
|
|
30
|
+
*
|
|
25
31
|
* Exported for tests.
|
|
26
32
|
*/
|
|
27
33
|
export declare function cargoTreeInvocation(dir: string): {
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
*/
|
|
43
43
|
import { execFileSync } from "node:child_process";
|
|
44
44
|
import * as fs from "node:fs";
|
|
45
|
-
import * as os from "node:os";
|
|
46
45
|
import * as path from "node:path";
|
|
46
|
+
import { serverDataDir } from "../db.js";
|
|
47
47
|
import { fileSignature } from "./file-signature.js";
|
|
48
48
|
/** Rust target triple for the current host, or null for a platform we do not map. */
|
|
49
49
|
export function hostTriple() {
|
|
@@ -133,10 +133,16 @@ function computeActiveCrates(dir) {
|
|
|
133
133
|
* untrusted input. `CARGO_TARGET_DIR` points at the same scratch directory so
|
|
134
134
|
* cargo's metadata writes never land in the user's tree either.
|
|
135
135
|
*
|
|
136
|
+
* The scratch directory lives in the server's per-user data directory, not
|
|
137
|
+
* the system temp directory. On a shared machine `/tmp` is writable by every
|
|
138
|
+
* user, so a `/tmp/.cargo/config.toml` or a pre-created
|
|
139
|
+
* `/tmp/4da-mcp-cargo-resolve/.cargo/config.toml` planted by another account
|
|
140
|
+
* could set `build.rustc-wrapper` and run code as this user (audit 6.2.5 C-2).
|
|
141
|
+
*
|
|
136
142
|
* Exported for tests.
|
|
137
143
|
*/
|
|
138
144
|
export function cargoTreeInvocation(dir) {
|
|
139
|
-
const scratch = path.join(
|
|
145
|
+
const scratch = path.join(serverDataDir(), "cargo-resolve");
|
|
140
146
|
return {
|
|
141
147
|
args: [
|
|
142
148
|
"tree",
|
package/dist/live/changelog.js
CHANGED
|
@@ -107,7 +107,36 @@ export function parseVersionHeading(raw) {
|
|
|
107
107
|
return null;
|
|
108
108
|
return { version, date: extractDate(rest) };
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
/**
|
|
111
|
+
* An ATX heading `## text ##` as [level, text], parsed by hand in linear time.
|
|
112
|
+
* The regex it replaces (`/^(#{1,6})\s+(.*?)\s*#*\s*$/`) was cubic on interior
|
|
113
|
+
* whitespace: a 1,000-space heading in a package's CHANGELOG took over 20 s on
|
|
114
|
+
* the event loop (audit 2026-10-12, B-10). Same result otherwise ("C#" reads "C").
|
|
115
|
+
*/
|
|
116
|
+
function parseAtx(line) {
|
|
117
|
+
const open = /^#{1,6}/.exec(line);
|
|
118
|
+
if (!open)
|
|
119
|
+
return null;
|
|
120
|
+
const level = open[0].length;
|
|
121
|
+
const after = line.slice(level);
|
|
122
|
+
const body = after.trimStart();
|
|
123
|
+
if (body.length === after.length)
|
|
124
|
+
return null; // "#tag" or "#######": not a heading
|
|
125
|
+
let end = body.length;
|
|
126
|
+
while (end > 0 && /\s/.test(body[end - 1]))
|
|
127
|
+
end--;
|
|
128
|
+
while (end > 0 && body[end - 1] === "#")
|
|
129
|
+
end--;
|
|
130
|
+
while (end > 0 && /\s/.test(body[end - 1]))
|
|
131
|
+
end--;
|
|
132
|
+
return [level, body.slice(0, end)];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Lines longer than this are cut before the line-shape regexes run, so every
|
|
136
|
+
* one of them works on bounded input and a hostile line costs milliseconds.
|
|
137
|
+
* No real heading, label or entry comes near it.
|
|
138
|
+
*/
|
|
139
|
+
const MAX_LINE = 4096;
|
|
111
140
|
/**
|
|
112
141
|
* A setext / reStructuredText underline. `=` and `-` at any length (Markdown);
|
|
113
142
|
* the other RST adornments need three, so a stray `**` is not a heading rule.
|
|
@@ -279,7 +308,10 @@ function adornmentLevel(ch) {
|
|
|
279
308
|
}
|
|
280
309
|
/** Parse a changelog into version sections, in document order (usually newest first). */
|
|
281
310
|
export function parseChangelog(text) {
|
|
282
|
-
const lines = text
|
|
311
|
+
const lines = text
|
|
312
|
+
.replace(/\r\n?/g, "\n")
|
|
313
|
+
.split("\n")
|
|
314
|
+
.map((l) => (l.length > MAX_LINE ? l.slice(0, MAX_LINE) : l));
|
|
283
315
|
const state = {
|
|
284
316
|
sections: [],
|
|
285
317
|
current: null,
|
|
@@ -318,14 +350,14 @@ export function parseChangelog(text) {
|
|
|
318
350
|
}
|
|
319
351
|
if (LINK_DEF.test(line))
|
|
320
352
|
continue;
|
|
321
|
-
const atx =
|
|
353
|
+
const atx = parseAtx(line);
|
|
322
354
|
if (atx) {
|
|
323
|
-
const
|
|
324
|
-
const
|
|
355
|
+
const [level, text] = atx;
|
|
356
|
+
const heading = parseVersionHeading(text);
|
|
325
357
|
if (heading)
|
|
326
358
|
openSection(state, heading, level);
|
|
327
359
|
else
|
|
328
|
-
subHeading(state,
|
|
360
|
+
subHeading(state, text, level);
|
|
329
361
|
continue;
|
|
330
362
|
}
|
|
331
363
|
const next = lines[i + 1];
|
|
@@ -10,4 +10,12 @@ import type { ResolvedDependency, VulnerabilityScanResult } from "./types.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function commonPathRoot(dirs: string[]): string | null;
|
|
12
12
|
export declare function dedupeDependencies(deps: ResolvedDependency[]): ResolvedDependency[];
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* A scan with no findings. `unchecked` dependencies (offline: none of them
|
|
15
|
+
* could be asked) are counted in `totalScanned` and reported as unchecked,
|
|
16
|
+
* never as clean.
|
|
17
|
+
*/
|
|
18
|
+
export declare function emptyVulnResult(projectPath: string, offline: boolean, unchecked?: {
|
|
19
|
+
count: number;
|
|
20
|
+
reason: string;
|
|
21
|
+
} | null): VulnerabilityScanResult;
|
|
@@ -77,12 +77,17 @@ function mergeInstallContext(existing, dep) {
|
|
|
77
77
|
existing.installedVersion = dep.installedVersion;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* A scan with no findings. `unchecked` dependencies (offline: none of them
|
|
82
|
+
* could be asked) are counted in `totalScanned` and reported as unchecked,
|
|
83
|
+
* never as clean.
|
|
84
|
+
*/
|
|
85
|
+
export function emptyVulnResult(projectPath, offline, unchecked = null) {
|
|
81
86
|
return {
|
|
82
87
|
scannedAt: new Date().toISOString(),
|
|
83
88
|
projectPath,
|
|
84
89
|
ecosystemsScanned: [],
|
|
85
|
-
totalScanned: 0,
|
|
90
|
+
totalScanned: unchecked?.count ?? 0,
|
|
86
91
|
totalVulnerable: 0,
|
|
87
92
|
platformInactiveVulnerable: 0,
|
|
88
93
|
bySeverity: { critical: 0, high: 0, medium: 0, low: 0, unknown: 0 },
|
|
@@ -91,5 +96,8 @@ export function emptyVulnResult(projectPath, offline) {
|
|
|
91
96
|
scanDurationMs: 0,
|
|
92
97
|
cached: false,
|
|
93
98
|
offline,
|
|
99
|
+
unchecked: unchecked?.count ?? 0,
|
|
100
|
+
uncheckedReason: unchecked?.reason ?? null,
|
|
101
|
+
staleAnswers: 0,
|
|
94
102
|
};
|
|
95
103
|
}
|