@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,525 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.2.5 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
### Pre-publish adversarial audit fixes (2026-10-26)
|
|
6
|
+
|
|
7
|
+
An independent six-track audit of the 6.2.5 candidate (oracle parity, fault
|
|
8
|
+
injection, security, honesty, install/release, cross-engine parity) found the
|
|
9
|
+
following. Each fix has a regression test that fails on the old code.
|
|
10
|
+
|
|
11
|
+
- **Suggested commands never carry text a repository controls.** `cargo add`
|
|
12
|
+
put a Cargo.toml `[target.'cfg(...)']` header inside `--target "..."` and a
|
|
13
|
+
renamed key after `--rename`, where a shell would expand it. Every command
|
|
14
|
+
builder now checks every value it splices; anything else gets words, not a command.
|
|
15
|
+
- **cargo runs from the server's own data directory**, not a fixed folder in
|
|
16
|
+
the shared temp directory where another account could plant a cargo config.
|
|
17
|
+
- **An unchecked fix target is never said to "resolve" anything.** When the
|
|
18
|
+
target check runs out of time the line says NOT CHECKED; an alias group's fix
|
|
19
|
+
is the highest any of its records names (netmask: 2.0.1, not 1.1.0).
|
|
20
|
+
- **A fix target is judged by what it installs.** A release that exact-pins (or
|
|
21
|
+
narrowly pins) an affected dependency is not offered: express 4.22.3, not
|
|
22
|
+
4.20.0, in vulnerability_scan, dependency_check and upgrade_planner.
|
|
23
|
+
- **One OSV record no longer blinds the scan.** An `affected[]` entry with no
|
|
24
|
+
`package` (EEF-CVE-2026-56812, npm phoenix) made every dependency NOT CHECKED.
|
|
25
|
+
- **Desktop mode reads a project the app does not track** from its own
|
|
26
|
+
lockfiles; the planner no longer answers "0 steps" for it.
|
|
27
|
+
- **Fail-closed, further:** an answer from an expired cache, a cache row
|
|
28
|
+
fetched "in the future", a malformed querybatch entry, a stalled response
|
|
29
|
+
body or a registry outage is never presented as a current, complete answer.
|
|
30
|
+
- **Malware** is critical in every tool, scan rows carry `malware`, and a copy
|
|
31
|
+
with a clean later release is an upgrade, not a removal.
|
|
32
|
+
- **Honesty:** unsound RustSec advisories are never hidden as notices; Maven /
|
|
33
|
+
Gradle / .NET tasks are never `safe_to_delegate`; a Maven-only scan has null
|
|
34
|
+
totals; a Go scan says the standard library is not checked; a flat
|
|
35
|
+
requirements.txt says its dependencies were not checked; aliases are counted
|
|
36
|
+
once; two-part versions (Django 4.2 -> 5.2) are majors; a clean stable release
|
|
37
|
+
comes before a prerelease fix; recalled memories are labelled untrusted data;
|
|
38
|
+
the privacy wording names private package names, HN search and opt-ins.
|
|
39
|
+
- **PyPI advice:** a compiled requirements file is re-compiled
|
|
40
|
+
(`--upgrade-package`), never hand-edited; pins of a fully pinned file are
|
|
41
|
+
re-resolved together.
|
|
42
|
+
- **Paths:** symlinked requirements files and Cargo workspace members outside
|
|
43
|
+
the project are not read; `[build-dependencies]` are normal dependencies.
|
|
44
|
+
- **Install:** `4da-mcp-setup` works on macOS and Linux (it was a no-op through
|
|
45
|
+
npm's symlinked bin); CI now installs the packed tarball on all three OSes;
|
|
46
|
+
the release verifies provenance on every run and pins npm.
|
|
47
|
+
- **Performance:** a large pnpm-lock.yaml is read in linear time (200k entries:
|
|
48
|
+
409 s -> 0.6 s).
|
|
49
|
+
|
|
50
|
+
### An incomplete scan is never a clean one (audit A1/B-1/B-2, critical)
|
|
51
|
+
|
|
52
|
+
6.2.4 turned an OSV.dev outage, a 503, a timeout or its own rate limit into a
|
|
53
|
+
clean report. Reproduced 2026-10-12 with an empty cache: the 4DA app's `src-tauri`
|
|
54
|
+
(10,568 dependencies) came back `total_vulnerable_packages: 0`,
|
|
55
|
+
`dependency_health` scored it 75/100 and `upgrade_planner` said
|
|
56
|
+
`vulnerabilityDataAvailable: true`; the only trace was `_meta.offline`.
|
|
57
|
+
|
|
58
|
+
- **Large trees are answered in full.** querybatch requests (1,000
|
|
59
|
+
dependencies each) go out as the query budget allows, waiting for it to
|
|
60
|
+
refill (60 s at most), instead of failing the whole scan past 10 batches.
|
|
61
|
+
The budget is 60 batches a minute. A failed batch is retried once. The same
|
|
62
|
+
tree, cold, now reports 11,203 dependencies and 431 vulnerable packages
|
|
63
|
+
(with `include_test_fixtures`; see below).
|
|
64
|
+
- **What OSV did not answer is counted, and leads the answer.** Dependencies
|
|
65
|
+
with no answer (and no earlier cached one) are `unchecked`, never clean.
|
|
66
|
+
`vulnerability_scan` then starts with `summary: "NOT CHECKED: OSV.dev
|
|
67
|
+
unreachable (...) — N of M dependencies have no vulnerability answer"`,
|
|
68
|
+
`coverage.unchecked_dependencies` counts them, and `total_vulnerable_packages`,
|
|
69
|
+
`by_severity` and `clean_dependencies` are null (findings in the checked part
|
|
70
|
+
are under `found_in_checked_dependencies`).
|
|
71
|
+
- `dependency_health` withholds `healthScore` and `vulnerableCount` (null) and
|
|
72
|
+
adds `vulnerabilityCoverage`; `upgrade_planner` sets
|
|
73
|
+
`vulnerabilityDataAvailable: false` and adds `vulnerabilityCoverage`. Both
|
|
74
|
+
summaries start with "NOT CHECKED". The tool descriptions and the server
|
|
75
|
+
instructions say an incomplete result is not a clean one.
|
|
76
|
+
|
|
77
|
+
### dependency_health says what each number counts
|
|
78
|
+
|
|
79
|
+
navcal read "Health: 19/100. 18 dependencies scanned, 26 vulnerable" (6.2.4
|
|
80
|
+
too): 18 was the direct runtime dependencies checked against their registry
|
|
81
|
+
(25 devDependencies left out), 26 the vulnerable packages among the 633
|
|
82
|
+
installed. The summary now names both populations ("18 direct dependencies
|
|
83
|
+
checked against their registries (25 devDependencies left out ...); 26
|
|
84
|
+
vulnerable packages in the full tree of 633 installed (1 direct, 25
|
|
85
|
+
transitive); 14 outdated (direct)"), the answer adds `treeDeps` and
|
|
86
|
+
`vulnerableDirectCount`, and the score states its penalties ("100 minus 55 for
|
|
87
|
+
vulnerabilities, 26 for outdated, 0 for deprecated") with `healthScoreBasis`.
|
|
88
|
+
`ecosystem_filter` now narrows the vulnerability counts too, and a project with
|
|
89
|
+
no direct dependencies reports 0, not 1.
|
|
90
|
+
|
|
91
|
+
### Coverage stated, not implied
|
|
92
|
+
|
|
93
|
+
- **Ruby and PHP are read** (audit A2): `Gemfile.lock` (RubyGems; git and path
|
|
94
|
+
gems are local code and are skipped, as the desktop app does) and
|
|
95
|
+
`composer.lock` (Packagist; `packages-dev` marked dev). Both are ratcheted at
|
|
96
|
+
1.0 in the conformance corpus (RubyGems 143/143 findings, Packagist 44/44).
|
|
97
|
+
- **Ecosystems not read are listed.** Maven (`pom.xml`), Gradle, .NET
|
|
98
|
+
(`*.csproj`, `packages.lock.json`, ...), Pub, Hex, SwiftPM, CocoaPods and a
|
|
99
|
+
`Gemfile` / `composer.json` with no lockfile are reported in
|
|
100
|
+
`coverage.not_covered`, in mixed repositories too. A project made only of
|
|
101
|
+
them is told "NOT COVERED: ... Nothing was checked" instead of "No manifest
|
|
102
|
+
files found ... Run this tool from your project root directory".
|
|
103
|
+
- **Test fixtures are not the project** (audit A3). Lockfiles under `tests/`,
|
|
104
|
+
`test/`, `fixtures/`, `testdata/`, `__fixtures__/`, `conformance/` and the like
|
|
105
|
+
are left out by default and counted in `coverage.not_scanned`;
|
|
106
|
+
`include_test_fixtures: true` scans them. The 4DA app's `src-tauri` is 905
|
|
107
|
+
dependencies of its own, not 10,568.
|
|
108
|
+
- **An unreadable lockfile is named** (audit B-9): merge-conflict markers,
|
|
109
|
+
invalid JSON or a damaged file is listed in `coverage.unreadable_lockfiles`
|
|
110
|
+
with the reason, instead of falling through to "no lockfile".
|
|
111
|
+
|
|
112
|
+
### Fix advice
|
|
113
|
+
|
|
114
|
+
- **No deprecated or yanked release as a fix target** (audit B-8): lodash
|
|
115
|
+
4.17.15 is sent to 4.18.1, not the deprecated 4.18.0 ("Bad release"), and
|
|
116
|
+
the note says why. See the readiness section below for what counts as
|
|
117
|
+
withdrawn.
|
|
118
|
+
- **`last_affected`-only advisories** get the first published release above the
|
|
119
|
+
installed version that no known advisory affects as a candidate, labelled as
|
|
120
|
+
such, instead of "no fix" (audit A6), in `vulnerability_scan` and
|
|
121
|
+
`upgrade_planner`.
|
|
122
|
+
- **A parent that will not move** (deprecated, or already at its latest
|
|
123
|
+
release) turns "waiting on upstream" into `override_or_replace`, with the
|
|
124
|
+
syntax for npm `overrides`, pnpm `pnpm.overrides`, yarn `resolutions`, or
|
|
125
|
+
Cargo `[patch]` (audit A4).
|
|
126
|
+
- npm `-security` placeholder packages (`fs@0.0.1-security`) are no longer
|
|
127
|
+
reported as malware (audit A5), as in the desktop app, under any advisory id
|
|
128
|
+
(see the readiness section below).
|
|
129
|
+
- `vulnerability_scan` includes direct devDependencies by default, labelled
|
|
130
|
+
`is_dev` and graded one level down, as the desktop app does (audit A8);
|
|
131
|
+
`include_dev: false` leaves out every dev-only copy (see the final audit
|
|
132
|
+
section below).
|
|
133
|
+
- `dependency_check` counts a vulnerability once across its alias records
|
|
134
|
+
(GHSA + RUSTSEC), keeping the most severe grade. tokio 1.18.0's "6
|
|
135
|
+
advisories" are 3, all moderate or low, so its `review` verdict was right
|
|
136
|
+
(audit A9).
|
|
137
|
+
|
|
138
|
+
### Found in the pre-release readiness audit (2026-10-16 to 10-18)
|
|
139
|
+
|
|
140
|
+
6.2.5 was held back after an adversarial audit of the release candidate found
|
|
141
|
+
5 high, 7 medium and 8 low findings, and each fix below was reviewed again
|
|
142
|
+
before it was accepted. Every rule shared with the desktop app is pinned by
|
|
143
|
+
the shared rule vectors, which both engines run.
|
|
144
|
+
|
|
145
|
+
**Fix targets and versions**
|
|
146
|
+
|
|
147
|
+
- **Packagist and RubyGems are ordered as Composer and Gem::Version order
|
|
148
|
+
them** (H2). Two-part OSV bounds such as `6.4` are padded, so targets stay on
|
|
149
|
+
the installed line: symfony/process 5.1.0 goes to 5.4.51 and rack 2.0.6 to
|
|
150
|
+
2.2.23, not 8.0.5 and 3.2.6. A two-part `fixed` bound no longer makes a
|
|
151
|
+
vulnerable version look clean, and a Packagist lower bound with no stability
|
|
152
|
+
suffix admits that release's prereleases (`>=8.0` admits 8.0.0-RC1, as
|
|
153
|
+
Composer reads it). An advisory's enumerated `versions` list is honoured too.
|
|
154
|
+
A RubyGems beta is never offered as a stable target.
|
|
155
|
+
- **A release its registry withdrew is never the target** (FX-H5, with the
|
|
156
|
+
app): crates.io and PyPI yanked releases, and npm deprecated releases when a
|
|
157
|
+
later, non-deprecated stable release exists on the same caret line. A
|
|
158
|
+
whole-major npm notice is not a withdrawal: glob 10.4.5 goes to 10.5.0, not
|
|
159
|
+
12.0.0. When every way out is withdrawn there is no target, `withheld` names
|
|
160
|
+
the withdrawn releases, and the answer says "No installable fix", never "No
|
|
161
|
+
fix published". The same rule applies to each advisory's own fix, in
|
|
162
|
+
vulnerability_scan, upgrade_planner, dependency_check and
|
|
163
|
+
what_should_i_know. If the registry cannot be read, the earlier answer stands
|
|
164
|
+
and the answer says the withdrawal check did not run.
|
|
165
|
+
- **A deprecated or yanked release is excluded even when OSV's per-package
|
|
166
|
+
query fails** (H5): lodash 4.17.20 goes to 4.18.1, labelled
|
|
167
|
+
`installed_advisories_only`.
|
|
168
|
+
|
|
169
|
+
**upgrade_planner**
|
|
170
|
+
|
|
171
|
+
- **A transitive pinned by a parent that has a newer release is
|
|
172
|
+
`upgrade_parent`, not "waiting on upstream"** (H1). `parentUpgrades` names
|
|
173
|
+
the smallest parent release whose own requirement admits a clean version,
|
|
174
|
+
and every declared dependency it arrives through (`levers`). That release is
|
|
175
|
+
clear of the parent's own advisories (one with no published fix is named
|
|
176
|
+
instead: "remains affected by ... (no fix published)"), and a dependency gets
|
|
177
|
+
the same release on every row: NodeGoat's express goes to 4.22.3, on its own row and as the
|
|
178
|
+
lever for path-to-regexp. When the parent's new requirement still admits the
|
|
179
|
+
installed copy, the step names the refresh to run after the install, because
|
|
180
|
+
npm keeps the copy (`refreshCommands`). A parent the lockfile cannot tie to a
|
|
181
|
+
declared dependency gets no command (`leverUnknown`) and is
|
|
182
|
+
`upgrade_parent_unverified`, not counted as fixed; a move across a major is
|
|
183
|
+
flagged `breaking`.
|
|
184
|
+
- **A direct bump is raised to the release that also clears the vulnerable
|
|
185
|
+
transitives it pins**, never onto a release its own advisories affect, and
|
|
186
|
+
across a major only when a row's only fix is across it (then `breaking`):
|
|
187
|
+
express 4.20.0 goes to 4.22.3.
|
|
188
|
+
- **Commands keep how a dependency is declared** (E8, with the app): an exact
|
|
189
|
+
npm pin gets `--save-exact`, and `^`/`~` are kept and quoted (`npm install
|
|
190
|
+
"x@~1.2.6"`, also pnpm, yarn 1 and yarn berry; Cargo `cargo add` with
|
|
191
|
+
`--dev`/`--build`/`--target`/`--rename`; Go `go get`). No command is named
|
|
192
|
+
for a spec it cannot carry (`>=`, `||`, tags, git, `workspace:`), for a yarn
|
|
193
|
+
workspace member, or when the declaration does not admit the installed copy.
|
|
194
|
+
Commands run in the directory that declares the dependency.
|
|
195
|
+
- **Dev-only vulnerable packages are planned by default**, labelled `isDev`,
|
|
196
|
+
and rows rank on the scan's scope-adjusted severity with runtime rows first
|
|
197
|
+
(H4). `include_dev: false` says what it left out.
|
|
198
|
+
- Advisories are counted once per alias group (M1). An advisory with only
|
|
199
|
+
`last_affected` below the target is reported as resolved by it (M2).
|
|
200
|
+
- **API:** a new `upgrade_parent` action; `parentUpgrades[]` with `levers[]`
|
|
201
|
+
(each with `commands`, `instructions` and `breaking`), `breaking`, `peer`,
|
|
202
|
+
`moreLevers`, `leverUnknown` and `refreshCommands`; direct rows can carry
|
|
203
|
+
`upgradeCommands` / `upgradeInstructions`.
|
|
204
|
+
|
|
205
|
+
**Severity**
|
|
206
|
+
|
|
207
|
+
- **One grade per advisory in every tool** (M6). An alias group no record of
|
|
208
|
+
which grades it takes the highest grade among all of its GHSA aliases: GO-2024-3333
|
|
209
|
+
is high, through GHSA-w32m-9786-jp63, in dependency_check,
|
|
210
|
+
vulnerability_scan, dependency_health and upgrade_planner alike.
|
|
211
|
+
`severity_from` names the alias the grade came from, and an alias that could
|
|
212
|
+
not be read within the 5 s budget or the cap is listed, never guessed. The
|
|
213
|
+
lookup uses the injected advisory source and makes no network call offline.
|
|
214
|
+
|
|
215
|
+
**Coverage and dependency_health**
|
|
216
|
+
|
|
217
|
+
- **Started inside a Maven, Gradle or fixtures-only project**, every tool says
|
|
218
|
+
NOT COVERED or "only test-fixture lockfiles" instead of "no manifest found"
|
|
219
|
+
(H3).
|
|
220
|
+
- dependency_health counts advisories, not OSV records, so its counts match
|
|
221
|
+
vulnerability_scan's (pygoat 187, not 348). It counts the same population in
|
|
222
|
+
its registry and vulnerable figures and says whether dev-only copies are
|
|
223
|
+
included (L2).
|
|
224
|
+
- **An unreadable lockfile withholds the health score**, with
|
|
225
|
+
`withheldReason` (M5). A refused requirements include, or a yarn.lock or
|
|
226
|
+
requirements entry over the length cap, is named under `refused_includes` /
|
|
227
|
+
`partial_lockfiles`. The score then covers the checked part only and says so
|
|
228
|
+
(`scoreCoverage`, "at least" counts) (M4, L5).
|
|
229
|
+
- `--hash` options no longer push a requirements entry past the length cap.
|
|
230
|
+
Hashed `pip-compile` pins with many wheels were dropped silently before.
|
|
231
|
+
- A `v`-prefixed version in an installed package.json is not install drift
|
|
232
|
+
(L1).
|
|
233
|
+
|
|
234
|
+
**dependency_check and the rest**
|
|
235
|
+
|
|
236
|
+
- **An unmaintained-package notice never moves the verdict** (L3). It is
|
|
237
|
+
listed in `maintenance_notices`, no longer counted in `affecting_to`, and no
|
|
238
|
+
longer turns `proceed` into `review`.
|
|
239
|
+
- **npm security holding placeholders** (`0.0.<n>-security`) are not installed
|
|
240
|
+
code under ANY advisory id (L4). crossenv 0.0.2-security under
|
|
241
|
+
GHSA-c2m4-w5hm-vqjw read as malware when only MAL- ids were held. A
|
|
242
|
+
publisher's own `1.0.0-security` prerelease stays affected.
|
|
243
|
+
- upgrade_impact reads every Cargo workspace member's features (M3).
|
|
244
|
+
- Fix paths read a parent's registry requirements without peerDependencies
|
|
245
|
+
(L7).
|
|
246
|
+
- In standalone mode, `resources/list` lists only the tools it serves, and the
|
|
247
|
+
startup line no longer claims working tools when the database is refused
|
|
248
|
+
(L6).
|
|
249
|
+
|
|
250
|
+
### Found in the final release audit (2026-10-18)
|
|
251
|
+
|
|
252
|
+
A last audit of the release candidate found 1 high, 5 medium and 7 low
|
|
253
|
+
findings; the 4DA app's review of the same rules added three more. Each fix
|
|
254
|
+
was checked live on NodeGoat, pygoat, xsv, navcal and victauri, and the rules
|
|
255
|
+
shared with the app are pinned by the shared vectors.
|
|
256
|
+
|
|
257
|
+
- **No tool offers a release an advisory affects** (H1). upgrade_planner sent
|
|
258
|
+
crossenv 0.0.2-security, npm's placeholder for removed malware, to 1.0.1, the
|
|
259
|
+
malware itself (GHSA-c2m4-w5hm-vqjw, last_affected 1.0.1); cross-env.js,
|
|
260
|
+
mongose, babelcli, d3.js, jquery.js, ffmepg, nodesass and nodefabric the
|
|
261
|
+
same. Every release a tool offers (the planner's newest-release target,
|
|
262
|
+
dependency_health's `latestSafeVersion`, what_should_i_know's `latest`) now
|
|
263
|
+
passes the advisory check dependency_check decides `proceed` by, and the
|
|
264
|
+
registry's withdrawals. A placeholder whose newer releases are all malware is
|
|
265
|
+
`remove_dependency` ("remove it"); otherwise the newest release that is
|
|
266
|
+
neither affected nor withdrawn is offered, or `hold_current` when none is.
|
|
267
|
+
dependency_health's `latestStableVersion` stays the registry's newest stable
|
|
268
|
+
release as published; when that release is affected or withdrawn,
|
|
269
|
+
`latestStableNote` says so and `latestSafeVersion` names the one offered.
|
|
270
|
+
- **No release brings in an advisory the installed version does not have**,
|
|
271
|
+
fixed or not (the 4DA lead's rule for both engines): not a fix target, a
|
|
272
|
+
parent or lever release, or a newest-release offer. An advisory with no
|
|
273
|
+
published fix is skipped only when the installed version already has it.
|
|
274
|
+
When nothing qualifies, no release is named and the answer says "every
|
|
275
|
+
fixing release brings in <ids>", naming every such advisory across all the
|
|
276
|
+
releases blocked this way, sorted, not only the smallest one's. A parent or
|
|
277
|
+
lever whose only admitting release carries such an advisory reports the
|
|
278
|
+
outcome `introduces` (shared vector pr-010), not "no release admits".
|
|
279
|
+
- **A maintenance notice never blocks a release** (both engines, the #994
|
|
280
|
+
review). An unmaintained or deprecated-package notice (the maintenance
|
|
281
|
+
predicate: OSV `informational`, or unmaintained wording) never makes a
|
|
282
|
+
release unclean, never leaves a row with no fix, and never produces
|
|
283
|
+
`introduces`. The copy's own fix-target walk and the clean-copy checks
|
|
284
|
+
behind parent moves and refreshes counted them before; a release affected
|
|
285
|
+
only by a notice is now the target.
|
|
286
|
+
- **A fix that lands on malware says so.** When every release a fix could land
|
|
287
|
+
on is flagged malicious, the row names no release and reads "<pkg> is
|
|
288
|
+
flagged malicious (<id>): remove or replace <pkg>", not "still affected by
|
|
289
|
+
MAL-x".
|
|
290
|
+
dependency_health adds `latestSafeVersion` and `latestStableNote` and
|
|
291
|
+
measures `versionsBehind` to the safe release; what_should_i_know's `latest`
|
|
292
|
+
adds `registry_latest` and `note`.
|
|
293
|
+
- **One clean-release rule everywhere, with the app** (#981). A malicious
|
|
294
|
+
advisory is never skipped:
|
|
295
|
+
a release it affects is never clean, never a target and never "newest", and
|
|
296
|
+
an installed copy it affects names no release ("... is flagged malicious:
|
|
297
|
+
remove or replace ..."). An advisory whose ranges cannot be read leaves the
|
|
298
|
+
decision undecided: no release, no command, and the reason. An advisory with
|
|
299
|
+
no published fix never blocks a move that fixes the rest, and the named
|
|
300
|
+
release says it "remains affected by ... (no fix published)". A parent's or
|
|
301
|
+
lever's newest release is the highest stable, non-withdrawn release no
|
|
302
|
+
malicious advisory affects, not its `latest` tag, which decides only the
|
|
303
|
+
"already its latest release" wording.
|
|
304
|
+
- **One malware predicate** (the 4DA lead): a MAL- id, a MAL- alias, CWE-506
|
|
305
|
+
in `database_specific.cwe_ids`, or GitHub's fixed advisory title ("Malicious
|
|
306
|
+
Package in ..." / "Malicious code in ..." at the start, after leading
|
|
307
|
+
whitespace; ids and CWEs trimmed, any case: the app's is_malware exactly); no
|
|
308
|
+
other summary wording. GitHub's GHSA-c2m4-w5hm-vqjw ("crossenv is malware") has no MAL- id
|
|
309
|
+
and is malware by CWE-506; GHSA-28f8-hqmc-7ph8 ("Malicious Package in
|
|
310
|
+
ember-power-timepicker") has neither, and is malware by its title. It decides the offered release, the clean-release rule, parent
|
|
311
|
+
standing and the placeholder note, and an installed malicious copy is
|
|
312
|
+
critical in every tool. Scan rows carry `malware`; the advisory cache key
|
|
313
|
+
moved so rows cached without it are not reused.
|
|
314
|
+
- **One grade per advisory, in every tool** (M2). vulnerability_scan kept the
|
|
315
|
+
grade of the record with the most detail; the other tools kept the most
|
|
316
|
+
severe. An alias group now takes the most severe own grade of its records,
|
|
317
|
+
each read by its curated label first and its CVSS band second (as the app
|
|
318
|
+
does: on the conformance corpus the label follows a CVSS v4 score neither
|
|
319
|
+
engine computes); a grade inherited from a GHSA alias applies only when no
|
|
320
|
+
record of the group has one. CVE-2026-4277 (GHSA-pwjp-ccjc-ghwg LOW,
|
|
321
|
+
PYSEC-2026-52 CVSS 9.8) is critical everywhere, and `severity_from` names the
|
|
322
|
+
record or alias the grade came from. dependency_health's
|
|
323
|
+
`vulnerabilitySummary` adds `unknown`, so it equals vulnerability_scan's
|
|
324
|
+
`advisory_by_severity`. upgrade_planner ranks by the scan's scope-adjusted
|
|
325
|
+
grade: a dev-only medium is low, an ungraded advisory medium, a MAL- record
|
|
326
|
+
high. The shared severity vectors run in the TypeScript runner.
|
|
327
|
+
- **devDependencies are declarations** (M3). The lever walk read only
|
|
328
|
+
dependencies, optionalDependencies and peerDependencies, so a dev-only
|
|
329
|
+
transitive had "no declared dependency above" it; navcal's @vitest/mocker
|
|
330
|
+
and tinypool now name vitest. A workspace member's own dependency
|
|
331
|
+
(packages/a devDependencies minimist ^1.2.5) is declared there, and its
|
|
332
|
+
command runs in the member's directory.
|
|
333
|
+
- **One release per dependency across all rows** (M4). NodeGoat named
|
|
334
|
+
express-session 1.18.1, 1.18.2 and 1.19.0 on three rows (1.18.1 still pins a
|
|
335
|
+
vulnerable on-headers); xsv crossbeam-channel 0.4.4 and 0.5.0. Each
|
|
336
|
+
dependency now gets the highest release its rows NEED (each lever's release,
|
|
337
|
+
and its own row's target when that row is a fix) that crosses no further
|
|
338
|
+
major and that its own advisories leave clean, on every row, with one
|
|
339
|
+
command; a freshness row follows that release and keeps the newest as
|
|
340
|
+
`latestVersion` (express-session 1.18.2 everywhere). The requirement,
|
|
341
|
+
`resolvesTo` and refresh are recomputed at that release. A release lifted
|
|
342
|
+
above its own smallest one records it (`smallest`); a lever through a
|
|
343
|
+
transitive parent records the parent's smallest admitting release (`floor`)
|
|
344
|
+
and the one it brings in (`brings`): NodeGoat's send copy reads "express
|
|
345
|
+
4.21.0 is the smallest express release that brings in a serve-static that
|
|
346
|
+
admits it, and express 4.22.3, the release express's own upgrade moves to,
|
|
347
|
+
also brings in serve-static 1.16.3".
|
|
348
|
+
- **A declared bump that drops the package from the path comes before an
|
|
349
|
+
override** (M5). xsv's memoffset 0.2.1 and lock_api 0.1.3 go away with
|
|
350
|
+
crossbeam-channel, which no longer depends on crossbeam-epoch or
|
|
351
|
+
parking_lot: the step says "goes away" and names the command, instead of
|
|
352
|
+
pointing `[patch.crates-io]` at a fork (`goesAway`).
|
|
353
|
+
- **`include_dev: false` means one thing** (M6): every dev-only copy (every
|
|
354
|
+
path to it is a dev one) is left out, in vulnerability_scan,
|
|
355
|
+
dependency_health and upgrade_planner, and each states the same population
|
|
356
|
+
("N dev-only copies left out"; the scan's `dev_scope`). NodeGoat: 34
|
|
357
|
+
vulnerable packages in all three, where health said 82 and the planner
|
|
358
|
+
dropped 90 copies.
|
|
359
|
+
- Smaller fixes: with OSV's per-package query and the registry both down, the
|
|
360
|
+
target says the withdrawal check did not run (7a); a lifted parent release
|
|
361
|
+
whose requirement no longer admits the installed copy names no refresh, and
|
|
362
|
+
none is named while another parent keeps the copy below the target (7b); a
|
|
363
|
+
parent upgrade whose levers name no release is `upgrade_parent_unverified`,
|
|
364
|
+
not "fixed by upgrading the parent" (7c); "admits" is said only of a
|
|
365
|
+
requirement that admits the target (7d); Go versions keep their `v` in
|
|
366
|
+
every target field (7e); a target with a whole-line npm deprecation notice
|
|
367
|
+
(glob 10.5.0) stays the target and says it is deprecated, as
|
|
368
|
+
dependency_check's `review` does (7f); a pin in a file included with `-r` is
|
|
369
|
+
declared, and its edit names that file (7g).
|
|
370
|
+
- Parent and lever candidates follow the fix-target withdrawal rule (8a), and a
|
|
371
|
+
parent stands as the app's does: deprecated (its latest release is), then
|
|
372
|
+
already its latest release (the `latest` tag), then the walk. A vulnerable
|
|
373
|
+
row with no fix names no release.
|
|
374
|
+
- **A declaration that already admits the fix is refreshed, not bumped** (with
|
|
375
|
+
the app). navcal's sharp, next and vite rows named `npm install "pkg@^x"` for
|
|
376
|
+
a `^` range that already admitted the target. The step is now the tool's
|
|
377
|
+
in-range refresh, each run live with the manifest left unchanged: `npm update
|
|
378
|
+
<pkg>`, `pnpm update <pkg> --no-save` (plain `pnpm update` rewrote the
|
|
379
|
+
range), `yarn upgrade <pkg>`, `yarn up -R <pkg>`, `cargo update -p
|
|
380
|
+
<crate>@<installed> --precise <target>`. Only an npm range bounded below the
|
|
381
|
+
next major (`^`, `~`, also on a prerelease base such as `^2.0.0-rc.4`) is
|
|
382
|
+
refreshed: a refresh installs the newest release in
|
|
383
|
+
range, so a `>=`, `||`, `*` or x-range could jump a major, and names nothing
|
|
384
|
+
(shared vector fc-059). A Cargo requirement is refreshed with `--precise`,
|
|
385
|
+
which pins the exact target. A row of refreshes reads "Fixable now with a lockfile
|
|
386
|
+
refresh (the declared range already admits the fix)"; a refresh among other
|
|
387
|
+
commands is labelled. An npm workspace member is refreshed in its own
|
|
388
|
+
directory, and a workspace-inherited, git, path or registry crate is never
|
|
389
|
+
refreshed this way.
|
|
390
|
+
- The shared rule vectors `severity` (sv-001..sv-022) and `parent_release`
|
|
391
|
+
(pr-001..pr-012, with the graph lines run through the whole planner, and
|
|
392
|
+
Cargo lines) run in the TypeScript runner.
|
|
393
|
+
- The conformance pin moves to 4DA 9450d1613 (#972, #976, #981, #986, #992,
|
|
394
|
+
#993, #994, #998, #1008, #1025): every shared vector line (435, nine files)
|
|
395
|
+
and the corpus suite pass at it. #1008's new lines hold the Rust engine to this
|
|
396
|
+
server's rules: a malware landing is named as malware (ft-070, ft-072), a
|
|
397
|
+
maintenance notice never blocks a target and is never one, even at its own
|
|
398
|
+
fixed bound (ft-071, ft-073, ring RUSTSEC-2025-0010), and a notice with a
|
|
399
|
+
published fix is still not graded from an alias (sv-023).
|
|
400
|
+
- **An unmaintained-crate notice is a notice wherever RustSec puts the flag.**
|
|
401
|
+
RustSec writes `informational` on each `affected[]` entry, never on the
|
|
402
|
+
record, so RUSTSEC-2018-0017 (`tempdir` crate has been deprecated), net2 and
|
|
403
|
+
structopt were graded vulnerabilities. A record is now a notice for a package
|
|
404
|
+
when its summary says unmaintained, or `informational` is `unmaintained` or
|
|
405
|
+
`notice` on the record or on that package's affected entry; `unsound` stays
|
|
406
|
+
a vulnerability (smallvec). The same predicate as the 4DA app (#1025),
|
|
407
|
+
pinned by ft-074, ft-075, sv-024 and sv-025.
|
|
408
|
+
- **A row's only fix across a major raises the row**, flagged `breaking`,
|
|
409
|
+
with the reason "the only release of <parent> that fixes <child> is <ver>, a
|
|
410
|
+
major upgrade" (the 4DA lead): xsv's crossbeam-channel row goes to 0.5.0,
|
|
411
|
+
the only release whose requirement admits a clean crossbeam-utils.
|
|
412
|
+
- **Known difference from the desktop app:** the app's live path raises a
|
|
413
|
+
row across a major only for npm. For Cargo the app holds the rule in its
|
|
414
|
+
shared vector (pr-009) but not yet on live data, because a live Cargo raise
|
|
415
|
+
needs per-version dependency data it does not read yet. Until that lands,
|
|
416
|
+
this server and the app can give different answers for a live Cargo row
|
|
417
|
+
whose only fix is across a major.
|
|
418
|
+
- **A lever that cannot move no longer hides its parent's release** (with the
|
|
419
|
+
app, shared vector pr-011). When a parent arrives through a declared
|
|
420
|
+
dependency that names no release (it is flagged malicious, every release
|
|
421
|
+
that would bring the fix in brings in an advisory it lacks, none does, or it
|
|
422
|
+
is at its latest), the parent's release is still named and that lever
|
|
423
|
+
carries `levers[].outcome` with its own reason ("no <lever> release is
|
|
424
|
+
named: ...", "replace <lever>"). The step stays `override_or_replace`,
|
|
425
|
+
with the override that forces the copy.
|
|
426
|
+
- **API:** new actions `upgrade_parent_unverified`, `remove_dependency` and
|
|
427
|
+
`hold_current`; `parentUpgrades[].goesAway` and `smallest`;
|
|
428
|
+
`levers[].smallest`, `floor`, `brings` and `outcome`; `upgradeCommands[].refresh` and
|
|
429
|
+
`levers[].commands[].refresh`; local lockfile edges carry `dir` and
|
|
430
|
+
`section`.
|
|
431
|
+
|
|
432
|
+
### Security: the working directory is never trusted (audit B-3/B-4/B-5/B-11)
|
|
433
|
+
|
|
434
|
+
- A `data/settings.json` or `data/4da.db` in (or beside) the directory the
|
|
435
|
+
server starts in is no longer read. A cloned repository could use them to
|
|
436
|
+
send stored memory and decisions to a host of its choosing, or to plant
|
|
437
|
+
"decisions". Settings come from `FOURDA_SETTINGS_PATH`, the `settings.json`
|
|
438
|
+
beside `FOURDA_DB_PATH`, or the desktop app's data directory; the database
|
|
439
|
+
from `FOURDA_DB_PATH`, the desktop app's data directory, or the server's own.
|
|
440
|
+
- A RELATIVE `FOURDA_DB_PATH` that names no file is refused with a clear error
|
|
441
|
+
instead of silently creating a database inside whatever directory the server
|
|
442
|
+
started in (`./data/4da.db`). An existing file still opens through a relative
|
|
443
|
+
path (4DA's own `.mcp.json` keeps working). An ABSOLUTE path that names no
|
|
444
|
+
file is created as a standalone database when its directory exists, as
|
|
445
|
+
before, and now says so: one stderr line and `_meta.database_note` in the
|
|
446
|
+
first tool answer; its directory missing is refused. The server used to pass
|
|
447
|
+
`FOURDA_DB_PATH` to the database as an explicit path, which skipped these
|
|
448
|
+
rules.
|
|
449
|
+
- Requirements `-r` / `-c` includes stay inside the project: absolute, UNC,
|
|
450
|
+
drive-letter, or outside (also through a symlink) targets are not read.
|
|
451
|
+
|
|
452
|
+
### Robustness
|
|
453
|
+
|
|
454
|
+
- Hostile input no longer blocks the server (audit B-10): changelog headings and
|
|
455
|
+
yarn.lock version and dependency lines are parsed in linear time, and lines
|
|
456
|
+
over 4 KB are skipped (requirements files too). A crafted CHANGELOG or lockfile
|
|
457
|
+
line took 5–20 s or more.
|
|
458
|
+
- `+build` metadata is not a prerelease (audit B-12): toml_edit's latest stable
|
|
459
|
+
is 0.25.17, not 0.23.9. PyPI `rc` / `a` / `b` / `.dev` versions now count as
|
|
460
|
+
prereleases in the latest-stable pick.
|
|
461
|
+
|
|
462
|
+
### upgrade_impact (audit B-6/B-7)
|
|
463
|
+
|
|
464
|
+
- A breaking entry that names a Cargo feature your manifest enables (reqwest
|
|
465
|
+
0.13's `rustls-tls` / `macos-system-configuration`) counts as touching your
|
|
466
|
+
code; the features are listed in `your_code.cargo_features`.
|
|
467
|
+
- A GitHub PR line ("... by @user in <url>") is no longer a second change, and
|
|
468
|
+
replacement syntax after "use" / "instead" / ";" is no longer read as old
|
|
469
|
+
syntax (thiserror 2 flagged every `{name}` format string; `r#` raw
|
|
470
|
+
identifiers no longer match).
|
|
471
|
+
- When the changelog is missing or partial, the answer says how to turn on the
|
|
472
|
+
opt-in GitHub fallback (`FOURDA_GITHUB_CHANGELOG=true`).
|
|
473
|
+
|
|
474
|
+
### Protocol and docs (audit B-13 to B-19)
|
|
475
|
+
|
|
476
|
+
- An unknown tool or prompt name is a JSON-RPC -32602 error, and an unknown
|
|
477
|
+
resource is resource-not-found, as the MCP spec says. `4da://schema/__proto__`
|
|
478
|
+
(and other names inherited from Object) no longer reach Object internals.
|
|
479
|
+
`4da://skills`, which never existed, is no longer advertised.
|
|
480
|
+
- `get_context` marks dependency names read from repository lockfiles as
|
|
481
|
+
untrusted data.
|
|
482
|
+
- README: the network-host list is complete (api.npmjs.org, index.crates.io,
|
|
483
|
+
hn.algolia.com and the opt-in hosts), and HTTP mode is documented as
|
|
484
|
+
single-tenant.
|
|
485
|
+
- The conformance fetch works with Windows long paths, and a missing corpus
|
|
486
|
+
prints a visible warning (the suites used to skip silently).
|
|
487
|
+
## 6.2.4 — 2026-10-12
|
|
488
|
+
|
|
489
|
+
### Fix paths held to the 4DA rule vectors
|
|
490
|
+
|
|
491
|
+
The fix-path rules (version order, OSV range evaluation, the fix target, the
|
|
492
|
+
refresh-or-parent verdict, the refresh command, `range_check`) now run in CI
|
|
493
|
+
against the shared rule vectors of the 4DA desktop app, the same 333 vectors
|
|
494
|
+
its Rust engine runs (see `conformance/README.md`). All pass. The first run
|
|
495
|
+
found these defects:
|
|
496
|
+
|
|
497
|
+
- **pnpm workspaces: the refresh command missed members.** At a pnpm workspace
|
|
498
|
+
root, `pnpm update <pkg>` touches only the root project, so a member's
|
|
499
|
+
vulnerable transitive copy stayed (measured with pnpm 10.34.6). The command
|
|
500
|
+
is now `pnpm update -r <pkg> --depth Infinity` there, and
|
|
501
|
+
`pnpm update <pkg> --depth Infinity` elsewhere (vectors fc-005..fc-008).
|
|
502
|
+
- **A blocking parent was not named when another parent's requirement could
|
|
503
|
+
not be read.** `upgrade_planner` gave up on the whole copy and kept a
|
|
504
|
+
generic "waiting on upstream". A requirement that excludes the target now
|
|
505
|
+
decides on its own, and the reason names it (rv-048).
|
|
506
|
+
- **Unread requirements fall back to semver, and say so.** When not every
|
|
507
|
+
parent's requirement can be read (the registry did not answer) and none read
|
|
508
|
+
excludes the target, the verdict comes from semver compatibility: a target
|
|
509
|
+
in the installed version's caret range is a `lockfile_refresh` with
|
|
510
|
+
`refreshInferred: true` and a reason that says it was inferred; any other
|
|
511
|
+
target stays `waiting_on_upstream` (AD-055 ¶1; rv-002..rv-032 even ids,
|
|
512
|
+
rv-049, rv-050).
|
|
513
|
+
- **npm aliases were unreadable requirements.** `"npm:string-width@^4.2.0"`,
|
|
514
|
+
how package-lock records cliui's `string-width-cjs`, is now read as `^4.2.0`
|
|
515
|
+
on string-width (rv-047).
|
|
516
|
+
- **PEP 440 local labels did not order.** `1.0`, `1.0+abc.5`, `1.0+abc.7` and
|
|
517
|
+
`1.0+5` compared equal; they now order as PEP 440 says (vo-013..vo-015), so a
|
|
518
|
+
`last_affected` bound equal to a local-labelled install is read correctly.
|
|
519
|
+
- Yarn 1 and bun have no command that moves one transitive copy in place, so
|
|
520
|
+
none is named; the manual steps (delete the lockfile entries, reinstall) are
|
|
521
|
+
unchanged.
|
|
522
|
+
|
|
3
523
|
## 6.2.3 — 2026-10-11
|
|
4
524
|
|
|
5
525
|
### Yarn 1 refresh instruction covers alias entries
|
package/README.md
CHANGED
|
@@ -106,15 +106,22 @@ Then ask your AI: **"What changes if I upgrade X to Y?"**, **"Scan for vulnerabi
|
|
|
106
106
|
|
|
107
107
|
## How It Works
|
|
108
108
|
|
|
109
|
-
The server reads every lockfile of the project it is started in, including independently-locked projects below the root (a repo with `src-tauri/Cargo.lock` beside a root `pnpm-lock.yaml` is scanned whole) and skipping what `.gitignore` excludes: `package-lock.json` / `npm-shrinkwrap.json`, `pnpm-lock.yaml` (v5–v9), `yarn.lock` (v1 and berry), `bun.lock`, `Cargo.lock` (Cargo workspaces included), `poetry.lock`, `uv.lock`, `pdm.lock`, `Pipfile.lock`, requirements pins (`*requirements*.txt`, `requirements/*.txt`, with `-r` and `-c`),
|
|
109
|
+
The server reads every lockfile of the project it is started in, including independently-locked projects below the root (a repo with `src-tauri/Cargo.lock` beside a root `pnpm-lock.yaml` is scanned whole) and skipping what `.gitignore` excludes: `package-lock.json` / `npm-shrinkwrap.json`, `pnpm-lock.yaml` (v5–v9), `yarn.lock` (v1 and berry), `bun.lock`, `Cargo.lock` (Cargo workspaces included), `poetry.lock`, `uv.lock`, `pdm.lock`, `Pipfile.lock`, requirements pins (`*requirements*.txt`, `requirements/*.txt`, with `-r` and `-c`), `go.mod` / `go.sum` (Go's build list), `Gemfile.lock` (RubyGems; git and path gems are local code, not releases) and `composer.lock` (Packagist). Every Python source in a folder is read, not only the first. Every installed copy is scanned, not one version per name, with the lockfile's own dev flags. It re-reads them whenever a lockfile changes, and for npm it also checks what `node_modules` actually holds. The walk goes 4 levels down and up to 256 projects; whatever it leaves out, and every manifest with no lockfile (its declared ranges are not installs and are not scanned), is listed in the answer's `coverage` block, never dropped silently. So are manifests of ecosystems it does not read (Maven `pom.xml`, Gradle, .NET `*.csproj` / `packages.lock.json`, Pub, Hex, SwiftPM, CocoaPods: `coverage.not_covered`), lockfiles that exist but cannot be read (merge-conflict markers, invalid JSON: `coverage.unreadable_lockfiles`), and lockfiles under test-fixture directories (`tests/`, `test/`, `fixtures/`, `testdata/`, `__fixtures__/`, `conformance/` ...), which are not scanned as the project's own unless you pass `include_test_fixtures: true` (`coverage.not_scanned`).
|
|
110
|
+
|
|
111
|
+
**An incomplete answer is never a clean one.** When OSV.dev cannot answer for some dependencies (outage, HTTP error, timeout, the query budget), `vulnerability_scan` starts with `summary: "NOT CHECKED: ... N of M dependencies have no vulnerability answer"`, counts them in `coverage.unchecked_dependencies`, and sets `total_vulnerable_packages` and `by_severity` to null (what the checked part found is under `found_in_checked_dependencies`); `dependency_health` withholds its score and vulnerable count, and `upgrade_planner` sets `vulnerabilityDataAvailable: false`. Large trees are queried in batches of 1,000 as OSV's budget allows, so a tree past 10,000 dependencies is answered in full.
|
|
110
112
|
|
|
111
113
|
Measured against osv-scanner on 19 public repositories (npm, pnpm v5-v9, yarn v1 and berry, bun, Cargo, Poetry, uv, Pipfile, requirements, Go; 2026-10-10), every difference settled by OSV's own API: precision 1.00 over 19,115 findings, recall 19,115 of 19,116 on the lockfiles it reads (Maven/Gradle are not read). Newly read requirement files were also checked against pip-audit.
|
|
112
114
|
|
|
113
115
|
- **OSV.dev** for known vulnerabilities, matched to exact installed versions
|
|
114
|
-
- **npm registry, crates.io, PyPI, Go module proxy** for versions, deprecations and yanks
|
|
116
|
+
- **npm registry, crates.io, PyPI, Go module proxy** (registry.npmjs.org, crates.io and index.crates.io, pypi.org, proxy.golang.org) for versions, deprecations and yanks
|
|
117
|
+
- **npm download counts** (api.npmjs.org), for `dependency_health`'s weekly-downloads figure
|
|
115
118
|
- **The package's own registry archive** (registry.npmjs.org, static.crates.io, files.pythonhosted.org, proxy.golang.org) for the changelog `upgrade_impact` reads; GitHub only if you opt in with `FOURDA_GITHUB_CHANGELOG=true` (below)
|
|
116
119
|
- **npm full packument**, the **crates.io versions API**, the **PyPI JSON API** and the **Go proxy's version list** for `dependency_check` (publish times, yanks and retractions; on npm and crates.io also publishers, install scripts, per-version dependencies). These requests carry the package name only; the version you have installed is never sent to a registry, except that the Go proxy's list has no dates, so it is asked for the `.info` of the versions being judged (the same request `go get` makes). OSV.dev's advisory list for the package lets `dependency_check` name the safe releases in a constraint. Packuments are cached on disk and revalidated with `If-None-Match`; crates.io API reads are spaced one per second.
|
|
117
|
-
- **Hacker News Algolia API
|
|
120
|
+
- **Hacker News Algolia API** (hn.algolia.com), only when you call `ecosystem_pulse`
|
|
121
|
+
- **GitHub** (api.github.com, raw.githubusercontent.com), only with `FOURDA_GITHUB_CHANGELOG=true`
|
|
122
|
+
- **Your embedding provider**, only if you configure one for semantic recall (a local Ollama URL, or api.openai.com; see below)
|
|
123
|
+
|
|
124
|
+
Those are all the hosts the server contacts.
|
|
118
125
|
|
|
119
126
|
Results are cached (24h for registry data, 1h for vulnerabilities, 30min for news) and rate-limited per source.
|
|
120
127
|
|
|
@@ -126,9 +133,11 @@ Results are cached (24h for registry data, 1h for vulnerabilities, 30min for new
|
|
|
126
133
|
|
|
127
134
|
When the 4DA desktop app is installed, the server reads the app's database (`4da.db`) through a **read-only** connection and never creates or alters anything in it: no tables, no columns, no cache. The only writes are rows in the four app-owned tables the tools exist to fill, so the app sees them: decisions (`decision_memory` -> `developer_decisions`), agent memory (`agent_memory`), and feedback (`record_feedback` -> `interactions` and `feedback`, which the app's calibration reads). Those go through a separate write connection that waits briefly for the app's lock. If your app version lacks the table or a column a record needs, the record goes to `standalone.db` instead and the response says so in `_meta`. (Before 6.1 the server also created a `live_cache` table inside the app's database; it now keeps that cache in `cache.db`.)
|
|
128
135
|
|
|
136
|
+
**Where configuration and the database come from:** `FOURDA_DB_PATH` if set (a relative path resolves against the server's working directory and must name an existing file; an absolute path that names no file is created as a new standalone database when its directory exists, and the first tool answer says so in `_meta.database_note`; otherwise it is refused with an error), else the desktop app's database in its data directory, else the server's own `standalone.db`. Embedding settings come from `FOURDA_SETTINGS_PATH`, else the `settings.json` beside `FOURDA_DB_PATH`, else the desktop app's data directory. Nothing is picked up from the directory the server is started in: until 6.2.5 a `data/settings.json` or `data/4da.db` inside (or beside) the working directory was adopted, so a cloned repository could redirect semantic recall to a host of its choosing or plant "decisions".
|
|
137
|
+
|
|
129
138
|
> The one exception: if you *explicitly* configure an OpenAI embedding provider (`FOURDA_EMBED_PROVIDER=openai`) for semantic recall, the decision/memory text you store is sent to OpenAI to be embedded. The default — no embedding provider, or a local Ollama one — keeps everything on your machine, and `FOURDA_OFFLINE=true` overrides it regardless.
|
|
130
139
|
|
|
131
|
-
**Ecosystems supported:** npm, crates.io (Rust), PyPI (Python), Go. `upgrade_impact` reads
|
|
140
|
+
**Ecosystems supported:** npm, crates.io (Rust), PyPI (Python), Go; `vulnerability_scan` also reads RubyGems (`Gemfile.lock`) and Packagist (`composer.lock`). Maven, Gradle and .NET are not read, and are listed as not covered. `upgrade_impact` reads npm, crates.io, PyPI and Go. PyPI: versions and dates from the PyPI JSON API, the changelog from the release's sdist, and import sites matched on the module the distribution installs: read from your virtualenv's metadata (`.venv`, `venv`, `env`: `top_level.txt` or `RECORD`), else a table of well-known differences (`PyYAML` -> `yaml`, `Pillow` -> `PIL`), else the distribution name, which the answer then labels an assumption. Go: versions and dates from proxy.golang.org, the changelog from the module zip, and a major version 2+ reported as the module path change it is (`github.com/jackc/pgx/v4` -> `/v5`), with the files that import the old path. `dependency_check` reads all four too; on PyPI and Go, where the registry exposes no publisher identity or install hooks, those signals say "not applicable".
|
|
132
141
|
|
|
133
142
|
**Known limits, stated plainly:** a `requirements.txt` without a lockfile names only your direct pins, so their transitive dependencies are not scanned (use `uv lock`, `poetry lock` or `pip-compile`). Many packages ship no changelog in their registry archive (fastembed, vite, zod among them); `upgrade_impact` then says so and gives the release-notes URL instead of guessing. Breaking entries are flagged from the changelog's headings and wording, and every entry carries the heading it sits under (`under`). Measured on a panel of 40 real upgrades across npm, crates.io, PyPI and Go (1,618 entries, gold from three blind raters with verbatim evidence; the panel and the measurement are in the test suite): on the 20 upgrades held out from rule-making, 95% of entries flagged breaking were breaking (95% CI 89-98%) and about 72% of breaking entries were flagged; Go release notes written as prose are the weakest (about 54% recall). Treat the flags as a pre-sort: read every entry of a major upgrade. Each answer says this in `_meta.classification`.
|
|
134
143
|
|
|
@@ -158,7 +167,7 @@ When the 4DA desktop app is installed, the server reads the app's database (`4da
|
|
|
158
167
|
| `upgrade_impact` | What changes between the installed and a target version of one dependency (npm, crates.io, PyPI, Go): releases in between, changelog entries classified breaking / deprecation / security, the breaking ones that touch your code (symbols you import, and route or pattern syntax in your string literals, e.g. axum 0.8's `/:id` -> `/{id}`), the files that import it, advisories fixed. |
|
|
159
168
|
| `vulnerability_scan` | Every installed copy in every lockfile matched against OSV.dev. Scope-adjusted severity, an upgrade target checked against every advisory of the package (the highest per-advisory fix can itself be affected by another), where each version is pinned, and a `coverage` block whenever the scan is partial. Concise by default (one row per vulnerable package version, the 40 most severe, about 4k tokens on a 290-advisory project); `package` for one dependency; `response_format: "detailed"` for every advisory. |
|
|
160
169
|
| `dependency_health` | Version freshness, deprecation (of the version you run) and vulnerability counts per dependency. |
|
|
161
|
-
| `upgrade_planner` | The smallest version no advisory affects for each vulnerable package, majors flagged (a 0.x minor counts as a major). Transitive fixes: `lockfile_refresh` with the exact command (`npm update`, `pnpm update`, `yarn up -R`, `cargo update -p ... --precise`) when every parent's declared requirement already admits a clean version,
|
|
170
|
+
| `upgrade_planner` | The smallest version no advisory affects for each vulnerable package, majors flagged (a 0.x minor counts as a major). Direct steps (`upgrade_direct`) carry `upgradeCommands`, the command per declaring project directory, keeping how the dependency is declared (`npm install x@v --save-exact` for an exact pin, `npm install "x@^v"`, `pnpm up`, `yarn upgrade` / `yarn up`, `cargo add` with `--dev` / `--build` / `--target` / `--rename`, `go get`; none for a `>=` / `||` / tag / git spec or a yarn workspace member), and a direct target is raised when a slightly newer release also clears a vulnerable transitive it pins. Transitive fixes: `lockfile_refresh` with the exact command (`npm update`, `pnpm update`, `yarn up -R`, `cargo update -p ... --precise`) when every parent's declared requirement already admits a clean version; `upgrade_parent` when a parent pins it but a newer release of that parent admits one, with `parentUpgrades` naming the smallest such release that is itself clear of the parent's advisories (read from the registry), the declared dependencies to bump (`levers`, with their commands; none when the lockfile cannot tie the parent to a declaration) and, when the new requirement still admits the installed copy, the refresh to run after the install (`refreshCommands`); `override_or_replace` with the override syntax (npm `overrides`, pnpm `pnpm.overrides`, yarn `resolutions`, Cargo `[patch]`) when the parent will not move (deprecated, already its latest release, or no release admits a clean version); `waiting_on_upstream` when the parent's releases could not be read, and for a copy bundled inside its owner's package, which only a new owner release (or replacing the owner) can move. Dev-only packages are included and labelled `isDev`, as in `vulnerability_scan`. For one project: `project_path` (default: the project the server was started in; `"*"` for every project the desktop app tracks). `package` for a one-package plan. |
|
|
162
171
|
| `dependency_check` | Call before writing any dependency version into a manifest: adding, pinning, upgrading, bumping, downgrading. `to` is the exact version, or the constraint you were given (`"7.x"`, `"^1.20"`, `"~=3.1"`, `"v1"`): then `range_check` lists the smallest and newest release in it that no known advisory affects, and the verdict is for the one it picks. An exact version an advisory affects gets the safe releases on its line. Verdict per item (`proceed` / `wait` / `review` / `avoid` / `unknown`) with evidence: advisories on the target, release age (holds releases under 3 days unless they fix an advisory you have), publish-trust drop, new install scripts, brand-new transitive dependencies, yanked or deprecated, and a breaking version range (a major, or a 0.x minor) is `review` with a pointer to `upgrade_impact`. npm, crates.io, PyPI and Go (publisher trust, install scripts and new transitive dependencies are npm/crates.io signals and say "not applicable" elsewhere). |
|
|
163
172
|
|
|
164
173
|
### Intelligence
|
|
@@ -242,6 +251,12 @@ every request must carry a Bearer token whose HMAC-SHA256 signature verifies
|
|
|
242
251
|
against that secret, and the token's role is enforced per tool (`viewer` is
|
|
243
252
|
read-only; `member` and `admin` may write). Put TLS in front of it.
|
|
244
253
|
|
|
254
|
+
**HTTP mode is single-tenant.** A token's `team_id` is verified but not used to
|
|
255
|
+
separate data: every authenticated client shares one database, one agent memory
|
|
256
|
+
and one set of decisions, and `project_path` lets any authenticated client scan
|
|
257
|
+
any directory on the host the server can read. Run one server per team (or per
|
|
258
|
+
trust boundary), and only behind clients you would give that access.
|
|
259
|
+
|
|
245
260
|
## CLI Reference
|
|
246
261
|
|
|
247
262
|
```
|
|
@@ -256,7 +271,8 @@ npx @4da/mcp-server --version # Print version
|
|
|
256
271
|
|
|
257
272
|
| Variable | Description | Default |
|
|
258
273
|
|----------|-------------|---------|
|
|
259
|
-
| `FOURDA_DB_PATH` | Path to 4DA's SQLite database |
|
|
274
|
+
| `FOURDA_DB_PATH` | Path to 4DA's SQLite database. An existing file is used. A relative path (resolved against the working directory) that names no file is refused. An absolute path that names no file is created as a standalone database if its directory exists (one stderr line and `_meta.database_note` in the first tool answer say so), and refused if the directory does not exist. | The desktop app's database, else the server's own |
|
|
275
|
+
| `FOURDA_SETTINGS_PATH` | `settings.json` holding the optional `embedding` block for semantic recall | Beside `FOURDA_DB_PATH`, else the desktop app's data directory |
|
|
260
276
|
| `FOURDA_OFFLINE` | Disable all network calls | `false` |
|
|
261
277
|
| `FOURDA_GITHUB_CHANGELOG` | `upgrade_impact`: read GitHub release notes when the registry archive has no changelog for the range (`true` / `1` / `yes` / `on`) | Off |
|
|
262
278
|
| `GITHUB_TOKEN` | Used only with `FOURDA_GITHUB_CHANGELOG`: raises GitHub's rate limit from 60 to 5,000 requests an hour | Unset |
|