@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,451 @@
1
+ export const id = 675;
2
+ export const ids = [675];
3
+ export const modules = {
4
+
5
+ /***/ 6675:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ runOnce: () => (/* binding */ runOnce)
10
+ /* harmony export */ });
11
+ /* unused harmony exports readDeps, _queryOsvForDep, loadState, persistState, loadConfig, formatPayload, _internals */
12
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
13
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
14
+ // Continuous CVE-watch daemon.
15
+ //
16
+ // Polls OSV for the project's dependency tree on a schedule (or one-shot),
17
+ // diffs new advisories against state, fires the configured webhook on each
18
+ // new ID. Multi-ecosystem: reads npm / pip / cargo / gem / pub / composer /
19
+ // go.mod / maven manifests via the same DEP_FILE_NAMES list runScan uses.
20
+ //
21
+ // Why this exists separately from `/scan --sca`:
22
+ // - `/scan` is interactive; this runs unattended, daily, in a cron / GitHub Action.
23
+ // - `/scan` reports the FULL set of advisories for the tree; this reports
24
+ // only the DELTA since the last run.
25
+ // - This produces a webhook fire, not a console report.
26
+ //
27
+ // Configuration is read from `.agentic-security/cve-alerts.json`:
28
+ // {
29
+ // "alertUrl": "https://hooks.slack.com/services/...",
30
+ // "alertType": "slack" | "discord" | "generic",
31
+ // "minSeverity": "low" | "medium" | "high" | "critical" (optional)
32
+ // }
33
+ //
34
+ // State is persisted to `.agentic-security/cve-alerts-state.json`:
35
+ // { "known": ["CVE-2024-1234", "GHSA-...", ...], "lastRun": "2026-…" }
36
+ //
37
+ // The daemon NEVER fires a webhook for an advisory ID it has already seen;
38
+ // the state file is the deduplication primitive.
39
+
40
+
41
+
42
+
43
+ const OSV_API = 'https://api.osv.dev/v1/query';
44
+ const CFG_PATH = '.agentic-security/cve-alerts.json';
45
+ const STATE_PATH = '.agentic-security/cve-alerts-state.json';
46
+
47
+ // Multi-ecosystem dep extraction. Each entry: { manifest filename → ecosystem }
48
+ const ECOSYSTEM_BY_MANIFEST = {
49
+ 'package.json': 'npm',
50
+ 'requirements.txt':'PyPI',
51
+ 'pyproject.toml': 'PyPI',
52
+ 'Pipfile': 'PyPI',
53
+ 'Pipfile.lock': 'PyPI',
54
+ 'poetry.lock': 'PyPI',
55
+ 'Gemfile': 'RubyGems',
56
+ 'Gemfile.lock': 'RubyGems',
57
+ 'go.mod': 'Go',
58
+ 'go.sum': 'Go',
59
+ 'Cargo.toml': 'crates.io',
60
+ 'Cargo.lock': 'crates.io',
61
+ 'composer.json': 'Packagist',
62
+ 'composer.lock': 'Packagist',
63
+ 'pom.xml': 'Maven',
64
+ 'build.gradle': 'Maven',
65
+ 'build.gradle.kts':'Maven',
66
+ 'pubspec.yaml': 'Pub',
67
+ 'pubspec.lock': 'Pub',
68
+ };
69
+
70
+ // ─── Manifest readers ─────────────────────────────────────────────────────
71
+
72
+ function _readPackageJson(content) {
73
+ try {
74
+ const j = JSON.parse(content);
75
+ return [
76
+ ...Object.keys(j.dependencies || {}),
77
+ ...Object.keys(j.devDependencies || {}),
78
+ ...Object.keys(j.peerDependencies || {}),
79
+ ...Object.keys(j.optionalDependencies || {}),
80
+ ];
81
+ } catch { return []; }
82
+ }
83
+
84
+ function _readRequirementsTxt(content) {
85
+ // Each non-blank, non-comment line is "pkg" or "pkg==1.2.3" or "pkg>=1.2".
86
+ const out = [];
87
+ for (const raw of content.split('\n')) {
88
+ const line = raw.trim();
89
+ if (!line || line.startsWith('#') || line.startsWith('-')) continue;
90
+ const m = /^([A-Za-z0-9_.\-]+)/.exec(line);
91
+ if (m) out.push(m[1]);
92
+ }
93
+ return out;
94
+ }
95
+
96
+ function _readPyprojectToml(content) {
97
+ // We don't ship a TOML parser; do a best-effort line scan for the common
98
+ // dep-table shapes. Caller is OK if this misses some — the full SCA scan
99
+ // does the heavy lifting.
100
+ const out = new Set();
101
+ // PEP 621 [project] dependencies = ["foo", "bar>=1.0"]
102
+ // Poetry [tool.poetry.dependencies] foo = "^1.0"
103
+ let inDeps = false;
104
+ for (const raw of content.split('\n')) {
105
+ const line = raw.trim();
106
+ if (!line || line.startsWith('#')) continue;
107
+ if (/^\[(?:tool\.poetry\.)?(?:dev-)?dependencies\]/i.test(line) || /^\[project\.optional-dependencies\.[^\]]+\]/.test(line)) {
108
+ inDeps = true; continue;
109
+ }
110
+ if (/^\[[^\]]+\]/.test(line)) { inDeps = false; continue; }
111
+ // Inline "dependencies = [...]" form within [project]
112
+ const inline = /dependencies\s*=\s*\[([^\]]+)\]/i.exec(line);
113
+ if (inline) {
114
+ for (const item of inline[1].split(',')) {
115
+ const m = /^[\s"']*([A-Za-z0-9_.\-]+)/.exec(item);
116
+ if (m) out.add(m[1]);
117
+ }
118
+ }
119
+ if (inDeps) {
120
+ const m = /^([A-Za-z0-9_.\-]+)\s*=/.exec(line);
121
+ if (m && m[1] !== 'python') out.add(m[1]);
122
+ }
123
+ }
124
+ return [...out];
125
+ }
126
+
127
+ function _readGemfile(content) {
128
+ const out = [];
129
+ for (const raw of content.split('\n')) {
130
+ const m = /^\s*gem\s+['"]([^'"]+)['"]/.exec(raw);
131
+ if (m) out.push(m[1]);
132
+ }
133
+ return out;
134
+ }
135
+
136
+ function _readGoMod(content) {
137
+ const out = [];
138
+ let inRequire = false;
139
+ for (const raw of content.split('\n')) {
140
+ const line = raw.trim();
141
+ if (line.startsWith('require (')) { inRequire = true; continue; }
142
+ if (inRequire && line === ')') { inRequire = false; continue; }
143
+ if (inRequire || line.startsWith('require ')) {
144
+ // "require module/path v1.2.3" or just "module/path v1.2.3" inside block
145
+ const m = /(?:require\s+)?([A-Za-z0-9_./\-]+)\s+v/.exec(line);
146
+ if (m) out.push(m[1]);
147
+ }
148
+ }
149
+ return out;
150
+ }
151
+
152
+ function _readCargoToml(content) {
153
+ // Lines under [dependencies] / [dev-dependencies] / [build-dependencies]
154
+ // shaped as `pkg = "1.0"` or `pkg = { … }`.
155
+ const out = new Set();
156
+ let inDeps = false;
157
+ for (const raw of content.split('\n')) {
158
+ const line = raw.trim();
159
+ if (/^\[(?:dev-|build-)?dependencies\]/.test(line)) { inDeps = true; continue; }
160
+ if (/^\[[^\]]+\]/.test(line)) { inDeps = false; continue; }
161
+ if (inDeps) {
162
+ const m = /^([A-Za-z0-9_\-]+)\s*=/.exec(line);
163
+ if (m) out.add(m[1]);
164
+ }
165
+ }
166
+ return [...out];
167
+ }
168
+
169
+ function _readComposerJson(content) {
170
+ try {
171
+ const j = JSON.parse(content);
172
+ return [
173
+ ...Object.keys(j.require || {}).filter(k => k !== 'php'),
174
+ ...Object.keys(j['require-dev'] || {}).filter(k => k !== 'php'),
175
+ ];
176
+ } catch { return []; }
177
+ }
178
+
179
+ function _readMavenLikeXml(content) {
180
+ // crude <artifactId>x</artifactId> extraction. Misses parent POM inheritance;
181
+ // for full coverage the /scan --sca path is the right tool.
182
+ const out = [];
183
+ const re = /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/g;
184
+ let m;
185
+ while ((m = re.exec(content)) !== null) out.push(m[1]);
186
+ return out;
187
+ }
188
+
189
+ function _readPubspecYaml(content) {
190
+ const out = new Set();
191
+ let inDeps = false;
192
+ for (const raw of content.split('\n')) {
193
+ const line = raw.trimEnd();
194
+ if (/^(dev_|)dependencies\s*:/i.test(line)) { inDeps = true; continue; }
195
+ if (line && !line.startsWith(' ') && !line.startsWith('\t')) {
196
+ // top-level key other than dependencies / dev_dependencies
197
+ if (!/^(dev_|)dependencies\s*:/i.test(line)) inDeps = false;
198
+ }
199
+ if (inDeps) {
200
+ const m = /^\s+([A-Za-z0-9_]+)\s*:/.exec(line);
201
+ if (m && m[1] !== 'sdk' && m[1] !== 'flutter') out.add(m[1]);
202
+ }
203
+ }
204
+ return [...out];
205
+ }
206
+
207
+ const MANIFEST_READERS = {
208
+ 'package.json': _readPackageJson,
209
+ 'requirements.txt': _readRequirementsTxt,
210
+ 'pyproject.toml': _readPyprojectToml,
211
+ 'Pipfile': _readPyprojectToml, // close-enough shape
212
+ 'Gemfile': _readGemfile,
213
+ 'go.mod': _readGoMod,
214
+ 'Cargo.toml': _readCargoToml,
215
+ 'composer.json': _readComposerJson,
216
+ 'pom.xml': _readMavenLikeXml,
217
+ 'build.gradle': _readMavenLikeXml,
218
+ 'build.gradle.kts': _readMavenLikeXml,
219
+ 'pubspec.yaml': _readPubspecYaml,
220
+ };
221
+
222
+ function readDeps(scanRoot) {
223
+ // Walks the scan root looking for known manifest files. Returns a flat
224
+ // [{name, ecosystem}, ...] list; deduplicated across manifests so a
225
+ // package in both package.json and yarn.lock isn't queried twice.
226
+ const seen = new Set();
227
+ const out = [];
228
+ function walk(dir, depth) {
229
+ if (depth > 4) return; // shallow walk; manifests live near the root
230
+ let entries;
231
+ try { entries = node_fs__WEBPACK_IMPORTED_MODULE_0__.readdirSync(dir, { withFileTypes: true }); } catch { return; }
232
+ for (const e of entries) {
233
+ if (e.name.startsWith('.') && e.name !== '.gradle') continue;
234
+ if (e.name === 'node_modules' || e.name === 'venv' || e.name === '__pycache__'
235
+ || e.name === 'vendor' || e.name === 'dist' || e.name === 'build'
236
+ || e.name === 'target') continue;
237
+ const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(dir, e.name);
238
+ if (e.isDirectory()) { walk(fp, depth + 1); continue; }
239
+ if (!ECOSYSTEM_BY_MANIFEST[e.name]) continue;
240
+ let content;
241
+ try { content = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8'); } catch { continue; }
242
+ const reader = MANIFEST_READERS[e.name];
243
+ if (!reader) continue;
244
+ const ecosystem = ECOSYSTEM_BY_MANIFEST[e.name];
245
+ for (const name of reader(content)) {
246
+ const key = `${ecosystem}::${name}`;
247
+ if (seen.has(key)) continue;
248
+ seen.add(key);
249
+ out.push({ name, ecosystem });
250
+ }
251
+ }
252
+ }
253
+ walk(scanRoot, 0);
254
+ return out;
255
+ }
256
+
257
+ // ─── OSV query ────────────────────────────────────────────────────────────
258
+
259
+ async function _queryOsvForDep({ name, ecosystem }, { fetchImpl = globalThis.fetch, timeoutMs = 4000 } = {}) {
260
+ // POST { package: { name, ecosystem } } — returns the full vulnerability
261
+ // list for the package (any version). The daemon doesn't filter by version
262
+ // because we treat "new advisory ID" as the signal, not "your installed
263
+ // version is affected" — that's what /scan --sca produces.
264
+ try {
265
+ const controller = new AbortController();
266
+ const t = setTimeout(() => controller.abort(), timeoutMs);
267
+ const res = await fetchImpl(OSV_API, {
268
+ method: 'POST',
269
+ headers: { 'Content-Type': 'application/json' },
270
+ body: JSON.stringify({ package: { name, ecosystem } }),
271
+ signal: controller.signal,
272
+ });
273
+ clearTimeout(t);
274
+ if (!res.ok) return [];
275
+ const j = await res.json();
276
+ return Array.isArray(j.vulns) ? j.vulns : [];
277
+ } catch { return []; }
278
+ }
279
+
280
+ // ─── State persistence ───────────────────────────────────────────────────
281
+
282
+ function loadState(scanRoot) {
283
+ const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_PATH);
284
+ if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return { known: new Set(), lastRun: null };
285
+ try {
286
+ const j = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8'));
287
+ return { known: new Set(j.known || []), lastRun: j.lastRun || null };
288
+ } catch { return { known: new Set(), lastRun: null }; }
289
+ }
290
+
291
+ function persistState(scanRoot, state) {
292
+ const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_PATH);
293
+ node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(node_path__WEBPACK_IMPORTED_MODULE_1__.dirname(fp), { recursive: true });
294
+ node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(fp, JSON.stringify({
295
+ known: [...state.known].sort(),
296
+ lastRun: state.lastRun,
297
+ }, null, 2));
298
+ }
299
+
300
+ function loadConfig(scanRoot) {
301
+ const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, CFG_PATH);
302
+ if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
303
+ try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); } catch { return null; }
304
+ }
305
+
306
+ // ─── Webhook formatting ──────────────────────────────────────────────────
307
+
308
+ function _truncate(s, n) { s = String(s || ''); return s.length > n ? s.slice(0, n) + '…' : s; }
309
+
310
+ function formatPayload(alertType, newAdvisories) {
311
+ // alertType: 'slack' | 'discord' | 'generic'
312
+ // Slack and Discord both accept `{ text }` for simple messages.
313
+ const lines = newAdvisories.slice(0, 10).map(a =>
314
+ `• \`${a.ecosystem}:${a.pkg}\` — ${a.id} (${a.severity || 'unknown'})${a.summary ? ' — ' + _truncate(a.summary, 100) : ''}`
315
+ );
316
+ const overflow = newAdvisories.length > 10 ? `\n+${newAdvisories.length - 10} more` : '';
317
+ const text = `🚨 *New CVEs in your dependencies* (${newAdvisories.length})\n` + lines.join('\n') + overflow + '\n\nRun `/scan --all` to assess impact.';
318
+ if (alertType === 'slack' || alertType === 'discord') return { text };
319
+ // Generic webhook: full structured payload.
320
+ return { event: 'new_cve', count: newAdvisories.length, advisories: newAdvisories };
321
+ }
322
+
323
+ async function _post(url, payload, { fetchImpl = globalThis.fetch, timeoutMs = 4000 } = {}) {
324
+ try {
325
+ const controller = new AbortController();
326
+ const t = setTimeout(() => controller.abort(), timeoutMs);
327
+ const res = await fetchImpl(url, {
328
+ method: 'POST',
329
+ headers: { 'Content-Type': 'application/json' },
330
+ body: JSON.stringify(payload),
331
+ signal: controller.signal,
332
+ });
333
+ clearTimeout(t);
334
+ return { ok: res.ok, status: res.status };
335
+ } catch (e) { return { ok: false, error: String(e?.message || e).slice(0, 200) }; }
336
+ }
337
+
338
+ // ─── Severity filter ─────────────────────────────────────────────────────
339
+
340
+ const SEV_RANK = { critical: 4, high: 3, medium: 2, low: 1, unknown: 0 };
341
+
342
+ function _normalizeSeverity(vuln) {
343
+ // OSV severity has a few possible shapes. Best-effort.
344
+ const dbSpec = vuln.database_specific?.severity || '';
345
+ if (/critical/i.test(dbSpec)) return 'critical';
346
+ if (/high/i.test(dbSpec)) return 'high';
347
+ if (/medium|moderate/i.test(dbSpec)) return 'medium';
348
+ if (/low/i.test(dbSpec)) return 'low';
349
+ // CVSS score
350
+ const score = vuln.severity?.[0]?.score;
351
+ if (typeof score === 'number') {
352
+ if (score >= 9) return 'critical';
353
+ if (score >= 7) return 'high';
354
+ if (score >= 4) return 'medium';
355
+ return 'low';
356
+ }
357
+ return 'unknown';
358
+ }
359
+
360
+ // ─── Main entry — runOnce ───────────────────────────────────────────────
361
+
362
+ async function runOnce(scanRoot, opts = {}) {
363
+ // opts: { alertUrl, alertType, minSeverity, dryRun, offline, fetchImpl,
364
+ // logger, throttleMs }
365
+ const log = opts.logger || ((msg) => process.stderr.write(msg + '\n'));
366
+ const cfg = loadConfig(scanRoot);
367
+ const alertUrl = opts.alertUrl || cfg?.alertUrl || process.env.CVE_ALERT_URL;
368
+ const alertType = opts.alertType || cfg?.alertType || 'slack';
369
+ const minSeverity = opts.minSeverity || cfg?.minSeverity || 'low';
370
+ const dryRun = !!opts.dryRun;
371
+ const offline = !!opts.offline || process.env.AGENTIC_SECURITY_OFFLINE === '1';
372
+ const fetchImpl = opts.fetchImpl || globalThis.fetch;
373
+ const throttleMs = typeof opts.throttleMs === 'number' ? opts.throttleMs : 50;
374
+
375
+ if (offline && !dryRun) {
376
+ return { ok: false, reason: 'offline-mode-set' };
377
+ }
378
+ if (typeof fetchImpl !== 'function') {
379
+ return { ok: false, reason: 'fetch-unavailable' };
380
+ }
381
+
382
+ const deps = readDeps(scanRoot);
383
+ if (deps.length === 0) {
384
+ return { ok: true, depsChecked: 0, newAdvisories: [], alertSent: false, reason: 'no-deps-found' };
385
+ }
386
+
387
+ const state = loadState(scanRoot);
388
+ const newAdvisories = [];
389
+ const sevFloor = SEV_RANK[minSeverity] ?? 0;
390
+
391
+ for (const dep of deps) {
392
+ const vulns = await _queryOsvForDep(dep, { fetchImpl, timeoutMs: opts.timeoutMs });
393
+ for (const v of vulns) {
394
+ if (!v.id || state.known.has(v.id)) continue;
395
+ const sev = _normalizeSeverity(v);
396
+ if ((SEV_RANK[sev] ?? 0) < sevFloor) {
397
+ // Below user's floor; remember the ID so we don't re-fire next run,
398
+ // but don't surface in the alert.
399
+ state.known.add(v.id);
400
+ continue;
401
+ }
402
+ newAdvisories.push({
403
+ id: v.id,
404
+ pkg: dep.name,
405
+ ecosystem: dep.ecosystem,
406
+ severity: sev,
407
+ summary: v.summary || '',
408
+ published: v.published || null,
409
+ });
410
+ state.known.add(v.id);
411
+ }
412
+ if (throttleMs > 0) await new Promise(r => setTimeout(r, throttleMs));
413
+ }
414
+
415
+ state.lastRun = new Date().toISOString();
416
+ if (!dryRun) persistState(scanRoot, state);
417
+
418
+ let alertSent = false;
419
+ let alertError = null;
420
+ if (newAdvisories.length > 0 && alertUrl && !dryRun) {
421
+ const payload = formatPayload(alertType, newAdvisories);
422
+ const r = await _post(alertUrl, payload, { fetchImpl, timeoutMs: opts.timeoutMs });
423
+ alertSent = r.ok;
424
+ if (!r.ok) alertError = r.error || `HTTP ${r.status}`;
425
+ }
426
+
427
+ log(`agentic-security cve-watch: deps=${deps.length} new=${newAdvisories.length} sent=${alertSent}${alertError ? ' err=' + alertError : ''}${dryRun ? ' (dry-run)' : ''}`);
428
+
429
+ return {
430
+ ok: true,
431
+ depsChecked: deps.length,
432
+ newAdvisories,
433
+ alertSent,
434
+ alertError,
435
+ minSeverity,
436
+ dryRun,
437
+ };
438
+ }
439
+
440
+ const _internals = {
441
+ ECOSYSTEM_BY_MANIFEST,
442
+ MANIFEST_READERS,
443
+ _readPackageJson, _readRequirementsTxt, _readPyprojectToml, _readGemfile,
444
+ _readGoMod, _readCargoToml, _readComposerJson, _readMavenLikeXml, _readPubspecYaml,
445
+ _normalizeSeverity,
446
+ };
447
+
448
+
449
+ /***/ })
450
+
451
+ };
@@ -0,0 +1,188 @@
1
+ export const id = 826;
2
+ export const ids = [826];
3
+ export const modules = {
4
+
5
+ /***/ 2826:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ renderPrComment: () => (/* binding */ renderPrComment)
10
+ /* harmony export */ });
11
+ /* unused harmony export _internal */
12
+ // Advisor-tone PR comment renderer (v0.72).
13
+ //
14
+ // Replaces the typical "12 findings detected, see SARIF" wall of text
15
+ // with a single security-advisor's note:
16
+ //
17
+ // "I noticed you added /api/admin/users in this PR. I checked 4 things:
18
+ // ✓ auth (route is behind requireAdmin)
19
+ // ⚠ rate-limit (no rateLimit() middleware found)
20
+ // ✓ input validation (express-validator chain present)
21
+ // ✓ audit log (logger.security() called on success path)
22
+ //
23
+ // The rate-limit gap matters because /admin endpoints are common
24
+ // credential-stuffing targets. A 5-line fix:
25
+ //
26
+ // ```js
27
+ // import rateLimit from 'express-rate-limit';
28
+ // app.use('/api/admin', rateLimit({ windowMs: 15*60_000, max: 30 }));
29
+ // ```"
30
+ //
31
+ // The pure-narrative format optimizes for SCREENSHOTABILITY — engineers
32
+ // share security-tool comments that read like a person, not a table.
33
+ //
34
+ // Generation is deterministic (no LLM) for the v1: we render from the
35
+ // delta + a per-CWE narrative template. A future v2 could optionally
36
+ // route through an LLM for richer prose when AGENTIC_SECURITY_LLM_ENDPOINT
37
+ // is configured.
38
+
39
+ const SEVERITY_GLYPH = {
40
+ critical: '🟥',
41
+ high: '🟧',
42
+ medium: '🟨',
43
+ low: '🟦',
44
+ info: '⬜',
45
+ };
46
+
47
+ const CWE_NARRATIVE = {
48
+ 'CWE-89': { name: 'SQL injection', why: 'A malicious payload like `1\' OR 1=1--` would dump every row.' },
49
+ 'CWE-79': { name: 'XSS', why: 'An attacker who controls this string can execute JavaScript in another user\'s browser.' },
50
+ 'CWE-78': { name: 'Command injection', why: 'A `;rm -rf /` style payload would run with the privileges of your service account.' },
51
+ 'CWE-22': { name: 'path traversal', why: 'A `../../etc/passwd` style payload would read files outside the intended directory.' },
52
+ 'CWE-918': { name: 'SSRF', why: 'An attacker can pivot to the cloud metadata endpoint (`169.254.169.254`) or internal services.' },
53
+ 'CWE-502': { name: 'insecure deserialization', why: 'A crafted payload triggers gadget chains — typically remote code execution.' },
54
+ 'CWE-611': { name: 'XXE', why: 'A malicious DOCTYPE can exfiltrate local files or trigger SSRF via entity expansion.' },
55
+ 'CWE-94': { name: 'template injection', why: 'A `{{7*7}}` style payload escapes to the template engine\'s expression evaluator (often RCE).' },
56
+ 'CWE-1321': { name: 'prototype pollution', why: 'A `__proto__` injection can alter the behavior of every downstream object check.' },
57
+ 'CWE-352': { name: 'CSRF', why: 'A cross-origin form can trigger authenticated state changes without the user\'s knowledge.' },
58
+ 'CWE-601': { name: 'open redirect', why: 'Trusted-domain bouncer for phishing — credentials get harvested on the second hop.' },
59
+ 'CWE-113': { name: 'HTTP response splitting', why: 'CR/LF injection lets an attacker forge an entire response, including cache poisoning.' },
60
+ 'CWE-798': { name: 'hardcoded secret', why: 'Committed credentials are scraped by automated scanners minutes after a push.' },
61
+ 'CWE-327': { name: 'weak crypto', why: 'Modern adversaries can crack MD5/SHA-1/RC4/3DES at practical cost.' },
62
+ 'CWE-1333': { name: 'regex DoS', why: 'A pathological input freezes the worker for seconds — DoS without much effort.' },
63
+ 'CWE-90': { name: 'LDAP injection', why: 'An attacker can extend the filter to enumerate users or bypass auth.' },
64
+ 'CWE-643': { name: 'XPath injection', why: 'A `\' or \'1\'=\'1` payload can return data the query never intended to expose.' },
65
+ 'CWE-1336': { name: 'prompt injection', why: 'Untrusted text in the LLM context can override your system prompt.' },
66
+ 'CWE-269': { name: 'privilege escalation', why: 'A low-privilege actor can reach a higher-privilege capability through this surface.' },
67
+ };
68
+
69
+ function _topCwes(findings, n = 3) {
70
+ const counts = new Map();
71
+ for (const f of findings) {
72
+ const k = f.cwe || 'unknown';
73
+ counts.set(k, (counts.get(k) || 0) + 1);
74
+ }
75
+ return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, n);
76
+ }
77
+
78
+ function _route(f) {
79
+ // Best-effort: pull a route-ish string from the file path / vuln text.
80
+ // Many SAST findings carry the route via `f.route` or in the vuln name.
81
+ if (f.route) return f.route;
82
+ const m = (f.vuln || '').match(/\b(GET|POST|PUT|DELETE|PATCH)\s+([\/\w:-]+)/i);
83
+ if (m) return `${m[1].toUpperCase()} ${m[2]}`;
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Top-level renderer. Takes a delta produced by `computePrDelta` and
89
+ * emits a Markdown string suitable for posting as a single PR comment.
90
+ *
91
+ * Mode is chosen automatically:
92
+ * - "clean": no introduced + (resolved > 0 || persistent > 0)
93
+ * - "trivial": no introduced + nothing resolved
94
+ * - "needs-work": one or more introduced findings
95
+ */
96
+ function renderPrComment(delta, { repoName, prNumber, prTitle } = {}) {
97
+ if (!delta) return '_no security delta available_';
98
+ const intro = delta.introduced || [];
99
+ const resolved = delta.resolved || [];
100
+ const shifted = delta.shifted || [];
101
+ const heading = repoName && prNumber
102
+ ? `### 🛡 agentic-security on ${repoName}#${prNumber}`
103
+ : `### 🛡 agentic-security`;
104
+ if (intro.length === 0 && resolved.length === 0 && shifted.length === 0) {
105
+ return [
106
+ heading,
107
+ ``,
108
+ `No security delta from \`${delta.baseRef}\` → \`${delta.headRef}\`. ` +
109
+ `${delta.changedFiles.length} file${delta.changedFiles.length === 1 ? '' : 's'} touched; ` +
110
+ `nothing in those changes introduced or resolved a finding. **Safe to merge.**`,
111
+ ``,
112
+ `<sub>Scanned ${delta.head?.summary?.total ?? 0} pre-existing findings on the head ref ` +
113
+ `(${delta.base?.summary?.total ?? 0} on base) — none of them moved in this PR.</sub>`,
114
+ ].join('\n');
115
+ }
116
+ if (intro.length === 0 && (resolved.length || shifted.length)) {
117
+ const r = delta.summary?.resolved || {};
118
+ return [
119
+ heading,
120
+ ``,
121
+ `This PR **resolves** ${resolved.length} finding${resolved.length === 1 ? '' : 's'}` +
122
+ (r.critical || r.high ? ` (including ${r.critical} critical + ${r.high} high)` : '') +
123
+ ` and introduces **none**. Nice cleanup work — safe to merge. ✨`,
124
+ ``,
125
+ `<sub>${delta.changedFiles.length} file${delta.changedFiles.length === 1 ? '' : 's'} ` +
126
+ `touched between \`${delta.baseRef}\` and \`${delta.headRef}\`.</sub>`,
127
+ ].join('\n');
128
+ }
129
+ // needs-work mode: narrative + per-finding paragraphs.
130
+ const lines = [];
131
+ lines.push(heading);
132
+ lines.push('');
133
+ const topCwes = _topCwes(intro);
134
+ const cweSummary = topCwes.map(([cwe, n]) => {
135
+ const meta = CWE_NARRATIVE[cwe];
136
+ return meta ? `${n} ${meta.name}` : `${n} ${cwe}`;
137
+ }).join(', ');
138
+ const hint = prTitle ? ` in "${prTitle}"` : '';
139
+ lines.push(`I looked at the ${delta.changedFiles.length} file${delta.changedFiles.length === 1 ? '' : 's'} ` +
140
+ `you changed${hint} and noticed **${intro.length} new finding${intro.length === 1 ? '' : 's'}** ` +
141
+ `that wasn't on \`${delta.baseRef}\`. Top concerns: ${cweSummary}.`);
142
+ lines.push('');
143
+ // Per-introduced-finding paragraph (cap at 5 for readability).
144
+ const SHOW = intro.slice(0, 5);
145
+ for (const f of SHOW) {
146
+ const meta = CWE_NARRATIVE[f.cwe];
147
+ const sev = SEVERITY_GLYPH[f.severity] || '⬜';
148
+ const route = _route(f);
149
+ const where = route ? `\`${route}\` (\`${f.file}:${f.line}\`)` : `\`${f.file}:${f.line}\``;
150
+ lines.push(`${sev} **${meta?.name || f.vuln}** — ${where}`);
151
+ if (meta) lines.push(` > ${meta.why}`);
152
+ if (f.remediation) {
153
+ const onelineFix = String(f.remediation).split('\n')[0].slice(0, 240);
154
+ lines.push(` Suggested fix: ${onelineFix}`);
155
+ }
156
+ if (f.confidence != null && f.confidence < 0.7) {
157
+ lines.push(` <sub>Lower confidence (${(f.confidence * 100).toFixed(0)}%) — may be a false positive worth a quick look.</sub>`);
158
+ }
159
+ lines.push('');
160
+ }
161
+ if (intro.length > SHOW.length) {
162
+ lines.push(`<sub>+${intro.length - SHOW.length} more new finding${intro.length - SHOW.length === 1 ? '' : 's'} — see the scan output for the full list.</sub>`);
163
+ lines.push('');
164
+ }
165
+ if (resolved.length) {
166
+ lines.push(`On the bright side: this PR **resolved ${resolved.length} pre-existing finding${resolved.length === 1 ? '' : 's'}**. 👏`);
167
+ lines.push('');
168
+ }
169
+ const i = delta.summary?.introduced || {};
170
+ const blockMerge = (i.critical || 0) + (i.high || 0) > 0;
171
+ if (blockMerge) {
172
+ lines.push(`---`);
173
+ lines.push(`**Blocking merge:** ${i.critical || 0} critical + ${i.high || 0} high severity ` +
174
+ `finding${(i.critical || 0) + (i.high || 0) === 1 ? '' : 's'} introduced. ` +
175
+ `Fix or suppress with \`// agentic-security-ignore: <rule-id>\` before merging.`);
176
+ } else {
177
+ lines.push(`---`);
178
+ lines.push(`Non-blocking: no critical/high severity findings introduced — but consider addressing the items above before this becomes harder to revisit.`);
179
+ }
180
+ return lines.join('\n');
181
+ }
182
+
183
+ const _internal = { CWE_NARRATIVE, SEVERITY_GLYPH, _topCwes };
184
+
185
+
186
+ /***/ })
187
+
188
+ };