@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/76.index.js DELETED
@@ -1,448 +0,0 @@
1
- export const id = 76;
2
- export const ids = [76];
3
- export const modules = {
4
-
5
- /***/ 80076:
6
- /***/ ((__webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- __webpack_require__.a(__webpack_module__, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try {
9
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
10
- /* harmony export */ startLspServer: () => (/* binding */ startLspServer)
11
- /* harmony export */ });
12
- /* unused harmony export _internals */
13
- /* harmony import */ var _language_discovery_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(41928);
14
- /* harmony import */ var _language_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(70634);
15
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(73024);
16
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(76760);
17
- /* harmony import */ var node_readline__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(80481);
18
- /* harmony import */ var _runScan_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(58307);
19
- /* harmony import */ var _posture_custom_rules_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(36885);
20
- /* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(69259);
21
- /* harmony import */ var _mcp_redact_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(31295);
22
- /* harmony import */ var _report_index_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(88818);
23
- var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([_runScan_js__WEBPACK_IMPORTED_MODULE_5__, _report_index_js__WEBPACK_IMPORTED_MODULE_8__]);
24
- ([_runScan_js__WEBPACK_IMPORTED_MODULE_5__, _report_index_js__WEBPACK_IMPORTED_MODULE_8__] = __webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__);
25
- // Minimal LSP server for agentic-security.
26
- //
27
- // Speaks the Language Server Protocol over stdio. On every textDocument/
28
- // didSave (and didOpen), the server runs runScan on the file and emits
29
- // textDocument/publishDiagnostics with the resulting findings mapped to
30
- // LSP Diagnostic objects.
31
- //
32
- // This is a STARTER implementation — feature-complete enough that JetBrains
33
- // (via LSP4IJ) and Neovim (via built-in LSP) can both attach and see
34
- // findings inline. The full feature set (code actions for /fix, inline
35
- // remediation hover, exploitability tooltip) is future work.
36
- //
37
- // Wire-format: vscode-jsonrpc framing (Content-Length headers). Stateless
38
- // per file — no incremental analysis yet.
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
- const PROTOCOL_VERSION = '3.17';
52
- const SERVER_NAME = 'agentic-security-lsp';
53
- const SERVER_VERSION = '0.1.0';
54
-
55
- let _rootUri = null;
56
- let _rootDir = process.cwd();
57
- let _stdoutMutex = Promise.resolve();
58
- const _diagnosticsByUri = new Map();
59
-
60
- function uriToPath(uri) {
61
- if (!uri) return null;
62
- if (uri.startsWith('file://')) return decodeURIComponent(uri.slice(7));
63
- return uri;
64
- }
65
-
66
- function pathToUri(p) {
67
- if (!p) return null;
68
- if (p.startsWith('file://')) return p;
69
- return 'file://' + encodeURI(node_path__WEBPACK_IMPORTED_MODULE_3__.resolve(p));
70
- }
71
-
72
- function sevToLsp(sev) {
73
- switch ((sev || '').toLowerCase()) {
74
- case 'critical': return 1; // Error
75
- case 'high': return 1; // Error
76
- case 'medium': return 2; // Warning
77
- case 'low': return 3; // Information
78
- default: return 4; // Hint
79
- }
80
- }
81
-
82
- // The most precise location a finding carries. Haskell and Nix findings keep their ORIGINAL span (0-based columns); a
83
- // finding with only a line is reported across that line, as before.
84
- function _rangeOf(f) {
85
- const o = f && f.originalLocation;
86
- if (o && Number.isInteger(o.startLine) && Number.isInteger(o.startColumn)) {
87
- return { start: { line: Math.max(0, o.startLine - 1), character: o.startColumn }, end: { line: Math.max(0, (Number.isInteger(o.endLine) ? o.endLine : o.startLine) - 1), character: Number.isInteger(o.endColumn) ? o.endColumn : o.startColumn + 1 } };
88
- }
89
- if (o && Number.isInteger(o.line) && Number.isInteger(o.column)) {
90
- const line = Math.max(0, o.line - 1);
91
- return { start: { line, character: o.column }, end: { line, character: 200 } };
92
- }
93
- const line = Math.max(0, ((f && f.line) || 1) - 1);
94
- return { start: { line, character: 0 }, end: { line, character: 200 } };
95
- }
96
-
97
- function findingToDiagnostic(f) {
98
- const line = Math.max(0, (f.line || 1) - 1);
99
- // Stage 6 correctness audit: this read f.remediation directly, but raw
100
- // scan.findings entries (what this consumes, pre-normalizeFindings) come
101
- // from two conventions — most posture/*.js and newer sast/*.js modules
102
- // set `remediation`, while ~127 of engine.js's own detectors set a `fix`
103
- // STRING field instead. _remediationOf carries the same precedence
104
- // report/index.js already established for this exact split (CMP-3).
105
- const remediation = (0,_report_index_js__WEBPACK_IMPORTED_MODULE_8__/* ._remediationOf */ .uV)(f);
106
- return {
107
- range: _rangeOf(f),
108
- severity: sevToLsp(f.severity),
109
- source: 'agentic-security',
110
- code: f.cwe || f.family || 'finding',
111
- // carried back by the client in codeAction requests, so a fix is offered for exactly this finding
112
- data: { id: f.id || null, stableId: f.stableId || null, rule: f.rule || null, family: f.family || null, language: f.language || (0,_language_context_js__WEBPACK_IMPORTED_MODULE_1__.languageOfFinding)(f) || null },
113
- message: `${f.vuln || 'Security finding'}${remediation ? '\n\n' + remediation : ''}`.slice(0, 2000),
114
- tags: [],
115
- };
116
- }
117
-
118
- function send(message) {
119
- const json = JSON.stringify({ jsonrpc: '2.0', ...message });
120
- _stdoutMutex = _stdoutMutex.then(() => new Promise(resolve => {
121
- process.stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`, resolve);
122
- }));
123
- return _stdoutMutex;
124
- }
125
-
126
- async function publishDiagnostics(uri, findings) {
127
- await send({
128
- method: 'textDocument/publishDiagnostics',
129
- params: { uri, diagnostics: findings.map(findingToDiagnostic) },
130
- });
131
- _diagnosticsByUri.set(uri, findings);
132
- }
133
-
134
- // Manifest / schema files that downstream passes (SCA, cross-language) read.
135
- // We walk the project tree once per LSP session and cache these so the
136
- // per-save scan has them.
137
- const DEP_BASE_NAMES = new Set([
138
- 'package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
139
- 'requirements.txt', 'pyproject.toml', 'poetry.lock', 'Pipfile.lock',
140
- 'composer.json', 'composer.lock', 'Gemfile', 'Gemfile.lock',
141
- 'go.mod', 'Cargo.toml', 'Cargo.lock',
142
- 'pom.xml', 'build.gradle', 'build.gradle.kts',
143
- ]);
144
- const DEP_EXT_RE = /\.(?:proto|graphql|gql|tf|cabal)$/i;
145
- // Haskell and Nix project manifests and lock files (cabal.project*, stack.yaml*, package.yaml, flake.lock, ...) are
146
- // recognised by the shared language discovery module, never by a second list here.
147
- const _isLanguageDep = (base) => (0,_language_discovery_js__WEBPACK_IMPORTED_MODULE_0__/* .isLanguageManifest */ .mD)(base);
148
- const DEP_NAME_RE = /(?:openapi|swagger)\.(?:ya?ml|json)$/i;
149
-
150
- let _depCache = { rootDir: null, depFileContents: {} };
151
-
152
- function _loadDepFileContents(rootDir) {
153
- if (_depCache.rootDir === rootDir) return _depCache.depFileContents;
154
- const out = {};
155
- const skipDirs = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'target', 'vendor', '.bench-cache']);
156
- function walk(dir) {
157
- let entries;
158
- try { entries = node_fs__WEBPACK_IMPORTED_MODULE_2__.readdirSync(dir, { withFileTypes: true }); } catch { return; }
159
- for (const e of entries) {
160
- if (skipDirs.has(e.name)) continue;
161
- const full = node_path__WEBPACK_IMPORTED_MODULE_3__.join(dir, e.name);
162
- if (e.isDirectory()) { walk(full); continue; }
163
- if (!e.isFile()) continue;
164
- const base = e.name;
165
- if (DEP_BASE_NAMES.has(base) || DEP_EXT_RE.test(base) || DEP_NAME_RE.test(base) || _isLanguageDep(base)) {
166
- let stat;
167
- try { stat = node_fs__WEBPACK_IMPORTED_MODULE_2__.statSync(full); } catch { continue; }
168
- if (stat.size > 500_000) continue;
169
- try { out[node_path__WEBPACK_IMPORTED_MODULE_3__.relative(rootDir, full)] = node_fs__WEBPACK_IMPORTED_MODULE_2__.readFileSync(full, 'utf8'); }
170
- catch { /* skip unreadable */ }
171
- }
172
- }
173
- }
174
- walk(rootDir);
175
- _depCache = { rootDir, depFileContents: out };
176
- return out;
177
- }
178
-
179
- async function scanFile(uri) {
180
- const filePath = uriToPath(uri);
181
- if (!filePath || !node_fs__WEBPACK_IMPORTED_MODULE_2__.existsSync(filePath)) return;
182
- // Incremental scan (premortem 2R4.5 / 2R-10): hand runScan a single-file
183
- // fileContents map for the saved code, AND a cached set of dep-manifest /
184
- // schema files so SCA + cross-language passes have their inputs. Without
185
- // depFileContents, the LSP path would silently drop CVE / OpenAPI / proto
186
- // findings on the saved file.
187
- try {
188
- const rel = node_path__WEBPACK_IMPORTED_MODULE_3__.relative(_rootDir, filePath);
189
- const content = node_fs__WEBPACK_IMPORTED_MODULE_2__.readFileSync(filePath, 'utf8');
190
- let fileContents = { [rel]: content };
191
- let depFileContents = _loadDepFileContents(_rootDir);
192
- // Haskell/Nix: scan the saved file together with the modules it imports and the ones that import it (bounded).
193
- const lc = (0,_language_context_js__WEBPACK_IMPORTED_MODULE_1__.withLanguageContext)(_rootDir, fileContents, depFileContents);
194
- fileContents = lc.fileContents; depFileContents = lc.depFileContents;
195
- // Premortem 4R-12 + 4R-15: reset the per-process custom-rules budget at
196
- // the start of each LSP scan. Each save is a logical scan session; without
197
- // the reset, a long-lived LSP server would accumulate budget across saves
198
- // and eventually start skipping custom rules.
199
- (0,_posture_custom_rules_js__WEBPACK_IMPORTED_MODULE_6__/* .resetCustomRulesBudget */ .zO)(_rootDir);
200
- // PRD R1 (docs/DETECTION_GAP_REMEDIATION_PRD.md): deep mode is
201
- // default-on for the interactive CLI scan but was never requested here,
202
- // so every on-save diagnostic pass was regex/AST-only — blind to any bug
203
- // whose source and sink are connected only through a call. Scoped to
204
- // exactly the saved file (fileContents has one entry), so this does not
205
- // turn every keystroke's save into a full-project deep scan.
206
- // withStateWritesDisabled, for the same reason mcp/tools.js's scan_diff
207
- // wraps its own partial-set scan (FR-704). This is a DIAGNOSTIC surface: it
208
- // runs on every file save, against the user's real project root, with a
209
- // fileContents map holding exactly one file. Without the wrapper,
210
- // runFullScan's state writers fire on every keystroke-save — dpia.md,
211
- // ropa.md, privacy-framework.json, threat-model.json and the rest, written
212
- // into the user's tree by an editor plugin they never asked to mutate
213
- // anything.
214
- //
215
- // The provenance lifecycle store makes that actively destructive rather
216
- // than merely noisy: updateLifecycle marks every open stableId ABSENT from
217
- // the finding set it is handed as `remediated`, and this set is one file's
218
- // worth of findings. Every save would remediate the whole project, and the
219
- // next real scan would reintroduce it.
220
- //
221
- // Chosen over forwarding `provenance:false` through runScan because that
222
- // would fix only the lifecycle half and leave the other state writers
223
- // firing. The flag is process-global (see its KNOWN LIMITATION), which is
224
- // harmless here: this server is a read-only surface whose every scan wants
225
- // writes off, so overlapping saves can only ever agree, and the `finally`
226
- // restores the prior value either way. exceptCategories:['provenance-cache']
227
- // (M2 §2.4) is the one deliberate exception — every OTHER write this scan
228
- // would make stays suppressed, but the provenance disk cache stays live so
229
- // repeated saves of the same file are not each paying the full uncached
230
- // resolution cost.
231
- const { scan } = await (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_7__/* .withStateWritesDisabled */ .Ao)(() =>
232
- (0,_runScan_js__WEBPACK_IMPORTED_MODULE_5__.runScan)(_rootDir, { fileContents, depFileContents, deep: true, deepInCi: true }),
233
- { exceptCategories: ['provenance-cache'] });
234
- // Stage 6 correctness audit: this only ever read scan.findings (the SAST
235
- // channel). scan.secrets and scan.logicVulns are separate arrays on the
236
- // raw runScan() result — normalizeFindings is what merges all four
237
- // channels, and that hasn't run here — so a saved file with a hardcoded
238
- // credential got a clean problem pane, no diagnostic at all. Unlike the
239
- // MCP surface, this server never applied redactFinding either (nothing
240
- // here imported mcp/redact.js), which would have been a landmine the
241
- // moment secrets/logicVulns were added without it: those channels are
242
- // exactly where raw secret material shows up in `snippet`. Both fixed
243
- // together — merge the channels AND redact — so the fix for one gap
244
- // doesn't open the other.
245
- const findings = [...(scan.findings || []), ...(scan.secrets || []), ...(scan.logicVulns || [])]
246
- .filter(f => f.file === rel)
247
- .map(f => (0,_mcp_redact_js__WEBPACK_IMPORTED_MODULE_9__/* .redactFinding */ .lE)(f));
248
- await publishDiagnostics(uri, findings);
249
- } catch (e) {
250
- process.stderr.write(`agentic-security-lsp: scan failed: ${e.message}\n`);
251
- }
252
- }
253
-
254
- function handleInitialize(params) {
255
- if (params.rootUri) {
256
- _rootUri = params.rootUri;
257
- _rootDir = uriToPath(params.rootUri) || process.cwd();
258
- } else if (params.rootPath) {
259
- _rootDir = params.rootPath;
260
- }
261
- return {
262
- capabilities: {
263
- textDocumentSync: {
264
- openClose: true,
265
- change: 1,
266
- save: { includeText: false },
267
- },
268
- diagnosticProvider: { interFileDependencies: true, workspaceDiagnostics: false },
269
- // quick fixes for Haskell and Nix findings (preview edits from the verified language fixers)
270
- codeActionProvider: { codeActionKinds: ['quickfix'], resolveProvider: false },
271
- },
272
- serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
273
- };
274
- }
275
-
276
- /**
277
- * textDocument/codeAction: a quick fix for each agentic-security diagnostic in range that a language fixer can repair.
278
- * The edit is a PREVIEW the editor applies; nothing is written here. A finding with no safe fix gets a disabled
279
- * action that says why, never a silent omission.
280
- */
281
- async function codeActionsFor(params) {
282
- const uri = params && params.textDocument && params.textDocument.uri;
283
- const diags = ((params && params.context && params.context.diagnostics) || []).filter((d) => d && d.source === 'agentic-security' && d.data && d.data.id);
284
- const stored = (uri && _diagnosticsByUri.get(uri)) || [];
285
- if (!diags.length || !stored.length) return [];
286
- let files = null;
287
- const actions = [];
288
- for (const d of diags) {
289
- const f = stored.find((x) => x && x.id === d.data.id);
290
- if (!f || !(0,_language_context_js__WEBPACK_IMPORTED_MODULE_1__.languageOfFinding)(f)) continue;
291
- if (!files) files = (0,_language_context_js__WEBPACK_IMPORTED_MODULE_1__.loadLanguageProject)(_rootDir).files;
292
- let prev;
293
- try { prev = await (0,_language_context_js__WEBPACK_IMPORTED_MODULE_1__.languageFixPreview)(f, files); } catch (e) { prev = { ok: false, reason: String((e && e.message) || e) }; }
294
- if (prev && prev.ok && prev.file) {
295
- // A fix that spans several files (the winning definitions of one option) is ONE workspace edit over all of them: offering
296
- // only the first file would leave the configuration in a state the verification never saw.
297
- const changes = {};
298
- for (const e of (prev.edits || [{ file: prev.file, before: prev.before, after: prev.after }])) changes[pathToUri(node_path__WEBPACK_IMPORTED_MODULE_3__.join(_rootDir, e.file))] = [(0,_language_context_js__WEBPACK_IMPORTED_MODULE_1__.minimalEdit)(e.before, e.after)];
299
- actions.push({
300
- title: `Fix${prev.label ? ` (${prev.label})` : ''}: ${f.vuln || f.rule || 'finding'}`,
301
- kind: 'quickfix', diagnostics: [d], isPreferred: false,
302
- edit: { changes },
303
- data: { stableId: f.stableId || null, verified: { syntax: true, rescan: true } },
304
- });
305
- } else {
306
- actions.push({ title: `No automatic fix: ${f.vuln || f.rule || 'finding'}`, kind: 'quickfix', diagnostics: [d], disabled: { reason: (prev && (prev.reason || prev.status)) || 'no deterministic fix exists for this finding' } });
307
- }
308
- }
309
- return actions;
310
- }
311
-
312
- async function handleMessage(msg) {
313
- if (msg.method === 'textDocument/codeAction') {
314
- return { id: msg.id, result: await codeActionsFor(msg.params || {}) };
315
- }
316
- if (msg.method === 'initialize') {
317
- return { id: msg.id, result: handleInitialize(msg.params || {}) };
318
- }
319
- if (msg.method === 'initialized' || msg.method === 'workspace/didChangeConfiguration') {
320
- return null; // notification, no response
321
- }
322
- if (msg.method === 'shutdown') {
323
- return { id: msg.id, result: null };
324
- }
325
- if (msg.method === 'exit') {
326
- process.exit(0);
327
- }
328
- if (msg.method === 'textDocument/didOpen') {
329
- const uri = msg.params?.textDocument?.uri;
330
- if (uri) scanFile(uri);
331
- return null;
332
- }
333
- if (msg.method === 'textDocument/didSave') {
334
- const uri = msg.params?.textDocument?.uri;
335
- if (uri) {
336
- // Premortem 3R-9 / 4R-5: when the user saves a manifest file, the
337
- // dep-cache entry for THAT file is stale. Granular invalidation (only
338
- // re-read the saved file from disk) avoids the O(project) re-walk that
339
- // 3R-9 introduced — important in monorepos where mass manifest edits
340
- // would otherwise re-scan thousands of files per save.
341
- const savedPath = uriToPath(uri);
342
- if (savedPath && _depCache.rootDir === _rootDir) {
343
- const base = node_path__WEBPACK_IMPORTED_MODULE_3__.basename(savedPath);
344
- if (DEP_BASE_NAMES.has(base) || DEP_EXT_RE.test(base) || DEP_NAME_RE.test(base) || _isLanguageDep(base)) {
345
- try {
346
- const rel = node_path__WEBPACK_IMPORTED_MODULE_3__.relative(_rootDir, savedPath);
347
- const st = node_fs__WEBPACK_IMPORTED_MODULE_2__.statSync(savedPath);
348
- if (st.size <= 500_000) {
349
- _depCache.depFileContents[rel] = node_fs__WEBPACK_IMPORTED_MODULE_2__.readFileSync(savedPath, 'utf8');
350
- } else {
351
- delete _depCache.depFileContents[rel];
352
- }
353
- } catch {
354
- // File vanished between save event and stat — drop from cache.
355
- try {
356
- const rel = node_path__WEBPACK_IMPORTED_MODULE_3__.relative(_rootDir, savedPath);
357
- delete _depCache.depFileContents[rel];
358
- } catch {}
359
- }
360
- }
361
- }
362
- scanFile(uri);
363
- }
364
- return null;
365
- }
366
- if (msg.method === 'textDocument/didClose') {
367
- const uri = msg.params?.textDocument?.uri;
368
- if (uri) await publishDiagnostics(uri, []);
369
- return null;
370
- }
371
- // Unknown method.
372
- if (msg.id != null) {
373
- return { id: msg.id, error: { code: -32601, message: `Method not found: ${msg.method}` } };
374
- }
375
- return null;
376
- }
377
-
378
- function startLspServer() {
379
- let buffer = Buffer.alloc(0);
380
- let expected = -1;
381
- process.stdin.on('data', async (chunk) => {
382
- buffer = Buffer.concat([buffer, chunk]);
383
- while (true) {
384
- if (expected < 0) {
385
- const headerEnd = buffer.indexOf('\r\n\r\n');
386
- if (headerEnd < 0) break;
387
- const headers = buffer.slice(0, headerEnd).toString('utf8');
388
- const m = headers.match(/Content-Length:\s*(\d+)/i);
389
- if (!m) {
390
- process.stderr.write('agentic-security-lsp: missing Content-Length header\n');
391
- buffer = buffer.slice(headerEnd + 4);
392
- continue;
393
- }
394
- expected = parseInt(m[1], 10);
395
- buffer = buffer.slice(headerEnd + 4);
396
- }
397
- if (buffer.length < expected) break;
398
- const body = buffer.slice(0, expected).toString('utf8');
399
- buffer = buffer.slice(expected);
400
- expected = -1;
401
- let msg;
402
- try { msg = JSON.parse(body); }
403
- catch { process.stderr.write('agentic-security-lsp: malformed JSON\n'); continue; }
404
- try {
405
- const response = await handleMessage(msg);
406
- if (response) await send(response);
407
- } catch (e) {
408
- if (msg.id != null) await send({ id: msg.id, error: { code: -32603, message: e.message } });
409
- }
410
- }
411
- });
412
- process.stdin.on('end', () => process.exit(0));
413
- }
414
-
415
- // Allow direct invocation as a bin entry: `node lsp/server.js`.
416
- //
417
- // `import.meta.url === file://${process.argv[1]}` looks equivalent but is
418
- // NOT: when this script is invoked through a symlink (exactly what
419
- // `npm install -g`, `npx`, and `node_modules/.bin/<name>` all do for a
420
- // package's `bin` entries — and `agentic-security-lsp` IS one of this
421
- // package's bin entries), Node resolves `import.meta.url` to the symlink's
422
- // realpath while `process.argv[1]` stays the symlink path as invoked, so the
423
- // two never match, the guard is always false, and the server silently exits
424
- // with no output — an editor would see the language server start and
425
- // immediately die with nothing on stderr to explain it. `import.meta.main` is
426
- // resolved correctly through a symlink. It was added in Node v24.2.0
427
- // (backported to v22.18.0) and is currently Stability 1.0 (early development)
428
- // per Node's own docs — NOT stable, and NOT available on v20.11. Concretely:
429
- // it is `undefined` on Node 24.0.0/24.1.x, which satisfy this repo's declared
430
- // `engines.node: ">=24.0.0"` floor, so `import.meta.main` alone would
431
- // reproduce this exact bug on a plain non-symlinked invocation under those two
432
- // point releases. The `??` fallback covers that gap without bumping the
433
- // engines floor. Identical to bin/agentic-security.js's guard, deliberately —
434
- // see the long-form note there.
435
- if (import.meta.main ?? (import.meta.url === `file://${process.argv[1]}`)) {
436
- startLspServer();
437
- }
438
-
439
- function _setRootDir(dir) { _rootDir = dir; _depCache = { rootDir: null, depFileContents: {} }; }
440
-
441
- const _internals = { findingToDiagnostic, scanFile, uriToPath, pathToUri, _diagnosticsByUri, _setRootDir };
442
-
443
- __webpack_async_result__();
444
- } catch(e) { __webpack_async_result__(e); } });
445
-
446
- /***/ })
447
-
448
- };
@@ -1,163 +0,0 @@
1
- export const id = 7879;
2
- export const ids = [7879];
3
- export const modules = {
4
-
5
- /***/ 7879:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ auditCall: () => (/* binding */ auditCall)
10
- /* harmony export */ });
11
- /* unused harmony export verifyAuditLog */
12
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
13
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
14
- /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(77598);
15
- /* harmony import */ var _redact_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(31295);
16
- /* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(69259);
17
- // Append-only audit log of MCP tool calls — OWASP MCP08.
18
- //
19
- // Format: one JSON object per line (NDJSON) at
20
- // <sessionRoot>/.agentic-security/mcp-audit.log
21
- //
22
- // Each entry carries `prev` — the SHA-256 of the previous entry's serialized
23
- // form. The first entry's prev is "GENESIS". Tampering with any line breaks
24
- // the chain from that point forward; a reader can detect partial truncation
25
- // or in-place edits.
26
- //
27
- // REMOTE SINK (post-recommendation #10). The local file alone cannot detect
28
- // a total rewrite — an attacker with FS write can re-author the whole log
29
- // with fresh hashes. Closing that blind spot requires an off-host witness.
30
- // Set $AGENTIC_SECURITY_AUDIT_WEBHOOK to a POST endpoint; every entry is
31
- // fire-and-forget POSTed there in addition to the local append. Failures
32
- // to reach the webhook are best-effort — they NEVER block a tool call,
33
- // because that would let a network outage become a denial of service. They
34
- // DO get recorded as `_remoteSinkErr` on the local entry, so an operator
35
- // reviewing the log later can spot a forging attempt that targeted the
36
- // remote (any gap between local-sequence and remote-sequence is evidence).
37
- //
38
- // Argument blobs are redacted (OWASP MCP01/MCP10) so credentials passed in
39
- // arguments cannot leak via the audit trail OR via the remote sink.
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
- const MAX_ARG_BYTES = 1024;
48
- const GENESIS = 'GENESIS';
49
- const REMOTE_TIMEOUT_MS = 1500;
50
-
51
- // Per-process session ID (harness-anatomy #9). Stamped on every audit entry
52
- // so downstream metrics can aggregate by session and surface outliers like
53
- // "200 apply_fix calls in one session." The ID is `<pid>-<short-ts>` — not
54
- // cryptographically unique, but enough to disambiguate concurrent runs on
55
- // the same host. Stable for the lifetime of this Node process.
56
- const SESSION_ID = `${process.pid}-${Date.now().toString(36).slice(-6)}`;
57
-
58
- function _summarize(args) {
59
- let s;
60
- try { s = JSON.stringify(args); } catch { s = '<unserializable>'; }
61
- s = (0,_redact_js__WEBPACK_IMPORTED_MODULE_4__/* .redactArgsBlob */ .MC)(s);
62
- if (s.length > MAX_ARG_BYTES) s = s.slice(0, MAX_ARG_BYTES) + `…(+${s.length - MAX_ARG_BYTES})`;
63
- return s;
64
- }
65
-
66
- function _sha(s) { return node_crypto__WEBPACK_IMPORTED_MODULE_2__.createHash('sha256').update(s).digest('hex'); }
67
-
68
- function _readLastEntryHash(logFile) {
69
- if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(logFile)) return GENESIS;
70
- try {
71
- const all = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(logFile, 'utf8');
72
- const lines = all.split('\n').filter(Boolean);
73
- if (!lines.length) return GENESIS;
74
- return _sha(lines[lines.length - 1]);
75
- } catch { return GENESIS; }
76
- }
77
-
78
- // Fire-and-forget POST to the remote sink. Resolves to null on success,
79
- // to a short error string on failure. Never throws; never blocks longer
80
- // than REMOTE_TIMEOUT_MS. The local audit append happens regardless.
81
- async function _postRemote(url, entry) {
82
- try {
83
- const controller = new AbortController();
84
- const t = setTimeout(() => controller.abort(), REMOTE_TIMEOUT_MS);
85
- const r = await fetch(url, {
86
- method: 'POST',
87
- headers: { 'Content-Type': 'application/json' },
88
- body: JSON.stringify(entry),
89
- signal: controller.signal,
90
- });
91
- clearTimeout(t);
92
- if (!r.ok) return `HTTP ${r.status}`;
93
- return null;
94
- } catch (e) {
95
- return String((e && e.message) || e).slice(0, 200);
96
- }
97
- }
98
-
99
- function auditCall({ sessionRoot, tool, args, outcome, reason }) {
100
- if (!sessionRoot) return;
101
- try {
102
- // Safety: only write audit log if sessionRoot looks like a project root
103
- const MARKERS = ['.git', 'package.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'pom.xml', 'composer.json', 'Gemfile'];
104
- let hasMarker = false;
105
- for (const m of MARKERS) { try { if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_1__.join(sessionRoot, m))) { hasMarker = true; break; } } catch {} }
106
- if (!hasMarker) return;
107
- const dir = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_3__/* .stateDir */ .Pn)(sessionRoot);
108
- node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
109
- const logFile = node_path__WEBPACK_IMPORTED_MODULE_1__.join(dir, 'mcp-audit.log');
110
- const entry = {
111
- ts: new Date().toISOString(),
112
- sessionId: SESSION_ID,
113
- tool,
114
- outcome,
115
- ...(reason ? { reason } : {}),
116
- args: _summarize(args),
117
- prev: _readLastEntryHash(logFile),
118
- };
119
- node_fs__WEBPACK_IMPORTED_MODULE_0__.appendFileSync(logFile, JSON.stringify(entry) + '\n');
120
- // Remote sink (post-recommendation #10). Fire-and-forget. We don't await
121
- // the promise so the tool call returns immediately; the remote POST runs
122
- // on its own microtask. Failures get logged to a sidecar file so the
123
- // operator can detect when the sink is unreachable.
124
- const webhook = process.env.AGENTIC_SECURITY_AUDIT_WEBHOOK;
125
- if (webhook) {
126
- _postRemote(webhook, entry).then((err) => {
127
- if (!err) return;
128
- try {
129
- const errFile = node_path__WEBPACK_IMPORTED_MODULE_1__.join(dir, 'mcp-audit.remote-errors.log');
130
- node_fs__WEBPACK_IMPORTED_MODULE_0__.appendFileSync(errFile, JSON.stringify({
131
- ts: new Date().toISOString(), entryTs: entry.ts, tool, err,
132
- }) + '\n');
133
- } catch { /* nothing else to do */ }
134
- });
135
- }
136
- } catch { /* audit failure must never break a tool call */ }
137
- }
138
-
139
- // Verify the chain from start to end. Returns
140
- // { ok: true, entries: N } if intact
141
- // { ok: false, brokenAt: <line-index>, expected, got } if any link breaks
142
- // Reader/operator-facing tool.
143
- function verifyAuditLog(logFile) {
144
- if (!fs.existsSync(logFile)) return { ok: true, entries: 0 };
145
- const text = fs.readFileSync(logFile, 'utf8');
146
- const lines = text.split('\n').filter(Boolean);
147
- let expectedPrev = GENESIS;
148
- for (let i = 0; i < lines.length; i++) {
149
- let entry;
150
- try { entry = JSON.parse(lines[i]); }
151
- catch { return { ok: false, brokenAt: i, reason: 'not JSON' }; }
152
- if (entry.prev !== expectedPrev) {
153
- return { ok: false, brokenAt: i, expected: expectedPrev, got: entry.prev };
154
- }
155
- expectedPrev = _sha(lines[i]);
156
- }
157
- return { ok: true, entries: lines.length };
158
- }
159
-
160
-
161
- /***/ })
162
-
163
- };