@clear-capabilities/agentic-security-scanner 0.74.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 (331) hide show
  1. package/CHANGELOG.md +1580 -0
  2. package/bin/.agentic-security/findings.json +1577 -0
  3. package/bin/.agentic-security/last-scan.json +1577 -0
  4. package/bin/.agentic-security/last-scan.json.sig +1 -0
  5. package/bin/.agentic-security/scan-history.json +465 -0
  6. package/bin/.agentic-security/streak.json +25 -0
  7. package/bin/agentic-security-audit.js +198 -0
  8. package/bin/agentic-security-consistency.js +80 -0
  9. package/bin/agentic-security-diff.js +136 -0
  10. package/bin/agentic-security-lsp.js +12 -0
  11. package/bin/agentic-security-mcp.js +40 -0
  12. package/bin/agentic-security-rule.js +153 -0
  13. package/bin/agentic-security.js +1683 -0
  14. package/dist/117.index.js +207 -0
  15. package/dist/178.index.js +250 -0
  16. package/dist/218.index.js +793 -0
  17. package/dist/227.index.js +192 -0
  18. package/dist/301.index.js +167 -0
  19. package/dist/384.index.js +18 -0
  20. package/dist/476.index.js +126 -0
  21. package/dist/513.index.js +373 -0
  22. package/dist/520.index.js +13 -0
  23. package/dist/601.index.js +1038 -0
  24. package/dist/634.index.js +1892 -0
  25. package/dist/637.index.js +216 -0
  26. package/dist/660.index.js +131 -0
  27. package/dist/675.index.js +451 -0
  28. package/dist/826.index.js +188 -0
  29. package/dist/830.index.js +133 -0
  30. package/dist/agentic-security.mjs +272 -0
  31. package/dist/agentic-security.mjs.sha256 +1 -0
  32. package/dist/calibration-seed.json +27 -0
  33. package/package.json +77 -0
  34. package/src/.agentic-security/findings.json +80844 -0
  35. package/src/.agentic-security/last-scan.json +80844 -0
  36. package/src/.agentic-security/last-scan.json.sig +1 -0
  37. package/src/.agentic-security/scan-history.json +8408 -0
  38. package/src/.agentic-security/streak.json +26 -0
  39. package/src/badge.js +188 -0
  40. package/src/compare.js +203 -0
  41. package/src/dataflow/.agentic-security/findings.json +3487 -0
  42. package/src/dataflow/.agentic-security/last-scan.json +3487 -0
  43. package/src/dataflow/.agentic-security/last-scan.json.sig +1 -0
  44. package/src/dataflow/.agentic-security/scan-history.json +735 -0
  45. package/src/dataflow/.agentic-security/streak.json +24 -0
  46. package/src/dataflow/CLAUDE.md +38 -0
  47. package/src/dataflow/access-paths.js +172 -0
  48. package/src/dataflow/async-sequencing.js +177 -0
  49. package/src/dataflow/backward.js +201 -0
  50. package/src/dataflow/catalog-expanded.js +485 -0
  51. package/src/dataflow/catalog.js +659 -0
  52. package/src/dataflow/cross-repo.js +219 -0
  53. package/src/dataflow/engine.js +588 -0
  54. package/src/dataflow/exception-flow.js +116 -0
  55. package/src/dataflow/exploit-prover.js +187 -0
  56. package/src/dataflow/higher-order.js +221 -0
  57. package/src/dataflow/ifds.js +347 -0
  58. package/src/dataflow/implicit-flow.js +129 -0
  59. package/src/dataflow/incremental.js +229 -0
  60. package/src/dataflow/index.js +181 -0
  61. package/src/dataflow/numeric-domain.js +192 -0
  62. package/src/dataflow/path-feasibility.js +114 -0
  63. package/src/dataflow/points-to.js +337 -0
  64. package/src/dataflow/polyglot.js +190 -0
  65. package/src/dataflow/proven-clean.js +159 -0
  66. package/src/dataflow/receiver-context.js +76 -0
  67. package/src/dataflow/sanitizer-proof.js +154 -0
  68. package/src/dataflow/soft-taint.js +140 -0
  69. package/src/dataflow/string-domain.js +234 -0
  70. package/src/dataflow/stub-aware-filter.js +100 -0
  71. package/src/dataflow/summaries.js +132 -0
  72. package/src/dataflow/symbolic-exec.js +238 -0
  73. package/src/dataflow/tabulation.js +135 -0
  74. package/src/engine.js +7763 -0
  75. package/src/history-scan.js +229 -0
  76. package/src/index.js +3 -0
  77. package/src/integrations/.agentic-security/findings.json +1504 -0
  78. package/src/integrations/.agentic-security/last-scan.json +1504 -0
  79. package/src/integrations/.agentic-security/scan-history.json +40 -0
  80. package/src/integrations/.agentic-security/streak.json +21 -0
  81. package/src/integrations/index.js +321 -0
  82. package/src/integrations/tickets.js +200 -0
  83. package/src/ir/.agentic-security/findings.json +3036 -0
  84. package/src/ir/.agentic-security/last-scan.json +3036 -0
  85. package/src/ir/.agentic-security/last-scan.json.sig +1 -0
  86. package/src/ir/.agentic-security/scan-history.json +364 -0
  87. package/src/ir/.agentic-security/streak.json +23 -0
  88. package/src/ir/CLAUDE.md +172 -0
  89. package/src/ir/callgraph.js +73 -0
  90. package/src/ir/class-hierarchy.js +195 -0
  91. package/src/ir/index.js +152 -0
  92. package/src/ir/parser-cs.js +260 -0
  93. package/src/ir/parser-java.js +286 -0
  94. package/src/ir/parser-js.js +413 -0
  95. package/src/ir/parser-kt.js +258 -0
  96. package/src/ir/parser-py-cst.js +136 -0
  97. package/src/ir/parser-py.helper.py +501 -0
  98. package/src/ir/parser-py.js +312 -0
  99. package/src/ir/ssa.js +315 -0
  100. package/src/ir/type-stubs.js +288 -0
  101. package/src/leaderboard.js +152 -0
  102. package/src/llm-validator/.agentic-security/findings.json +1891 -0
  103. package/src/llm-validator/.agentic-security/last-scan.json +1891 -0
  104. package/src/llm-validator/.agentic-security/last-scan.json.sig +1 -0
  105. package/src/llm-validator/.agentic-security/scan-history.json +168 -0
  106. package/src/llm-validator/.agentic-security/streak.json +20 -0
  107. package/src/llm-validator/consistency.js +141 -0
  108. package/src/llm-validator/index.js +437 -0
  109. package/src/lsp/.agentic-security/findings.json +28 -0
  110. package/src/lsp/.agentic-security/last-scan.json +28 -0
  111. package/src/lsp/.agentic-security/scan-history.json +79 -0
  112. package/src/lsp/.agentic-security/streak.json +22 -0
  113. package/src/lsp/server.js +275 -0
  114. package/src/mcp/.agentic-security/findings.json +8358 -0
  115. package/src/mcp/.agentic-security/last-scan.json +8358 -0
  116. package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
  117. package/src/mcp/.agentic-security/scan-history.json +1125 -0
  118. package/src/mcp/.agentic-security/streak.json +22 -0
  119. package/src/mcp/CLAUDE.md +54 -0
  120. package/src/mcp/audit.js +136 -0
  121. package/src/mcp/redact.js +75 -0
  122. package/src/mcp/server.js +158 -0
  123. package/src/mcp/stdio.js +83 -0
  124. package/src/mcp/tools.js +940 -0
  125. package/src/mcp/validate.js +49 -0
  126. package/src/personality.js +164 -0
  127. package/src/poc-video.js +239 -0
  128. package/src/posture/.agentic-security/findings.json +51239 -0
  129. package/src/posture/.agentic-security/last-scan.json +51239 -0
  130. package/src/posture/.agentic-security/last-scan.json.sig +1 -0
  131. package/src/posture/.agentic-security/scan-history.json +5557 -0
  132. package/src/posture/.agentic-security/streak.json +24 -0
  133. package/src/posture/CLAUDE.md +42 -0
  134. package/src/posture/adversarial-self-test.js +114 -0
  135. package/src/posture/adversary-agent.js +204 -0
  136. package/src/posture/agents-memory.js +135 -0
  137. package/src/posture/ai-code-fingerprint.js +171 -0
  138. package/src/posture/aibom.js +284 -0
  139. package/src/posture/api-inventory.js +96 -0
  140. package/src/posture/attack-playbooks.js +305 -0
  141. package/src/posture/auditor-agent.js +115 -0
  142. package/src/posture/auth-posture-import.js +135 -0
  143. package/src/posture/baseline-compare.js +114 -0
  144. package/src/posture/blast-radius.js +836 -0
  145. package/src/posture/bounty-prediction.js +141 -0
  146. package/src/posture/business-logic.js +239 -0
  147. package/src/posture/calibration-drift.js +93 -0
  148. package/src/posture/calibration-seed.json +27 -0
  149. package/src/posture/calibration.js +204 -0
  150. package/src/posture/clustering.js +75 -0
  151. package/src/posture/concurrency-checker.js +265 -0
  152. package/src/posture/confidence.js +65 -0
  153. package/src/posture/container-runtime.js +149 -0
  154. package/src/posture/counterfactual.js +109 -0
  155. package/src/posture/cross-lang-graphql.js +165 -0
  156. package/src/posture/cross-lang-grpc.js +166 -0
  157. package/src/posture/cross-lang-meta.js +101 -0
  158. package/src/posture/cross-lang-openapi.js +187 -0
  159. package/src/posture/cross-lang-orm.js +153 -0
  160. package/src/posture/cross-lang-queues.js +210 -0
  161. package/src/posture/crown-jewels.js +110 -0
  162. package/src/posture/custom-rules.js +361 -0
  163. package/src/posture/cve-alert-daemon.js +433 -0
  164. package/src/posture/cve-lookup.js +129 -0
  165. package/src/posture/dead-code.js +430 -0
  166. package/src/posture/defender-agent.js +158 -0
  167. package/src/posture/deploy-platform.js +204 -0
  168. package/src/posture/detector-fuzz.js +61 -0
  169. package/src/posture/deterministic.js +99 -0
  170. package/src/posture/drift.js +165 -0
  171. package/src/posture/epss.js +156 -0
  172. package/src/posture/exploitability-probability.js +212 -0
  173. package/src/posture/exploitability.js +121 -0
  174. package/src/posture/feature-flags.js +110 -0
  175. package/src/posture/finding-defaults.js +132 -0
  176. package/src/posture/fix-history.js +411 -0
  177. package/src/posture/fix-plan.js +121 -0
  178. package/src/posture/fix-verify-loop.js +157 -0
  179. package/src/posture/fix-verify.js +130 -0
  180. package/src/posture/flow-narration.js +105 -0
  181. package/src/posture/grader-calibration.js +156 -0
  182. package/src/posture/harness-discovery.js +113 -0
  183. package/src/posture/holdout-eval.js +144 -0
  184. package/src/posture/iac-reachability.js +163 -0
  185. package/src/posture/iam-policy.js +128 -0
  186. package/src/posture/integrity.js +97 -0
  187. package/src/posture/learning.js +166 -0
  188. package/src/posture/license-policy.js +109 -0
  189. package/src/posture/llm-redteam-prompts.js +418 -0
  190. package/src/posture/llm-redteam.js +303 -0
  191. package/src/posture/material-change.js +163 -0
  192. package/src/posture/mitigation-composite.js +55 -0
  193. package/src/posture/mttr.js +91 -0
  194. package/src/posture/network-policy-import.js +126 -0
  195. package/src/posture/path-predicates.js +99 -0
  196. package/src/posture/persona-prioritization.js +153 -0
  197. package/src/posture/poc-cwe-map.js +51 -0
  198. package/src/posture/poc-generator.js +500 -0
  199. package/src/posture/policy-gate.js +174 -0
  200. package/src/posture/pre-incident-archaeology.js +110 -0
  201. package/src/posture/profile.js +93 -0
  202. package/src/posture/reachability-filter.js +42 -0
  203. package/src/posture/regression-test-gen.js +200 -0
  204. package/src/posture/reverse-blast-radius.js +110 -0
  205. package/src/posture/router.js +109 -0
  206. package/src/posture/rule-overrides.js +198 -0
  207. package/src/posture/rule-pack-signing.js +209 -0
  208. package/src/posture/rule-packs.js +143 -0
  209. package/src/posture/rule-synthesis.js +108 -0
  210. package/src/posture/ruleset-version.js +71 -0
  211. package/src/posture/sbom.js +129 -0
  212. package/src/posture/schema-aware-bridge.js +207 -0
  213. package/src/posture/security-trend.js +87 -0
  214. package/src/posture/semantic-clone.js +114 -0
  215. package/src/posture/specification-mining.js +170 -0
  216. package/src/posture/stable-id.js +75 -0
  217. package/src/posture/stack-playbook.js +229 -0
  218. package/src/posture/streak.js +249 -0
  219. package/src/posture/suppressions.js +135 -0
  220. package/src/posture/telemetry-ingest.js +112 -0
  221. package/src/posture/threat-model.js +145 -0
  222. package/src/posture/three-agent-pipeline.js +74 -0
  223. package/src/posture/triage.js +146 -0
  224. package/src/posture/trust-boundary-diagram.js +115 -0
  225. package/src/posture/type-narrowing.js +129 -0
  226. package/src/posture/validator-metrics.js +179 -0
  227. package/src/posture/verifier-ephemeral.js +118 -0
  228. package/src/posture/verifier-target.js +147 -0
  229. package/src/posture/verifier.js +257 -0
  230. package/src/posture/version.js +75 -0
  231. package/src/posture/waf-ingest.js +200 -0
  232. package/src/posture/why-fired.js +141 -0
  233. package/src/pr-comment.js +172 -0
  234. package/src/pr-delta.js +198 -0
  235. package/src/report/.agentic-security/findings.json +79 -0
  236. package/src/report/.agentic-security/last-scan.json +79 -0
  237. package/src/report/.agentic-security/last-scan.json.sig +1 -0
  238. package/src/report/.agentic-security/scan-history.json +332 -0
  239. package/src/report/.agentic-security/streak.json +23 -0
  240. package/src/report/index.js +1136 -0
  241. package/src/report/mascot.js +42 -0
  242. package/src/runScan.js +141 -0
  243. package/src/sast/.agentic-security/findings.json +5051 -0
  244. package/src/sast/.agentic-security/last-scan.json +5051 -0
  245. package/src/sast/.agentic-security/last-scan.json.sig +1 -0
  246. package/src/sast/.agentic-security/scan-history.json +788 -0
  247. package/src/sast/.agentic-security/streak.json +23 -0
  248. package/src/sast/CLAUDE.md +39 -0
  249. package/src/sast/_comment-strip.js +46 -0
  250. package/src/sast/agent-tool-escalation.js +131 -0
  251. package/src/sast/auth-provider.js +171 -0
  252. package/src/sast/authz.js +236 -0
  253. package/src/sast/bench-shape/.agentic-security/findings.json +28 -0
  254. package/src/sast/bench-shape/.agentic-security/last-scan.json +28 -0
  255. package/src/sast/bench-shape/.agentic-security/scan-history.json +24 -0
  256. package/src/sast/bench-shape/.agentic-security/streak.json +22 -0
  257. package/src/sast/bench-shape/index.js +62 -0
  258. package/src/sast/claude-hook-injection.js +199 -0
  259. package/src/sast/claude-md-prompt-injection.js +170 -0
  260. package/src/sast/claude-settings.js +165 -0
  261. package/src/sast/client-side.js +149 -0
  262. package/src/sast/cpp-bench-extras.js +122 -0
  263. package/src/sast/cpp-dataflow.js +430 -0
  264. package/src/sast/cpp.js +248 -0
  265. package/src/sast/csharp.js +152 -0
  266. package/src/sast/csrf.js +82 -0
  267. package/src/sast/dart-flutter.js +173 -0
  268. package/src/sast/db-rls.js +147 -0
  269. package/src/sast/db-taint.js +215 -0
  270. package/src/sast/defi-deep.js +242 -0
  271. package/src/sast/deserialization-gadgets.js +113 -0
  272. package/src/sast/django-hardening.js +230 -0
  273. package/src/sast/env-hygiene.js +125 -0
  274. package/src/sast/fastapi-hardening.js +145 -0
  275. package/src/sast/go-extended.js +84 -0
  276. package/src/sast/host-header.js +106 -0
  277. package/src/sast/index.js +17 -0
  278. package/src/sast/java-ast-folding.js +561 -0
  279. package/src/sast/java-bench-extras.js +708 -0
  280. package/src/sast/java-collection-passthrough.js +178 -0
  281. package/src/sast/java-constant-fold.js +244 -0
  282. package/src/sast/java-deserialization.js +125 -0
  283. package/src/sast/jndi.js +104 -0
  284. package/src/sast/juliet-shape.js +324 -0
  285. package/src/sast/jwt-exp.js +104 -0
  286. package/src/sast/kotlin.js +82 -0
  287. package/src/sast/laravel-hardening.js +198 -0
  288. package/src/sast/ldap-injection.js +100 -0
  289. package/src/sast/llm-owasp.js +465 -0
  290. package/src/sast/llm-stored-prompt.js +103 -0
  291. package/src/sast/llm-trading-agent.js +161 -0
  292. package/src/sast/llm.js +308 -0
  293. package/src/sast/logic.js +140 -0
  294. package/src/sast/mass-assignment.js +101 -0
  295. package/src/sast/mcp-audit.js +242 -0
  296. package/src/sast/mobile-manifest.js +195 -0
  297. package/src/sast/model-load.js +164 -0
  298. package/src/sast/mutation-xss.js +87 -0
  299. package/src/sast/nosql-injection.js +82 -0
  300. package/src/sast/open-redirect.js +119 -0
  301. package/src/sast/php.js +91 -0
  302. package/src/sast/pipeline.js +122 -0
  303. package/src/sast/primary-cwe-java.js +155 -0
  304. package/src/sast/prompt-firewall.js +151 -0
  305. package/src/sast/prompt-template.js +157 -0
  306. package/src/sast/prototype-pollution.js +112 -0
  307. package/src/sast/python-sinks.js +195 -0
  308. package/src/sast/quarkus-hardening.js +102 -0
  309. package/src/sast/rag-poisoning.js +118 -0
  310. package/src/sast/rate-limit.js +128 -0
  311. package/src/sast/response-splitting.js +138 -0
  312. package/src/sast/ruby.js +108 -0
  313. package/src/sast/rust.js +105 -0
  314. package/src/sast/solidity.js +167 -0
  315. package/src/sast/springboot-hardening.js +186 -0
  316. package/src/sast/ssrf-cloud-metadata.js +80 -0
  317. package/src/sast/ssti.js +116 -0
  318. package/src/sast/swift.js +162 -0
  319. package/src/sast/toctou.js +95 -0
  320. package/src/sast/webhook.js +101 -0
  321. package/src/sast/xpath-injection.js +51 -0
  322. package/src/sast/xxe.js +140 -0
  323. package/src/sast/zip-slip.js +200 -0
  324. package/src/sca/base-images.json +45 -0
  325. package/src/sca/container.js +107 -0
  326. package/src/sca/dep-confusion.js +134 -0
  327. package/src/sca/index.js +6 -0
  328. package/src/sca/popular-packages.json +41 -0
  329. package/src/sca/sarif-ingest.js +187 -0
  330. package/src/sca/vuln-function-hints.json +89 -0
  331. package/src/secrets/index.js +4 -0
@@ -0,0 +1,275 @@
1
+ // Minimal LSP server for agentic-security.
2
+ //
3
+ // Speaks the Language Server Protocol over stdio. On every textDocument/
4
+ // didSave (and didOpen), the server runs runScan on the file and emits
5
+ // textDocument/publishDiagnostics with the resulting findings mapped to
6
+ // LSP Diagnostic objects.
7
+ //
8
+ // This is a STARTER implementation — feature-complete enough that JetBrains
9
+ // (via LSP4IJ) and Neovim (via built-in LSP) can both attach and see
10
+ // findings inline. The full feature set (code actions for /fix, inline
11
+ // remediation hover, exploitability tooltip) is future work.
12
+ //
13
+ // Wire-format: vscode-jsonrpc framing (Content-Length headers). Stateless
14
+ // per file — no incremental analysis yet.
15
+
16
+ import * as fs from 'node:fs';
17
+ import * as path from 'node:path';
18
+ import * as readline from 'node:readline';
19
+ import { runScan } from '../runScan.js';
20
+ import { resetCustomRulesBudget } from '../posture/custom-rules.js';
21
+
22
+ const PROTOCOL_VERSION = '3.17';
23
+ const SERVER_NAME = 'agentic-security-lsp';
24
+ const SERVER_VERSION = '0.1.0';
25
+
26
+ let _rootUri = null;
27
+ let _rootDir = process.cwd();
28
+ let _stdoutMutex = Promise.resolve();
29
+ const _diagnosticsByUri = new Map();
30
+
31
+ function uriToPath(uri) {
32
+ if (!uri) return null;
33
+ if (uri.startsWith('file://')) return decodeURIComponent(uri.slice(7));
34
+ return uri;
35
+ }
36
+
37
+ function pathToUri(p) {
38
+ if (!p) return null;
39
+ if (p.startsWith('file://')) return p;
40
+ return 'file://' + encodeURI(path.resolve(p));
41
+ }
42
+
43
+ function sevToLsp(sev) {
44
+ switch ((sev || '').toLowerCase()) {
45
+ case 'critical': return 1; // Error
46
+ case 'high': return 1; // Error
47
+ case 'medium': return 2; // Warning
48
+ case 'low': return 3; // Information
49
+ default: return 4; // Hint
50
+ }
51
+ }
52
+
53
+ function findingToDiagnostic(f) {
54
+ const line = Math.max(0, (f.line || 1) - 1);
55
+ return {
56
+ range: {
57
+ start: { line, character: 0 },
58
+ end: { line, character: 200 },
59
+ },
60
+ severity: sevToLsp(f.severity),
61
+ source: 'agentic-security',
62
+ code: f.cwe || f.family || 'finding',
63
+ message: `${f.vuln || 'Security finding'}${f.remediation ? '\n\n' + (typeof f.remediation === 'string' ? f.remediation : '') : ''}`.slice(0, 2000),
64
+ tags: [],
65
+ };
66
+ }
67
+
68
+ function send(message) {
69
+ const json = JSON.stringify({ jsonrpc: '2.0', ...message });
70
+ _stdoutMutex = _stdoutMutex.then(() => new Promise(resolve => {
71
+ process.stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`, resolve);
72
+ }));
73
+ return _stdoutMutex;
74
+ }
75
+
76
+ async function publishDiagnostics(uri, findings) {
77
+ await send({
78
+ method: 'textDocument/publishDiagnostics',
79
+ params: { uri, diagnostics: findings.map(findingToDiagnostic) },
80
+ });
81
+ _diagnosticsByUri.set(uri, findings);
82
+ }
83
+
84
+ // Manifest / schema files that downstream passes (SCA, cross-language) read.
85
+ // We walk the project tree once per LSP session and cache these so the
86
+ // per-save scan has them.
87
+ const DEP_BASE_NAMES = new Set([
88
+ 'package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
89
+ 'requirements.txt', 'pyproject.toml', 'poetry.lock', 'Pipfile.lock',
90
+ 'composer.json', 'composer.lock', 'Gemfile', 'Gemfile.lock',
91
+ 'go.mod', 'Cargo.toml', 'Cargo.lock',
92
+ 'pom.xml', 'build.gradle', 'build.gradle.kts',
93
+ ]);
94
+ const DEP_EXT_RE = /\.(?:proto|graphql|gql|tf)$/i;
95
+ const DEP_NAME_RE = /(?:openapi|swagger)\.(?:ya?ml|json)$/i;
96
+
97
+ let _depCache = { rootDir: null, depFileContents: {} };
98
+
99
+ function _loadDepFileContents(rootDir) {
100
+ if (_depCache.rootDir === rootDir) return _depCache.depFileContents;
101
+ const out = {};
102
+ const skipDirs = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'target', 'vendor', '.bench-cache']);
103
+ function walk(dir) {
104
+ let entries;
105
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
106
+ for (const e of entries) {
107
+ if (skipDirs.has(e.name)) continue;
108
+ const full = path.join(dir, e.name);
109
+ if (e.isDirectory()) { walk(full); continue; }
110
+ if (!e.isFile()) continue;
111
+ const base = e.name;
112
+ if (DEP_BASE_NAMES.has(base) || DEP_EXT_RE.test(base) || DEP_NAME_RE.test(base)) {
113
+ let stat;
114
+ try { stat = fs.statSync(full); } catch { continue; }
115
+ if (stat.size > 500_000) continue;
116
+ try { out[path.relative(rootDir, full)] = fs.readFileSync(full, 'utf8'); }
117
+ catch { /* skip unreadable */ }
118
+ }
119
+ }
120
+ }
121
+ walk(rootDir);
122
+ _depCache = { rootDir, depFileContents: out };
123
+ return out;
124
+ }
125
+
126
+ async function scanFile(uri) {
127
+ const filePath = uriToPath(uri);
128
+ if (!filePath || !fs.existsSync(filePath)) return;
129
+ // Incremental scan (premortem 2R4.5 / 2R-10): hand runScan a single-file
130
+ // fileContents map for the saved code, AND a cached set of dep-manifest /
131
+ // schema files so SCA + cross-language passes have their inputs. Without
132
+ // depFileContents, the LSP path would silently drop CVE / OpenAPI / proto
133
+ // findings on the saved file.
134
+ try {
135
+ const rel = path.relative(_rootDir, filePath);
136
+ const content = fs.readFileSync(filePath, 'utf8');
137
+ const fileContents = { [rel]: content };
138
+ const depFileContents = _loadDepFileContents(_rootDir);
139
+ // Premortem 4R-12 + 4R-15: reset the per-process custom-rules budget at
140
+ // the start of each LSP scan. Each save is a logical scan session; without
141
+ // the reset, a long-lived LSP server would accumulate budget across saves
142
+ // and eventually start skipping custom rules.
143
+ resetCustomRulesBudget(_rootDir);
144
+ const { scan } = await runScan(_rootDir, { fileContents, depFileContents });
145
+ const findings = (scan.findings || []).filter(f => f.file === rel);
146
+ await publishDiagnostics(uri, findings);
147
+ } catch (e) {
148
+ process.stderr.write(`agentic-security-lsp: scan failed: ${e.message}\n`);
149
+ }
150
+ }
151
+
152
+ function handleInitialize(params) {
153
+ if (params.rootUri) {
154
+ _rootUri = params.rootUri;
155
+ _rootDir = uriToPath(params.rootUri) || process.cwd();
156
+ } else if (params.rootPath) {
157
+ _rootDir = params.rootPath;
158
+ }
159
+ return {
160
+ capabilities: {
161
+ textDocumentSync: {
162
+ openClose: true,
163
+ change: 1,
164
+ save: { includeText: false },
165
+ },
166
+ diagnosticProvider: { interFileDependencies: true, workspaceDiagnostics: false },
167
+ },
168
+ serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
169
+ };
170
+ }
171
+
172
+ async function handleMessage(msg) {
173
+ if (msg.method === 'initialize') {
174
+ return { id: msg.id, result: handleInitialize(msg.params || {}) };
175
+ }
176
+ if (msg.method === 'initialized' || msg.method === 'workspace/didChangeConfiguration') {
177
+ return null; // notification, no response
178
+ }
179
+ if (msg.method === 'shutdown') {
180
+ return { id: msg.id, result: null };
181
+ }
182
+ if (msg.method === 'exit') {
183
+ process.exit(0);
184
+ }
185
+ if (msg.method === 'textDocument/didOpen') {
186
+ const uri = msg.params?.textDocument?.uri;
187
+ if (uri) scanFile(uri);
188
+ return null;
189
+ }
190
+ if (msg.method === 'textDocument/didSave') {
191
+ const uri = msg.params?.textDocument?.uri;
192
+ if (uri) {
193
+ // Premortem 3R-9 / 4R-5: when the user saves a manifest file, the
194
+ // dep-cache entry for THAT file is stale. Granular invalidation (only
195
+ // re-read the saved file from disk) avoids the O(project) re-walk that
196
+ // 3R-9 introduced — important in monorepos where mass manifest edits
197
+ // would otherwise re-scan thousands of files per save.
198
+ const savedPath = uriToPath(uri);
199
+ if (savedPath && _depCache.rootDir === _rootDir) {
200
+ const base = path.basename(savedPath);
201
+ if (DEP_BASE_NAMES.has(base) || DEP_EXT_RE.test(base) || DEP_NAME_RE.test(base)) {
202
+ try {
203
+ const rel = path.relative(_rootDir, savedPath);
204
+ const st = fs.statSync(savedPath);
205
+ if (st.size <= 500_000) {
206
+ _depCache.depFileContents[rel] = fs.readFileSync(savedPath, 'utf8');
207
+ } else {
208
+ delete _depCache.depFileContents[rel];
209
+ }
210
+ } catch {
211
+ // File vanished between save event and stat — drop from cache.
212
+ try {
213
+ const rel = path.relative(_rootDir, savedPath);
214
+ delete _depCache.depFileContents[rel];
215
+ } catch {}
216
+ }
217
+ }
218
+ }
219
+ scanFile(uri);
220
+ }
221
+ return null;
222
+ }
223
+ if (msg.method === 'textDocument/didClose') {
224
+ const uri = msg.params?.textDocument?.uri;
225
+ if (uri) await publishDiagnostics(uri, []);
226
+ return null;
227
+ }
228
+ // Unknown method.
229
+ if (msg.id != null) {
230
+ return { id: msg.id, error: { code: -32601, message: `Method not found: ${msg.method}` } };
231
+ }
232
+ return null;
233
+ }
234
+
235
+ export function startLspServer() {
236
+ let buffer = Buffer.alloc(0);
237
+ let expected = -1;
238
+ process.stdin.on('data', async (chunk) => {
239
+ buffer = Buffer.concat([buffer, chunk]);
240
+ while (true) {
241
+ if (expected < 0) {
242
+ const headerEnd = buffer.indexOf('\r\n\r\n');
243
+ if (headerEnd < 0) break;
244
+ const headers = buffer.slice(0, headerEnd).toString('utf8');
245
+ const m = headers.match(/Content-Length:\s*(\d+)/i);
246
+ if (!m) {
247
+ process.stderr.write('agentic-security-lsp: missing Content-Length header\n');
248
+ buffer = buffer.slice(headerEnd + 4);
249
+ continue;
250
+ }
251
+ expected = parseInt(m[1], 10);
252
+ buffer = buffer.slice(headerEnd + 4);
253
+ }
254
+ if (buffer.length < expected) break;
255
+ const body = buffer.slice(0, expected).toString('utf8');
256
+ buffer = buffer.slice(expected);
257
+ expected = -1;
258
+ let msg;
259
+ try { msg = JSON.parse(body); }
260
+ catch { process.stderr.write('agentic-security-lsp: malformed JSON\n'); continue; }
261
+ try {
262
+ const response = await handleMessage(msg);
263
+ if (response) await send(response);
264
+ } catch (e) {
265
+ if (msg.id != null) await send({ id: msg.id, error: { code: -32603, message: e.message } });
266
+ }
267
+ }
268
+ });
269
+ process.stdin.on('end', () => process.exit(0));
270
+ }
271
+
272
+ // Allow direct invocation as a bin entry: `node lsp/server.js`.
273
+ if (import.meta.url === `file://${process.argv[1]}`) {
274
+ startLspServer();
275
+ }