@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.
Files changed (123) hide show
  1. package/CHANGELOG.md +520 -0
  2. package/README.md +22 -6
  3. package/dist/db.d.ts +38 -0
  4. package/dist/db.js +78 -23
  5. package/dist/doctor.js +6 -3
  6. package/dist/embeddings.d.ts +1 -1
  7. package/dist/embeddings.js +16 -14
  8. package/dist/index.js +73 -103
  9. package/dist/live/advisory-groups.d.ts +68 -0
  10. package/dist/live/advisory-groups.js +131 -0
  11. package/dist/live/cache.js +11 -3
  12. package/dist/live/cargo-platform.d.ts +6 -0
  13. package/dist/live/cargo-platform.js +8 -2
  14. package/dist/live/changelog.js +38 -6
  15. package/dist/live/dependency-set.d.ts +9 -1
  16. package/dist/live/dependency-set.js +10 -2
  17. package/dist/live/fix-paths.d.ts +289 -16
  18. package/dist/live/fix-paths.js +876 -93
  19. package/dist/live/github-releases.js +3 -0
  20. package/dist/live/held-dependencies.d.ts +93 -0
  21. package/dist/live/held-dependencies.js +212 -0
  22. package/dist/live/held-targets.d.ts +30 -0
  23. package/dist/live/held-targets.js +107 -0
  24. package/dist/live/http-utils.js +13 -3
  25. package/dist/live/index.d.ts +7 -4
  26. package/dist/live/index.js +24 -19
  27. package/dist/live/install-state.js +6 -2
  28. package/dist/live/js-lockfile-readers.d.ts +8 -1
  29. package/dist/live/js-lockfile-readers.js +43 -6
  30. package/dist/live/lockfile-parents.d.ts +15 -0
  31. package/dist/live/lockfile-parents.js +85 -10
  32. package/dist/live/lockfile-parsers-pygo.d.ts +47 -2
  33. package/dist/live/lockfile-parsers-pygo.js +230 -12
  34. package/dist/live/lockfile-parsers-rbphp.d.ts +38 -0
  35. package/dist/live/lockfile-parsers-rbphp.js +123 -0
  36. package/dist/live/lockfile-parsers.d.ts +6 -0
  37. package/dist/live/lockfile-parsers.js +116 -4
  38. package/dist/live/lockfile-types.d.ts +28 -1
  39. package/dist/live/maintenance.d.ts +20 -4
  40. package/dist/live/maintenance.js +31 -2
  41. package/dist/live/malware.d.ts +20 -0
  42. package/dist/live/malware.js +31 -0
  43. package/dist/live/npm-packument.d.ts +4 -1
  44. package/dist/live/npm-packument.js +5 -2
  45. package/dist/live/offered-release.d.ts +53 -0
  46. package/dist/live/offered-release.js +117 -0
  47. package/dist/live/osv-ranges.d.ts +111 -0
  48. package/dist/live/osv-ranges.js +173 -14
  49. package/dist/live/osv-scanner.d.ts +87 -5
  50. package/dist/live/osv-scanner.js +310 -59
  51. package/dist/live/pinned-advisories.d.ts +2 -2
  52. package/dist/live/pinned-advisories.js +3 -3
  53. package/dist/live/pypi-advice.d.ts +36 -0
  54. package/dist/live/pypi-advice.js +158 -0
  55. package/dist/live/range-satisfies.d.ts +4 -2
  56. package/dist/live/range-satisfies.js +7 -5
  57. package/dist/live/rate-limiter.d.ts +7 -0
  58. package/dist/live/rate-limiter.js +22 -1
  59. package/dist/live/refresh-rule.d.ts +36 -0
  60. package/dist/live/refresh-rule.js +49 -0
  61. package/dist/live/release-metadata.d.ts +5 -0
  62. package/dist/live/resolution.d.ts +3 -0
  63. package/dist/live/resolution.js +6 -3
  64. package/dist/live/scan-coverage.d.ts +26 -0
  65. package/dist/live/scan-coverage.js +70 -0
  66. package/dist/live/semver-utils.d.ts +8 -0
  67. package/dist/live/semver-utils.js +26 -3
  68. package/dist/live/severity-scope.d.ts +26 -1
  69. package/dist/live/severity-scope.js +32 -0
  70. package/dist/live/shell-safe.d.ts +19 -0
  71. package/dist/live/shell-safe.js +36 -0
  72. package/dist/live/types.d.ts +134 -8
  73. package/dist/live/version-compare.d.ts +51 -0
  74. package/dist/live/version-compare.js +152 -1
  75. package/dist/live/withdrawn-releases.d.ts +50 -0
  76. package/dist/live/withdrawn-releases.js +125 -0
  77. package/dist/project-manifests.d.ts +7 -0
  78. package/dist/project-manifests.js +45 -5
  79. package/dist/project-scanner.js +6 -4
  80. package/dist/project-tree.d.ts +56 -0
  81. package/dist/project-tree.js +174 -5
  82. package/dist/schema-registry.d.ts +47 -4
  83. package/dist/schema-registry.js +76 -9
  84. package/dist/server-instructions.js +4 -2
  85. package/dist/setup.d.ts +8 -0
  86. package/dist/setup.js +21 -2
  87. package/dist/tool-dispatch.js +3 -3
  88. package/dist/tools/agent-memory.d.ts +5 -0
  89. package/dist/tools/agent-memory.js +9 -0
  90. package/dist/tools/briefing-task-scope.js +5 -2
  91. package/dist/tools/decision-memory.js +2 -0
  92. package/dist/tools/dependency-check-held.d.ts +44 -0
  93. package/dist/tools/dependency-check-held.js +98 -0
  94. package/dist/tools/dependency-check-signals.d.ts +19 -2
  95. package/dist/tools/dependency-check-signals.js +84 -13
  96. package/dist/tools/dependency-check-versions.d.ts +37 -4
  97. package/dist/tools/dependency-check-versions.js +48 -13
  98. package/dist/tools/dependency-check.d.ts +11 -1
  99. package/dist/tools/dependency-check.js +81 -29
  100. package/dist/tools/dependency-health.js +209 -45
  101. package/dist/tools/get-context.d.ts +3 -0
  102. package/dist/tools/get-context.js +4 -0
  103. package/dist/tools/project-scope.d.ts +8 -0
  104. package/dist/tools/project-scope.js +21 -0
  105. package/dist/tools/upgrade-impact-features.d.ts +37 -0
  106. package/dist/tools/upgrade-impact-features.js +176 -0
  107. package/dist/tools/upgrade-impact-literals.js +26 -4
  108. package/dist/tools/upgrade-impact-report.d.ts +5 -1
  109. package/dist/tools/upgrade-impact-report.js +26 -5
  110. package/dist/tools/upgrade-impact.js +10 -3
  111. package/dist/tools/upgrade-planner-fixpaths.d.ts +164 -5
  112. package/dist/tools/upgrade-planner-fixpaths.js +0 -0
  113. package/dist/tools/upgrade-planner.d.ts +70 -7
  114. package/dist/tools/upgrade-planner.js +237 -61
  115. package/dist/tools/vulnerability-scan-concise.d.ts +47 -2
  116. package/dist/tools/vulnerability-scan-concise.js +37 -3
  117. package/dist/tools/vulnerability-scan-format.d.ts +126 -24
  118. package/dist/tools/vulnerability-scan-format.js +193 -26
  119. package/dist/tools/vulnerability-scan.d.ts +224 -63
  120. package/dist/tools/vulnerability-scan.js +50 -15
  121. package/dist/tools/what-should-i-know.d.ts +6 -1
  122. package/dist/tools/what-should-i-know.js +72 -15
  123. 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`), and `go.mod` / `go.sum` (Go's build list). 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.
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**, only when you call `ecosystem_pulse`
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 all four. 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".
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, else `waiting_on_upstream` naming the requirement that blocks it. 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. |
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 | Auto-detected |
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 |