gitnexus 1.6.8-rc.8 → 1.6.8

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 (439) hide show
  1. package/README.md +3 -3
  2. package/dist/_shared/graph/types.d.ts +31 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/language-detection.d.ts.map +1 -1
  5. package/dist/_shared/language-detection.js +11 -1
  6. package/dist/_shared/language-detection.js.map +1 -1
  7. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  8. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  9. package/dist/_shared/lbug/schema-constants.js +6 -0
  10. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  11. package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
  12. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  13. package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
  14. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  15. package/dist/cli/ai-context.d.ts +31 -1
  16. package/dist/cli/ai-context.js +18 -16
  17. package/dist/cli/analyze-config.js +11 -0
  18. package/dist/cli/analyze.d.ts +14 -0
  19. package/dist/cli/analyze.js +98 -0
  20. package/dist/cli/embedding-dims.d.ts +33 -0
  21. package/dist/cli/embedding-dims.js +39 -0
  22. package/dist/cli/eval-server.d.ts +8 -0
  23. package/dist/cli/eval-server.js +242 -2
  24. package/dist/cli/help-i18n.js +19 -0
  25. package/dist/cli/i18n/en.d.ts +15 -1
  26. package/dist/cli/i18n/en.js +15 -1
  27. package/dist/cli/i18n/resources.d.ts +29 -1
  28. package/dist/cli/i18n/zh-CN.d.ts +14 -0
  29. package/dist/cli/i18n/zh-CN.js +15 -1
  30. package/dist/cli/index.js +89 -1
  31. package/dist/cli/mcp.d.ts +6 -1
  32. package/dist/cli/mcp.js +26 -1
  33. package/dist/cli/optional-grammars.d.ts +6 -8
  34. package/dist/cli/optional-grammars.js +8 -11
  35. package/dist/cli/setup.d.ts +3 -1
  36. package/dist/cli/setup.js +67 -17
  37. package/dist/cli/skill-gen.js +2 -1
  38. package/dist/cli/tool.d.ts +19 -1
  39. package/dist/cli/tool.js +96 -3
  40. package/dist/core/embeddings/hf-env.d.ts +0 -66
  41. package/dist/core/embeddings/http-client.d.ts +7 -0
  42. package/dist/core/embeddings/http-client.js +5 -3
  43. package/dist/core/embeddings/server-mapping.d.ts +1 -1
  44. package/dist/core/embeddings/server-mapping.js +7 -3
  45. package/dist/core/graph/import-cycles.d.ts +10 -0
  46. package/dist/core/graph/import-cycles.js +103 -0
  47. package/dist/core/group/cross-impact.d.ts +2 -1
  48. package/dist/core/group/cross-impact.js +5 -1
  49. package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
  50. package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
  51. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  52. package/dist/core/group/extractors/http-route-extractor.js +22 -4
  53. package/dist/core/group/extractors/include-extractor.js +22 -13
  54. package/dist/core/group/sync.d.ts +27 -1
  55. package/dist/core/group/sync.js +0 -0
  56. package/dist/core/incremental/subgraph-extract.js +18 -1
  57. package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
  58. package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
  59. package/dist/core/ingestion/cfg/collect.d.ts +51 -0
  60. package/dist/core/ingestion/cfg/collect.js +77 -0
  61. package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
  62. package/dist/core/ingestion/cfg/control-dependence.js +182 -0
  63. package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
  64. package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
  65. package/dist/core/ingestion/cfg/emit.d.ts +293 -0
  66. package/dist/core/ingestion/cfg/emit.js +646 -0
  67. package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
  68. package/dist/core/ingestion/cfg/post-dominators.js +176 -0
  69. package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
  70. package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
  71. package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
  72. package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
  73. package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
  74. package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
  75. package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
  76. package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
  77. package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
  78. package/dist/core/ingestion/cfg/traversal-result.js +2 -0
  79. package/dist/core/ingestion/cfg/types.d.ts +274 -0
  80. package/dist/core/ingestion/cfg/types.js +13 -0
  81. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
  82. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
  83. package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
  84. package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
  85. package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
  86. package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
  87. package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
  88. package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
  89. package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
  90. package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
  91. package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
  92. package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
  93. package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
  94. package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
  95. package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
  96. package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
  97. package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
  98. package/dist/core/ingestion/cfg/visitors/go.js +638 -0
  99. package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
  100. package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
  101. package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
  102. package/dist/core/ingestion/cfg/visitors/java.js +812 -0
  103. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
  104. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
  105. package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
  106. package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
  107. package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
  108. package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
  109. package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
  110. package/dist/core/ingestion/cfg/visitors/php.js +721 -0
  111. package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
  112. package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
  113. package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
  114. package/dist/core/ingestion/cfg/visitors/python.js +558 -0
  115. package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
  116. package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
  117. package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
  118. package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
  119. package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
  120. package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
  121. package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
  122. package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
  123. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
  124. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
  125. package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
  126. package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
  127. package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
  128. package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
  129. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
  130. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
  131. package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
  132. package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
  133. package/dist/core/ingestion/import-resolvers/utils.js +2 -0
  134. package/dist/core/ingestion/language-provider.d.ts +23 -2
  135. package/dist/core/ingestion/languages/c-cpp.js +17 -5
  136. package/dist/core/ingestion/languages/cobol.js +4 -0
  137. package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
  138. package/dist/core/ingestion/languages/cpp/captures.js +50 -1
  139. package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
  140. package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
  141. package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
  142. package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
  143. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
  144. package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
  145. package/dist/core/ingestion/languages/cpp/query.js +23 -0
  146. package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
  147. package/dist/core/ingestion/languages/csharp.js +2 -0
  148. package/dist/core/ingestion/languages/dart.js +2 -0
  149. package/dist/core/ingestion/languages/go.js +2 -0
  150. package/dist/core/ingestion/languages/java.js +3 -0
  151. package/dist/core/ingestion/languages/kotlin/query.js +3 -2
  152. package/dist/core/ingestion/languages/kotlin.js +8 -1
  153. package/dist/core/ingestion/languages/php.js +2 -0
  154. package/dist/core/ingestion/languages/python.js +2 -0
  155. package/dist/core/ingestion/languages/ruby.js +2 -0
  156. package/dist/core/ingestion/languages/rust.js +2 -0
  157. package/dist/core/ingestion/languages/swift.js +2 -0
  158. package/dist/core/ingestion/languages/typescript.js +5 -0
  159. package/dist/core/ingestion/languages/vue.js +6 -0
  160. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
  161. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  162. package/dist/core/ingestion/method-types.d.ts +2 -0
  163. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  164. package/dist/core/ingestion/model/symbol-table.js +1 -0
  165. package/dist/core/ingestion/mro-processor.js +109 -0
  166. package/dist/core/ingestion/parsing-processor.js +44 -0
  167. package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
  168. package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
  169. package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
  170. package/dist/core/ingestion/pipeline-phases/index.js +2 -0
  171. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
  172. package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
  173. package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
  174. package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
  175. package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
  176. package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
  177. package/dist/core/ingestion/pipeline.d.ts +92 -10
  178. package/dist/core/ingestion/pipeline.js +11 -2
  179. package/dist/core/ingestion/scope-extractor.js +12 -2
  180. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
  181. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
  182. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
  183. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
  184. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
  185. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
  186. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
  187. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
  188. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
  189. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
  190. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
  191. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
  192. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
  193. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
  194. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
  195. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
  196. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
  197. package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
  198. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
  199. package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
  200. package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
  201. package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
  202. package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
  203. package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
  204. package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
  205. package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
  206. package/dist/core/ingestion/taint/call-summary-model.js +25 -0
  207. package/dist/core/ingestion/taint/emit.d.ts +125 -0
  208. package/dist/core/ingestion/taint/emit.js +208 -0
  209. package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
  210. package/dist/core/ingestion/taint/interproc-emit.js +88 -0
  211. package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
  212. package/dist/core/ingestion/taint/interproc-solver.js +312 -0
  213. package/dist/core/ingestion/taint/match.d.ts +153 -0
  214. package/dist/core/ingestion/taint/match.js +278 -0
  215. package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
  216. package/dist/core/ingestion/taint/path-codec.js +190 -0
  217. package/dist/core/ingestion/taint/propagate.d.ts +216 -0
  218. package/dist/core/ingestion/taint/propagate.js +659 -0
  219. package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
  220. package/dist/core/ingestion/taint/site-safety.js +98 -0
  221. package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
  222. package/dist/core/ingestion/taint/source-sink-config.js +35 -12
  223. package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
  224. package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
  225. package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
  226. package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
  227. package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
  228. package/dist/core/ingestion/taint/summary-harvest.js +537 -0
  229. package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
  230. package/dist/core/ingestion/taint/summary-model.js +86 -0
  231. package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
  232. package/dist/core/ingestion/taint/typescript-model.js +102 -0
  233. package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
  234. package/dist/core/ingestion/utils/env.d.ts +7 -0
  235. package/dist/core/ingestion/utils/env.js +12 -0
  236. package/dist/core/ingestion/utils/method-props.js +1 -0
  237. package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
  238. package/dist/core/ingestion/workers/clone-safety.js +465 -0
  239. package/dist/core/ingestion/workers/parse-worker.d.ts +23 -0
  240. package/dist/core/ingestion/workers/parse-worker.js +81 -53
  241. package/dist/core/ingestion/workers/post-result.d.ts +22 -0
  242. package/dist/core/ingestion/workers/post-result.js +87 -0
  243. package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
  244. package/dist/core/ingestion/workers/result-merge.js +43 -0
  245. package/dist/core/ingestion/workers/worker-pool.d.ts +15 -40
  246. package/dist/core/ingestion/workers/worker-pool.js +43 -16
  247. package/dist/core/lbug/csv-generator.d.ts +51 -3
  248. package/dist/core/lbug/csv-generator.js +369 -255
  249. package/dist/core/lbug/lbug-adapter.d.ts +58 -18
  250. package/dist/core/lbug/lbug-adapter.js +346 -84
  251. package/dist/core/lbug/lbug-config.d.ts +19 -0
  252. package/dist/core/lbug/lbug-config.js +29 -0
  253. package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
  254. package/dist/core/lbug/pdg-emit-sink.js +358 -0
  255. package/dist/core/lbug/pool-adapter.d.ts +35 -0
  256. package/dist/core/lbug/pool-adapter.js +90 -1
  257. package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
  258. package/dist/core/lbug/rel-pair-routing.js +140 -0
  259. package/dist/core/lbug/schema.d.ts +2 -2
  260. package/dist/core/lbug/schema.js +3 -0
  261. package/dist/core/logger.d.ts +0 -5
  262. package/dist/core/run-analyze.d.ts +96 -0
  263. package/dist/core/run-analyze.js +207 -3
  264. package/dist/core/tree-sitter/parser-loader.js +5 -4
  265. package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
  266. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  267. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  268. package/dist/core/wiki/generator.js +3 -1
  269. package/dist/core/wiki/graph-queries.d.ts +6 -0
  270. package/dist/core/wiki/graph-queries.js +9 -1
  271. package/dist/mcp/http-transport.d.ts +134 -0
  272. package/dist/mcp/http-transport.js +503 -0
  273. package/dist/mcp/local/local-backend.d.ts +110 -24
  274. package/dist/mcp/local/local-backend.js +1225 -19
  275. package/dist/mcp/local/pdg-impact.d.ts +384 -0
  276. package/dist/mcp/local/pdg-impact.js +1972 -0
  277. package/dist/mcp/resources.js +13 -1
  278. package/dist/mcp/tools.d.ts +12 -0
  279. package/dist/mcp/tools.js +214 -7
  280. package/dist/server/analyze-launch.d.ts +29 -0
  281. package/dist/server/analyze-launch.js +137 -0
  282. package/dist/server/analyze-upload.d.ts +33 -0
  283. package/dist/server/analyze-upload.js +123 -0
  284. package/dist/server/analyze-worker-ipc.d.ts +58 -0
  285. package/dist/server/analyze-worker-ipc.js +16 -0
  286. package/dist/server/analyze-worker.d.ts +17 -1
  287. package/dist/server/analyze-worker.js +7 -1
  288. package/dist/server/api.d.ts +17 -0
  289. package/dist/server/api.js +132 -168
  290. package/dist/server/git-clone.d.ts +43 -2
  291. package/dist/server/git-clone.js +211 -16
  292. package/dist/server/mcp-http.d.ts +5 -6
  293. package/dist/server/mcp-http.js +9 -82
  294. package/dist/server/middleware.d.ts +40 -0
  295. package/dist/server/middleware.js +92 -0
  296. package/dist/server/private-ip.d.ts +1 -0
  297. package/dist/server/private-ip.js +15 -0
  298. package/dist/server/upload-ingest.d.ts +56 -0
  299. package/dist/server/upload-ingest.js +276 -0
  300. package/dist/server/upload-paths.d.ts +37 -0
  301. package/dist/server/upload-paths.js +57 -0
  302. package/dist/server/upload-sweep.d.ts +20 -0
  303. package/dist/server/upload-sweep.js +57 -0
  304. package/dist/storage/git.js +5 -8
  305. package/dist/storage/parse-cache.d.ts +22 -1
  306. package/dist/storage/parse-cache.js +44 -10
  307. package/dist/storage/repo-manager.d.ts +121 -10
  308. package/dist/storage/repo-manager.js +43 -5
  309. package/dist/types/pipeline.d.ts +9 -0
  310. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
  311. package/hooks/claude/gitnexus-hook.cjs +110 -13
  312. package/hooks/claude/hook-db-lock-probe.cjs +513 -26
  313. package/package.json +8 -2
  314. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  315. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  316. package/scripts/cross-platform-tests.ts +1 -0
  317. package/skills/gitnexus-debugging.md +16 -4
  318. package/skills/gitnexus-exploring.md +3 -3
  319. package/skills/gitnexus-guide.md +33 -0
  320. package/skills/gitnexus-pdg-query.md +89 -0
  321. package/skills/gitnexus-refactoring.md +1 -1
  322. package/skills/gitnexus-taint-analysis.md +178 -0
  323. package/vendor/tree-sitter-c/package.json +1 -1
  324. package/vendor/tree-sitter-dart/package.json +1 -1
  325. package/vendor/tree-sitter-kotlin/package.json +1 -1
  326. package/vendor/tree-sitter-proto/package.json +1 -1
  327. package/vendor/tree-sitter-swift/package.json +1 -1
  328. package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
  329. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
  330. package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
  331. package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
  332. package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
  333. package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
  334. package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
  335. package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
  336. package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
  337. package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
  338. package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
  339. package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
  340. package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
  341. package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
  342. package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
  343. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
  344. package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
  345. package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
  346. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
  347. package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
  348. package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
  349. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
  350. package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
  351. package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
  352. package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
  353. package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
  354. package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
  355. package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
  356. package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
  357. package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
  358. package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
  359. package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
  360. package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
  361. package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
  362. package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
  363. package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
  364. package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
  365. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
  366. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
  367. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
  368. package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
  369. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
  370. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
  371. package/web/assets/index-BvYev1UJ.js +635 -0
  372. package/web/assets/index-_lgn7hs5.css +2 -0
  373. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
  374. package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
  375. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
  376. package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
  377. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
  378. package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
  379. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
  380. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
  381. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
  382. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
  383. package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
  384. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
  385. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
  386. package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
  387. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
  388. package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
  389. package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
  390. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
  391. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
  392. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
  393. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
  394. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
  395. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
  396. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
  397. package/web/index.html +13 -15
  398. package/scripts/materialize-vendor-grammars.cjs +0 -97
  399. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
  400. package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
  401. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
  402. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
  403. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
  404. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
  405. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
  406. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
  407. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
  408. package/web/assets/chunk-CilyBKbf.js +0 -1
  409. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
  410. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
  411. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
  412. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
  413. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
  414. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
  415. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
  416. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
  417. package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
  418. package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
  419. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
  420. package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
  421. package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
  422. package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
  423. package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
  424. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
  425. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
  426. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
  427. package/web/assets/index-BKWA-m7o.css +0 -2
  428. package/web/assets/index-CG6q8eTs.js +0 -626
  429. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
  430. package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
  431. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
  432. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
  433. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
  434. package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
  435. package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
  436. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
  437. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
  438. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
  439. /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
@@ -0,0 +1,659 @@
1
+ /**
2
+ * Pure intra-procedural taint propagation engine (#2083 M3 U3).
3
+ *
4
+ * Forward taint reachability over one function's reaching-definition facts
5
+ * (M2 `computeReachingDefs`) and matched taint sites (U2 `matchFunctionSites`)
6
+ * — sources in, findings + sanitizer kills + coverage status out. PURE AND
7
+ * DETERMINISTIC, mirroring the reaching-defs contract: no graph, no I/O, no
8
+ * logger; insertion-ordered worklist; explicitly sorted outputs; snapshot
9
+ * tests and content-derived edge ids (U4) rely on it.
10
+ *
11
+ * PRECONDITIONS: the caller gates the CFG through `hasTaintSafeSites`
12
+ * (taint/site-safety.ts) and the emit-safety checks before calling — this
13
+ * module dereferences binding/site/statement indices without re-validating.
14
+ *
15
+ * ## The two-rule model (plan HTD)
16
+ *
17
+ * - **Rule (b), statement-local:** a matched SOURCE occurrence (member read)
18
+ * whose intra-statement occurrence path — the member-read's `parent` chain —
19
+ * reaches a matched SINK argument position produces an immediate single-hop
20
+ * finding (`exec(req.body)`). The same statement SEEDS taint: every binding
21
+ * the statement defines becomes tainted (see the precision floor below).
22
+ * - **Rule (a), worklist:** for each tainted `(binding, defPoint)`, every
23
+ * def→use fact delivers the taint to a use statement, where occurrences of
24
+ * the binding in matched sink argument positions produce findings and the
25
+ * statement's own defs are tainted onward. The fact graph contains genuine
26
+ * cycles (loop back-edges, same-statement self-facts) — the visited-set
27
+ * discipline below is load-bearing, not defensive.
28
+ *
29
+ * ## Sanitizer semantics — the KIND-SET exclusion model (KTD4, sharpened)
30
+ *
31
+ * The plan sketches a binary kill; this module implements the strictly more
32
+ * precise SOUND refinement: a taint carries a set of *excluded* (neutralized)
33
+ * `SinkKind`s accumulated through sanitizer hops, and a sink fires unless its
34
+ * kind is in the taint's exclusion set. A binary kill is the special case
35
+ * where the sanitizer neutralizes the sink's kind; the kind-set model
36
+ * additionally keeps `const b = escape(req.body); db.query(b)` a FINDING
37
+ * (an HTML escaper does not neutralize SQL — un-tainting `b` outright would
38
+ * be a suppressed live injection, the forbidden false-negative direction)
39
+ * while still suppressing `res.send(b)` (xss IS neutralized).
40
+ *
41
+ * - **Occurrence interposition (KTD4a):** evaluated over the U1 site
42
+ * structure. An occurrence reaching a sink arg / def-feeding position
43
+ * through a matched sanitizer site accumulates that sanitizer's
44
+ * `neutralizes` kinds on that PATH; a direct occurrence contributes the
45
+ * empty set. Per-position narrowing (`entry.args`) is respected; receiver
46
+ * flow through a sanitizer is NOT neutralized (the receiver is not the
47
+ * sanitized payload), and spread/template positions are never neutralized
48
+ * (position unprovable) — both sound-direction choices (under-kill).
49
+ * - **Intersection over paths:** a def fed by several occurrence paths
50
+ * excludes a kind only when EVERY path neutralizes it
51
+ * (`const c = cond ? escape(b) : b` taints `c` with NO exclusions — the
52
+ * direct arm's ∅ intersects everything away). Equally, a taint re-derived
53
+ * along a second route keeps the INTERSECTION of the exclusion sets and is
54
+ * re-processed whenever the set SHRINKS — a less-neutralized taint is
55
+ * strictly more dangerous. Exclusion sets only shrink over a finite
56
+ * lattice, so the worklist terminates.
57
+ * - **Kill locality (KTD4b):** a kill applies to the def the sanitizer
58
+ * produces (`SiteRecord.resultDefs`) only; the flowing binding's own taint
59
+ * is untouched (`const c = escape(b); exec(b)` still finds `b`'s flow).
60
+ * `x = escape(x)` works because taint keys on the DEF POINT: the
61
+ * sanitizer statement's def enters the set with the sanitizer's kinds
62
+ * excluded, while the seed def keeps flowing wherever the CFG still
63
+ * carries it (zero-iteration loops, conditional sanitizers — may-path
64
+ * mechanics need no special handling here, kills are absent from facts).
65
+ *
66
+ * ## Statement-coalescing precision floor (documented FP)
67
+ *
68
+ * Statement facts conflate multi-declarator statements: a statement that
69
+ * uses tainted `b` and defines `c` taints `c` with NO exclusions even when
70
+ * the two are textually unrelated (`const a = clean(z), b = g(t)` floor-
71
+ * taints `a` from `t` — pinned by a test). The per-declarator `resultDefs`
72
+ * precision narrows the EXCLUSION computation (and powers kills) only — a
73
+ * def in a call's `resultDefs` is fed exactly through that call, so its
74
+ * exclusions come from the paths into it; when the tainted input provably
75
+ * never flows into that call, the floor still taints the def (sound) but
76
+ * records no kill (a kill requires evidence of flow through the sanitizer).
77
+ *
78
+ * ## Propagate-through (KTD5)
79
+ *
80
+ * Taint in any argument or in the receiver of an UNMODELED call flows to
81
+ * the call's result defs, marked `viaCall` on the hop so `explain` can
82
+ * express lower confidence. An occurrence that reaches the unmodeled call
83
+ * only through a sanitizer carries the neutralization through
84
+ * (`const y = unknownFn(escape(b))` excludes the sanitizer's kinds — the
85
+ * plan's deliberate precision choice over flat-conservative).
86
+ *
87
+ * ## Kills output
88
+ *
89
+ * `kills` records every sanitizer that ACTUALLY neutralized kinds on a
90
+ * flowing taint — U4 emits `SANITIZES` edges from them. Two shapes share the
91
+ * record: result-def kills (`killedDef` = the def the sanitizer produces;
92
+ * `bindingIdx` = that def's binding) and value-position interposition kills
93
+ * (`exec(escape(x))` — no def exists; `killedDef` = the sink statement's own
94
+ * point, `bindingIdx` = the interposed binding). Interposition kills are
95
+ * recorded only when the (input, sink, position) produced no finding — a
96
+ * bypassed sanitizer (`exec(x + escape(x))`) killed nothing.
97
+ */
98
+ import { pointKey } from '../cfg/reaching-defs.js';
99
+ import { SINK_KIND_ORDER as KIND_ORDER, sortSinkKinds as sortKinds, } from './source-sink-config.js';
100
+ /**
101
+ * Default per-function findings cap (U5 config resolution; cfg/emit.ts
102
+ * DEFAULT_* pattern). Resolved into the RepoMeta `pdg` stamp by
103
+ * `resolvePdgConfig` so a cap change trips full writeback; `0` = unlimited
104
+ * is preserved like the other pdg caps. 200 is generous — a real function
105
+ * with more deduped source→sink findings is a fixture or a disaster, and
106
+ * the truncation is deterministic + counted (`droppedFindings`).
107
+ */
108
+ export const DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION = 200;
109
+ /**
110
+ * Default per-finding hop cap (U5; joins the RepoMeta `pdg` stamp like the
111
+ * findings cap). Bounds the persisted `reason` hop encoding (KTD6 pins the
112
+ * hop cap in config); 32 intra-procedural def→use hops is far beyond any
113
+ * legible path — overflow keeps the source-side prefix and sets
114
+ * `hopsTruncated`, parsed downstream as "path incomplete", never an error.
115
+ */
116
+ export const DEFAULT_PDG_MAX_TAINT_HOPS = 32;
117
+ // Canonical SinkKind order + sort live in source-sink-config.ts (shared with
118
+ // the M4 summary harvest so the deterministic order never drifts); imported
119
+ // above as KIND_ORDER / sortKinds. `kindRank` is the local comparator index.
120
+ const kindRank = new Map(KIND_ORDER.map((k, i) => [k, i]));
121
+ const EMPTY_KINDS = new Set();
122
+ const DIRECT_PATH = { kinds: EMPTY_KINDS, viaCall: false, sanitizers: [] };
123
+ /**
124
+ * Compute taint flows for one function. See the module doc for the two-rule
125
+ * model, the kind-set exclusion semantics, and the precision floor.
126
+ */
127
+ export function computeTaintFlows(cfg, defUse, matches, limits) {
128
+ if (defUse.status !== 'computed') {
129
+ return {
130
+ status: 'coverage-gap',
131
+ gapReason: defUse.status,
132
+ findings: [],
133
+ kills: [],
134
+ droppedFindings: 0,
135
+ };
136
+ }
137
+ const bindings = defUse.bindings;
138
+ // ── per-statement context (built lazily; statements revisit often) ────────
139
+ const matchByPoint = new Map();
140
+ for (const sm of matches.statements) {
141
+ matchByPoint.set(`${sm.blockIndex}:${sm.statementIndex}`, sm);
142
+ }
143
+ const ctxCache = new Map();
144
+ const contextAt = (blockIndex, stmtIndex) => {
145
+ const key = `${blockIndex}:${stmtIndex}`;
146
+ if (ctxCache.has(key))
147
+ return ctxCache.get(key);
148
+ const facts = cfg.blocks[blockIndex]?.statements?.[stmtIndex];
149
+ let ctx;
150
+ if (facts) {
151
+ const sm = matchByPoint.get(key);
152
+ const sinksBySite = new Map();
153
+ const sanitizersBySite = new Map();
154
+ for (const s of sm?.sinks ?? []) {
155
+ const list = sinksBySite.get(s.siteIndex);
156
+ if (list)
157
+ list.push(s);
158
+ else
159
+ sinksBySite.set(s.siteIndex, [s]);
160
+ }
161
+ for (const s of sm?.sanitizers ?? []) {
162
+ const list = sanitizersBySite.get(s.siteIndex);
163
+ if (list)
164
+ list.push(s);
165
+ else
166
+ sanitizersBySite.set(s.siteIndex, [s]);
167
+ }
168
+ const resultDefSites = new Map();
169
+ facts.sites?.forEach((site, siteIndex) => {
170
+ for (const d of site.resultDefs ?? []) {
171
+ const list = resultDefSites.get(d);
172
+ if (list)
173
+ list.push(siteIndex);
174
+ else
175
+ resultDefSites.set(d, [siteIndex]);
176
+ }
177
+ });
178
+ ctx = {
179
+ point: { blockIndex, stmtIndex, line: facts.line },
180
+ facts,
181
+ sites: facts.sites ?? [],
182
+ sinksBySite,
183
+ sanitizersBySite,
184
+ resultDefSites,
185
+ };
186
+ }
187
+ ctxCache.set(key, ctx);
188
+ return ctx;
189
+ };
190
+ /** Kinds the matched sanitizers at `siteIndex` neutralize for input position `argPos`. */
191
+ const neutralizedAt = (ctx, siteIndex, argPos) => {
192
+ const sans = ctx.sanitizersBySite.get(siteIndex);
193
+ if (!sans)
194
+ return [];
195
+ const site = ctx.sites[siteIndex];
196
+ // Spread/template positions are never provably the sanitized argument —
197
+ // do not neutralize (sound: under-kill). Exact positions check `args`.
198
+ if (site.template === true || (site.spread !== undefined && argPos >= site.spread))
199
+ return [];
200
+ const kinds = [];
201
+ for (const san of sans) {
202
+ if (san.entry.args === undefined || san.entry.args.includes(argPos)) {
203
+ kinds.push(...san.entry.neutralizes);
204
+ }
205
+ }
206
+ return sortKinds(kinds);
207
+ };
208
+ /** A call/new site the model does not understand (anything but a matched sanitizer). */
209
+ const isUnmodeledCall = (ctx, siteIndex) => {
210
+ const site = ctx.sites[siteIndex];
211
+ return site.kind !== 'member-read' && !ctx.sanitizersBySite.has(siteIndex);
212
+ };
213
+ const emerge = (ctx, siteIndex, argPos, inner) => {
214
+ const added = neutralizedAt(ctx, siteIndex, argPos);
215
+ return {
216
+ kinds: added.length === 0 ? inner.kinds : new Set([...inner.kinds, ...added]),
217
+ viaCall: inner.viaCall || isUnmodeledCall(ctx, siteIndex),
218
+ sanitizers: added.length === 0 ? inner.sanitizers : [...inner.sanitizers, { siteIndex, kinds: added }],
219
+ };
220
+ };
221
+ /**
222
+ * STRICT occurrence paths of binding `b` flowing OUT of site `siteIndex`'s
223
+ * result — found arg entries and the receiver only, each with the site's
224
+ * own neutralization/viaCall applied. Empty when `b` provably never flows
225
+ * in (the caller falls back to the floor and records NO kill). `guard`
226
+ * breaks corrupted-store via cycles (site-safety checks ranges, not
227
+ * acyclicity).
228
+ */
229
+ const flowsOutOf = (ctx, b, siteIndex, guard) => {
230
+ if (guard.has(siteIndex))
231
+ return [];
232
+ guard.add(siteIndex);
233
+ const site = ctx.sites[siteIndex];
234
+ const out = [];
235
+ site.args?.forEach((entries, argPos) => {
236
+ for (const e of entries) {
237
+ if (typeof e === 'number') {
238
+ if (e === b)
239
+ out.push(emerge(ctx, siteIndex, argPos, DIRECT_PATH));
240
+ }
241
+ else if (e[0] === b) {
242
+ // Via-tagged: the occurrence reaches this position THROUGH the
243
+ // nested site. When the nested site shows no recognized channel
244
+ // for `b` (callee-chain occurrences — dynamic subscript keys), the
245
+ // via-tag is still evidence of flow: fall back to a direct,
246
+ // UN-neutralized path (sound: never a false kill).
247
+ const inner = flowsOutOf(ctx, b, e[1], guard);
248
+ const paths = inner.length > 0
249
+ ? inner
250
+ : [{ ...DIRECT_PATH, viaCall: isUnmodeledCall(ctx, e[1]) }];
251
+ for (const p of paths)
252
+ out.push(emerge(ctx, siteIndex, argPos, p));
253
+ }
254
+ }
255
+ });
256
+ if (site.receiver === b) {
257
+ // Receiver TITO (KTD5): the receiver's value flows through the call
258
+ // into its result — but a sanitizer does not neutralize its receiver.
259
+ out.push({ ...DIRECT_PATH, viaCall: isUnmodeledCall(ctx, siteIndex) });
260
+ }
261
+ guard.delete(siteIndex);
262
+ return out;
263
+ };
264
+ /** Occurrence paths of `b` INTO sink position (s, p) — no emergence from s. */
265
+ const pathsIntoPosition = (ctx, b, siteIndex, argPos) => {
266
+ const entries = ctx.sites[siteIndex].args?.[argPos] ?? [];
267
+ const out = [];
268
+ for (const e of entries) {
269
+ if (typeof e === 'number') {
270
+ if (e === b)
271
+ out.push(DIRECT_PATH);
272
+ }
273
+ else if (e[0] === b) {
274
+ const inner = flowsOutOf(ctx, b, e[1], new Set());
275
+ if (inner.length > 0)
276
+ out.push(...inner);
277
+ else
278
+ out.push({ ...DIRECT_PATH, viaCall: isUnmodeledCall(ctx, e[1]) });
279
+ }
280
+ }
281
+ return out;
282
+ };
283
+ /**
284
+ * Walk a SOURCE member-read's `parent` chain. Linear (each site has one
285
+ * parent); invokes `onPosition` with the accumulated path BEFORE the
286
+ * ancestor's own emergence (the value flows INTO the ancestor at that
287
+ * position) — sink checks and stop-at-site joins both hang off it.
288
+ */
289
+ const climbSourceChain = (ctx, srcSiteIndex, onPosition) => {
290
+ const visited = new Set([srcSiteIndex]);
291
+ let cur = ctx.sites[srcSiteIndex];
292
+ let sofar = DIRECT_PATH;
293
+ while (cur.parent) {
294
+ const [siteIndex, argPos] = cur.parent;
295
+ if (visited.has(siteIndex))
296
+ return; // corrupted-store parent cycle
297
+ visited.add(siteIndex);
298
+ if (onPosition(siteIndex, argPos, sofar))
299
+ return;
300
+ sofar = emerge(ctx, siteIndex, argPos, sofar);
301
+ cur = ctx.sites[siteIndex];
302
+ }
303
+ };
304
+ /** Source path INTO site `target` (with target's emergence), or undefined. */
305
+ const sourceFlowsOutOf = (ctx, srcSiteIndex, target) => {
306
+ let found;
307
+ climbSourceChain(ctx, srcSiteIndex, (siteIndex, argPos, sofar) => {
308
+ if (siteIndex !== target)
309
+ return false;
310
+ found = emerge(ctx, target, argPos, sofar);
311
+ return true;
312
+ });
313
+ return found;
314
+ };
315
+ // ── accumulators ──────────────────────────────────────────────────────────
316
+ const findingsByIdentity = new Map();
317
+ const killsByIdentity = new Map();
318
+ const recordKill = (sanitizer, killedDef, bindingIdx, kinds) => {
319
+ if (kinds.length === 0)
320
+ return;
321
+ const key = `${pointKey(sanitizer)}|${pointKey(killedDef)}|${bindingIdx}`;
322
+ const existing = killsByIdentity.get(key);
323
+ if (existing)
324
+ for (const k of kinds)
325
+ existing.kinds.add(k);
326
+ else
327
+ killsByIdentity.set(key, {
328
+ kill: { sanitizer, killedDef, bindingIdx },
329
+ kinds: new Set(kinds),
330
+ });
331
+ };
332
+ // KTD6 statement-level finding identity: source occurrence + sink occurrence
333
+ // + kind (NOT entryName). Computed standalone so the worklist can dedup-check
334
+ // BEFORE the cost of chainHops (first write wins; dedup-before-budget).
335
+ const findingKey = (sinkKind, source, sink) => [
336
+ sinkKind,
337
+ pointKey(source.point),
338
+ source.siteIndex,
339
+ source.objectBindingIdx,
340
+ source.property,
341
+ pointKey(sink.point),
342
+ sink.siteIndex,
343
+ sink.argIndex,
344
+ sink.bindingIdx,
345
+ ].join('|');
346
+ const recordFinding = (sinkKind, source, sink, hops, hopsTruncated) => {
347
+ const key = findingKey(sinkKind, source, sink);
348
+ if (findingsByIdentity.has(key))
349
+ return;
350
+ const maxHops = limits?.maxHops && limits.maxHops > 0 ? limits.maxHops : Infinity;
351
+ let truncated = hopsTruncated;
352
+ let kept = hops;
353
+ if (hops.length > maxHops) {
354
+ kept = hops.slice(0, maxHops);
355
+ truncated = true;
356
+ }
357
+ findingsByIdentity.set(key, {
358
+ sinkKind,
359
+ source,
360
+ sink,
361
+ hops: kept,
362
+ ...(truncated ? { hopsTruncated: true } : {}),
363
+ });
364
+ };
365
+ // ── taint state ───────────────────────────────────────────────────────────
366
+ const taints = new Map();
367
+ const queue = [];
368
+ /** The (binding, def-point) portion of a state key — the def→use fact-table
369
+ * lookup key, source-independent. */
370
+ const defKey = (bindingIdx, point) => `${bindingIdx}:${pointKey(point)}`;
371
+ /** Full taint-state key: the def-point portion plus a ROOT source-occurrence
372
+ * discriminator ({point, siteIndex} — the same source fields recordFinding's
373
+ * identity uses, deliberately excluding `kind`). Distinct sources reaching
374
+ * one def get distinct states, so a second source is no longer dropped
375
+ * (KTD6); same-source multi-path derivations still share a key so their
376
+ * exclusion sets intersect (the raw arm soundly wins). */
377
+ const stateKey = (bindingIdx, point, source) => `${defKey(bindingIdx, point)}#${pointKey(source.point)}:${source.siteIndex}`;
378
+ const deriveTaint = (bindingIdx, point, exclusions, parentKey, source, viaCall) => {
379
+ const key = stateKey(bindingIdx, point, source);
380
+ const existing = taints.get(key);
381
+ if (!existing) {
382
+ taints.set(key, {
383
+ bindingIdx,
384
+ point,
385
+ exclusions,
386
+ parentKey,
387
+ source,
388
+ viaCall,
389
+ processedSize: -1,
390
+ });
391
+ queue.push(key);
392
+ return;
393
+ }
394
+ // Monotone shrink: keep the intersection; re-process only when it got
395
+ // strictly smaller (a less-neutralized derivation is more dangerous).
396
+ const inter = new Set();
397
+ for (const k of existing.exclusions)
398
+ if (exclusions.has(k))
399
+ inter.add(k);
400
+ if (inter.size < existing.exclusions.size) {
401
+ existing.exclusions = inter;
402
+ existing.parentKey = parentKey;
403
+ existing.source = source;
404
+ existing.viaCall = viaCall;
405
+ queue.push(key);
406
+ }
407
+ };
408
+ /** Taint-chain hops from seed to `key`, with a cycle guard (re-derivation
409
+ * can rewire parents into a loop — truncate instead of spinning). */
410
+ const chainHops = (key) => {
411
+ const reversed = [];
412
+ const seen = new Set();
413
+ let cur = key;
414
+ let truncated = false;
415
+ while (cur !== undefined) {
416
+ if (seen.has(cur)) {
417
+ truncated = true;
418
+ break;
419
+ }
420
+ seen.add(cur);
421
+ const t = taints.get(cur);
422
+ if (!t)
423
+ break;
424
+ reversed.push({
425
+ bindingIdx: t.bindingIdx,
426
+ name: bindings[t.bindingIdx]?.name ?? `#${t.bindingIdx}`,
427
+ point: t.point,
428
+ ...(t.viaCall ? { viaCall: true } : {}),
429
+ });
430
+ cur = t.parentKey;
431
+ }
432
+ return { hops: reversed.reverse(), truncated };
433
+ };
434
+ /** Intersection of path kind-sets; viaCall = any path through a call. */
435
+ const summarizePaths = (paths) => {
436
+ let kinds;
437
+ let viaCall = false;
438
+ for (const p of paths) {
439
+ viaCall ||= p.viaCall;
440
+ if (kinds === undefined) {
441
+ kinds = p.kinds;
442
+ }
443
+ else {
444
+ const inter = new Set();
445
+ for (const k of kinds)
446
+ if (p.kinds.has(k))
447
+ inter.add(k);
448
+ kinds = inter;
449
+ }
450
+ }
451
+ return { kinds: kinds ?? EMPTY_KINDS, viaCall };
452
+ };
453
+ /**
454
+ * Taint every def of `ctx`'s statement from one input. `pathsInto(c)`
455
+ * supplies the input's strict occurrence paths into call site `c` —
456
+ * defining the resultDefs precision and the kill evidence; an empty list
457
+ * means "no provable flow" and the floor applies (taint with the input's
458
+ * own exclusions, no kill).
459
+ */
460
+ const feedDefs = (ctx, inputExclusions, parentKey, source, pathsInto) => {
461
+ const defs = [...ctx.facts.defs, ...(ctx.facts.mayDefs ?? [])];
462
+ if (defs.length === 0)
463
+ return;
464
+ const seen = new Set();
465
+ for (const d of defs) {
466
+ if (seen.has(d))
467
+ continue;
468
+ seen.add(d);
469
+ const rdSites = ctx.resultDefSites.get(d);
470
+ let addKinds = EMPTY_KINDS;
471
+ let viaCall = false;
472
+ if (rdSites) {
473
+ const paths = [];
474
+ for (const c of rdSites)
475
+ paths.push(...pathsInto(c));
476
+ if (paths.length > 0) {
477
+ const summary = summarizePaths(paths);
478
+ addKinds = summary.kinds;
479
+ viaCall = summary.viaCall;
480
+ for (const p of paths) {
481
+ for (const san of p.sanitizers) {
482
+ recordKill(ctx.point, ctx.point, d, san.kinds);
483
+ }
484
+ }
485
+ }
486
+ // else: floor — tainted with no exclusions added, no kill (the input
487
+ // provably never flows into the producing call; conflation FP pinned).
488
+ }
489
+ const exclusions = addKinds.size === 0 ? inputExclusions : new Set([...inputExclusions, ...addKinds]);
490
+ deriveTaint(d, ctx.point, exclusions, parentKey, source, viaCall);
491
+ }
492
+ };
493
+ // ── rule (b) + seeding: statements with matched sources ───────────────────
494
+ for (const sm of matches.statements) {
495
+ if (sm.sources.length === 0)
496
+ continue;
497
+ const ctx = contextAt(sm.blockIndex, sm.statementIndex);
498
+ if (!ctx)
499
+ continue;
500
+ for (const src of sm.sources) {
501
+ const srcSite = ctx.sites[src.siteIndex];
502
+ if (srcSite?.object === undefined || srcSite.property === undefined)
503
+ continue;
504
+ const sourceOcc = {
505
+ point: ctx.point,
506
+ siteIndex: src.siteIndex,
507
+ objectBindingIdx: srcSite.object,
508
+ property: srcSite.property,
509
+ kind: src.entry.kind,
510
+ };
511
+ // Statement-local sink checks along the member-read's parent chain.
512
+ climbSourceChain(ctx, src.siteIndex, (siteIndex, argPos, sofar) => {
513
+ for (const sink of ctx.sinksBySite.get(siteIndex) ?? []) {
514
+ if (!sink.argPositions.includes(argPos))
515
+ continue;
516
+ const kind = sink.entry.kind;
517
+ if (!sofar.kinds.has(kind)) {
518
+ recordFinding(kind, sourceOcc, {
519
+ point: ctx.point,
520
+ siteIndex,
521
+ argIndex: argPos,
522
+ bindingIdx: srcSite.object,
523
+ entryName: sink.entry.name,
524
+ }, [
525
+ {
526
+ bindingIdx: srcSite.object,
527
+ name: bindings[srcSite.object]?.name ?? `#${srcSite.object}`,
528
+ point: ctx.point,
529
+ ...(sofar.viaCall ? { viaCall: true } : {}),
530
+ },
531
+ ], false);
532
+ }
533
+ else {
534
+ for (const san of sofar.sanitizers) {
535
+ if (san.kinds.includes(kind)) {
536
+ recordKill(ctx.point, ctx.point, srcSite.object, san.kinds);
537
+ }
538
+ }
539
+ }
540
+ }
541
+ return false;
542
+ });
543
+ // Seed every def of the statement (precision floor + resultDefs kills).
544
+ feedDefs(ctx, EMPTY_KINDS, undefined, sourceOcc, (c) => {
545
+ const p = sourceFlowsOutOf(ctx, src.siteIndex, c);
546
+ return p ? [p] : [];
547
+ });
548
+ }
549
+ }
550
+ // ── rule (a): worklist over def→use facts ─────────────────────────────────
551
+ const factsByDef = new Map();
552
+ for (const f of defUse.facts) {
553
+ const key = defKey(f.bindingIdx, f.def);
554
+ const list = factsByDef.get(key);
555
+ if (list)
556
+ list.push(f);
557
+ else
558
+ factsByDef.set(key, [f]);
559
+ }
560
+ // Strict-FIFO worklist via a head cursor (not Array.shift, which is O(N) per
561
+ // dequeue). FIFO order is load-bearing beyond perf: chainHops reconstructs
562
+ // hops from the live `taints` map, whose parentKey/source/viaCall are
563
+ // rewritten order-sensitively on monotone shrink — so hop-content
564
+ // determinism is contingent on dequeue order matching enqueue order. Do NOT
565
+ // sort or reprioritize the worklist.
566
+ let head = 0;
567
+ while (head < queue.length) {
568
+ const key = queue[head++];
569
+ // Reclaim the consumed prefix periodically so the array doesn't grow
570
+ // unbounded across a long run (order-preserving — pure memory hygiene).
571
+ if (head > 1024 && head * 2 > queue.length) {
572
+ queue.splice(0, head);
573
+ head = 0;
574
+ }
575
+ const t = taints.get(key);
576
+ if (t.processedSize === t.exclusions.size)
577
+ continue; // no-op requeue
578
+ t.processedSize = t.exclusions.size;
579
+ const b = t.bindingIdx;
580
+ const E = t.exclusions;
581
+ // Facts are keyed by (binding, def-point) only — look up by the def portion
582
+ // of this state, not the source-discriminated state key.
583
+ for (const fact of factsByDef.get(defKey(b, t.point)) ?? []) {
584
+ const ctx = contextAt(fact.use.blockIndex, fact.use.stmtIndex);
585
+ if (!ctx)
586
+ continue;
587
+ // Sink check: occurrences of `b` at matched sink argument positions.
588
+ for (const [siteIndex, sinks] of ctx.sinksBySite) {
589
+ for (const sink of sinks) {
590
+ const kind = sink.entry.kind;
591
+ for (const argPos of sink.argPositions) {
592
+ const paths = pathsIntoPosition(ctx, b, siteIndex, argPos);
593
+ if (paths.length === 0)
594
+ continue;
595
+ if (E.has(kind))
596
+ continue; // suppressed at def time; kill already recorded
597
+ const justify = paths.find((p) => !p.kinds.has(kind));
598
+ if (justify) {
599
+ const sinkOcc = {
600
+ point: ctx.point,
601
+ siteIndex,
602
+ argIndex: argPos,
603
+ bindingIdx: b,
604
+ entryName: sink.entry.name,
605
+ };
606
+ // Dedup BEFORE chainHops: already-recorded identities discard
607
+ // their hop chain anyway (first write wins), so skip the walk.
608
+ if (findingsByIdentity.has(findingKey(kind, t.source, sinkOcc)))
609
+ continue;
610
+ const chain = chainHops(key);
611
+ chain.hops.push({
612
+ bindingIdx: b,
613
+ name: bindings[b]?.name ?? `#${b}`,
614
+ point: ctx.point,
615
+ ...(justify.viaCall ? { viaCall: true } : {}),
616
+ });
617
+ recordFinding(kind, t.source, sinkOcc, chain.hops, chain.truncated);
618
+ }
619
+ else {
620
+ // EVERY path interposed — value-position kill(s) held.
621
+ for (const p of paths) {
622
+ for (const san of p.sanitizers) {
623
+ if (san.kinds.includes(kind))
624
+ recordKill(ctx.point, ctx.point, b, san.kinds);
625
+ }
626
+ }
627
+ }
628
+ }
629
+ }
630
+ }
631
+ // Def-feed: the use statement's own defs become tainted.
632
+ feedDefs(ctx, E, key, t.source, (c) => flowsOutOf(ctx, b, c, new Set()));
633
+ }
634
+ }
635
+ // ── deterministic assembly ────────────────────────────────────────────────
636
+ const comparePoints = (a, b) => a.blockIndex - b.blockIndex || a.stmtIndex - b.stmtIndex;
637
+ const findings = [...findingsByIdentity.values()].sort((a, b) => comparePoints(a.source.point, b.source.point) ||
638
+ a.source.siteIndex - b.source.siteIndex ||
639
+ comparePoints(a.sink.point, b.sink.point) ||
640
+ a.sink.siteIndex - b.sink.siteIndex ||
641
+ a.sink.argIndex - b.sink.argIndex ||
642
+ a.sink.bindingIdx - b.sink.bindingIdx ||
643
+ (kindRank.get(a.sinkKind) ?? 99) - (kindRank.get(b.sinkKind) ?? 99));
644
+ const maxFindings = limits?.maxFindingsPerFunction && limits.maxFindingsPerFunction > 0
645
+ ? limits.maxFindingsPerFunction
646
+ : Infinity;
647
+ const kept = findings.length > maxFindings ? findings.slice(0, maxFindings) : findings;
648
+ const kills = [...killsByIdentity.values()]
649
+ .map(({ kill, kinds }) => ({ ...kill, neutralized: sortKinds(kinds) }))
650
+ .sort((a, b) => comparePoints(a.sanitizer, b.sanitizer) ||
651
+ comparePoints(a.killedDef, b.killedDef) ||
652
+ a.bindingIdx - b.bindingIdx);
653
+ return {
654
+ status: 'computed',
655
+ findings: kept,
656
+ kills,
657
+ droppedFindings: findings.length - kept.length,
658
+ };
659
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Taint-site safety validation (#2083 M3 U1, plan KTD2).
3
+ *
4
+ * Mirrors `hasEmitSafeFacts` (cfg/emit.ts): an untrusted `cfgSideChannel`
5
+ * element — possibly from a corrupted durable parsedfile store — must never
6
+ * crash the taint pass or fabricate matches from out-of-range indices. The
7
+ * degradation contract is per-FUNCTION and one-directional: a CFG whose sites
8
+ * fail this check is SKIPPED FOR TAINT ONLY — the BasicBlock/CFG layer and
9
+ * the REACHING_DEF projection (guarded by their own checks) are unaffected.
10
+ *
11
+ * Checked: exactly the indices the taint matcher dereferences — binding
12
+ * indices (`receiver`/`object`/`resultDefs`/arg occurrences) against the
13
+ * function's binding table, and intra-statement site references (`parent`
14
+ * site / via-tags) against the OWNING statement's `sites` array. Site
15
+ * references are statement-local by construction (each statement's
16
+ * FactAccumulator starts at index 0); a cross-statement reference is
17
+ * corruption, not a feature.
18
+ *
19
+ * Lives in `taint/` (not cfg/emit.ts): U4's taint emit path is the only
20
+ * consumer, and the guard must evolve with the matcher that dereferences
21
+ * these fields.
22
+ */
23
+ import type { FunctionCfg } from '../cfg/types.js';
24
+ /**
25
+ * Whether a structurally-valid CFG's M3 `sites` annotations are safe to feed
26
+ * to the taint matcher/propagator. `true` when no statement carries sites
27
+ * (pre-M3 channel, or no calls) — absence is the well-formed empty case.
28
+ */
29
+ export declare const hasTaintSafeSites: (cfg: FunctionCfg) => boolean;