@clear-capabilities/agentic-security-scanner 0.184.0 → 0.186.1

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 (228) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/agentic-security.js +111 -1
  3. package/dist/1128.index.js +912 -0
  4. package/dist/1665.index.js +828 -0
  5. package/dist/2584.index.js +1 -1
  6. package/dist/4171.index.js +2 -2
  7. package/dist/5752.index.js +466 -0
  8. package/dist/7405.index.js +1 -1
  9. package/dist/8158.index.js +1713 -0
  10. package/dist/8798.index.js +1 -1
  11. package/dist/9382.index.js +2 -2
  12. package/dist/9579.index.js +1 -1
  13. package/dist/agentic-security.mjs +29 -29
  14. package/dist/agentic-security.mjs.sha256 +1 -1
  15. package/dist/{dad58d1113a12c23a142.json → ca62ee93079be4c24eee.json} +29 -12
  16. package/dist/frontend/src/shell.js +12 -2
  17. package/package.json +29 -12
  18. package/src/dataflow/CLAUDE.md +46 -1
  19. package/src/dataflow/access-paths.js +12 -2
  20. package/src/dataflow/backward.js +42 -7
  21. package/src/dataflow/cross-service-taint.js +83 -1
  22. package/src/dataflow/demand-query.js +1168 -0
  23. package/src/dataflow/dep-fingerprint.js +718 -0
  24. package/src/dataflow/engine.js +48 -6
  25. package/src/dataflow/exploit-prover.js +6 -2
  26. package/src/dataflow/ifds-precise.js +22 -0
  27. package/src/dataflow/incremental-replay.js +118 -0
  28. package/src/dataflow/incremental-session.js +257 -0
  29. package/src/dataflow/incremental.js +237 -35
  30. package/src/dataflow/index.js +25 -94
  31. package/src/dataflow/ir-cache.js +268 -0
  32. package/src/dataflow/privacy-taint.js +7 -0
  33. package/src/dataflow/summaries.js +72 -6
  34. package/src/dataflow/tabulation.js +80 -24
  35. package/src/engine.js +113 -8
  36. package/src/graph/CLAUDE.md +31 -0
  37. package/src/graph/adapters/artifact.js +81 -0
  38. package/src/graph/adapters/crossrepo.js +75 -0
  39. package/src/graph/adapters/dataflow.js +76 -0
  40. package/src/graph/adapters/provenance.js +92 -0
  41. package/src/graph/adapters/semantic.js +89 -0
  42. package/src/graph/crosswalk-builders.js +152 -0
  43. package/src/graph/crosswalk.js +146 -0
  44. package/src/graph/index.js +15 -0
  45. package/src/graph/observation.js +57 -0
  46. package/src/graph/refs.js +105 -0
  47. package/src/graph/registry.js +129 -0
  48. package/src/graph/relations.js +81 -0
  49. package/src/graph/scc.js +241 -0
  50. package/src/graph/view.js +254 -0
  51. package/src/ir/CLAUDE.md +4 -0
  52. package/src/ir/callgraph.js +36 -8
  53. package/src/ir/index.js +18 -4
  54. package/src/ir/parser-js.js +10 -0
  55. package/src/language/CLAUDE.md +13 -0
  56. package/src/language/nix-option-types.js +170 -0
  57. package/src/language/nix-script-taint.js +46 -3
  58. package/src/language/nix-secrets.js +20 -2
  59. package/src/lineage/CLAUDE.md +72 -8
  60. package/src/lineage/DESIGN_GRAPH_BUILDER.md +1 -1
  61. package/src/lineage/DESIGN_PATH_PROVENANCE.md +6 -0
  62. package/src/lineage/boundary-contract.js +273 -0
  63. package/src/lineage/completion.js +205 -0
  64. package/src/lineage/continuation.js +140 -0
  65. package/src/lineage/control-credit.js +313 -0
  66. package/src/lineage/correlation.js +199 -0
  67. package/src/lineage/coverage.js +8 -1
  68. package/src/lineage/cross-repo-link.js +22 -0
  69. package/src/lineage/cut-planner.js +548 -0
  70. package/src/lineage/driver.js +219 -66
  71. package/src/lineage/engine.js +166 -21
  72. package/src/lineage/export-json.js +5 -0
  73. package/src/lineage/graph-builder.js +84 -7
  74. package/src/lineage/haskell-view.js +59 -0
  75. package/src/lineage/index.js +3 -0
  76. package/src/lineage/nix-view.js +35 -0
  77. package/src/lineage/owned-build.js +77 -0
  78. package/src/lineage/owned-worker-entry.js +22 -0
  79. package/src/lineage/owned-worker.js +95 -0
  80. package/src/lineage/path-evidence.js +123 -0
  81. package/src/lineage/path-predicates.js +529 -0
  82. package/src/lineage/path-query.js +189 -20
  83. package/src/lineage/path-store.js +191 -17
  84. package/src/lineage/scc-solver.js +186 -0
  85. package/src/lineage/smt-orchestrator.js +174 -0
  86. package/src/lineage/smt-z3-backend.js +294 -0
  87. package/src/lineage/source-seeding.js +36 -2
  88. package/src/lineage/summaries.js +66 -4
  89. package/src/lineage/value-versions.js +130 -0
  90. package/src/lineage/witness-selection.js +95 -0
  91. package/src/pipeline/scan-health.js +18 -0
  92. package/src/posture/CLAUDE.md +17 -0
  93. package/src/posture/business-logic.js +16 -7
  94. package/src/posture/dag-commands.js +442 -0
  95. package/src/posture/evidence-bundle.js +1 -1
  96. package/src/posture/finding-defaults.js +19 -0
  97. package/src/posture/git-history.js +91 -1
  98. package/src/posture/witness-bundle.js +573 -0
  99. package/src/posture/witness-runtime.js +276 -0
  100. package/src/report/index.js +2 -0
  101. package/src/sast/CLAUDE.md +2 -0
  102. package/src/sast/_call-args.js +70 -0
  103. package/src/sast/init-scope.js +87 -0
  104. package/src/sast/listen-bind-address.js +49 -0
  105. package/src/sast/redirect-target.js +106 -0
  106. package/src/sast/reflected-input.js +76 -0
  107. package/src/sast/send-file-root.js +27 -0
  108. package/src/sast/sensitive-data-logging.js +38 -1
  109. package/dist/119.index.js +0 -197
  110. package/dist/128.index.js +0 -129
  111. package/dist/1293.index.js +0 -5689
  112. package/dist/1336.index.js +0 -16182
  113. package/dist/1351.index.js +0 -133
  114. package/dist/1387.index.js +0 -271
  115. package/dist/1437.index.js +0 -23
  116. package/dist/1445.index.js +0 -110
  117. package/dist/1505.index.js +0 -151
  118. package/dist/1557.index.js +0 -293
  119. package/dist/1621.index.js +0 -311
  120. package/dist/1751.index.js +0 -160
  121. package/dist/1765.index.js +0 -727
  122. package/dist/1861.index.js +0 -690
  123. package/dist/1906.index.js +0 -433
  124. package/dist/208.index.js +0 -1631
  125. package/dist/2223.index.js +0 -159
  126. package/dist/2238.index.js +0 -295
  127. package/dist/2530.index.js +0 -152
  128. package/dist/2565.index.js +0 -116
  129. package/dist/2574.index.js +0 -127
  130. package/dist/2620.index.js +0 -345
  131. package/dist/2773.index.js +0 -98
  132. package/dist/2775.index.js +0 -32
  133. package/dist/2966.index.js +0 -171
  134. package/dist/2972.index.js +0 -87
  135. package/dist/2991.index.js +0 -131
  136. package/dist/3063.index.js +0 -833
  137. package/dist/3075.index.js +0 -227
  138. package/dist/3121.index.js +0 -297
  139. package/dist/3314.index.js +0 -263
  140. package/dist/3480.index.js +0 -1822
  141. package/dist/3481.index.js +0 -24
  142. package/dist/3506.index.js +0 -268
  143. package/dist/3533.index.js +0 -591
  144. package/dist/3653.index.js +0 -450
  145. package/dist/3740.index.js +0 -722
  146. package/dist/3822.index.js +0 -131
  147. package/dist/3868.index.js +0 -292
  148. package/dist/3918.index.js +0 -192
  149. package/dist/4090.index.js +0 -288
  150. package/dist/4113.index.js +0 -1286
  151. package/dist/4121.index.js +0 -1190
  152. package/dist/4142.index.js +0 -690
  153. package/dist/4168.index.js +0 -509
  154. package/dist/4281.index.js +0 -993
  155. package/dist/4336.index.js +0 -436
  156. package/dist/4387.index.js +0 -532
  157. package/dist/4413.index.js +0 -302
  158. package/dist/4476.index.js +0 -4510
  159. package/dist/4491.index.js +0 -133
  160. package/dist/4538.index.js +0 -185
  161. package/dist/4684.index.js +0 -319
  162. package/dist/4740.index.js +0 -165
  163. package/dist/4780.index.js +0 -209
  164. package/dist/4802.index.js +0 -225
  165. package/dist/4812.index.js +0 -379
  166. package/dist/4835.index.js +0 -126
  167. package/dist/494.index.js +0 -226
  168. package/dist/4991.index.js +0 -549
  169. package/dist/5199.index.js +0 -305
  170. package/dist/5216.index.js +0 -109
  171. package/dist/5439.index.js +0 -301
  172. package/dist/5443.index.js +0 -97
  173. package/dist/5496.index.js +0 -377
  174. package/dist/5738.index.js +0 -240
  175. package/dist/5801.index.js +0 -588
  176. package/dist/5817.index.js +0 -714
  177. package/dist/5858.index.js +0 -223
  178. package/dist/5933.index.js +0 -143
  179. package/dist/601.index.js +0 -23
  180. package/dist/6173.index.js +0 -273
  181. package/dist/6291.index.js +0 -1871
  182. package/dist/6312.index.js +0 -666
  183. package/dist/634.index.js +0 -16182
  184. package/dist/644.index.js +0 -87
  185. package/dist/6508.index.js +0 -283
  186. package/dist/6560.index.js +0 -645
  187. package/dist/6576.index.js +0 -422
  188. package/dist/6579.index.js +0 -271
  189. package/dist/6655.index.js +0 -3221
  190. package/dist/6745.index.js +0 -305
  191. package/dist/6818.index.js +0 -21642
  192. package/dist/6932.index.js +0 -1190
  193. package/dist/6972.index.js +0 -167
  194. package/dist/6995.index.js +0 -163
  195. package/dist/7249.index.js +0 -191
  196. package/dist/7372.index.js +0 -1197
  197. package/dist/7546.index.js +0 -541
  198. package/dist/76.index.js +0 -448
  199. package/dist/7879.index.js +0 -163
  200. package/dist/8025.index.js +0 -130
  201. package/dist/8054.index.js +0 -248
  202. package/dist/808.index.js +0 -1330
  203. package/dist/8097.index.js +0 -201
  204. package/dist/8126.index.js +0 -191
  205. package/dist/8178.index.js +0 -78
  206. package/dist/8382.index.js +0 -93
  207. package/dist/8408.index.js +0 -261
  208. package/dist/8540.index.js +0 -174
  209. package/dist/8635.index.js +0 -802
  210. package/dist/8688.index.js +0 -410
  211. package/dist/8699.index.js +0 -779
  212. package/dist/8891.index.js +0 -457
  213. package/dist/8916.index.js +0 -885
  214. package/dist/9146.index.js +0 -170
  215. package/dist/9168.index.js +0 -841
  216. package/dist/9292.index.js +0 -369
  217. package/dist/9422.index.js +0 -866
  218. package/dist/9440.index.js +0 -453
  219. package/dist/9479.index.js +0 -152
  220. package/dist/9481.index.js +0 -220
  221. package/dist/9539.index.js +0 -100
  222. package/dist/9547.index.js +0 -103
  223. package/dist/9692.index.js +0 -90
  224. package/dist/9800.index.js +0 -462
  225. package/dist/9899.index.js +0 -138
  226. package/dist/9981.index.js +0 -206
  227. package/dist/999.index.js +0 -117
  228. package/dist/9999.index.js +0 -1698
package/dist/644.index.js DELETED
@@ -1,87 +0,0 @@
1
- export const id = 644;
2
- export const ids = [644];
3
- export const modules = {
4
-
5
- /***/ 60644:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ detectSourceArtifactMismatch: () => (/* binding */ detectSourceArtifactMismatch)
10
- /* harmony export */ });
11
- // PRD 5 P0 §7 — Source vs Artifact Mismatch. This is the PRD's own "Killer
12
- // Demonstration": a developer fixes a vulnerability in source on Monday, but
13
- // production is still serving Friday's image.
14
- //
15
- // Scenario A — "Git: fixed / Production image: vulnerable": an OSV advisory
16
- // the DEPLOYED ARTIFACT's own vuln-correlate.js result reports against the
17
- // version it actually has installed, that the CURRENT source scan does NOT
18
- // report against the version source has installed/declared — i.e. source
19
- // has moved past this specific advisory (patched, upgraded) but the
20
- // artifact's installed version has not.
21
- //
22
- // Scenario B — "Git: vulnerable / Built artifact: affected component
23
- // absent": the current source scan flags an advisory against some package,
24
- // but that PACKAGE (by ecosystem+name, independent of version) is not
25
- // present anywhere in the artifact's own component inventory at all — a
26
- // dev-only/optional dependency that never actually shipped in this build.
27
- //
28
- // The matching key for a scenario-A/B pair is the OSV advisory id ALONE
29
- // (`osvId`) — never the purl, which bakes the VERSION into the key. Keying
30
- // on purl was tried first and is wrong: source and artifact are, by this
31
- // comparison's entire premise, usually running DIFFERENT versions of the
32
- // same package, so a purl-based key would make every version difference
33
- // look like two independent mismatches instead of comparing "does this
34
- // specific advisory affect each side's own installed version". An OSV
35
- // advisory id already uniquely identifies one vulnerability against one
36
- // package, so comparing by id alone correctly asks "is THIS advisory
37
- // present on one side and absent on the other", regardless of which exact
38
- // version is installed on either side.
39
- //
40
- // Pure comparison only — no new detection. `sourceVulnerabilities` is the
41
- // output of a normal source-tree SCA scan (queryOSV-shaped `vulnerable_dep`
42
- // entries); `artifactVulnerabilities`/`artifactComponents` are
43
- // vuln-correlate.js's/inventory.js's output for one specific image.
44
- function advisoryKey(v) {
45
- return v.osvId || `no-osv-id:${v.ecosystem}:${v.name}`;
46
- }
47
-
48
- function packageIdentity(c) {
49
- return `${c.ecosystem}:${c.name}`;
50
- }
51
-
52
- function detectSourceArtifactMismatch({ sourceVulnerabilities, artifactVulnerabilities, artifactComponents }) {
53
- if (!Array.isArray(sourceVulnerabilities) || !Array.isArray(artifactVulnerabilities) || !Array.isArray(artifactComponents)) {
54
- throw new TypeError('detectSourceArtifactMismatch requires sourceVulnerabilities/artifactVulnerabilities/artifactComponents arrays');
55
- }
56
-
57
- const sourceKeyed = new Map();
58
- for (const v of sourceVulnerabilities) {
59
- if (v.type && v.type !== 'vulnerable_dep') continue;
60
- sourceKeyed.set(advisoryKey(v), v);
61
- }
62
- const artifactKeyed = new Map();
63
- for (const v of artifactVulnerabilities) {
64
- if (v.type && v.type !== 'vulnerable_dep') continue;
65
- artifactKeyed.set(advisoryKey(v), v);
66
- }
67
- const artifactPackageIdentities = new Set(artifactComponents.map(packageIdentity));
68
-
69
- const scenarioA = [...artifactKeyed.entries()]
70
- .filter(([key]) => !sourceKeyed.has(key))
71
- .map(([, v]) => v);
72
-
73
- const scenarioB = [...sourceKeyed.entries()]
74
- .filter(([, v]) => !artifactPackageIdentities.has(packageIdentity(v)))
75
- .map(([, v]) => v);
76
-
77
- return {
78
- scenarioA,
79
- scenarioB,
80
- hasMismatch: scenarioA.length > 0 || scenarioB.length > 0,
81
- };
82
- }
83
-
84
-
85
- /***/ })
86
-
87
- };
@@ -1,283 +0,0 @@
1
- export const id = 6508;
2
- export const ids = [6508];
3
- export const modules = {
4
-
5
- /***/ 16508:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- __webpack_require__.r(__webpack_exports__);
9
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
10
- /* harmony export */ applyScaUpgrade: () => (/* binding */ applyScaUpgrade),
11
- /* harmony export */ planScaUpgrade: () => (/* binding */ planScaUpgrade)
12
- /* harmony export */ });
13
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
14
- /* harmony import */ var node_fs_promises__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(51455);
15
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(76760);
16
- /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(77598);
17
- /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(31421);
18
- /* harmony import */ var node_util__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(57975);
19
- /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(69259);
20
- // SCA upgrade engine: dry-run plan + worktree-isolated apply.
21
- //
22
- // Phase 3 / Item 5 of the SCA improvement plan. The MCP `apply_fix` path
23
- // refuses to write manifest files (package.json, *-lock.*, poetry.lock,
24
- // Cargo.lock, etc.) for safety. SCA findings need a separate path that:
25
- // 1. Generates an upgrade *plan* via the ecosystem's native dry-run
26
- // command (npm install --dry-run, pip install --dry-run, etc.).
27
- // 2. Applies the upgrade via the package manager itself, with a backup
28
- // + test-gate so a peer-dep break or test regression rolls back.
29
- //
30
- // Caller pattern: plan first (read-only), inspect the breaking-change
31
- // flag / peer warnings, then apply with confirm:true.
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
- const execFileAsync = (0,node_util__WEBPACK_IMPORTED_MODULE_5__.promisify)(node_child_process__WEBPACK_IMPORTED_MODULE_4__.execFile);
42
-
43
- // Per-ecosystem command/manifest map. Add ecosystems by extending this
44
- // table — every other place in the module reads it.
45
- const ECOSYSTEM = {
46
- npm: {
47
- manifests: ['package.json', 'package-lock.json'],
48
- altManifests: [['yarn.lock'], ['pnpm-lock.yaml']],
49
- dryRun: (pkg, ver) => ({ cmd: 'npm', args: ['install', `${pkg}@${ver}`, '--dry-run', '--json'] }),
50
- apply: (pkg, ver) => ({ cmd: 'npm', args: ['install', `${pkg}@${ver}`, '--save'] }),
51
- parseDryRun(stdout) {
52
- try {
53
- const j = JSON.parse(stdout);
54
- const peerDeps = Array.isArray(j.warnings) ? j.warnings.filter(w => /peer dep/i.test(w)) : [];
55
- const transitiveImpact = (j.added || []).length + (j.updated || []).length + (j.removed || []).length;
56
- return { peerDeps, transitiveImpact, rawSummary: { added: (j.added || []).length, updated: (j.updated || []).length, removed: (j.removed || []).length } };
57
- } catch { return { peerDeps: [], transitiveImpact: 0, rawSummary: null }; }
58
- },
59
- },
60
- pypi: {
61
- manifests: ['requirements.txt', 'pyproject.toml'],
62
- altManifests: [['poetry.lock'], ['Pipfile.lock']],
63
- dryRun: (pkg, ver) => ({ cmd: 'pip', args: ['install', '--dry-run', `${pkg}==${ver}`] }),
64
- apply: (pkg, ver) => ({ cmd: 'pip', args: ['install', '--upgrade', `${pkg}==${ver}`] }),
65
- parseDryRun() {
66
- // pip --dry-run output is human-readable; we don't parse it for v1.
67
- return { peerDeps: [], transitiveImpact: 0, rawSummary: null };
68
- },
69
- },
70
- cargo: {
71
- manifests: ['Cargo.toml', 'Cargo.lock'],
72
- altManifests: [],
73
- dryRun: (pkg, _ver) => ({ cmd: 'cargo', args: ['update', '--package', pkg, '--dry-run'] }),
74
- apply: (pkg, _ver) => ({ cmd: 'cargo', args: ['update', '--package', pkg] }),
75
- parseDryRun() { return { peerDeps: [], transitiveImpact: 0, rawSummary: null }; },
76
- },
77
- golang: {
78
- manifests: ['go.mod', 'go.sum'],
79
- altManifests: [],
80
- dryRun: (_pkg, _ver) => null, // `go get` has no dry-run flag; we skip dry-run in v1.
81
- apply: (pkg, ver) => ({ cmd: 'go', args: ['get', `${pkg}@v${ver}`] }),
82
- parseDryRun() { return { peerDeps: [], transitiveImpact: 0, rawSummary: null }; },
83
- },
84
- // Other ecosystems (rubygems, packagist, pub, maven) return a structured
85
- // "manual" plan in v1 — no native dry-run + the install side-effects
86
- // (gem build artifacts, composer cache) are easier for the user to
87
- // confirm interactively.
88
- };
89
-
90
- function _majorVersion(v) {
91
- const m = String(v || '').match(/^(\d+)/);
92
- return m ? parseInt(m[1], 10) : null;
93
- }
94
-
95
- function _detectTestCommand(scanRoot) {
96
- try {
97
- const pkg = node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, 'package.json');
98
- if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(pkg)) {
99
- const j = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(pkg, 'utf8'));
100
- if (j.scripts?.test && !/no test specified/i.test(j.scripts.test)) {
101
- return { cmd: 'npm', args: ['test'] };
102
- }
103
- }
104
- } catch {}
105
- if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, 'Cargo.toml'))) return { cmd: 'cargo', args: ['test'] };
106
- if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, 'go.mod'))) return { cmd: 'go', args: ['test', './...'] };
107
- if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, 'pyproject.toml'))) return { cmd: 'pytest', args: [] };
108
- return null;
109
- }
110
-
111
- // Produce a structured upgrade plan WITHOUT modifying anything on disk.
112
- // Safe to call repeatedly; runs the ecosystem's --dry-run command.
113
- async function planScaUpgrade({ scanRoot, finding }) {
114
- if (!finding || finding.type !== 'vulnerable_dep') {
115
- return { ok: false, reason: 'not a vulnerable_dep finding' };
116
- }
117
- const eco = ECOSYSTEM[finding.ecosystem];
118
- if (!eco) {
119
- return {
120
- ok: true,
121
- mode: 'manual',
122
- reason: `ecosystem '${finding.ecosystem}' has no automated upgrade in v1`,
123
- package: finding.name, currentVersion: finding.version,
124
- targetVersion: (finding.fixedVersions && finding.fixedVersions[0]) || null,
125
- command: null,
126
- };
127
- }
128
- const target = (finding.fixedVersions && finding.fixedVersions[0]) || null;
129
- if (!target) {
130
- return { ok: false, reason: 'no fixed version in OSV record' };
131
- }
132
- const isBreaking = (_majorVersion(target) ?? 0) > (_majorVersion(finding.version) ?? 0);
133
- const apply = eco.apply(finding.name, target);
134
- const dryRunSpec = eco.dryRun(finding.name, target);
135
- let peerDeps = [], transitiveImpact = 0, rawSummary = null, dryRunOk = null;
136
- if (dryRunSpec) {
137
- try {
138
- const r = await execFileAsync(dryRunSpec.cmd, dryRunSpec.args, {
139
- cwd: scanRoot, timeout: 60_000, maxBuffer: 8 * 1024 * 1024,
140
- });
141
- const parsed = eco.parseDryRun(r.stdout);
142
- peerDeps = parsed.peerDeps;
143
- transitiveImpact = parsed.transitiveImpact;
144
- rawSummary = parsed.rawSummary;
145
- dryRunOk = true;
146
- } catch (e) {
147
- // Dry-run failed (e.g. peer-dep resolution conflict). Surface the
148
- // error structurally so the caller can decide whether to proceed.
149
- dryRunOk = false;
150
- const stderr = (e && e.stderr) || (e && e.message) || '';
151
- peerDeps = /peer dep/i.test(stderr) ? [String(stderr).slice(0, 500)] : [];
152
- }
153
- }
154
- return {
155
- ok: true,
156
- mode: 'auto',
157
- ecosystem: finding.ecosystem,
158
- package: finding.name,
159
- currentVersion: finding.version,
160
- targetVersion: target,
161
- isBreaking,
162
- command: `${apply.cmd} ${apply.args.join(' ')}`,
163
- manifestFiles: eco.manifests,
164
- dryRun: { ok: dryRunOk, command: dryRunSpec ? `${dryRunSpec.cmd} ${dryRunSpec.args.join(' ')}` : null, peerDeps, transitiveImpact, rawSummary },
165
- testCommand: (() => { const t = _detectTestCommand(scanRoot); return t ? `${t.cmd} ${t.args.join(' ')}` : null; })(),
166
- };
167
- }
168
-
169
- // Apply the upgrade. Backs up affected manifests, runs the install, runs
170
- // the project's test command if detected, and ROLLS BACK on failure.
171
- // Audit-logged via the MCP audit layer at the call site.
172
- async function applyScaUpgrade({ scanRoot, finding, runTests = true }) {
173
- const plan = await planScaUpgrade({ scanRoot, finding });
174
- if (!plan.ok) return { applied: false, reason: plan.reason };
175
- if (plan.mode === 'manual') {
176
- return { applied: false, reason: plan.reason, plan };
177
- }
178
- const eco = ECOSYSTEM[finding.ecosystem];
179
- const target = plan.targetVersion;
180
-
181
- // Backup pass — record original contents of every relevant manifest so
182
- // we can restore on test failure. node_modules / vendor dirs are NOT
183
- // backed up (too big); they'll be rebuilt by re-running the install on
184
- // restore.
185
- const stateDir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__.statePath)(scanRoot, 'sca-upgrade-history');
186
- node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(stateDir, { recursive: true });
187
- const upgradeId = node_crypto__WEBPACK_IMPORTED_MODULE_3__.randomBytes(8).toString('hex');
188
- const backups = {};
189
- for (const mf of eco.manifests) {
190
- const abs = node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, mf);
191
- if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(abs)) continue;
192
- const content = await node_fs_promises__WEBPACK_IMPORTED_MODULE_1__.readFile(abs, 'utf8');
193
- const backupPath = node_path__WEBPACK_IMPORTED_MODULE_2__.join(stateDir, `${upgradeId}-${mf.replace(/[\/\\]/g, '_')}.bak`);
194
- await node_fs_promises__WEBPACK_IMPORTED_MODULE_1__.writeFile(backupPath, content);
195
- backups[mf] = { abs, backupPath };
196
- }
197
- if (!Object.keys(backups).length) {
198
- return { applied: false, reason: `no ${finding.ecosystem} manifest files found in scan root` };
199
- }
200
-
201
- // Run the install.
202
- const apply = eco.apply(finding.name, target);
203
- let installOutput = '';
204
- try {
205
- const r = await execFileAsync(apply.cmd, apply.args, {
206
- cwd: scanRoot, timeout: 300_000, maxBuffer: 16 * 1024 * 1024,
207
- });
208
- installOutput = (r.stdout || '') + (r.stderr || '');
209
- } catch (e) {
210
- // Install failed; restore backups (manifests may have been touched).
211
- for (const { abs, backupPath } of Object.values(backups)) {
212
- try { await node_fs_promises__WEBPACK_IMPORTED_MODULE_1__.copyFile(backupPath, abs); } catch {}
213
- }
214
- return {
215
- applied: false,
216
- reason: `install failed: ${(e && e.message) || e}`.slice(0, 600),
217
- installOutput: ((e && e.stdout) || '').slice(0, 1500),
218
- restored: true,
219
- upgradeId,
220
- };
221
- }
222
-
223
- // Optionally run the project's test command. On failure, restore.
224
- let testResult = null;
225
- if (runTests) {
226
- const test = _detectTestCommand(scanRoot);
227
- if (test) {
228
- try {
229
- const r = await execFileAsync(test.cmd, test.args, {
230
- cwd: scanRoot, timeout: 600_000, maxBuffer: 16 * 1024 * 1024,
231
- });
232
- testResult = { ok: true, command: `${test.cmd} ${test.args.join(' ')}`, output: ((r.stdout || '') + (r.stderr || '')).slice(0, 2000) };
233
- } catch (e) {
234
- // Tests failed — restore manifests so the working tree is clean.
235
- for (const { abs, backupPath } of Object.values(backups)) {
236
- try { await node_fs_promises__WEBPACK_IMPORTED_MODULE_1__.copyFile(backupPath, abs); } catch {}
237
- }
238
- return {
239
- applied: false,
240
- reason: `tests failed after upgrade: ${(e && e.message) || e}`.slice(0, 300),
241
- testOutput: ((e && e.stdout) || '').slice(0, 2000),
242
- restored: true,
243
- upgradeId,
244
- };
245
- }
246
- }
247
- }
248
-
249
- // Success path — write a log entry so the user can audit / undo.
250
- const logEntry = {
251
- id: upgradeId,
252
- timestamp: new Date().toISOString(),
253
- ecosystem: finding.ecosystem,
254
- package: finding.name,
255
- from: finding.version,
256
- to: target,
257
- backups: Object.fromEntries(Object.entries(backups).map(([k, v]) => [k, v.backupPath])),
258
- testResult,
259
- isBreaking: plan.isBreaking,
260
- finding: { id: finding.id, osvId: finding.osvId, cveAliases: finding.cveAliases },
261
- };
262
- const logFp = node_path__WEBPACK_IMPORTED_MODULE_2__.join(stateDir, 'log.json');
263
- let log = [];
264
- try { log = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(logFp, 'utf8')); } catch {}
265
- log.push(logEntry);
266
- node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(logFp, JSON.stringify(log, null, 2));
267
-
268
- return {
269
- applied: true,
270
- upgradeId,
271
- package: finding.name,
272
- from: finding.version,
273
- to: target,
274
- isBreaking: plan.isBreaking,
275
- testResult,
276
- installOutput: installOutput.slice(0, 1500),
277
- };
278
- }
279
-
280
-
281
- /***/ })
282
-
283
- };