@clear-capabilities/agentic-security-scanner 0.172.1 → 0.174.0

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 (201) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/agentic-security.js +37 -0
  3. package/dist/119.index.js +197 -0
  4. package/dist/128.index.js +129 -0
  5. package/dist/1293.index.js +5689 -0
  6. package/dist/1351.index.js +133 -0
  7. package/dist/1387.index.js +271 -0
  8. package/dist/1437.index.js +23 -0
  9. package/dist/1445.index.js +110 -0
  10. package/dist/1505.index.js +151 -0
  11. package/dist/1557.index.js +293 -0
  12. package/dist/1621.index.js +311 -0
  13. package/dist/1751.index.js +160 -0
  14. package/dist/1765.index.js +727 -0
  15. package/dist/1861.index.js +690 -0
  16. package/dist/1898.index.js +116 -0
  17. package/dist/1906.index.js +433 -0
  18. package/dist/1920.index.js +1 -291
  19. package/dist/208.index.js +1631 -0
  20. package/dist/2199.index.js +0 -239
  21. package/dist/2223.index.js +159 -0
  22. package/dist/2530.index.js +152 -0
  23. package/dist/2565.index.js +116 -0
  24. package/dist/2574.index.js +127 -0
  25. package/dist/2620.index.js +345 -0
  26. package/dist/2773.index.js +98 -0
  27. package/dist/2775.index.js +32 -0
  28. package/dist/2966.index.js +171 -0
  29. package/dist/2972.index.js +87 -0
  30. package/dist/2991.index.js +131 -0
  31. package/dist/3063.index.js +833 -0
  32. package/dist/3075.index.js +227 -0
  33. package/dist/3121.index.js +297 -0
  34. package/dist/3314.index.js +263 -0
  35. package/dist/3480.index.js +1822 -0
  36. package/dist/3481.index.js +24 -0
  37. package/dist/3506.index.js +268 -0
  38. package/dist/3533.index.js +591 -0
  39. package/dist/3653.index.js +450 -0
  40. package/dist/3822.index.js +131 -0
  41. package/dist/3868.index.js +292 -0
  42. package/dist/3917.index.js +152 -0
  43. package/dist/3918.index.js +192 -0
  44. package/dist/4090.index.js +288 -0
  45. package/dist/4121.index.js +1190 -0
  46. package/dist/4142.index.js +0 -239
  47. package/dist/4336.index.js +436 -0
  48. package/dist/4387.index.js +532 -0
  49. package/dist/4413.index.js +302 -0
  50. package/dist/4476.index.js +4510 -0
  51. package/dist/4491.index.js +133 -0
  52. package/dist/4538.index.js +185 -0
  53. package/dist/4684.index.js +319 -0
  54. package/dist/4740.index.js +165 -0
  55. package/dist/4780.index.js +209 -0
  56. package/dist/4802.index.js +225 -0
  57. package/dist/4812.index.js +379 -0
  58. package/dist/4835.index.js +126 -0
  59. package/dist/494.index.js +226 -0
  60. package/dist/4991.index.js +549 -0
  61. package/dist/5199.index.js +305 -0
  62. package/dist/5216.index.js +109 -0
  63. package/dist/5439.index.js +301 -0
  64. package/dist/5443.index.js +97 -0
  65. package/dist/5496.index.js +377 -0
  66. package/dist/550.index.js +2 -1
  67. package/dist/5801.index.js +588 -0
  68. package/dist/5858.index.js +223 -0
  69. package/dist/5933.index.js +143 -0
  70. package/dist/601.index.js +23 -0
  71. package/dist/6173.index.js +273 -0
  72. package/dist/6291.index.js +1871 -0
  73. package/dist/6312.index.js +666 -0
  74. package/dist/634.index.js +16182 -0
  75. package/dist/644.index.js +87 -0
  76. package/dist/6508.index.js +283 -0
  77. package/dist/6560.index.js +645 -0
  78. package/dist/6576.index.js +422 -0
  79. package/dist/6579.index.js +271 -0
  80. package/dist/6745.index.js +305 -0
  81. package/dist/6818.index.js +21642 -0
  82. package/dist/6972.index.js +167 -0
  83. package/dist/6995.index.js +163 -0
  84. package/dist/7249.index.js +191 -0
  85. package/dist/7372.index.js +1197 -0
  86. package/dist/7546.index.js +541 -0
  87. package/dist/76.index.js +448 -0
  88. package/dist/7879.index.js +163 -0
  89. package/dist/8025.index.js +130 -0
  90. package/dist/8054.index.js +248 -0
  91. package/dist/8097.index.js +201 -0
  92. package/dist/8126.index.js +191 -0
  93. package/dist/8178.index.js +78 -0
  94. package/dist/818.index.js +212 -0
  95. package/dist/8382.index.js +93 -0
  96. package/dist/8408.index.js +261 -0
  97. package/dist/8540.index.js +174 -0
  98. package/dist/8635.index.js +802 -0
  99. package/dist/8688.index.js +410 -0
  100. package/dist/8699.index.js +779 -0
  101. package/dist/8891.index.js +457 -0
  102. package/dist/8916.index.js +885 -0
  103. package/dist/9146.index.js +170 -0
  104. package/dist/9168.index.js +841 -0
  105. package/dist/9292.index.js +369 -0
  106. package/dist/9382.index.js +135 -11
  107. package/dist/9422.index.js +866 -0
  108. package/dist/9440.index.js +453 -0
  109. package/dist/9479.index.js +152 -0
  110. package/dist/9481.index.js +220 -0
  111. package/dist/9539.index.js +100 -0
  112. package/dist/9547.index.js +103 -0
  113. package/dist/9560.index.js +41 -61
  114. package/dist/9692.index.js +90 -0
  115. package/dist/9800.index.js +213 -1
  116. package/dist/9899.index.js +138 -0
  117. package/dist/9981.index.js +206 -0
  118. package/dist/999.index.js +117 -0
  119. package/dist/agentic-security.mjs +27 -27
  120. package/dist/agentic-security.mjs.sha256 +1 -1
  121. package/package.json +31 -6
  122. package/src/context-intelligence/candidates.js +2 -1
  123. package/src/context-intelligence/capabilities.js +2 -1
  124. package/src/context-intelligence/cli.js +2 -0
  125. package/src/context-intelligence/config.js +3 -1
  126. package/src/context-intelligence/controls.js +49 -1
  127. package/src/context-intelligence/counterexample.js +200 -0
  128. package/src/context-intelligence/cross-route.js +111 -0
  129. package/src/context-intelligence/findings.js +166 -0
  130. package/src/context-intelligence/invariants.js +197 -0
  131. package/src/context-intelligence/queue.js +6 -1
  132. package/src/context-intelligence/reachability.js +2 -2
  133. package/src/context-intelligence/review.js +68 -4
  134. package/src/dataflow/access-paths.js +11 -0
  135. package/src/dataflow/analysis-limits.js +66 -0
  136. package/src/dataflow/engine.js +282 -24
  137. package/src/dataflow/service-edges.js +174 -0
  138. package/src/dataflow/summaries.js +7 -1
  139. package/src/discovery/judge.js +2 -1
  140. package/src/engine.js +82 -14
  141. package/src/fix/apply-fix-service.js +55 -8
  142. package/src/fix/batch-journal.js +84 -0
  143. package/src/fix/exploit-replay.js +51 -0
  144. package/src/fix/footprint.js +65 -0
  145. package/src/fix/patch-policy.js +49 -0
  146. package/src/fix/secret-persistence.js +39 -0
  147. package/src/fix/test-commands.js +84 -0
  148. package/src/fix/verification-adapters.js +97 -0
  149. package/src/fix/verification-record.js +243 -0
  150. package/src/fix/verify-patch.js +89 -0
  151. package/src/ir/CLAUDE.md +5 -0
  152. package/src/ir/callgraph.js +23 -1
  153. package/src/ir/module-resolver.js +315 -0
  154. package/src/ir/parser-js.js +21 -2
  155. package/src/ir/semantic-graph.js +245 -0
  156. package/src/language/haskell-findings.js +2 -0
  157. package/src/language/haskell-guards.js +1 -1
  158. package/src/language/haskell-ir.js +3 -1
  159. package/src/language/haskell-models.js +94 -0
  160. package/src/language/haskell-security-rules.js +20 -0
  161. package/src/language/nixos-hardening.js +72 -0
  162. package/src/lineage/build-attested-correlation.js +63 -0
  163. package/src/lineage/deployment-identity.js +76 -0
  164. package/src/mcp/tools.js +13 -34
  165. package/src/pipeline/canonical-output.js +64 -0
  166. package/src/pipeline/detector-runner.js +7 -0
  167. package/src/pipeline/scan-health.js +14 -0
  168. package/src/pipeline/supervised-analyzers.js +125 -0
  169. package/src/pipeline/supervised-child.js +17 -0
  170. package/src/pipeline/telemetry.js +79 -0
  171. package/src/posture/accuracy-scorecard.js +98 -9
  172. package/src/posture/artifact-registry.js +5 -0
  173. package/src/posture/assurance/evidence-registry.js +215 -0
  174. package/src/posture/assurance/release-package-verify.js +135 -0
  175. package/src/posture/assurance/release-package.js +118 -0
  176. package/src/posture/assurance/release-verdict.js +94 -0
  177. package/src/posture/assurance/waiver.js +200 -0
  178. package/src/posture/compliance-truth.js +100 -0
  179. package/src/posture/evaluation/scan-child.js +3 -1
  180. package/src/posture/evaluation/why-missed.js +3 -1
  181. package/src/posture/exploitability-evidence.js +141 -0
  182. package/src/posture/fix-verify.js +4 -2
  183. package/src/posture/fleet/exposure-ranking.js +115 -0
  184. package/src/posture/fleet/service-chain-graph.js +150 -0
  185. package/src/posture/graph-reconciliation.js +154 -0
  186. package/src/posture/provenance/coverage.js +103 -0
  187. package/src/posture/provenance/git-evidence.js +84 -0
  188. package/src/posture/provenance/origin-resolver.js +7 -1
  189. package/src/posture/provenance/predicate-replay.js +14 -4
  190. package/src/posture/runtime-correlation.js +61 -15
  191. package/src/posture/runtime-explanation.js +116 -0
  192. package/src/posture/runtime-trace-ingest.js +194 -0
  193. package/src/posture/scan-checkpoint.js +38 -10
  194. package/src/posture/waf-ingest.js +7 -2
  195. package/src/report/index.js +4 -0
  196. package/src/report/oscal.js +38 -0
  197. package/src/report/perspectives.js +150 -0
  198. package/src/sast/_comment-strip.js +8 -1
  199. package/src/sast/ruby.js +26 -0
  200. package/src/sast/zip-slip.js +79 -0
  201. package/src/util/string-memo.js +38 -0
@@ -0,0 +1,448 @@
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
+ };
@@ -0,0 +1,163 @@
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
+ };