0sec-cli 0.15.0 → 0.18.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 (209) hide show
  1. package/0sec.js +327 -0
  2. package/README.md +68 -48
  3. package/attacks/data-exfiltration/pii-leakage.yaml +27 -0
  4. package/attacks/encoding-bypass/base64-encoding.yaml +24 -0
  5. package/attacks/jailbreak/dan-roleplay.yaml +27 -0
  6. package/attacks/jailbreak/hypothetical-scenario.yaml +25 -0
  7. package/attacks/jailbreak/multilingual-bypass.yaml +22 -0
  8. package/attacks/output-manipulation/harmful-content.yaml +25 -0
  9. package/attacks/prompt-injection/context-manipulation.yaml +32 -0
  10. package/attacks/prompt-injection/direct-injection.yaml +28 -0
  11. package/attacks/prompt-injection/indirect-injection.yaml +33 -0
  12. package/attacks/system-prompt-extraction/direct-ask.yaml +30 -0
  13. package/attacks/system-prompt-extraction/markdown-exfil.yaml +26 -0
  14. package/attacks/tool-misuse/ssrf-via-tools.yaml +27 -0
  15. package/chunks/adapt-loop-SMH6V4PD.js +18 -0
  16. package/chunks/adgraph-JLGA6RYI.js +54 -0
  17. package/chunks/agent/skills/frameworks/entra-id.yaml +63 -0
  18. package/chunks/agent/skills/frameworks/graphql-introspection.yaml +116 -0
  19. package/chunks/agent/skills/frameworks/nextjs.yaml +82 -0
  20. package/chunks/agent/skills/frameworks/python-web.yaml +85 -0
  21. package/chunks/agent/skills/frameworks/supabase.yaml +83 -0
  22. package/chunks/agent/skills/frameworks/wordpress-deep.yaml +122 -0
  23. package/chunks/agent/skills/techniques/ad-attack-paths.yaml +57 -0
  24. package/chunks/agent/skills/techniques/advisory-disclosure.yaml +59 -0
  25. package/chunks/agent/skills/techniques/assumption-mining.yaml +53 -0
  26. package/chunks/agent/skills/techniques/blind-exploitation.yaml +143 -0
  27. package/chunks/agent/skills/techniques/crypto-misuse.yaml +88 -0
  28. package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +82 -0
  29. package/chunks/agent/skills/techniques/entra-attack-paths.yaml +55 -0
  30. package/chunks/agent/skills/techniques/http-conformance-diff.yaml +48 -0
  31. package/chunks/agent/skills/techniques/jwt-attacks.yaml +137 -0
  32. package/chunks/agent/skills/techniques/kernel-weaponization.yaml +53 -0
  33. package/chunks/agent/skills/techniques/llm-excessive-agency.yaml +55 -0
  34. package/chunks/agent/skills/techniques/llm-insecure-output-handling.yaml +56 -0
  35. package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +54 -0
  36. package/chunks/agent/skills/techniques/llm-prompt-layer-write.yaml +68 -0
  37. package/chunks/agent/skills/techniques/llm-rag-poisoning.yaml +57 -0
  38. package/chunks/agent/skills/techniques/llm-safety-eval.yaml +51 -0
  39. package/chunks/agent/skills/techniques/npm-ecosystem.yaml +51 -0
  40. package/chunks/agent/skills/techniques/poc-verification.yaml +55 -0
  41. package/chunks/agent/skills/techniques/race-condition.yaml +122 -0
  42. package/chunks/agent/skills/techniques/scoped-fix.yaml +49 -0
  43. package/chunks/agent/skills/techniques/seedless-depth-review.yaml +59 -0
  44. package/chunks/agent/skills/techniques/spec-differential.yaml +48 -0
  45. package/chunks/agent/skills/techniques/variant-hunting.yaml +55 -0
  46. package/chunks/agent/skills/vulnerabilities/cardano-eutxo-validators.yaml +72 -0
  47. package/chunks/agent/skills/vulnerabilities/command-injection.yaml +99 -0
  48. package/chunks/agent/skills/vulnerabilities/deserialization-chains.yaml +106 -0
  49. package/chunks/agent/skills/vulnerabilities/native-memory-safety.yaml +93 -0
  50. package/chunks/agent/skills/vulnerabilities/path-traversal.yaml +78 -0
  51. package/chunks/agent/skills/vulnerabilities/prototype-pollution.yaml +132 -0
  52. package/chunks/agent/skills/vulnerabilities/request-smuggling.yaml +129 -0
  53. package/chunks/agent/skills/vulnerabilities/sqli-advanced.yaml +102 -0
  54. package/chunks/agent/skills/vulnerabilities/ssrf-bypass.yaml +84 -0
  55. package/chunks/agent/skills/vulnerabilities/ssti-exploitation.yaml +112 -0
  56. package/chunks/agent/skills/vulnerabilities/structural-sqli.yaml +91 -0
  57. package/chunks/appsec-catalog-CBWHGTEL.js +24 -0
  58. package/chunks/artifact-scraper-HQ7HLP6V.js +34 -0
  59. package/chunks/assumption-mining-5SNIVVDN.js +79 -0
  60. package/chunks/chunk-242TMK6G.js +110 -0
  61. package/chunks/chunk-2JCCA2JL.js +381 -0
  62. package/chunks/chunk-2RMLOJVB.js +47 -0
  63. package/chunks/chunk-3KPWWJCI.js +1422 -0
  64. package/chunks/chunk-3MOLBTLS.js +953 -0
  65. package/chunks/chunk-53G27VPS.js +136 -0
  66. package/chunks/chunk-57ZENEX2.js +169 -0
  67. package/chunks/chunk-5G3ZXFBW.js +182 -0
  68. package/chunks/chunk-5JI7L7KV.js +74240 -0
  69. package/chunks/chunk-6L26OBWI.js +1094 -0
  70. package/chunks/chunk-6LKRLK2R.js +542 -0
  71. package/chunks/chunk-72CISA2D.js +2597 -0
  72. package/chunks/chunk-7DQEV5QI.js +287 -0
  73. package/chunks/chunk-AD7WXDH6.js +3250 -0
  74. package/chunks/chunk-ADOLI6DT.js +102 -0
  75. package/chunks/chunk-AHTBZC3E.js +596 -0
  76. package/chunks/chunk-ASUR522M.js +518 -0
  77. package/chunks/chunk-ATATQACO.js +471 -0
  78. package/chunks/chunk-BATRQBOR.js +1732 -0
  79. package/chunks/chunk-BFR2CDV5.js +5735 -0
  80. package/chunks/chunk-BFRB6HB2.js +235 -0
  81. package/chunks/chunk-BKZFDZ23.js +361 -0
  82. package/chunks/chunk-BVAZO4WA.js +193 -0
  83. package/chunks/chunk-BZNEY2ZS.js +33225 -0
  84. package/chunks/chunk-C2WXWFBB.js +1070 -0
  85. package/chunks/chunk-CMKXP5RR.js +917 -0
  86. package/chunks/chunk-DW5UWPFY.js +275 -0
  87. package/chunks/chunk-F3WBKITT.js +687 -0
  88. package/chunks/chunk-GMT2AZKM.js +2439 -0
  89. package/chunks/chunk-H2FFLZNK.js +3 -0
  90. package/chunks/chunk-IOL7D5YV.js +515 -0
  91. package/chunks/chunk-IR537GON.js +49 -0
  92. package/chunks/chunk-JQHOLLTD.js +3729 -0
  93. package/chunks/chunk-KLTNTE2Z.js +121 -0
  94. package/chunks/chunk-LP3HHYQU.js +245 -0
  95. package/chunks/chunk-MYVT64FN.js +16 -0
  96. package/chunks/chunk-N7BOUYEV.js +852 -0
  97. package/chunks/chunk-OEFNRYI2.js +150 -0
  98. package/chunks/chunk-P6WKNFWX.js +1968 -0
  99. package/chunks/chunk-QI233I24.js +333 -0
  100. package/chunks/chunk-RJWOYEOG.js +271 -0
  101. package/chunks/chunk-RRMJC3ZE.js +105 -0
  102. package/chunks/chunk-RWONANDA.js +973 -0
  103. package/chunks/chunk-SAFFWQW4.js +5921 -0
  104. package/chunks/chunk-SF4KZ4O3.js +502 -0
  105. package/chunks/chunk-SVJEDVYK.js +808 -0
  106. package/chunks/chunk-TSK2AG6J.js +5108 -0
  107. package/chunks/chunk-U5FLXGHT.js +2010 -0
  108. package/chunks/chunk-UCYRA73C.js +662 -0
  109. package/chunks/chunk-UR4ELX2Y.js +4784 -0
  110. package/chunks/chunk-VQG4FT5D.js +685 -0
  111. package/chunks/chunk-WEBMRWEG.js +9337 -0
  112. package/chunks/chunk-WU6AFRAZ.js +1167 -0
  113. package/chunks/chunk-WVTBZEQO.js +1531 -0
  114. package/chunks/chunk-WXQ6BJ6A.js +596 -0
  115. package/chunks/chunk-XM5NLHYU.js +600 -0
  116. package/chunks/chunk-YLMN3N25.js +4054 -0
  117. package/chunks/chunk-YYRBQVFE.js +2572 -0
  118. package/chunks/chunk-Z5FA2XOX.js +2798 -0
  119. package/chunks/commands-TBC2F5CE.js +16999 -0
  120. package/chunks/corpus-v1.json +403 -0
  121. package/chunks/cost-ledger-NU63MYZM.js +13 -0
  122. package/chunks/data/appsec-archetypes.json +102 -0
  123. package/chunks/data/chromium-archetypes.json +204 -0
  124. package/chunks/data/freebsd-archetypes.json +171 -0
  125. package/chunks/data/kernel-archetypes.json +611 -0
  126. package/chunks/db-KFWOAUUB.js +16 -0
  127. package/chunks/disclose-EKTWSCGW.js +132 -0
  128. package/chunks/dist-4MAW5X6L.js +74 -0
  129. package/chunks/dist-4YICS2J2.js +2776 -0
  130. package/chunks/dist-FMGSR3DW.js +159 -0
  131. package/chunks/eval-runner-7ZF472KV.js +27 -0
  132. package/chunks/example-manifest.json +95 -0
  133. package/chunks/exploit-agent-45YQOHPJ.js +98 -0
  134. package/chunks/exploit-autoclimb-UZGEMGYT.js +81 -0
  135. package/chunks/exploit-climb-SG23KB2G.js +358 -0
  136. package/chunks/fix-IG7LYXH3.js +12 -0
  137. package/chunks/github-issues-MJ6OYOOU.js +157 -0
  138. package/chunks/harness-7ODOLOWU.js +22 -0
  139. package/chunks/http-conformance-DU66MZIU.js +11 -0
  140. package/chunks/http-sender-GWH2IYEA.js +10 -0
  141. package/chunks/hunt-scan-MMKUOETU.js +38 -0
  142. package/chunks/identity-6ZAIIWOR.js +191 -0
  143. package/chunks/kernel-primitive-7U6CWHXD.js +44 -0
  144. package/chunks/kernel-vm-runner-3JAU4O6J.js +62 -0
  145. package/chunks/memsafety-scan-J7SECV5W.js +16 -0
  146. package/chunks/native-loop-RJCJPOUS.js +54 -0
  147. package/chunks/npm-detectors-KB5Y5ZFX.js +69 -0
  148. package/chunks/npm-dynamic-discovery-AHROOZDB.js +13 -0
  149. package/chunks/orchestrate-OC2VZN2X.js +62 -0
  150. package/chunks/pipeline-BZK5BLJR.js +14 -0
  151. package/chunks/pre-recon-cve-66EB6G4M.js +351 -0
  152. package/chunks/prepare-MYY743TK.js +13 -0
  153. package/chunks/process-LQRCS5OY.js +13 -0
  154. package/chunks/replay-runner-45G3VU7X.js +39 -0
  155. package/chunks/run-D56MPDKY.js +32716 -0
  156. package/chunks/runtime-G7YWIO3B.js +43 -0
  157. package/chunks/runtime-J7PLXZNM.js +12 -0
  158. package/chunks/scan-stream-FHI2FYZE.js +96 -0
  159. package/chunks/scope-BI7BF4ZY.js +18 -0
  160. package/chunks/session-store-GZ4A4KYT.js +28 -0
  161. package/chunks/source-files-PWRQR6LY.js +12 -0
  162. package/chunks/specdrift-WCLH6TTQ.js +19 -0
  163. package/chunks/variant-candidates-YERCYMPD.js +13 -0
  164. package/chunks/web-recon-prepass-JJGPEOLE.js +1148 -0
  165. package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
  166. package/dashboard/assets/bot-yILPnRgD.js +1 -0
  167. package/dashboard/assets/chevron-down-BeDJ-Vka.js +1 -0
  168. package/dashboard/assets/circle-alert-BEnMBMT-.js +1 -0
  169. package/dashboard/assets/client-DRYsuOGl.js +9 -0
  170. package/dashboard/assets/copy-Bi5vzoF9.js +1 -0
  171. package/dashboard/assets/desktop-CXTQonNm.js +32 -0
  172. package/dashboard/assets/desktop-NOekk4QS.css +1 -0
  173. package/dashboard/assets/dist-CmX-h7a4.js +1 -0
  174. package/dashboard/assets/dist-DAwYQmf_.js +45 -0
  175. package/dashboard/assets/findings-page-CACW9nw2.js +5 -0
  176. package/dashboard/assets/format-PoISqsES.js +1 -0
  177. package/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  178. package/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  179. package/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  180. package/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  181. package/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  182. package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  183. package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  184. package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  185. package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  186. package/dashboard/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  187. package/dashboard/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  188. package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  189. package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  190. package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  191. package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  192. package/dashboard/assets/jsx-runtime-C7oxC63R.js +1 -0
  193. package/dashboard/assets/live-page-DrgQqQK5.js +1 -0
  194. package/dashboard/assets/meta-tile-CcskIV_o.js +1 -0
  195. package/dashboard/assets/operations-BKE8T-vY.css +2 -0
  196. package/dashboard/assets/operations-D8nUP5_m.js +4 -0
  197. package/dashboard/assets/operations-app-CQQhG54n.js +2 -0
  198. package/dashboard/assets/overview-page-DTGS8dPo.js +1 -0
  199. package/dashboard/assets/page-header-MFVvEtZW.js +1 -0
  200. package/dashboard/assets/play-Dwo-9xt6.js +1 -0
  201. package/dashboard/assets/scans-page-ChVtq8Us.js +1 -0
  202. package/dashboard/assets/search-CiooTLck.js +1 -0
  203. package/dashboard/assets/siren-CCBwTSuk.js +1 -0
  204. package/dashboard/assets/table-BYS-nIVA.js +1 -0
  205. package/dashboard/assets/tabs-DivxbhQy.js +1 -0
  206. package/dashboard/desktop.html +21 -0
  207. package/dashboard/index.html +15 -0
  208. package/package.json +30 -18
  209. package/bin/0sec.cjs +0 -361
@@ -0,0 +1,2572 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __0secCreateRequire } from "node:module";
3
+ const require = __0secCreateRequire(import.meta.url);
4
+ import {
5
+ homeStateDir
6
+ } from "./chunk-6L26OBWI.js";
7
+
8
+ // packages/core/dist/scope/waf-detect.js
9
+ function readHeader(headers, name) {
10
+ const lower = name.toLowerCase();
11
+ if (headers == null)
12
+ return null;
13
+ const maybeGet = headers.get;
14
+ if (typeof maybeGet === "function") {
15
+ const v = headers.get(name);
16
+ return v ?? null;
17
+ }
18
+ for (const [k, v] of Object.entries(headers)) {
19
+ if (k.toLowerCase() === lower) {
20
+ if (Array.isArray(v))
21
+ return v.join(", ");
22
+ return v == null ? null : String(v);
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+ function readCookies(resp) {
28
+ if (resp.cookies && resp.cookies.length > 0)
29
+ return resp.cookies;
30
+ const h = resp.headers;
31
+ const getSet = h.getSetCookie;
32
+ if (typeof getSet === "function") {
33
+ try {
34
+ const arr = getSet.call(h);
35
+ if (Array.isArray(arr) && arr.length)
36
+ return arr;
37
+ } catch {
38
+ }
39
+ }
40
+ const single = readHeader(h, "set-cookie");
41
+ return single ? [single] : [];
42
+ }
43
+ var WAF_SIGNATURES = [
44
+ {
45
+ vendor: "cloudflare",
46
+ label: "Cloudflare",
47
+ headers: [
48
+ { name: "server", contains: "cloudflare" },
49
+ { name: "cf-ray" },
50
+ { name: "cf-mitigated" }
51
+ ],
52
+ cookies: ["__cfduid", "cf_clearance", "__cf_bm"],
53
+ body: ["attention required! | cloudflare", "cf-error-details", "ray id:", "cloudflare to restrict access"]
54
+ },
55
+ {
56
+ vendor: "akamai",
57
+ label: "Akamai (Kona/Ghost)",
58
+ headers: [
59
+ { name: "server", contains: "akamaighost" },
60
+ { name: "x-akamai-transformed" }
61
+ ],
62
+ cookies: ["ak_bmsc", "ak_bmsc", "bm_sz", "bm_sv"],
63
+ body: ["access denied", "reference #", "you don't have permission to access"]
64
+ },
65
+ {
66
+ vendor: "aws-waf",
67
+ label: "AWS WAF",
68
+ headers: [
69
+ { name: "x-amzn-requestid" },
70
+ { name: "x-amz-cf-id" },
71
+ { name: "x-amzn-waf-action" }
72
+ ],
73
+ cookies: ["aws-waf-token"],
74
+ body: ["request blocked", "aws waf"]
75
+ },
76
+ {
77
+ vendor: "imperva-incapsula",
78
+ label: "Imperva / Incapsula",
79
+ headers: [
80
+ { name: "x-iinfo" },
81
+ { name: "x-cdn", contains: "incapsula" }
82
+ ],
83
+ cookies: ["incap_ses", "visid_incap", "nlbi_"],
84
+ body: ["incapsula incident id", "powered by incapsula", "_incapsula_resource"]
85
+ },
86
+ {
87
+ vendor: "sucuri",
88
+ label: "Sucuri CloudProxy",
89
+ headers: [
90
+ { name: "server", contains: "sucuri" },
91
+ { name: "x-sucuri-id" },
92
+ { name: "x-sucuri-cache" }
93
+ ],
94
+ body: ["access denied - sucuri website firewall", "sucuri/cloudproxy", "questions about why you are blocked"]
95
+ },
96
+ {
97
+ vendor: "f5-big-ip",
98
+ label: "F5 BIG-IP ASM",
99
+ headers: [{ name: "server", contains: "big-ip" }],
100
+ cookies: ["ts", "bigipserver", "f5_cspm"],
101
+ body: ["the requested url was rejected", "support id", "please consult with your administrator"]
102
+ },
103
+ {
104
+ vendor: "fortinet-fortiweb",
105
+ label: "Fortinet FortiWeb",
106
+ headers: [{ name: "server", contains: "fortiweb" }],
107
+ cookies: ["cookiesession1", "fortiwafsid"],
108
+ body: ["web page blocked", ".fgd_icon", "blocked by fortiweb"]
109
+ },
110
+ {
111
+ vendor: "barracuda",
112
+ label: "Barracuda WAF",
113
+ headers: [{ name: "server", contains: "barracuda" }],
114
+ cookies: ["barra_counter_session", "bni_"],
115
+ body: ["you are being redirected", "barracuda"]
116
+ },
117
+ {
118
+ vendor: "wordfence",
119
+ label: "Wordfence",
120
+ body: ["generated by wordfence", "your access to this site has been limited", "wordfence"]
121
+ },
122
+ {
123
+ vendor: "modsecurity",
124
+ label: "ModSecurity / OWASP CRS",
125
+ headers: [{ name: "server", contains: "mod_security" }],
126
+ body: ["mod_security", "modsecurity", "this error was generated by mod_security", "not acceptable!"]
127
+ }
128
+ ];
129
+ var WAF_BLOCK_STATUSES = /* @__PURE__ */ new Set([403, 406, 418, 429, 501, 503]);
130
+ var GENERIC_BLOCK_MARKERS = [
131
+ "request blocked",
132
+ "access denied",
133
+ "forbidden",
134
+ "the requested url was rejected",
135
+ "your request has been blocked",
136
+ "this request was blocked",
137
+ "security policy",
138
+ "web application firewall",
139
+ "blocked for security reasons"
140
+ ];
141
+ function fingerprintWaf(resp) {
142
+ const headers = resp.headers;
143
+ const cookies = readCookies(resp).map((c) => c.toLowerCase());
144
+ const body = (resp.body ?? "").toLowerCase();
145
+ let best = null;
146
+ for (const sig of WAF_SIGNATURES) {
147
+ const signals = [];
148
+ let weight = 0;
149
+ for (const h of sig.headers ?? []) {
150
+ const val = readHeader(headers, h.name);
151
+ if (val == null)
152
+ continue;
153
+ if (h.contains == null || val.toLowerCase().includes(h.contains)) {
154
+ signals.push({ kind: "header", detail: `${h.name}: ${val}` });
155
+ weight += 3;
156
+ }
157
+ }
158
+ for (const cookiePrefix of sig.cookies ?? []) {
159
+ const hit = cookies.find((c) => c.startsWith(cookiePrefix) || c.includes(`${cookiePrefix}=`) || c.includes(` ${cookiePrefix}`));
160
+ if (hit) {
161
+ signals.push({ kind: "cookie", detail: cookiePrefix });
162
+ weight += 3;
163
+ }
164
+ }
165
+ for (const marker of sig.body ?? []) {
166
+ if (body.includes(marker)) {
167
+ signals.push({ kind: "body", detail: marker });
168
+ weight += 1;
169
+ }
170
+ }
171
+ if (signals.length > 0 && (best == null || weight > best.weight)) {
172
+ best = { sig, signals, weight };
173
+ }
174
+ }
175
+ if (!best)
176
+ return null;
177
+ const confidence = Math.min(1, best.weight / 6);
178
+ return {
179
+ vendor: best.sig.vendor,
180
+ label: best.sig.label,
181
+ confidence: Math.round(confidence * 100) / 100,
182
+ signals: best.signals
183
+ };
184
+ }
185
+ function classifyResponse(resp) {
186
+ const status = resp.status;
187
+ const fingerprint = fingerprintWaf(resp);
188
+ const body = (resp.body ?? "").toLowerCase();
189
+ const signals = fingerprint ? [...fingerprint.signals] : [];
190
+ const statusIsBlock = WAF_BLOCK_STATUSES.has(status);
191
+ if (statusIsBlock)
192
+ signals.push({ kind: "status", detail: String(status) });
193
+ const genericMarker = GENERIC_BLOCK_MARKERS.find((m) => body.includes(m));
194
+ const challengeMarker = body.includes("checking your browser") || body.includes("just a moment") || body.includes("enable javascript and cookies to continue") || body.includes("verify you are a human") || body.includes("ddos protection by");
195
+ let blocked = false;
196
+ let reason;
197
+ if (fingerprint && statusIsBlock) {
198
+ blocked = true;
199
+ reason = `${fingerprint.label} block (HTTP ${status})`;
200
+ } else if (fingerprint && (genericMarker || challengeMarker)) {
201
+ blocked = true;
202
+ reason = `${fingerprint.label} interstitial/challenge page`;
203
+ } else if (statusIsBlock && genericMarker) {
204
+ blocked = true;
205
+ if (genericMarker)
206
+ signals.push({ kind: "body", detail: genericMarker });
207
+ reason = `generic WAF block marker "${genericMarker}" (HTTP ${status})`;
208
+ } else if (status === 429) {
209
+ blocked = true;
210
+ reason = "HTTP 429 rate-limit rejection (edge throttle)";
211
+ } else if (challengeMarker) {
212
+ blocked = true;
213
+ reason = "browser-challenge interstitial";
214
+ } else {
215
+ reason = fingerprint ? `${fingerprint.label} fingerprinted but response looks legitimate (HTTP ${status})` : `no WAF block detected (HTTP ${status})`;
216
+ }
217
+ return { blocked, status, fingerprint, reason, signals };
218
+ }
219
+ var EVASION_KEYWORDS = [
220
+ "select",
221
+ "union",
222
+ "insert",
223
+ "update",
224
+ "delete",
225
+ "drop",
226
+ "from",
227
+ "where",
228
+ "or",
229
+ "and",
230
+ "script",
231
+ "alert",
232
+ "onerror",
233
+ "onload",
234
+ "javascript",
235
+ "eval",
236
+ "etc/passwd",
237
+ "concat",
238
+ "sleep",
239
+ "benchmark"
240
+ ];
241
+ function escapeRegExp(s) {
242
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
243
+ }
244
+ function percentEncodeAll(value) {
245
+ let out = "";
246
+ for (const ch of Buffer.from(value, "utf8")) {
247
+ out += "%" + ch.toString(16).toUpperCase().padStart(2, "0");
248
+ }
249
+ return out;
250
+ }
251
+ function transformParamValues(parts, fn) {
252
+ let changed = false;
253
+ let url = parts.url;
254
+ try {
255
+ const u = new URL(parts.url);
256
+ const next = new URLSearchParams();
257
+ for (const [k, v] of u.searchParams.entries()) {
258
+ const nv = fn(v);
259
+ if (nv !== v)
260
+ changed = true;
261
+ next.append(k, nv);
262
+ }
263
+ const qs = Array.from(next.entries()).map(([k, v]) => `${encodeURIComponent(k)}=${v}`).join("&");
264
+ u.search = qs ? `?${qs}` : "";
265
+ url = u.toString();
266
+ } catch {
267
+ }
268
+ let body = parts.body;
269
+ if (typeof body === "string" && body.length > 0) {
270
+ const nb = fn(body);
271
+ if (nb !== body) {
272
+ changed = true;
273
+ body = nb;
274
+ }
275
+ }
276
+ return { parts: { ...parts, url, body }, changed };
277
+ }
278
+ function caseFoldKeywords(value) {
279
+ let out = value;
280
+ for (const kw of EVASION_KEYWORDS) {
281
+ const re = new RegExp(escapeRegExp(kw), "gi");
282
+ out = out.replace(re, (match) => match.split("").map((c, i) => i % 2 === 0 ? c.toUpperCase() : c.toLowerCase()).join(""));
283
+ }
284
+ return out;
285
+ }
286
+ function inlineCommentKeywords(value) {
287
+ let out = value;
288
+ for (const kw of EVASION_KEYWORDS) {
289
+ if (kw.length < 3 || kw.includes("/"))
290
+ continue;
291
+ const re = new RegExp(escapeRegExp(kw), "gi");
292
+ out = out.replace(re, (match) => {
293
+ const mid = Math.floor(match.length / 2);
294
+ return match.slice(0, mid) + "/**/" + match.slice(mid);
295
+ });
296
+ }
297
+ return out;
298
+ }
299
+ function altWhitespace(value) {
300
+ return value.includes(" ") ? value.replace(/ /g, "/**/") : value;
301
+ }
302
+ function varyHeaderCasing(parts) {
303
+ const next = {};
304
+ let changed = false;
305
+ for (const [k, v] of Object.entries(parts.headers)) {
306
+ const recased = k.split("-").map((seg, i) => i % 2 === 0 ? seg.toLowerCase() : seg.toUpperCase()).join("-");
307
+ if (recased !== k)
308
+ changed = true;
309
+ next[recased] = v;
310
+ }
311
+ if (!("x-forwarded-for" in parts.headers) && !("X-Forwarded-For" in parts.headers)) {
312
+ next["X-Forwarded-For"] = "127.0.0.1";
313
+ changed = true;
314
+ }
315
+ return { headers: next, changed };
316
+ }
317
+ var EVASION_STRATEGIES = [
318
+ {
319
+ name: "percent-encode-params",
320
+ description: "percent-encode query/body values (origin URL-decodes; defeats literal signature match)",
321
+ apply(parts) {
322
+ const r = transformParamValues(parts, percentEncodeAll);
323
+ return { parts: r.parts, changed: r.changed, note: "percent-encoded parameter values" };
324
+ }
325
+ },
326
+ {
327
+ name: "double-encode-params",
328
+ description: "double percent-encode values (defeats single-pass URL-decode normalizers)",
329
+ apply(parts) {
330
+ const r = transformParamValues(parts, (v) => percentEncodeAll(percentEncodeAll(v)));
331
+ return { parts: r.parts, changed: r.changed, note: "double percent-encoded parameter values" };
332
+ }
333
+ },
334
+ {
335
+ name: "case-fold-keywords",
336
+ description: "alternate keyword casing (SeLeCt) to slip case-sensitive rules",
337
+ apply(parts) {
338
+ const r = transformParamValues(parts, caseFoldKeywords);
339
+ return { parts: r.parts, changed: r.changed, note: "case-folded SQL/XSS keywords" };
340
+ }
341
+ },
342
+ {
343
+ name: "inline-comment-keywords",
344
+ description: "insert SQL inline comments inside keywords (UN/**/ION)",
345
+ apply(parts) {
346
+ const r = transformParamValues(parts, inlineCommentKeywords);
347
+ return { parts: r.parts, changed: r.changed, note: "inline-commented keywords" };
348
+ }
349
+ },
350
+ {
351
+ name: "alt-whitespace",
352
+ description: "replace spaces with /**/ to evade whitespace-anchored rules",
353
+ apply(parts) {
354
+ const r = transformParamValues(parts, altWhitespace);
355
+ return { parts: r.parts, changed: r.changed, note: "alternate whitespace tokens" };
356
+ }
357
+ },
358
+ {
359
+ name: "header-casing",
360
+ description: "vary header-name casing + add benign X-Forwarded-For (IP/geo rule probe)",
361
+ apply(parts) {
362
+ const r = varyHeaderCasing(parts);
363
+ return { parts: { ...parts, headers: r.headers }, changed: r.changed, note: "varied header casing / origin headers" };
364
+ }
365
+ }
366
+ ];
367
+ function jitterFor(attempt, baseMs = 250, rng) {
368
+ const factor = attempt + 1;
369
+ if (rng) {
370
+ return Math.floor(rng() * baseMs * factor);
371
+ }
372
+ return baseMs * factor;
373
+ }
374
+ function defaultSleep(ms) {
375
+ return new Promise((r) => setTimeout(r, ms));
376
+ }
377
+ async function runEvasionCampaign(blockedParts, send, opts = {}) {
378
+ const ladder = opts.strategies ?? EVASION_STRATEGIES;
379
+ const max = Math.min(opts.maxAttempts ?? ladder.length, ladder.length);
380
+ const sleepFn = opts.sleepFn ?? defaultSleep;
381
+ const attempts = [];
382
+ let lastResponse = null;
383
+ let attemptNo = 0;
384
+ for (let i = 0; i < max; i++) {
385
+ const strategy = ladder[i];
386
+ const transformed = strategy.apply(blockedParts);
387
+ if (!transformed.changed) {
388
+ attempts.push({
389
+ strategy: strategy.name,
390
+ description: strategy.description,
391
+ note: `skipped (no-op for this request)`,
392
+ jitterMs: 0,
393
+ status: lastResponse?.status ?? 0,
394
+ blocked: true,
395
+ url: blockedParts.url
396
+ });
397
+ continue;
398
+ }
399
+ const jitterMs = jitterFor(attemptNo, opts.jitterBaseMs, opts.rng);
400
+ attemptNo += 1;
401
+ if (jitterMs > 0) {
402
+ await sleepFn(jitterMs);
403
+ }
404
+ const res = await send(transformed.parts);
405
+ lastResponse = res;
406
+ const verdict = classifyResponse(res);
407
+ attempts.push({
408
+ strategy: strategy.name,
409
+ description: strategy.description,
410
+ note: transformed.note,
411
+ jitterMs,
412
+ status: res.status,
413
+ blocked: verdict.blocked,
414
+ url: transformed.parts.url
415
+ });
416
+ if (!verdict.blocked) {
417
+ return {
418
+ final: res,
419
+ attempts,
420
+ bypassed: true,
421
+ bypassStrategy: strategy.name
422
+ };
423
+ }
424
+ }
425
+ return {
426
+ final: lastResponse ?? { status: 0, headers: {}, body: "" },
427
+ attempts,
428
+ bypassed: false,
429
+ bypassStrategy: null
430
+ };
431
+ }
432
+ var WafDetector = class {
433
+ nowFn;
434
+ hosts = /* @__PURE__ */ new Map();
435
+ evidenceLog = [];
436
+ /** Cap so a pathological scan can't grow the evidence log unbounded. */
437
+ maxEvidence;
438
+ constructor(opts = {}) {
439
+ this.nowFn = opts.nowFn ?? (() => Date.now());
440
+ this.maxEvidence = opts.maxEvidence ?? 500;
441
+ }
442
+ /** Host key for a URL (lowercased hostname; falls back to the raw string). */
443
+ hostOf(url) {
444
+ try {
445
+ return new URL(url).hostname.toLowerCase();
446
+ } catch {
447
+ return url.toLowerCase();
448
+ }
449
+ }
450
+ /**
451
+ * Record a detected block for `url`. Returns the verdict so the caller can
452
+ * decide whether to launch an evasion campaign. Updates per-host state.
453
+ */
454
+ recordBlock(url, verdict) {
455
+ const host = this.hostOf(url);
456
+ const vendor = verdict.fingerprint?.vendor ?? "generic";
457
+ const label = verdict.fingerprint?.label ?? "unfingerprinted WAF";
458
+ const confidence = verdict.fingerprint?.confidence ?? 0.3;
459
+ const now = this.nowFn();
460
+ let state = this.hosts.get(host);
461
+ if (!state) {
462
+ state = {
463
+ host,
464
+ vendor,
465
+ label,
466
+ confidence,
467
+ firstSeenMs: now,
468
+ blockCount: 0,
469
+ evasionAttempts: 0,
470
+ bypasses: 0
471
+ };
472
+ this.hosts.set(host, state);
473
+ } else if (confidence > state.confidence) {
474
+ state.vendor = vendor;
475
+ state.label = label;
476
+ state.confidence = confidence;
477
+ }
478
+ state.blockCount += 1;
479
+ }
480
+ /** Record the outcome of an evasion campaign as evidence. */
481
+ recordEvasion(url, verdict, campaign) {
482
+ const host = this.hostOf(url);
483
+ const state = this.hosts.get(host);
484
+ if (state) {
485
+ state.evasionAttempts += campaign.attempts.filter((a) => a.jitterMs > 0 || a.note !== "skipped (no-op for this request)").length;
486
+ if (campaign.bypassed)
487
+ state.bypasses += 1;
488
+ }
489
+ if (this.evidenceLog.length < this.maxEvidence) {
490
+ this.evidenceLog.push({
491
+ ts: this.nowFn(),
492
+ host,
493
+ url,
494
+ vendor: verdict.fingerprint?.vendor ?? "generic",
495
+ label: verdict.fingerprint?.label ?? "unfingerprinted WAF",
496
+ reason: verdict.reason,
497
+ attempts: campaign.attempts,
498
+ bypassed: campaign.bypassed,
499
+ authorizedEngagement: true
500
+ });
501
+ }
502
+ }
503
+ /** True if we've ever seen a block for this URL's host. */
504
+ isKnownWafHost(url) {
505
+ return this.hosts.has(this.hostOf(url));
506
+ }
507
+ /** All per-host WAF states. */
508
+ hostStates() {
509
+ return Array.from(this.hosts.values());
510
+ }
511
+ /** Full evidence trail (capped). */
512
+ evidence() {
513
+ return [...this.evidenceLog];
514
+ }
515
+ /** Compact summary for the report. */
516
+ summary() {
517
+ const hosts = this.hostStates();
518
+ return {
519
+ waf_detected: hosts.length > 0,
520
+ hosts,
521
+ total_blocks: hosts.reduce((n, h) => n + h.blockCount, 0),
522
+ total_evasion_attempts: hosts.reduce((n, h) => n + h.evasionAttempts, 0),
523
+ total_bypasses: hosts.reduce((n, h) => n + h.bypasses, 0)
524
+ };
525
+ }
526
+ };
527
+
528
+ // packages/core/dist/recon/subdomains.js
529
+ var DEFAULT_TIMEOUT_MS = 15e3;
530
+ var CRT_SH_BASE = "https://crt.sh";
531
+ function isApexOrEmpty(host, apex) {
532
+ return host === apex || host.length === 0;
533
+ }
534
+ function normalizeCertName(raw, apex) {
535
+ let host = raw.trim().toLowerCase();
536
+ if (!host)
537
+ return void 0;
538
+ if (host.startsWith("*."))
539
+ host = host.slice(2);
540
+ if (host.includes("*"))
541
+ return void 0;
542
+ if (host.endsWith("."))
543
+ host = host.slice(0, -1);
544
+ if (!host)
545
+ return void 0;
546
+ if (host !== apex && !host.endsWith(`.${apex}`))
547
+ return void 0;
548
+ return host;
549
+ }
550
+ function parseCrtShRows(rows, apex) {
551
+ if (!Array.isArray(rows))
552
+ return [];
553
+ const hosts = /* @__PURE__ */ new Set();
554
+ for (const row of rows) {
555
+ const fields = [];
556
+ if (typeof row?.common_name === "string")
557
+ fields.push(row.common_name);
558
+ if (typeof row?.name_value === "string")
559
+ fields.push(...row.name_value.split(/\n+/));
560
+ for (const field of fields) {
561
+ const host = normalizeCertName(field, apex);
562
+ if (host && !isApexOrEmpty(host, apex))
563
+ hosts.add(host);
564
+ }
565
+ }
566
+ return [...hosts].sort();
567
+ }
568
+ async function defaultFetchJson(url, timeoutMs) {
569
+ if (new URL(url).hostname.toLowerCase() !== "crt.sh") {
570
+ throw new Error(`subdomains: refusing non-crt.sh fetch (${url})`);
571
+ }
572
+ const controller = new AbortController();
573
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
574
+ try {
575
+ const res = await fetch(url, {
576
+ signal: controller.signal,
577
+ headers: { Accept: "application/json", "User-Agent": "0sec-recon" }
578
+ });
579
+ if (!res.ok)
580
+ throw new Error(`crt.sh HTTP ${res.status}`);
581
+ return await res.json();
582
+ } finally {
583
+ clearTimeout(timer);
584
+ }
585
+ }
586
+ async function defaultResolve(host) {
587
+ const dns = await import("node:dns/promises");
588
+ const [a, aaaa, cname] = await Promise.allSettled([
589
+ dns.resolve4(host),
590
+ dns.resolve6(host),
591
+ dns.resolveCname(host)
592
+ ]);
593
+ const addresses = [
594
+ ...a.status === "fulfilled" ? a.value : [],
595
+ ...aaaa.status === "fulfilled" ? aaaa.value : []
596
+ ];
597
+ if (addresses.length === 0)
598
+ throw new Error(`no A/AAAA records for ${host}`);
599
+ const cnameTarget = cname.status === "fulfilled" ? cname.value[0] : void 0;
600
+ return { addresses, ...cnameTarget ? { cname: cnameTarget } : {} };
601
+ }
602
+ async function enumerateSubdomains(opts) {
603
+ const apex = opts.domain.trim().toLowerCase().replace(/\.$/, "");
604
+ if (!apex)
605
+ throw new Error("subdomains: empty domain");
606
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
607
+ const fetchJson2 = opts.fetchJson ?? ((url2) => defaultFetchJson(url2, timeoutMs));
608
+ const resolve = opts.resolve ?? defaultResolve;
609
+ const url = `${CRT_SH_BASE}/?q=${encodeURIComponent(`%.${apex}`)}&output=json`;
610
+ let candidates;
611
+ try {
612
+ candidates = parseCrtShRows(await fetchJson2(url), apex);
613
+ } catch {
614
+ return [];
615
+ }
616
+ const resolved = await Promise.allSettled(candidates.map(async (host) => {
617
+ const { addresses, cname } = await resolve(host);
618
+ return {
619
+ host,
620
+ source: "crt.sh",
621
+ addresses,
622
+ ...cname ? { cname } : {}
623
+ };
624
+ }));
625
+ const out = [];
626
+ for (const result of resolved) {
627
+ if (result.status === "fulfilled")
628
+ out.push(result.value);
629
+ }
630
+ out.sort((a, b) => a.host.localeCompare(b.host));
631
+ return out;
632
+ }
633
+
634
+ // packages/core/dist/recon/js-artifacts.js
635
+ var SECRET_PATTERNS = [
636
+ // ---- Known-public / expected keys (noise, never a real finding) ----
637
+ // PostHog project key — shipped client-side by design.
638
+ { kind: "posthog_public_key", source: /\bphc_[A-Za-z0-9]{32,}\b/, confidence: "low" },
639
+ // Vercel Web Analytics / Speed Insights public token.
640
+ { kind: "vercel_analytics_key", source: /\b(?:va_pub_|vercel_analytics_)[A-Za-z0-9]{16,}\b/, confidence: "low" },
641
+ // ---- High-signal credentials (ported from foxguard src/secrets.rs) ----
642
+ // Stripe live secret / restricted key.
643
+ { kind: "stripe_live_key", source: /\b(?:sk|rk)_live_[A-Za-z0-9]{20,}\b/, confidence: "high" },
644
+ // AWS access key id.
645
+ { kind: "aws_access_key_id", source: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/, confidence: "high" },
646
+ // AWS secret access key — assignment form (the 40-char secret is meaningless
647
+ // without the `aws_secret_access_key =` anchor, so we require it).
648
+ {
649
+ kind: "aws_secret_access_key",
650
+ source: /aws_secret_access_key\s*[:=]\s*["']?[A-Za-z0-9/+=]{40}["']?/i,
651
+ confidence: "high"
652
+ },
653
+ // Google API key.
654
+ { kind: "google_api_key", source: /\bAIza[A-Za-z0-9_-]{35}\b/, confidence: "high" },
655
+ // GitHub personal-access / app tokens (ghp_/gho_/ghu_/ghs_/ghr_ + fine-grained github_pat_).
656
+ { kind: "github_token", source: /\bgh[pousr]_[A-Za-z0-9]{36,}\b|\bgithub_pat_[A-Za-z0-9_]{20,}\b/, confidence: "high" },
657
+ // GitLab personal-access token.
658
+ { kind: "gitlab_token", source: /\bglpat-[A-Za-z0-9\-_]{20,}\b/, confidence: "high" },
659
+ // npm access token.
660
+ { kind: "npm_token", source: /\bnpm_[A-Za-z0-9]{36}\b/, confidence: "high" },
661
+ // Slack token.
662
+ { kind: "slack_token", source: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, confidence: "high" },
663
+ // Private-key material (RSA/DSA/EC/OpenSSH/PKCS#8 PEM header).
664
+ { kind: "private_key", source: /-----BEGIN (?:RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----/, confidence: "high" },
665
+ // Generic credential assignment: `api_key`/`apikey`/`secret`/`token`/`password`
666
+ // assigned a non-trivial quoted literal. Lower-precision than the vendor
667
+ // patterns above, so it sits last and is the catch-all for a hardcoded
668
+ // `const API_KEY = "…"` in a bundle (the CodeWall Bain move). Still `high`:
669
+ // a quoted 16+ char credential literal in served JS is a real leak.
670
+ {
671
+ kind: "generic_api_key",
672
+ source: /\b(?:api[_-]?key|apikey|secret|token|passwd|password)\b\s*[:=]\s*["'][A-Za-z0-9_\-./+=]{16,}["']/i,
673
+ confidence: "high"
674
+ },
675
+ // HTTP Authorization bearer token embedded in a bundle.
676
+ {
677
+ kind: "bearer_token",
678
+ source: /\bbearer\s+[A-Za-z0-9_\-.=]{20,}\b/i,
679
+ confidence: "high"
680
+ },
681
+ // JWT (three base64url segments). Catches Supabase service_role keys, which
682
+ // are JWTs — a service_role key in a client bundle is a serious leak.
683
+ {
684
+ kind: "jwt",
685
+ source: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/,
686
+ confidence: "high"
687
+ }
688
+ ];
689
+ var SOURCE_MAPPING_URL_RE = /\/\/[#@]\s*sourceMappingURL=([^\s'"]+)/;
690
+ function redactSecret(value) {
691
+ const prefixLen = Math.min(6, value.length);
692
+ const prefix = value.slice(0, prefixLen);
693
+ return `${prefix}\u2026(${value.length} chars)`;
694
+ }
695
+ function resolveSourceMapUrl(bundleUrl, mappingValue) {
696
+ const trimmed = mappingValue.trim();
697
+ if (!trimmed || trimmed.startsWith("data:"))
698
+ return void 0;
699
+ try {
700
+ return new URL(trimmed, bundleUrl).toString();
701
+ } catch {
702
+ return void 0;
703
+ }
704
+ }
705
+ function scanBody(chunkUrl, body) {
706
+ const hits = [];
707
+ const seen = /* @__PURE__ */ new Set();
708
+ for (const pattern of SECRET_PATTERNS) {
709
+ const flags = pattern.source.flags.includes("g") ? pattern.source.flags : `${pattern.source.flags}g`;
710
+ const re = new RegExp(pattern.source.source, flags);
711
+ let m;
712
+ while ((m = re.exec(body)) !== null) {
713
+ const raw = m[0];
714
+ const dedupeKey = `${pattern.kind}::${raw}`;
715
+ if (seen.has(dedupeKey))
716
+ continue;
717
+ seen.add(dedupeKey);
718
+ hits.push({
719
+ kind: pattern.kind,
720
+ match: redactSecret(raw),
721
+ chunk: chunkUrl,
722
+ confidence: pattern.confidence
723
+ });
724
+ if (m.index === re.lastIndex)
725
+ re.lastIndex++;
726
+ }
727
+ }
728
+ return hits;
729
+ }
730
+ async function probeSourceMaps(chunkUrl, body, fetchText) {
731
+ const candidates = /* @__PURE__ */ new Set();
732
+ const comment = body.match(SOURCE_MAPPING_URL_RE);
733
+ if (comment) {
734
+ const resolved = resolveSourceMapUrl(chunkUrl, comment[1]);
735
+ if (resolved)
736
+ candidates.add(resolved);
737
+ }
738
+ try {
739
+ const sibling = new URL(chunkUrl);
740
+ sibling.search = "";
741
+ sibling.hash = "";
742
+ candidates.add(`${sibling.toString()}.map`);
743
+ } catch {
744
+ }
745
+ const results = [];
746
+ for (const url of candidates) {
747
+ let res;
748
+ try {
749
+ res = await fetchText(url);
750
+ } catch {
751
+ results.push({ url, exposed: false });
752
+ continue;
753
+ }
754
+ results.push({ url, exposed: res.status === 200 && res.body.trim().length > 0 });
755
+ }
756
+ return results;
757
+ }
758
+ async function scanJsArtifacts(opts) {
759
+ const { chunkUrls, fetchText } = opts;
760
+ const sourceMaps = [];
761
+ const secrets = [];
762
+ const seenMapUrls = /* @__PURE__ */ new Set();
763
+ for (const chunkUrl of chunkUrls) {
764
+ let res;
765
+ try {
766
+ res = await fetchText(chunkUrl);
767
+ } catch {
768
+ continue;
769
+ }
770
+ if (res.status !== 200 || !res.body)
771
+ continue;
772
+ for (const sm of await probeSourceMaps(chunkUrl, res.body, fetchText)) {
773
+ if (seenMapUrls.has(sm.url))
774
+ continue;
775
+ seenMapUrls.add(sm.url);
776
+ sourceMaps.push(sm);
777
+ }
778
+ secrets.push(...scanBody(chunkUrl, res.body));
779
+ }
780
+ return { sourceMaps, secrets };
781
+ }
782
+
783
+ // packages/core/dist/intel/cache.js
784
+ import { createHash } from "node:crypto";
785
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
786
+ import { dirname, join } from "node:path";
787
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
788
+ function defaultIntelCacheDir() {
789
+ return join(homeStateDir(), "intel-cache");
790
+ }
791
+ var IntelCache = class {
792
+ cacheDir;
793
+ constructor(cacheDir = defaultIntelCacheDir()) {
794
+ this.cacheDir = cacheDir;
795
+ }
796
+ get(source, key, opts = {}) {
797
+ const path = this.pathFor(source, key);
798
+ if (!existsSync(path))
799
+ return null;
800
+ try {
801
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
802
+ const expiresAtMs = Date.parse(parsed.expiresAt);
803
+ if (Number.isNaN(expiresAtMs))
804
+ return null;
805
+ if (!opts.offline && expiresAtMs <= Date.now())
806
+ return null;
807
+ return parsed.value;
808
+ } catch {
809
+ return null;
810
+ }
811
+ }
812
+ set(source, key, value, ttlMs = DEFAULT_TTL_MS) {
813
+ const path = this.pathFor(source, key);
814
+ mkdirSync(dirname(path), { recursive: true });
815
+ const now = /* @__PURE__ */ new Date();
816
+ const envelope = {
817
+ key,
818
+ source,
819
+ fetchedAt: now.toISOString(),
820
+ expiresAt: new Date(now.getTime() + ttlMs).toISOString(),
821
+ value
822
+ };
823
+ writeFileSync(path, `${JSON.stringify(envelope, null, 2)}
824
+ `);
825
+ }
826
+ pathFor(source, key) {
827
+ const digest = createHash("sha256").update(key).digest("hex");
828
+ return join(this.cacheDir, source, `${digest}.json`);
829
+ }
830
+ };
831
+ async function cachedJson(cache, source, key, fetcher, opts = {}) {
832
+ const cached = cache.get(source, key, { offline: opts.offline });
833
+ if (cached)
834
+ return cached;
835
+ if (opts.offline) {
836
+ throw new Error(`offline cache miss for ${source}:${key}`);
837
+ }
838
+ const value = await fetcher();
839
+ cache.set(source, key, value, opts.ttlMs);
840
+ return value;
841
+ }
842
+ async function fetchJson(url, init = {}, opts = {}) {
843
+ const controller = new AbortController();
844
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 2e4);
845
+ try {
846
+ const res = await (opts.fetchImpl ?? fetch)(url, {
847
+ ...init,
848
+ signal: controller.signal
849
+ });
850
+ if (!res.ok) {
851
+ throw new Error(`HTTP ${res.status} for ${url}`);
852
+ }
853
+ return await res.json();
854
+ } finally {
855
+ clearTimeout(timer);
856
+ }
857
+ }
858
+
859
+ // packages/core/dist/intel/normalize.js
860
+ function normalizeSeverity(value) {
861
+ const normalized = String(value ?? "").trim().toLowerCase();
862
+ if (normalized === "critical")
863
+ return "critical";
864
+ if (normalized === "high")
865
+ return "high";
866
+ if (normalized === "moderate" || normalized === "medium")
867
+ return "medium";
868
+ if (normalized === "low")
869
+ return "low";
870
+ return "info";
871
+ }
872
+ function uniqueStrings(values) {
873
+ return [...new Set(values.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()))];
874
+ }
875
+ function uniqueReferences(refs) {
876
+ const seen = /* @__PURE__ */ new Set();
877
+ const out = [];
878
+ for (const ref of refs) {
879
+ if (!ref.url || seen.has(ref.url))
880
+ continue;
881
+ seen.add(ref.url);
882
+ out.push(ref);
883
+ }
884
+ return out;
885
+ }
886
+ function primaryId(ids, fallback) {
887
+ const cve = ids.find((id) => /^CVE-\d{4}-\d{4,}$/i.test(id));
888
+ if (cve)
889
+ return cve.toUpperCase();
890
+ const ghsa = ids.find((id) => /^GHSA-/i.test(id));
891
+ if (ghsa)
892
+ return ghsa.toUpperCase();
893
+ const osv = ids.find(Boolean);
894
+ return osv ?? fallback;
895
+ }
896
+ function normalizeCveId(cveId) {
897
+ const normalized = cveId.trim().toUpperCase();
898
+ if (!/^CVE-\d{4}-\d{4,}$/.test(normalized)) {
899
+ throw new Error(`invalid CVE id: ${cveId}`);
900
+ }
901
+ return normalized;
902
+ }
903
+ function parseCvssVectorSeverity(vector) {
904
+ if (!vector)
905
+ return void 0;
906
+ if (vector.trim().toUpperCase().startsWith("CVSS:"))
907
+ return void 0;
908
+ return normalizeSeverity(vector);
909
+ }
910
+ function mergeIntel(intels) {
911
+ const byKey = /* @__PURE__ */ new Map();
912
+ for (const intel of intels) {
913
+ const allIds = uniqueStrings([intel.id, ...intel.aliases]).map((id) => id.toUpperCase());
914
+ const existingKey = allIds.find((id) => byKey.has(id));
915
+ if (!existingKey) {
916
+ const copy = {
917
+ ...intel,
918
+ aliases: uniqueStrings([intel.id, ...intel.aliases]).map((id) => id.toUpperCase()),
919
+ sources: uniqueStrings(intel.sources),
920
+ references: uniqueReferences(intel.references),
921
+ cwes: uniqueStrings(intel.cwes).map((cwe) => cwe.toUpperCase()),
922
+ affectedRanges: uniqueStrings(intel.affectedRanges),
923
+ fixedVersions: uniqueStrings(intel.fixedVersions)
924
+ };
925
+ for (const id of allIds)
926
+ byKey.set(id, copy);
927
+ continue;
928
+ }
929
+ const existing = byKey.get(existingKey);
930
+ const merged = {
931
+ ...existing,
932
+ summary: existing.summary ?? intel.summary,
933
+ details: existing.details ?? intel.details,
934
+ package: existing.package ?? intel.package,
935
+ aliases: uniqueStrings([existing.id, ...existing.aliases, intel.id, ...intel.aliases]).map((id) => id.toUpperCase()),
936
+ sources: uniqueStrings([...existing.sources, ...intel.sources]),
937
+ affectedRanges: uniqueStrings([...existing.affectedRanges, ...intel.affectedRanges]),
938
+ fixedVersions: uniqueStrings([...existing.fixedVersions, ...intel.fixedVersions]),
939
+ severity: higherSeverity(existing.severity, intel.severity),
940
+ cvss: existing.cvss?.score && (!intel.cvss?.score || existing.cvss.score >= intel.cvss.score) ? existing.cvss : intel.cvss ?? existing.cvss,
941
+ cwes: uniqueStrings([...existing.cwes, ...intel.cwes]).map((cwe) => cwe.toUpperCase()),
942
+ references: uniqueReferences([...existing.references, ...intel.references]),
943
+ kev: existing.kev ?? intel.kev,
944
+ publishedAt: existing.publishedAt ?? intel.publishedAt,
945
+ modifiedAt: existing.modifiedAt ?? intel.modifiedAt,
946
+ fetchedAt: existing.fetchedAt > intel.fetchedAt ? existing.fetchedAt : intel.fetchedAt
947
+ };
948
+ for (const id of merged.aliases)
949
+ byKey.set(id, merged);
950
+ byKey.set(merged.id.toUpperCase(), merged);
951
+ }
952
+ return [...new Set(byKey.values())];
953
+ }
954
+ function severityRank(severity) {
955
+ return { info: 1, low: 2, medium: 3, high: 4, critical: 5 }[severity];
956
+ }
957
+ function higherSeverity(a, b) {
958
+ return severityRank(a) >= severityRank(b) ? a : b;
959
+ }
960
+ function toGraphSnapshot(intels) {
961
+ const nodes = /* @__PURE__ */ new Map();
962
+ const edges = [];
963
+ const addNode = (node) => {
964
+ if (!nodes.has(node.id))
965
+ nodes.set(node.id, node);
966
+ };
967
+ const addEdge = (edge) => {
968
+ edges.push(edge);
969
+ };
970
+ for (const intel of intels) {
971
+ const advisoryId = `advisory:${intel.id}`;
972
+ addNode({
973
+ id: advisoryId,
974
+ kind: "advisory",
975
+ key: intel.id,
976
+ title: intel.summary ?? intel.id,
977
+ data: {
978
+ severity: intel.severity,
979
+ sources: intel.sources,
980
+ publishedAt: intel.publishedAt,
981
+ modifiedAt: intel.modifiedAt
982
+ }
983
+ });
984
+ for (const alias of intel.aliases) {
985
+ if (alias === intel.id)
986
+ continue;
987
+ const aliasId = `advisory:${alias}`;
988
+ addNode({ id: aliasId, kind: "advisory", key: alias });
989
+ addEdge({ from: advisoryId, to: aliasId, kind: "HAS_ALIAS" });
990
+ }
991
+ if (intel.package) {
992
+ const pkgId = `package:${intel.package.ecosystem}:${intel.package.name}`;
993
+ addNode({
994
+ id: pkgId,
995
+ kind: "package",
996
+ key: `${intel.package.ecosystem}:${intel.package.name}`,
997
+ title: intel.package.name
998
+ });
999
+ addEdge({ from: advisoryId, to: pkgId, kind: "AFFECTS_PACKAGE" });
1000
+ }
1001
+ for (const fixed of intel.fixedVersions) {
1002
+ const versionId = `version:${intel.package?.ecosystem ?? "unknown"}:${intel.package?.name ?? "unknown"}:${fixed}`;
1003
+ addNode({ id: versionId, kind: "version", key: fixed });
1004
+ addEdge({ from: advisoryId, to: versionId, kind: "FIXED_IN" });
1005
+ }
1006
+ for (const cwe of intel.cwes) {
1007
+ const cweId = `cwe:${cwe}`;
1008
+ addNode({ id: cweId, kind: "cwe", key: cwe });
1009
+ addEdge({ from: advisoryId, to: cweId, kind: "MAPS_TO_CWE" });
1010
+ }
1011
+ for (const ref of intel.references) {
1012
+ const refId = `reference:${ref.url}`;
1013
+ addNode({ id: refId, kind: "reference", key: ref.url, title: ref.kind });
1014
+ addEdge({ from: advisoryId, to: refId, kind: "REFERENCES", data: { kind: ref.kind, source: ref.source } });
1015
+ }
1016
+ if (intel.kev?.knownExploited) {
1017
+ const kevId = `kev:${intel.id}`;
1018
+ addNode({ id: kevId, kind: "kev", key: intel.id, title: intel.kev.vulnerabilityName });
1019
+ addEdge({ from: advisoryId, to: kevId, kind: "KNOWN_EXPLOITED" });
1020
+ }
1021
+ }
1022
+ return { nodes: [...nodes.values()], edges };
1023
+ }
1024
+
1025
+ // packages/core/dist/intel/cisa-kev.js
1026
+ var DEFAULT_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
1027
+ async function lookupKev(input, opts = {}) {
1028
+ const cveId = normalizeCveId(input.cveId);
1029
+ const catalog = await fetchKevCatalog(input, opts);
1030
+ const entry = (catalog.vulnerabilities ?? []).find((item) => item.cveID?.toUpperCase() === cveId);
1031
+ if (!entry)
1032
+ return null;
1033
+ return {
1034
+ knownExploited: true,
1035
+ dateAdded: entry.dateAdded,
1036
+ dueDate: entry.dueDate,
1037
+ requiredAction: entry.requiredAction,
1038
+ ransomwareUse: entry.knownRansomwareCampaignUse,
1039
+ vulnerabilityName: entry.vulnerabilityName
1040
+ };
1041
+ }
1042
+ async function fetchKevCatalog(input, opts) {
1043
+ const cache = new IntelCache(input.cacheDir);
1044
+ const url = process.env["0SEC_CISA_KEV_URL"] ?? DEFAULT_KEV_URL;
1045
+ return await cachedJson(cache, "cisa-kev", url, async () => await fetchJson(url, { headers: { "User-Agent": "0sec-intel/0.1" } }, opts), { offline: input.offline, ttlMs: input.ttlMs });
1046
+ }
1047
+
1048
+ // packages/core/dist/intel/github.js
1049
+ import { execFileSync } from "node:child_process";
1050
+ async function queryGitHubAdvisories(input, opts = {}) {
1051
+ const cache = new IntelCache(input.cacheDir);
1052
+ const key = JSON.stringify({
1053
+ ecosystem: input.ecosystem,
1054
+ packageName: input.packageName
1055
+ });
1056
+ const raw = await cachedJson(cache, "github-advisories", key, async () => {
1057
+ const query = new URLSearchParams({
1058
+ ecosystem: githubEcosystem(input.ecosystem),
1059
+ affects: input.packageName,
1060
+ per_page: "100"
1061
+ });
1062
+ return await fetchGitHubAdvisoryPages(`https://api.github.com/advisories?${query}`, opts);
1063
+ }, { offline: input.offline, ttlMs: input.ttlMs });
1064
+ return parseGitHubAdvisories(raw);
1065
+ }
1066
+ async function lookupGitHubAdvisory(input, opts = {}) {
1067
+ const ghsaId = normalizeGhsaId(input.ghsaId);
1068
+ const cache = new IntelCache(input.cacheDir);
1069
+ const raw = await cachedJson(cache, "github-advisory", ghsaId, async () => await fetchGitHubAdvisory(`https://api.github.com/advisories/${ghsaId}`, opts), { offline: input.offline, ttlMs: input.ttlMs });
1070
+ if (!raw)
1071
+ return null;
1072
+ return parseGitHubAdvisories([raw])[0] ?? null;
1073
+ }
1074
+ function normalizeGhsaId(ghsaId) {
1075
+ const normalized = ghsaId.trim().toUpperCase();
1076
+ if (!/^GHSA(-[0-9A-Z]{4}){3}$/.test(normalized)) {
1077
+ throw new Error(`invalid GHSA id: ${ghsaId}`);
1078
+ }
1079
+ return normalized;
1080
+ }
1081
+ async function fetchGitHubAdvisory(url, opts) {
1082
+ const controller = new AbortController();
1083
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 2e4);
1084
+ try {
1085
+ const res = await (opts.fetchImpl ?? fetch)(url, {
1086
+ headers: githubHeaders(opts.headers),
1087
+ signal: controller.signal
1088
+ });
1089
+ if (res.status === 404)
1090
+ return null;
1091
+ if (!res.ok)
1092
+ throw new Error(`HTTP ${res.status} for ${url}`);
1093
+ return await res.json();
1094
+ } finally {
1095
+ clearTimeout(timer);
1096
+ }
1097
+ }
1098
+ async function fetchGitHubAdvisoryPages(url, opts) {
1099
+ const out = [];
1100
+ let nextUrl = url;
1101
+ while (nextUrl) {
1102
+ const { data, next } = await fetchGitHubAdvisoryPage(nextUrl, opts);
1103
+ out.push(...data);
1104
+ nextUrl = next;
1105
+ }
1106
+ return out;
1107
+ }
1108
+ async function fetchGitHubAdvisoryPage(url, opts) {
1109
+ const controller = new AbortController();
1110
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 2e4);
1111
+ try {
1112
+ const res = await (opts.fetchImpl ?? fetch)(url, {
1113
+ headers: githubHeaders(opts.headers),
1114
+ signal: controller.signal
1115
+ });
1116
+ if (!res.ok)
1117
+ throw new Error(`HTTP ${res.status} for ${url}`);
1118
+ return {
1119
+ data: await res.json(),
1120
+ next: parseNextLink(res.headers.get("link"))
1121
+ };
1122
+ } finally {
1123
+ clearTimeout(timer);
1124
+ }
1125
+ }
1126
+ function parseNextLink(link) {
1127
+ if (!link)
1128
+ return void 0;
1129
+ for (const part of link.split(",")) {
1130
+ const match = part.match(/<([^>]+)>;\s*rel="next"/i);
1131
+ if (match?.[1])
1132
+ return match[1];
1133
+ }
1134
+ return void 0;
1135
+ }
1136
+ function parseGitHubAdvisories(raw) {
1137
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1138
+ return raw.flatMap((advisory) => {
1139
+ const ids = uniqueStrings([
1140
+ advisory.ghsa_id,
1141
+ advisory.cve_id ?? void 0,
1142
+ ...(advisory.identifiers ?? []).map((id2) => id2.value)
1143
+ ]).map((id2) => id2.toUpperCase());
1144
+ const id = advisory.cve_id?.toUpperCase() ?? advisory.ghsa_id?.toUpperCase();
1145
+ if (!id)
1146
+ return [];
1147
+ const vuln = advisory.vulnerabilities?.[0];
1148
+ return [{
1149
+ id,
1150
+ aliases: ids.length > 0 ? ids : [id],
1151
+ source: "github",
1152
+ sources: ["github"],
1153
+ summary: advisory.summary,
1154
+ details: advisory.description,
1155
+ package: vuln?.package?.name ? {
1156
+ ecosystem: vuln.package.ecosystem ?? "",
1157
+ name: vuln.package.name
1158
+ } : void 0,
1159
+ affectedRanges: uniqueStrings((advisory.vulnerabilities ?? []).map((v) => v.vulnerable_version_range)),
1160
+ fixedVersions: uniqueStrings((advisory.vulnerabilities ?? []).map((v) => v.first_patched_version?.identifier)),
1161
+ severity: normalizeSeverity(advisory.severity),
1162
+ cvss: advisory.cvss ? { score: advisory.cvss.score, vector: advisory.cvss.vector_string, version: "3.x" } : void 0,
1163
+ cwes: uniqueStrings((advisory.cwes ?? []).map((cwe) => cwe.cwe_id)).map((cwe) => cwe.toUpperCase()),
1164
+ references: uniqueReferences([
1165
+ ...advisory.html_url ? [{ url: advisory.html_url, kind: "advisory", source: "github" }] : [],
1166
+ ...(advisory.references ?? []).map((url) => ({ url, kind: "reference", source: "github" }))
1167
+ ]),
1168
+ publishedAt: advisory.published_at,
1169
+ modifiedAt: advisory.updated_at,
1170
+ fetchedAt: now
1171
+ }];
1172
+ });
1173
+ }
1174
+ function githubEcosystem(ecosystem) {
1175
+ const normalized = ecosystem.trim().toLowerCase();
1176
+ if (normalized === "pypi" || normalized === "python")
1177
+ return "pip";
1178
+ if (normalized === "cargo" || normalized === "crates.io" || normalized === "rust")
1179
+ return "rust";
1180
+ return normalized;
1181
+ }
1182
+ function githubHeaders(extra) {
1183
+ const headers = {
1184
+ Accept: "application/vnd.github+json",
1185
+ "X-GitHub-Api-Version": "2022-11-28",
1186
+ "User-Agent": "0sec-intel/0.1",
1187
+ ...extra ?? {}
1188
+ };
1189
+ const token = resolveGitHubToken();
1190
+ if (token)
1191
+ headers.Authorization = `Bearer ${token}`;
1192
+ return headers;
1193
+ }
1194
+ var cachedGitHubToken;
1195
+ function resolveGitHubToken() {
1196
+ const envToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env["0SEC_GITHUB_TOKEN"];
1197
+ if (envToken && envToken.trim())
1198
+ return envToken.trim();
1199
+ if (cachedGitHubToken !== void 0)
1200
+ return cachedGitHubToken || void 0;
1201
+ cachedGitHubToken = readGhCliToken() ?? false;
1202
+ return cachedGitHubToken || void 0;
1203
+ }
1204
+ function readGhCliToken() {
1205
+ try {
1206
+ const out = execFileSync("gh", ["auth", "token"], {
1207
+ encoding: "utf8",
1208
+ stdio: ["ignore", "pipe", "ignore"],
1209
+ timeout: 5e3
1210
+ }).trim();
1211
+ return out || void 0;
1212
+ } catch {
1213
+ return void 0;
1214
+ }
1215
+ }
1216
+
1217
+ // packages/core/dist/intel/audit-graph.js
1218
+ function buildPriorVulnerabilityAuditGraph(playbooks) {
1219
+ const nodes = /* @__PURE__ */ new Map();
1220
+ const edges = /* @__PURE__ */ new Map();
1221
+ const entrypointNodeIds = [];
1222
+ const addNode = (node) => {
1223
+ if (!nodes.has(node.id))
1224
+ nodes.set(node.id, node);
1225
+ };
1226
+ const addEdge = (edge) => {
1227
+ edges.set(`${edge.from}\0${edge.kind}\0${edge.to}`, edge);
1228
+ };
1229
+ for (const playbook of playbooks) {
1230
+ const playbookId = stableNodeId("bug-class", playbook.id);
1231
+ entrypointNodeIds.push(playbookId);
1232
+ addNode({
1233
+ id: playbookId,
1234
+ kind: "bug_class",
1235
+ key: playbook.id,
1236
+ title: playbook.bugClass,
1237
+ data: {
1238
+ cwes: playbook.cwes,
1239
+ relevance: playbook.relevance
1240
+ }
1241
+ });
1242
+ for (const priorId of playbook.priorVulnerabilityIds) {
1243
+ const priorNodeId = stableNodeId("prior", priorId);
1244
+ addNode({
1245
+ id: priorNodeId,
1246
+ kind: "prior_vulnerability",
1247
+ key: priorId,
1248
+ title: priorId
1249
+ });
1250
+ addEdge({ from: priorNodeId, to: playbookId, kind: "INFORMS" });
1251
+ }
1252
+ let previousStepId;
1253
+ for (const [index, step] of playbook.steps.entries()) {
1254
+ const stepNodeId = stableNodeId(playbookId, "step", step.id);
1255
+ addNode({
1256
+ id: stepNodeId,
1257
+ kind: "investigation_step",
1258
+ key: step.id,
1259
+ title: step.title,
1260
+ data: {
1261
+ order: index + 1,
1262
+ rationale: step.rationale,
1263
+ actions: step.actions
1264
+ }
1265
+ });
1266
+ addEdge({ from: playbookId, to: stepNodeId, kind: "HAS_STEP", data: { order: index + 1 } });
1267
+ if (previousStepId) {
1268
+ addEdge({ from: previousStepId, to: stepNodeId, kind: "NEXT_STEP" });
1269
+ }
1270
+ previousStepId = stepNodeId;
1271
+ for (const [evidenceIndex, expectedEvidence] of step.expectedEvidence.entries()) {
1272
+ const evidenceNodeId = stableNodeId(stepNodeId, "evidence", String(evidenceIndex + 1), expectedEvidence);
1273
+ addNode({
1274
+ id: evidenceNodeId,
1275
+ kind: "evidence_query",
1276
+ key: expectedEvidence,
1277
+ title: expectedEvidence,
1278
+ data: { order: evidenceIndex + 1 }
1279
+ });
1280
+ addEdge({
1281
+ from: stepNodeId,
1282
+ to: evidenceNodeId,
1283
+ kind: "SEEKS_EVIDENCE",
1284
+ data: { order: evidenceIndex + 1 }
1285
+ });
1286
+ }
1287
+ }
1288
+ }
1289
+ return {
1290
+ entrypointNodeIds,
1291
+ nodes: [...nodes.values()],
1292
+ edges: [...edges.values()]
1293
+ };
1294
+ }
1295
+ function stableNodeId(...parts) {
1296
+ return parts.map((part) => part.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node").join(":");
1297
+ }
1298
+
1299
+ // packages/core/dist/intel/target-history.js
1300
+ import { readFileSync as readFileSync2, realpathSync } from "node:fs";
1301
+ import { join as join2, sep } from "node:path";
1302
+
1303
+ // packages/core/dist/intel/dossier.js
1304
+ async function buildIntelDossierFromSearch(input, searchAdvisoriesFn, searchSimilarFn, opts = {}) {
1305
+ const advisoryResult = await searchAdvisoriesFn(input, opts);
1306
+ const advisories = advisoryResult.advisories;
1307
+ const cwes = uniqueStrings(advisories.flatMap((advisory) => advisory.cwes));
1308
+ const similarTerms = dossierSearchTerms(input, advisories, cwes);
1309
+ const similarResult = input.includeSimilar === false || cwes.length === 0 && similarTerms.length === 0 ? { advisories: [], graph: { nodes: [], edges: [] } } : await searchSimilarFn({
1310
+ cwe: cwes[0],
1311
+ ecosystem: input.ecosystem,
1312
+ keywords: similarTerms,
1313
+ limit: input.similarLimit ?? 10,
1314
+ cacheDir: input.cacheDir,
1315
+ offline: input.offline,
1316
+ ttlMs: input.ttlMs
1317
+ }, opts);
1318
+ const variantLeads = toVariantLeads(advisories, similarResult.advisories);
1319
+ const playbooks = buildPriorVulnerabilityPlaybooks(advisories, variantLeads);
1320
+ const auditGraph = buildPriorVulnerabilityAuditGraph(playbooks);
1321
+ const graph = mergeGraphs(advisoryResult.graph, similarResult.graph);
1322
+ const sources = uniqueStrings([
1323
+ ...advisories.flatMap((advisory) => advisory.sources),
1324
+ ...similarResult.advisories.flatMap((advisory) => advisory.sources)
1325
+ ]);
1326
+ return {
1327
+ package: {
1328
+ ecosystem: input.ecosystem,
1329
+ name: input.packageName
1330
+ },
1331
+ version: input.version,
1332
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1333
+ summary: summarizeDossier(advisories, variantLeads, playbooks),
1334
+ advisories,
1335
+ variantLeads,
1336
+ playbooks,
1337
+ auditGraph,
1338
+ graph,
1339
+ provenance: {
1340
+ sources,
1341
+ offline: input.offline || void 0
1342
+ }
1343
+ };
1344
+ }
1345
+ function buildPriorVulnerabilityPlaybooks(advisories, variantLeads) {
1346
+ const byClass = /* @__PURE__ */ new Map();
1347
+ for (const item of [...advisories, ...variantLeads]) {
1348
+ const bugClass = classifyBugClass(item.cwes, item.summary);
1349
+ const entry = byClass.get(bugClass.id) ?? { cwes: /* @__PURE__ */ new Set(), ids: /* @__PURE__ */ new Set(), summaries: [] };
1350
+ for (const cwe of item.cwes)
1351
+ entry.cwes.add(cwe);
1352
+ entry.ids.add(item.id);
1353
+ for (const alias of item.aliases)
1354
+ entry.ids.add(alias);
1355
+ if (item.summary)
1356
+ entry.summaries.push(item.summary);
1357
+ byClass.set(bugClass.id, entry);
1358
+ }
1359
+ return [...byClass.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([id, entry]) => {
1360
+ const bugClass = bugClassLabel(id);
1361
+ const cwes = [...entry.cwes].sort((a, b) => a.localeCompare(b));
1362
+ const priorVulnerabilityIds = [...entry.ids].filter((value) => /^(CVE-|GHSA-)/i.test(value)).sort((a, b) => a.localeCompare(b)).slice(0, 12);
1363
+ return {
1364
+ id: `playbook:${id}`,
1365
+ bugClass,
1366
+ cwes,
1367
+ priorVulnerabilityIds,
1368
+ relevance: playbookRelevance(bugClass, cwes, priorVulnerabilityIds),
1369
+ steps: playbookSteps(id, bugClass, cwes)
1370
+ };
1371
+ }).filter((playbook) => playbook.priorVulnerabilityIds.length > 0).slice(0, 6);
1372
+ }
1373
+ function classifyBugClass(cwes, summary) {
1374
+ const text = `${cwes.join(" ")} ${summary ?? ""}`.toLowerCase();
1375
+ if (text.includes("cwe-22") || text.includes("path traversal") || text.includes("zip slip"))
1376
+ return { id: "path-traversal" };
1377
+ if (text.includes("cwe-434") || text.includes("file upload") || text.includes("unrestricted upload"))
1378
+ return { id: "file-upload" };
1379
+ if (text.includes("cwe-338") || text.includes("random") || text.includes("cryptographic"))
1380
+ return { id: "weak-randomness" };
1381
+ if (text.includes("cwe-78") || text.includes("command injection"))
1382
+ return { id: "command-injection" };
1383
+ if (text.includes("cwe-89") || text.includes("sql injection"))
1384
+ return { id: "sql-injection" };
1385
+ if (text.includes("cwe-79") || text.includes("xss") || text.includes("cross-site scripting"))
1386
+ return { id: "xss" };
1387
+ if (text.includes("cwe-918") || text.includes("ssrf") || text.includes("server-side request forgery") || text.includes("server side request forgery"))
1388
+ return { id: "ssrf" };
1389
+ if (text.includes("prototype pollution"))
1390
+ return { id: "prototype-pollution" };
1391
+ if (text.includes("deserialization"))
1392
+ return { id: "deserialization" };
1393
+ if (text.includes("auth bypass") || text.includes("authentication bypass") || text.includes("authorization bypass") || text.includes("access control bypass") || text.includes("broken access control") || text.includes("privilege escalation") && text.includes("bypass"))
1394
+ return { id: "auth-bypass" };
1395
+ return { id: cwes[0]?.toLowerCase() ?? "generic-vulnerability" };
1396
+ }
1397
+ function bugClassLabel(id) {
1398
+ const labels = {
1399
+ "path-traversal": "Path traversal / archive extraction escape",
1400
+ "file-upload": "Unrestricted or unsafe file upload",
1401
+ "weak-randomness": "Predictable randomness or identifier generation",
1402
+ "command-injection": "Command injection",
1403
+ "sql-injection": "SQL injection",
1404
+ xss: "Cross-site scripting",
1405
+ ssrf: "Server-side request forgery",
1406
+ "prototype-pollution": "Prototype pollution",
1407
+ deserialization: "Unsafe deserialization",
1408
+ "auth-bypass": "Authentication or authorization bypass"
1409
+ };
1410
+ return labels[id] ?? id.toUpperCase();
1411
+ }
1412
+ function playbookRelevance(bugClass, cwes, ids) {
1413
+ const cweText = cwes.length > 0 ? ` (${cwes.slice(0, 3).join(", ")})` : "";
1414
+ return `${bugClass}${cweText} appears in prior advisories ${ids.slice(0, 4).join(", ")}; use the old bug shape to drive source/sink/guard verification before reporting.`;
1415
+ }
1416
+ function playbookSteps(id, bugClass, cwes) {
1417
+ const cweHint = cwes.length > 0 ? `Relevant CWE context: ${cwes.join(", ")}.` : `Relevant bug class: ${bugClass}.`;
1418
+ const common = [
1419
+ {
1420
+ id: "map-entrypoints",
1421
+ title: "Map reachable entry points",
1422
+ rationale: `Prior vulnerabilities only matter if the same bug shape is reachable. ${cweHint}`,
1423
+ actions: [
1424
+ "Find exported APIs, HTTP handlers, CLI commands, parser hooks, and framework integrations that accept attacker-controlled input.",
1425
+ "Record the concrete input object, parameter, file, URL, archive entry, or request field that enters the code."
1426
+ ],
1427
+ expectedEvidence: [
1428
+ "File/function references for attacker-controlled sources.",
1429
+ "A short source-to-parser call chain."
1430
+ ]
1431
+ },
1432
+ {
1433
+ id: "trace-sinks",
1434
+ title: "Trace into dangerous sinks",
1435
+ rationale: "A prior CVE shape becomes actionable when user-controlled data reaches a sink with insufficient transformation.",
1436
+ actions: [
1437
+ "Follow the input through normalization, decoding, validation, and wrapper helpers.",
1438
+ "Identify filesystem, process, template, database, network, deserialization, or object-merge sinks."
1439
+ ],
1440
+ expectedEvidence: [
1441
+ "Source-to-sink path with function names.",
1442
+ "Sink arguments showing which values remain attacker-influenced."
1443
+ ]
1444
+ }
1445
+ ];
1446
+ return [...common, ...classSpecificSteps(id), {
1447
+ id: "prove-or-retire",
1448
+ title: "Prove impact or retire the lead",
1449
+ rationale: "Intel leads are not findings until local behavior proves reachability and impact.",
1450
+ actions: [
1451
+ "Write the smallest local harness or command that exercises the source-to-sink path.",
1452
+ "Test the old payload shape and at least one bypass mutation against current guards.",
1453
+ "If guards hold, record why the prior vulnerability does not apply."
1454
+ ],
1455
+ expectedEvidence: [
1456
+ "Passing exploit/negative test output, or a concise non-applicability note.",
1457
+ "Version/commit context tying the conclusion to the scanned target."
1458
+ ]
1459
+ }];
1460
+ }
1461
+ function classSpecificSteps(id) {
1462
+ const steps = {
1463
+ "path-traversal": [{
1464
+ id: "exercise-path-bypasses",
1465
+ title: "Exercise traversal and archive-entry bypasses",
1466
+ rationale: "Historical traversal bugs often hide in decode order, separator handling, and archive entry normalization.",
1467
+ actions: [
1468
+ "Check whether paths are decoded before or after validation.",
1469
+ "Try absolute paths, dot-dot segments, mixed separators, symlinks, unicode separators, and nested archive entries.",
1470
+ "Verify final resolved paths remain inside the intended base directory."
1471
+ ],
1472
+ expectedEvidence: [
1473
+ "Resolved-path comparison at the final write/read sink.",
1474
+ "Positive or negative payload matrix for traversal mutations."
1475
+ ]
1476
+ }],
1477
+ "file-upload": [{
1478
+ id: "audit-upload-controls",
1479
+ title: "Audit upload naming, type, and execution controls",
1480
+ rationale: "Prior upload CVEs usually depend on extension handling, filename trust, storage location, or executable delivery.",
1481
+ actions: [
1482
+ "Trace uploaded filename, generated name, extension, MIME sniffing, and destination directory.",
1483
+ "Check whether attacker input can influence executable extensions, overwrite paths, or public serving paths.",
1484
+ "Test double extensions, null bytes where relevant, path separators, content-type mismatch, and archive/polyglot files."
1485
+ ],
1486
+ expectedEvidence: [
1487
+ "Filename/type validation logic with sink arguments.",
1488
+ "Upload harness output showing accepted/rejected payloads."
1489
+ ]
1490
+ }],
1491
+ "weak-randomness": [{
1492
+ id: "measure-identifier-predictability",
1493
+ title: "Measure identifier entropy and guessability",
1494
+ rationale: "Prior randomness bugs become exploitable when generated IDs protect access to attacker-reachable objects.",
1495
+ actions: [
1496
+ "Locate ID/token generation and the security decision that relies on secrecy.",
1497
+ "Estimate entropy from alphabet, length, seed source, truncation, and collision behavior.",
1498
+ "Check whether partial disclosure or predictable prefixes reduce the search space."
1499
+ ],
1500
+ expectedEvidence: [
1501
+ "Generator code and entropy estimate.",
1502
+ "Object access path showing whether guessing has security impact."
1503
+ ]
1504
+ }],
1505
+ "command-injection": [{
1506
+ id: "separate-argv-from-shell",
1507
+ title: "Separate argv-safe execution from shell execution",
1508
+ rationale: "Command-injection variants hinge on whether attacker data enters a shell-parsed string.",
1509
+ actions: [
1510
+ "Find process execution calls and determine whether they use shell strings or argv arrays.",
1511
+ "Trace quoting, escaping, allowlists, and environment-variable influence.",
1512
+ "Test metacharacters, argument injection, option smuggling, and newline payloads."
1513
+ ],
1514
+ expectedEvidence: [
1515
+ "Exact command construction site.",
1516
+ "Harness showing payload behavior at the process boundary."
1517
+ ]
1518
+ }]
1519
+ };
1520
+ return steps[id] ?? [{
1521
+ id: "derive-bug-shape",
1522
+ title: "Derive the historical bug shape locally",
1523
+ rationale: "No specialized deterministic playbook exists yet, so preserve the prior advisory shape and verify it against local code.",
1524
+ actions: [
1525
+ "Extract source, transformation, sink, guard, and impact from the prior advisory summaries and references.",
1526
+ "Map each element to equivalent local code paths.",
1527
+ "Create a small proof or negative test for the most similar path."
1528
+ ],
1529
+ expectedEvidence: [
1530
+ "Prior-to-local mapping table.",
1531
+ "Proof or non-applicability result."
1532
+ ]
1533
+ }];
1534
+ }
1535
+ function summarizeDossier(advisories, variantLeads, playbooks) {
1536
+ const criticalCount = advisories.filter((advisory) => advisory.severity === "critical").length;
1537
+ const highCount = advisories.filter((advisory) => advisory.severity === "high").length;
1538
+ const kevCount = advisories.filter((advisory) => advisory.kev?.knownExploited).length;
1539
+ const cwes = uniqueStrings(advisories.flatMap((advisory) => advisory.cwes));
1540
+ const topSeverity = highestSeverity(advisories.map((advisory) => advisory.severity));
1541
+ const riskScore = computeRiskScore(advisories, variantLeads.length);
1542
+ return {
1543
+ advisoryCount: advisories.length,
1544
+ variantLeadCount: variantLeads.length,
1545
+ playbookCount: playbooks.length,
1546
+ criticalCount,
1547
+ highCount,
1548
+ kevCount,
1549
+ cweCount: cwes.length,
1550
+ topSeverity,
1551
+ riskScore,
1552
+ riskLevel: riskLevel(riskScore),
1553
+ recommendedFocus: recommendedFocus(advisories, variantLeads)
1554
+ };
1555
+ }
1556
+ function computeRiskScore(advisories, variantLeadCount) {
1557
+ const severityScore = advisories.reduce((score, advisory) => score + severityPoints(advisory.severity), 0);
1558
+ const kevScore = advisories.filter((advisory) => advisory.kev?.knownExploited).length * 45;
1559
+ const cvssScore = Math.max(0, ...advisories.map((advisory) => advisory.cvss?.score ?? 0));
1560
+ return Math.min(100, Math.round(severityScore + kevScore + cvssScore + Math.min(variantLeadCount, 5) * 2));
1561
+ }
1562
+ function severityPoints(severity) {
1563
+ if (severity === "critical")
1564
+ return 35;
1565
+ if (severity === "high")
1566
+ return 25;
1567
+ if (severity === "medium")
1568
+ return 12;
1569
+ if (severity === "low")
1570
+ return 4;
1571
+ return 0;
1572
+ }
1573
+ function highestSeverity(severities) {
1574
+ const order = ["critical", "high", "medium", "low", "info"];
1575
+ return order.find((severity) => severities.includes(severity)) ?? "info";
1576
+ }
1577
+ function riskLevel(score) {
1578
+ if (score <= 0)
1579
+ return "none";
1580
+ if (score >= 80)
1581
+ return "critical";
1582
+ if (score >= 55)
1583
+ return "high";
1584
+ if (score >= 25)
1585
+ return "medium";
1586
+ return "low";
1587
+ }
1588
+ function recommendedFocus(advisories, variantLeads) {
1589
+ const focus = /* @__PURE__ */ new Set();
1590
+ if (advisories.some((advisory) => advisory.kev?.knownExploited))
1591
+ focus.add("known exploited CVEs");
1592
+ for (const cwe of uniqueStrings(advisories.flatMap((advisory) => advisory.cwes)).slice(0, 4))
1593
+ focus.add(cwe);
1594
+ for (const advisory of advisories.filter((item) => item.severity === "critical" || item.severity === "high").slice(0, 3)) {
1595
+ focus.add(advisory.id);
1596
+ }
1597
+ if (variantLeads.length > 0)
1598
+ focus.add("variant-hunt similar advisories");
1599
+ return [...focus];
1600
+ }
1601
+ function dossierSearchTerms(input, advisories, cwes) {
1602
+ const summaryTerms = advisories.flatMap((advisory) => extractSecurityTerms(advisory.summary ?? advisory.details ?? ""));
1603
+ return uniqueStrings([
1604
+ ...input.keywords ?? [],
1605
+ ...cwes,
1606
+ ...summaryTerms
1607
+ ]).slice(0, 8);
1608
+ }
1609
+ function extractSecurityTerms(text) {
1610
+ const lower = text.toLowerCase();
1611
+ const terms = [];
1612
+ for (const term of [
1613
+ "path traversal",
1614
+ "zip slip",
1615
+ "prototype pollution",
1616
+ "command injection",
1617
+ "sql injection",
1618
+ "cross-site scripting",
1619
+ "xss",
1620
+ "ssrf",
1621
+ "deserialization",
1622
+ "auth bypass"
1623
+ ]) {
1624
+ if (lower.includes(term))
1625
+ terms.push(term);
1626
+ }
1627
+ return terms;
1628
+ }
1629
+ function toVariantLeads(advisories, similar) {
1630
+ const knownIds = new Set(advisories.flatMap((advisory) => [advisory.id, ...advisory.aliases]).map((id) => id.toUpperCase()));
1631
+ return mergeIntel(similar).filter((lead) => ![lead.id, ...lead.aliases].some((id) => knownIds.has(id.toUpperCase()))).slice(0, 10).map((lead) => ({
1632
+ id: lead.id,
1633
+ aliases: lead.aliases,
1634
+ severity: lead.severity,
1635
+ cwes: lead.cwes,
1636
+ summary: lead.summary,
1637
+ reason: lead.cwes.length > 0 ? `Shares ${lead.cwes.slice(0, 3).join(", ")} with package advisory context.` : "Matched dossier variant-hunt keywords.",
1638
+ references: lead.references.slice(0, 3)
1639
+ }));
1640
+ }
1641
+ function mergeGraphs(left, right) {
1642
+ const nodes = new Map(left.nodes.map((node) => [node.id, node]));
1643
+ const edges = new Map(left.edges.map((edge) => [`${edge.from}\0${edge.kind}\0${edge.to}`, edge]));
1644
+ for (const node of right.nodes)
1645
+ nodes.set(node.id, node);
1646
+ for (const edge of right.edges)
1647
+ edges.set(`${edge.from}\0${edge.kind}\0${edge.to}`, edge);
1648
+ return {
1649
+ nodes: [...nodes.values()],
1650
+ edges: [...edges.values()]
1651
+ };
1652
+ }
1653
+
1654
+ // packages/core/dist/intel/target-history.js
1655
+ var MIN_LOOSE_HINT_LENGTH = 4;
1656
+ var MIN_STRONG_HINT_LENGTH = 3;
1657
+ var CONFIDENCE_RANK = { high: 3, medium: 2, low: 1 };
1658
+ function buildTargetHistoryResult(input, advisories) {
1659
+ const hints = targetHistoryHints(input);
1660
+ const matcher = buildTargetMatcher(input);
1661
+ const filtered = mergeIntel(advisories).flatMap((advisory) => {
1662
+ const confidence = scoreTargetMatch(advisory, matcher);
1663
+ return confidence ? [{ ...advisory, matchConfidence: confidence }] : [];
1664
+ }).sort((a, b) => CONFIDENCE_RANK[b.matchConfidence] - CONFIDENCE_RANK[a.matchConfidence]);
1665
+ const playbooks = buildPriorVulnerabilityPlaybooks(filtered, []);
1666
+ const auditGraph = buildPriorVulnerabilityAuditGraph(playbooks);
1667
+ const graph = toGraphSnapshot(filtered);
1668
+ return {
1669
+ target: {
1670
+ target: input.target,
1671
+ repoPath: input.repoPath,
1672
+ repository: normalizeRepositoryHint(input.repository ?? input.target),
1673
+ ecosystem: input.ecosystem,
1674
+ packageName: input.packageName,
1675
+ product: input.product,
1676
+ vendor: input.vendor,
1677
+ keywords: input.keywords ?? []
1678
+ },
1679
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1680
+ summary: summarizeTargetHistory(filtered, playbooks.length, hints),
1681
+ advisories: filtered,
1682
+ playbooks,
1683
+ auditGraph,
1684
+ graph,
1685
+ provenance: {
1686
+ sources: uniqueStrings(filtered.flatMap((advisory) => advisory.sources)),
1687
+ offline: input.offline || void 0
1688
+ }
1689
+ };
1690
+ }
1691
+ function resolveTargetHistoryInput(input) {
1692
+ if (!input.repoPath)
1693
+ return input;
1694
+ const inferred = inferTargetHistoryInputFromRepo(input.repoPath).input;
1695
+ return {
1696
+ ...inferred,
1697
+ ...input,
1698
+ repository: input.repository ?? inferred.repository,
1699
+ ecosystem: input.ecosystem ?? inferred.ecosystem,
1700
+ packageName: input.packageName ?? inferred.packageName,
1701
+ product: input.product ?? inferred.product,
1702
+ vendor: input.vendor ?? inferred.vendor,
1703
+ keywords: uniqueStrings([...inferred.keywords ?? [], ...input.keywords ?? []])
1704
+ };
1705
+ }
1706
+ function inferTargetHistoryInputFromRepo(repoPath) {
1707
+ const input = { repoPath };
1708
+ const sources = [];
1709
+ let canonicalRoot;
1710
+ try {
1711
+ canonicalRoot = realpathSync(repoPath);
1712
+ } catch {
1713
+ return { input, sources };
1714
+ }
1715
+ const packageJson = readJsonFile(join2(canonicalRoot, "package.json"), canonicalRoot);
1716
+ if (packageJson) {
1717
+ sources.push("package.json");
1718
+ const name = typeof packageJson.name === "string" ? packageJson.name : void 0;
1719
+ const repo2 = repositoryFromPackageJson(packageJson.repository);
1720
+ input.ecosystem = "npm";
1721
+ input.packageName = name;
1722
+ input.product = unscopedPackageName(name);
1723
+ input.repository = repo2;
1724
+ }
1725
+ const pyproject = readTextFile(join2(canonicalRoot, "pyproject.toml"), canonicalRoot);
1726
+ if (pyproject) {
1727
+ sources.push("pyproject.toml");
1728
+ input.ecosystem ??= "pypi";
1729
+ input.packageName ??= matchTomlString(pyproject, "name");
1730
+ input.product ??= input.packageName;
1731
+ input.repository ??= normalizeRepositoryHint(matchTomlUrl(pyproject, "repository") ?? matchTomlUrl(pyproject, "source") ?? matchTomlUrl(pyproject, "homepage"));
1732
+ }
1733
+ const cargo = readTextFile(join2(canonicalRoot, "Cargo.toml"), canonicalRoot);
1734
+ if (cargo) {
1735
+ sources.push("Cargo.toml");
1736
+ input.ecosystem ??= "cargo";
1737
+ input.packageName ??= matchTomlString(cargo, "name");
1738
+ input.product ??= input.packageName;
1739
+ input.repository ??= normalizeRepositoryHint(matchTomlString(cargo, "repository"));
1740
+ }
1741
+ const goMod = readTextFile(join2(canonicalRoot, "go.mod"), canonicalRoot);
1742
+ if (goMod) {
1743
+ sources.push("go.mod");
1744
+ input.ecosystem ??= "Go";
1745
+ const module = goMod.match(/^module\s+(\S+)/m)?.[1];
1746
+ input.packageName ??= module;
1747
+ input.product ??= module?.split("/").filter(Boolean).at(-1);
1748
+ input.repository ??= normalizeRepositoryHint(module);
1749
+ }
1750
+ const gitConfig = readTextFile(join2(canonicalRoot, ".git", "config"), canonicalRoot);
1751
+ if (gitConfig) {
1752
+ sources.push(".git/config");
1753
+ input.repository ??= normalizeRepositoryHint(matchGitRemoteUrl(gitConfig));
1754
+ }
1755
+ const repo = normalizeRepositoryHint(input.repository);
1756
+ const repoName = repo?.split("/")[1];
1757
+ input.repository = repo ?? input.repository;
1758
+ input.vendor ??= repo?.split("/")[0];
1759
+ input.keywords = uniqueStrings([
1760
+ input.product,
1761
+ input.packageName,
1762
+ repoName
1763
+ ].filter((value) => value && value !== input.product));
1764
+ return { input, sources };
1765
+ }
1766
+ function targetHistoryHints(input) {
1767
+ const { repository, repoName, strongTokens, looseTokens } = buildTargetMatcher(input);
1768
+ return uniqueStrings([
1769
+ repository,
1770
+ repoName,
1771
+ ...strongTokens,
1772
+ ...looseTokens
1773
+ ]);
1774
+ }
1775
+ function buildTargetMatcher(input) {
1776
+ const repository = normalizeRepositoryHint(input.repository ?? input.target)?.toLowerCase();
1777
+ const repoName = repository?.split("/")[1];
1778
+ const packageName = input.packageName?.toLowerCase();
1779
+ const unscoped = unscopedPackageName(packageName);
1780
+ const product = input.product?.toLowerCase();
1781
+ const strongTokens = uniqueStrings([repoName, product, unscoped].map((t) => t ?? void 0)).filter((token) => token.length >= MIN_STRONG_HINT_LENGTH);
1782
+ const vendorProduct = input.vendor && input.product ? `${input.vendor} ${input.product}`.toLowerCase() : void 0;
1783
+ const looseTokens = uniqueStrings([vendorProduct, ...(input.keywords ?? []).map((k) => k.toLowerCase())]).filter((token) => token.length >= MIN_LOOSE_HINT_LENGTH);
1784
+ const exactPackageNames = uniqueStrings([packageName, unscoped, product].map((t) => t ?? void 0));
1785
+ return { repository, repoName, strongTokens, looseTokens, exactPackageNames };
1786
+ }
1787
+ function readJsonFile(path, root) {
1788
+ const text = readTextFile(path, root);
1789
+ if (!text)
1790
+ return void 0;
1791
+ try {
1792
+ const parsed = JSON.parse(text);
1793
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
1794
+ } catch {
1795
+ return void 0;
1796
+ }
1797
+ }
1798
+ function readTextFile(path, root) {
1799
+ try {
1800
+ const canonicalPath = realpathSync(path);
1801
+ const prefix = root.endsWith(sep) ? root : root + sep;
1802
+ if (canonicalPath !== root && !canonicalPath.startsWith(prefix))
1803
+ return void 0;
1804
+ return readFileSync2(canonicalPath, "utf-8");
1805
+ } catch {
1806
+ return void 0;
1807
+ }
1808
+ }
1809
+ function repositoryFromPackageJson(value) {
1810
+ if (typeof value === "string")
1811
+ return normalizeRepositoryHint(value);
1812
+ if (value && typeof value === "object" && "url" in value && typeof value.url === "string") {
1813
+ return normalizeRepositoryHint(value.url);
1814
+ }
1815
+ return void 0;
1816
+ }
1817
+ function unscopedPackageName(name) {
1818
+ if (!name)
1819
+ return void 0;
1820
+ return name.replace(/^@[^/]+\//, "");
1821
+ }
1822
+ function matchTomlString(text, key) {
1823
+ return text.match(new RegExp(`^\\s*${escapeRegExp2(key)}\\s*=\\s*["']([^"']+)["']`, "m"))?.[1];
1824
+ }
1825
+ function matchTomlUrl(text, key) {
1826
+ const direct = matchTomlString(text, key);
1827
+ if (direct)
1828
+ return direct;
1829
+ return text.match(new RegExp(`^\\s*${escapeRegExp2(key)}\\s*=\\s*\\{[^}]*url\\s*=\\s*["']([^"']+)["']`, "m"))?.[1];
1830
+ }
1831
+ function matchGitRemoteUrl(text) {
1832
+ const originBlock = text.match(/\[remote "origin"\]([\s\S]*?)(?:\n\[|$)/)?.[1];
1833
+ return (originBlock ?? text).match(/^\s*url\s*=\s*(\S+)/m)?.[1];
1834
+ }
1835
+ function escapeRegExp2(value) {
1836
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1837
+ }
1838
+ function normalizeRepositoryHint(value) {
1839
+ if (!value)
1840
+ return void 0;
1841
+ const trimmed = value.trim().replace(/^git\+/, "");
1842
+ if (!trimmed)
1843
+ return void 0;
1844
+ const plainMatch = trimmed.match(/^([^/\s:]+)\/([^/\s]+?)(?:\.git)?$/i);
1845
+ if (plainMatch?.[1] && plainMatch[2] && !plainMatch[1].includes("."))
1846
+ return `${plainMatch[1]}/${plainMatch[2]}`;
1847
+ const sshMatch = trimmed.match(/^git@github\.com:([^/\s:]+)\/([^/\s]+?)(?:\.git)?$/i);
1848
+ if (sshMatch?.[1] && sshMatch[2])
1849
+ return `${sshMatch[1]}/${sshMatch[2]}`;
1850
+ try {
1851
+ const url = new URL(trimmed.startsWith("http") ? trimmed : `https://${trimmed}`);
1852
+ if (!/github\.com$/i.test(url.hostname))
1853
+ return void 0;
1854
+ const [owner, repo] = url.pathname.split("/").filter(Boolean);
1855
+ if (!owner || !repo)
1856
+ return void 0;
1857
+ return `${owner}/${repo.replace(/\.git$/i, "")}`;
1858
+ } catch {
1859
+ const match = trimmed.match(/(?:github\.com[:/])?([^/\s:]+)\/([^/\s]+?)(?:\.git)?$/i);
1860
+ return match?.[1] && match[2] ? `${match[1]}/${match[2]}` : void 0;
1861
+ }
1862
+ }
1863
+ function scoreTargetMatch(advisory, matcher) {
1864
+ const hasCriteria = Boolean(matcher.repository) || matcher.strongTokens.length > 0 || matcher.looseTokens.length > 0 || matcher.exactPackageNames.length > 0;
1865
+ if (!hasCriteria)
1866
+ return null;
1867
+ if (matcher.repository && advisory.references.some((ref) => referenceMatchesRepo(ref.url, matcher.repository))) {
1868
+ return "high";
1869
+ }
1870
+ const advisoryPackage = advisory.package?.name?.toLowerCase();
1871
+ if (advisoryPackage && matcher.exactPackageNames.includes(advisoryPackage)) {
1872
+ return "high";
1873
+ }
1874
+ const haystack = [
1875
+ advisory.summary,
1876
+ advisory.details,
1877
+ advisory.package?.name
1878
+ ].filter((value) => Boolean(value)).join("\n").toLowerCase();
1879
+ if (matcher.strongTokens.some((token) => tokenMatch(haystack, token)))
1880
+ return "medium";
1881
+ if (matcher.looseTokens.some((token) => tokenMatch(haystack, token)))
1882
+ return "low";
1883
+ return null;
1884
+ }
1885
+ function referenceMatchesRepo(url, repository) {
1886
+ try {
1887
+ const parsed = new URL(url);
1888
+ if (!/github\.com$/i.test(parsed.hostname))
1889
+ return false;
1890
+ const [owner, repo] = parsed.pathname.split("/").filter(Boolean);
1891
+ if (!owner || !repo)
1892
+ return false;
1893
+ return `${owner}/${repo.replace(/\.git$/i, "")}`.toLowerCase() === repository;
1894
+ } catch {
1895
+ return false;
1896
+ }
1897
+ }
1898
+ function tokenMatch(haystack, needle) {
1899
+ const trimmed = needle.trim().toLowerCase();
1900
+ if (!trimmed)
1901
+ return false;
1902
+ const escaped = trimmed.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1903
+ return new RegExp(`(^|[^a-z0-9])${escaped}(?=[^a-z0-9]|$)`, "i").test(haystack);
1904
+ }
1905
+ function summarizeTargetHistory(advisories, playbookCount, hints) {
1906
+ const criticalCount = advisories.filter((advisory) => advisory.severity === "critical").length;
1907
+ const highCount = advisories.filter((advisory) => advisory.severity === "high").length;
1908
+ const kevCount = advisories.filter((advisory) => advisory.kev?.knownExploited).length;
1909
+ const cwes = uniqueStrings(advisories.flatMap((advisory) => advisory.cwes));
1910
+ const confidenceCounts = {
1911
+ high: advisories.filter((advisory) => advisory.matchConfidence === "high").length,
1912
+ medium: advisories.filter((advisory) => advisory.matchConfidence === "medium").length,
1913
+ low: advisories.filter((advisory) => advisory.matchConfidence === "low").length
1914
+ };
1915
+ return {
1916
+ advisoryCount: advisories.length,
1917
+ playbookCount,
1918
+ criticalCount,
1919
+ highCount,
1920
+ kevCount,
1921
+ cweCount: cwes.length,
1922
+ topSeverity: highestSeverity2(advisories.map((advisory) => advisory.severity)),
1923
+ matchedHints: hints.slice(0, 10),
1924
+ confidenceCounts
1925
+ };
1926
+ }
1927
+ function highestSeverity2(severities) {
1928
+ const order = ["critical", "high", "medium", "low", "info"];
1929
+ return order.find((severity) => severities.includes(severity)) ?? "info";
1930
+ }
1931
+
1932
+ // packages/core/dist/intel/github-issues.js
1933
+ var GITHUB_SEARCH_URL = "https://api.github.com/search/issues";
1934
+ var DEFAULT_LIMIT = 15;
1935
+ var MAX_LIMIT = 30;
1936
+ var SNIPPET_LENGTH = 280;
1937
+ function buildPublicReportQuery(input) {
1938
+ const parts = [];
1939
+ const repo = normalizeRepositoryHint(input.repository) ?? input.repository?.trim();
1940
+ if (repo)
1941
+ parts.push(`repo:${repo}`);
1942
+ if (input.type === "issue")
1943
+ parts.push("is:issue");
1944
+ else if (input.type === "pr")
1945
+ parts.push("is:pr");
1946
+ const terms = input.terms?.trim();
1947
+ if (terms)
1948
+ parts.push(terms);
1949
+ return parts.join(" ").trim();
1950
+ }
1951
+ async function searchGitHubIssues(input, opts = {}) {
1952
+ const q = buildPublicReportQuery(input);
1953
+ if (!q) {
1954
+ throw new Error("search_public_reports requires repository and/or terms");
1955
+ }
1956
+ const limit = Math.min(Math.max(Math.trunc(input.limit ?? DEFAULT_LIMIT), 1), MAX_LIMIT);
1957
+ const cache = new IntelCache(input.cacheDir);
1958
+ const key = JSON.stringify({ q, limit });
1959
+ const raw = await cachedJson(cache, "github-issues", key, async () => {
1960
+ const query = new URLSearchParams({
1961
+ q,
1962
+ per_page: String(limit),
1963
+ sort: "updated",
1964
+ order: "desc"
1965
+ });
1966
+ return await fetchGitHubIssueSearch(`${GITHUB_SEARCH_URL}?${query}`, opts);
1967
+ }, { offline: input.offline, ttlMs: input.ttlMs });
1968
+ const reports = (raw.items ?? []).slice(0, limit).flatMap(parseIssue);
1969
+ return {
1970
+ query: q,
1971
+ totalCount: typeof raw.total_count === "number" ? raw.total_count : reports.length,
1972
+ reports,
1973
+ provenance: {
1974
+ source: "github",
1975
+ offline: input.offline || void 0,
1976
+ unverified: true
1977
+ }
1978
+ };
1979
+ }
1980
+ async function fetchGitHubIssueSearch(url, opts) {
1981
+ const controller = new AbortController();
1982
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 2e4);
1983
+ try {
1984
+ const res = await (opts.fetchImpl ?? fetch)(url, {
1985
+ headers: githubHeaders(opts.headers),
1986
+ signal: controller.signal
1987
+ });
1988
+ if (res.status === 403 || res.status === 429) {
1989
+ const remaining = res.headers.get("x-ratelimit-remaining");
1990
+ const reset = res.headers.get("x-ratelimit-reset");
1991
+ throw new Error(`GitHub search rate limit (HTTP ${res.status}) for ${url}` + (remaining ? ` \u2014 remaining ${remaining}` : "") + (reset ? `, resets at ${reset}` : "") + " (set GITHUB_TOKEN/GH_TOKEN to raise the limit)");
1992
+ }
1993
+ if (!res.ok)
1994
+ throw new Error(`HTTP ${res.status} for ${url}`);
1995
+ return await res.json();
1996
+ } finally {
1997
+ clearTimeout(timer);
1998
+ }
1999
+ }
2000
+ function parseIssue(item) {
2001
+ if (!item.html_url || typeof item.number !== "number")
2002
+ return [];
2003
+ return [{
2004
+ title: item.title ?? `#${item.number}`,
2005
+ url: item.html_url,
2006
+ state: item.state ?? "unknown",
2007
+ number: item.number,
2008
+ isPullRequest: item.pull_request != null,
2009
+ createdAt: item.created_at,
2010
+ bodySnippet: snippet(item.body)
2011
+ }];
2012
+ }
2013
+ function snippet(body) {
2014
+ if (!body)
2015
+ return void 0;
2016
+ const collapsed = body.replace(/\s+/g, " ").trim();
2017
+ if (!collapsed)
2018
+ return void 0;
2019
+ return collapsed.length > SNIPPET_LENGTH ? `${collapsed.slice(0, SNIPPET_LENGTH)}\u2026` : collapsed;
2020
+ }
2021
+
2022
+ // packages/core/dist/intel/nvd.js
2023
+ async function lookupNvdCve(input, opts = {}) {
2024
+ const cveId = normalizeCveId(input.cveId);
2025
+ const cache = new IntelCache(input.cacheDir);
2026
+ const raw = await cachedJson(cache, "nvd-cve", cveId, async () => await fetchJson(`https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=${encodeURIComponent(cveId)}`, { headers: nvdHeaders(opts.headers) }, opts), { offline: input.offline, ttlMs: input.ttlMs });
2027
+ return parseNvdResponse(raw)[0] ?? null;
2028
+ }
2029
+ async function searchNvdSimilar(input, opts = {}) {
2030
+ const terms = uniqueStrings([input.cwe, ...input.keywords ?? []]);
2031
+ if (terms.length === 0)
2032
+ return [];
2033
+ const limit = Math.min(Math.max(input.limit ?? 10, 1), 50);
2034
+ const key = JSON.stringify({ terms, limit });
2035
+ const cache = new IntelCache(input.cacheDir);
2036
+ const query = new URLSearchParams({
2037
+ keywordSearch: terms.join(" "),
2038
+ resultsPerPage: String(limit)
2039
+ });
2040
+ const raw = await cachedJson(cache, "nvd-search", key, async () => await fetchJson(`https://services.nvd.nist.gov/rest/json/cves/2.0?${query}`, { headers: nvdHeaders(opts.headers) }, opts), { offline: input.offline, ttlMs: input.ttlMs });
2041
+ return parseNvdResponse(raw);
2042
+ }
2043
+ async function searchNvdTargetHistory(input, opts = {}) {
2044
+ const terms = targetHistorySearchTerms(input);
2045
+ if (terms.length === 0)
2046
+ return [];
2047
+ const limit = Math.min(Math.max(input.limit ?? 20, 1), 50);
2048
+ const cache = new IntelCache(input.cacheDir);
2049
+ const results = await Promise.all(terms.slice(0, 8).map(async (term) => {
2050
+ const key = JSON.stringify({ term, limit });
2051
+ const query = new URLSearchParams({
2052
+ keywordSearch: term,
2053
+ resultsPerPage: String(limit)
2054
+ });
2055
+ const raw = await cachedJson(cache, "nvd-target-history", key, async () => await fetchJson(`https://services.nvd.nist.gov/rest/json/cves/2.0?${query}`, { headers: nvdHeaders(opts.headers) }, opts), { offline: input.offline, ttlMs: input.ttlMs });
2056
+ return parseNvdResponse(raw);
2057
+ }));
2058
+ return results.flat();
2059
+ }
2060
+ function targetHistorySearchTerms(input) {
2061
+ const repo = normalizeRepositoryHint(input.repository ?? input.target);
2062
+ const repoParts = repo?.split("/") ?? [];
2063
+ return uniqueStrings([
2064
+ input.product,
2065
+ input.packageName,
2066
+ input.vendor && input.product ? `${input.vendor} ${input.product}` : void 0,
2067
+ repo,
2068
+ repoParts[1],
2069
+ ...input.keywords ?? []
2070
+ ]).filter((term) => term.length >= 3);
2071
+ }
2072
+ function parseNvdResponse(raw) {
2073
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2074
+ return (raw.vulnerabilities ?? []).flatMap((item) => {
2075
+ const cve = item.cve;
2076
+ if (!cve?.id)
2077
+ return [];
2078
+ const metric = pickMetric(cve);
2079
+ const cwes = uniqueStrings((cve.weaknesses ?? []).flatMap((w) => (w.description ?? []).map((d) => d.value).filter((value) => /^CWE-\d+$/i.test(value ?? "")))).map((cwe) => cwe.toUpperCase());
2080
+ const references = Array.isArray(cve.references) ? cve.references : cve.references?.referenceData ?? [];
2081
+ const kev = cve.cisaExploitAdd ? {
2082
+ knownExploited: true,
2083
+ dateAdded: cve.cisaExploitAdd,
2084
+ dueDate: cve.cisaActionDue,
2085
+ requiredAction: cve.cisaRequiredAction,
2086
+ vulnerabilityName: cve.cisaVulnerabilityName
2087
+ } : void 0;
2088
+ return [{
2089
+ id: cve.id.toUpperCase(),
2090
+ aliases: [cve.id.toUpperCase()],
2091
+ source: "nvd",
2092
+ sources: ["nvd"],
2093
+ summary: cve.descriptions?.find((d) => d.lang === "en")?.value,
2094
+ affectedRanges: [],
2095
+ fixedVersions: [],
2096
+ severity: normalizeSeverity(metric?.cvssData?.baseSeverity),
2097
+ cvss: metric?.cvssData ? {
2098
+ score: metric.cvssData.baseScore,
2099
+ vector: metric.cvssData.vectorString,
2100
+ version: metric.cvssData.version
2101
+ } : void 0,
2102
+ cwes,
2103
+ references: uniqueReferences(references.filter((ref) => typeof ref.url === "string").map((ref) => ({ url: ref.url, kind: ref.tags?.join(","), source: "nvd" }))),
2104
+ kev,
2105
+ publishedAt: cve.published,
2106
+ modifiedAt: cve.lastModified,
2107
+ fetchedAt: now
2108
+ }];
2109
+ });
2110
+ }
2111
+ function pickMetric(cve) {
2112
+ return cve.metrics?.cvssMetricV40?.[0] ?? cve.metrics?.cvssMetricV31?.[0] ?? cve.metrics?.cvssMetricV30?.[0] ?? cve.metrics?.cvssMetricV2?.[0];
2113
+ }
2114
+ function nvdHeaders(extra) {
2115
+ const headers = {
2116
+ "User-Agent": "0sec-intel/0.1",
2117
+ ...extra ?? {}
2118
+ };
2119
+ if (process.env.NVD_API_KEY)
2120
+ headers.apiKey = process.env.NVD_API_KEY;
2121
+ return headers;
2122
+ }
2123
+
2124
+ // packages/core/dist/intel/osv.js
2125
+ function toOsvEcosystem(ecosystem) {
2126
+ const normalized = ecosystem.trim().toLowerCase();
2127
+ if (normalized === "npm")
2128
+ return "npm";
2129
+ if (normalized === "pypi" || normalized === "python")
2130
+ return "PyPI";
2131
+ if (normalized === "cargo" || normalized === "crates.io" || normalized === "rust")
2132
+ return "crates.io";
2133
+ if (normalized === "go" || normalized === "golang")
2134
+ return "Go";
2135
+ if (normalized === "maven")
2136
+ return "Maven";
2137
+ return null;
2138
+ }
2139
+ async function queryOsvAdvisories(input, opts = {}) {
2140
+ const osvEcosystem = toOsvEcosystem(input.ecosystem);
2141
+ if (!osvEcosystem)
2142
+ return [];
2143
+ const cache = new IntelCache(input.cacheDir);
2144
+ const key = JSON.stringify({
2145
+ ecosystem: osvEcosystem,
2146
+ packageName: input.packageName,
2147
+ version: input.version ?? null
2148
+ });
2149
+ const raw = await cachedJson(cache, "osv-query", key, async () => {
2150
+ const body = {
2151
+ package: { ecosystem: osvEcosystem, name: input.packageName }
2152
+ };
2153
+ if (input.version)
2154
+ body.version = input.version;
2155
+ return await fetchJson("https://api.osv.dev/v1/query", {
2156
+ method: "POST",
2157
+ headers: { "Content-Type": "application/json" },
2158
+ body: JSON.stringify(body)
2159
+ }, opts);
2160
+ }, { offline: input.offline, ttlMs: input.ttlMs });
2161
+ return parseOsvResponse(input.packageName, raw);
2162
+ }
2163
+ function parseOsvResponse(packageName, raw) {
2164
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2165
+ return (raw.vulns ?? []).map((vuln) => {
2166
+ const aliases = uniqueStrings([vuln.id, ...vuln.aliases ?? []]).map((id2) => id2.toUpperCase());
2167
+ const id = primaryId(aliases, vuln.id ?? "OSV advisory");
2168
+ const packageInfo = vuln.affected?.find((a) => a.package?.name)?.package;
2169
+ const affectedRanges = [];
2170
+ const fixedVersions = [];
2171
+ for (const affected of vuln.affected ?? []) {
2172
+ for (const range of affected.ranges ?? []) {
2173
+ const parts = (range.events ?? []).flatMap((event) => {
2174
+ const out = [];
2175
+ if (event.introduced)
2176
+ out.push(`introduced:${event.introduced}`);
2177
+ if (event.fixed) {
2178
+ out.push(`fixed:${event.fixed}`);
2179
+ fixedVersions.push(event.fixed);
2180
+ }
2181
+ if (event.last_affected)
2182
+ out.push(`last_affected:${event.last_affected}`);
2183
+ return out;
2184
+ });
2185
+ if (parts.length > 0)
2186
+ affectedRanges.push(`${range.type ?? "range"}:${parts.join(",")}`);
2187
+ }
2188
+ }
2189
+ const cvss = vuln.severity?.find((s) => typeof s.score === "string")?.score;
2190
+ const severity = parseCvssVectorSeverity(cvss) ?? normalizeSeverity(vuln.database_specific?.severity);
2191
+ return {
2192
+ id,
2193
+ aliases: aliases.length > 0 ? aliases : [id],
2194
+ source: "osv",
2195
+ sources: ["osv"],
2196
+ summary: vuln.summary,
2197
+ details: vuln.details,
2198
+ package: {
2199
+ ecosystem: packageInfo?.ecosystem ?? "",
2200
+ name: packageInfo?.name ?? packageName
2201
+ },
2202
+ affectedRanges: uniqueStrings(affectedRanges),
2203
+ fixedVersions: uniqueStrings(fixedVersions),
2204
+ severity,
2205
+ cvss: cvss ? { vector: cvss } : void 0,
2206
+ cwes: [],
2207
+ references: uniqueReferences((vuln.references ?? []).filter((ref) => typeof ref.url === "string").map((ref) => ({ url: ref.url, kind: ref.type, source: "osv" }))),
2208
+ publishedAt: vuln.published,
2209
+ modifiedAt: vuln.modified,
2210
+ fetchedAt: now
2211
+ };
2212
+ });
2213
+ }
2214
+
2215
+ // packages/core/dist/intel/prompt.js
2216
+ function formatTargetHistoryForPrompt(history) {
2217
+ if (history.summary.advisoryCount === 0 && history.playbooks.length === 0)
2218
+ return null;
2219
+ const label = history.target.repository ?? history.target.packageName ?? history.target.product ?? history.target.target ?? history.target.repoPath ?? "target";
2220
+ const lines = [
2221
+ "## Prior Vulnerability Audit Graph",
2222
+ `Target: ${label}`,
2223
+ `Historical advisories: ${history.summary.advisoryCount}; playbooks: ${history.summary.playbookCount}; audit graph: ${history.auditGraph.nodes.length} nodes / ${history.auditGraph.edges.length} edges.`
2224
+ ];
2225
+ if (history.summary.matchedHints.length > 0) {
2226
+ lines.push(`Matched hints: ${history.summary.matchedHints.slice(0, 8).join(", ")}`);
2227
+ }
2228
+ lines.push("The playbooks below document ground already covered by prior analysis. These known bug classes and their prior findings will be automatically deduplicated. DO NOT re-derive or re-report them. Instead treat this as a coverage map: focus effort on UNEXPLORED attack surface -- files, entry points, and vulnerability classes not represented in the list below.");
2229
+ for (const playbook of history.playbooks.slice(0, 4)) {
2230
+ lines.push("", `### ${playbook.bugClass}`);
2231
+ if (playbook.cwes.length > 0)
2232
+ lines.push(`CWE: ${playbook.cwes.slice(0, 5).join(", ")}`);
2233
+ lines.push(`Prior IDs: ${playbook.priorVulnerabilityIds.slice(0, 8).join(", ")}`);
2234
+ lines.push(`Relevance: ${playbook.relevance}`);
2235
+ for (const [index, step] of playbook.steps.slice(0, 4).entries()) {
2236
+ lines.push(`${index + 1}. ${step.title}`);
2237
+ lines.push(` Rationale: ${step.rationale}`);
2238
+ if (step.expectedEvidence.length > 0) {
2239
+ lines.push(` Evidence to collect: ${step.expectedEvidence.slice(0, 3).join(" | ")}`);
2240
+ }
2241
+ }
2242
+ }
2243
+ return lines.join("\n");
2244
+ }
2245
+
2246
+ // packages/core/dist/intel/index.js
2247
+ async function buildIntelDossier(input, opts = {}) {
2248
+ return await buildIntelDossierFromSearch(input, searchAdvisories, searchSimilar, opts);
2249
+ }
2250
+ async function searchAdvisories(input, opts = {}) {
2251
+ const sources = ["queryOsvAdvisories", "queryGitHubAdvisories"];
2252
+ const results = await Promise.allSettled([
2253
+ queryOsvAdvisories(input, opts),
2254
+ queryGitHubAdvisories(input, opts)
2255
+ ]);
2256
+ warnRejectedSources(sources, results, { ecosystem: input.ecosystem, packageName: input.packageName });
2257
+ const packageAdvisories = mergeIntel(results.flatMap((result) => result.status === "fulfilled" ? result.value : []));
2258
+ const advisoryLeads = packageAdvisories.length === 0 ? await goStdlibNvdFallback(input, opts) : [];
2259
+ const advisories = mergeIntel([...packageAdvisories, ...advisoryLeads]);
2260
+ if (input.enrich !== false) {
2261
+ const enriched = await enrichCveAliases(advisories, input, opts);
2262
+ const merged = mergeIntel([...advisories, ...enriched]);
2263
+ return { advisories: merged, graph: toGraphSnapshot(merged) };
2264
+ }
2265
+ return { advisories, graph: toGraphSnapshot(advisories) };
2266
+ }
2267
+ async function lookupCve(input, opts = {}) {
2268
+ const cveId = normalizeCveId(input.cveId);
2269
+ const [nvdResult, kevResult] = await Promise.allSettled([
2270
+ lookupNvdCve({ ...input, cveId }, opts),
2271
+ lookupKev({ ...input, cveId }, opts)
2272
+ ]);
2273
+ warnRejectedSources(["lookupNvdCve", "lookupKev"], [nvdResult, kevResult], { cveId });
2274
+ const nvd = nvdResult.status === "fulfilled" ? nvdResult.value : null;
2275
+ const kev = kevResult.status === "fulfilled" ? kevResult.value : null;
2276
+ if (!nvd && !kev)
2277
+ return null;
2278
+ if (!nvd) {
2279
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2280
+ return {
2281
+ id: cveId,
2282
+ aliases: [cveId],
2283
+ source: "cisa-kev",
2284
+ sources: ["cisa-kev"],
2285
+ affectedRanges: [],
2286
+ fixedVersions: [],
2287
+ severity: "info",
2288
+ cwes: [],
2289
+ references: [],
2290
+ kev: kev ?? void 0,
2291
+ fetchedAt: now
2292
+ };
2293
+ }
2294
+ return {
2295
+ ...nvd,
2296
+ sources: kev ? mergeSources(nvd.sources, ["cisa-kev"]) : nvd.sources,
2297
+ kev: kev ?? nvd.kev
2298
+ };
2299
+ }
2300
+ async function searchSimilar(input, opts = {}) {
2301
+ const advisories = mergeIntel(await searchNvdSimilar(input, opts));
2302
+ return { advisories, graph: toGraphSnapshot(advisories) };
2303
+ }
2304
+ async function searchTargetHistory(input, opts = {}) {
2305
+ const resolvedInput = resolveTargetHistoryInput(input);
2306
+ const packageLookup = resolvedInput.ecosystem && resolvedInput.packageName ? searchAdvisories({
2307
+ ecosystem: resolvedInput.ecosystem,
2308
+ packageName: resolvedInput.packageName,
2309
+ enrich: false,
2310
+ cacheDir: resolvedInput.cacheDir,
2311
+ offline: resolvedInput.offline,
2312
+ ttlMs: resolvedInput.ttlMs
2313
+ }, opts) : Promise.resolve({ advisories: [], graph: { nodes: [], edges: [] } });
2314
+ const results = await Promise.allSettled([
2315
+ packageLookup,
2316
+ searchNvdTargetHistory(resolvedInput, opts)
2317
+ ]);
2318
+ warnRejectedSources(["searchAdvisories", "searchNvdTargetHistory"], results, {
2319
+ target: resolvedInput.target,
2320
+ repository: resolvedInput.repository,
2321
+ packageName: resolvedInput.packageName,
2322
+ product: resolvedInput.product
2323
+ });
2324
+ const packageResult = results[0]?.status === "fulfilled" ? results[0].value.advisories : [];
2325
+ const nvdAdvisories = results[1]?.status === "fulfilled" ? results[1].value : [];
2326
+ return buildTargetHistoryResult(resolvedInput, [...packageResult, ...nvdAdvisories]);
2327
+ }
2328
+ async function lookupAdvisory(input, opts = {}) {
2329
+ return await lookupGitHubAdvisory(input, opts);
2330
+ }
2331
+ async function searchPublicReports(input, opts = {}) {
2332
+ return await searchGitHubIssues(input, opts);
2333
+ }
2334
+ var BROAD_REPORT_TERMS = "security OR vulnerability OR CVE OR GHSA OR SSRF OR auth OR traversal OR injection OR memory OR DoS";
2335
+ var SWEEP_CAVEAT = "LEADS ONLY \u2014 'no public duplicate found' is NOT 'definitely unique'. Private/draft GitHub advisories, embargoed advisories, HackerOne (and other bug-bounty) reports, and internal tickets are invisible to this sweep. Verify local reachability before reporting a new bug.";
2336
+ async function advisorySweep(input, opts = {}) {
2337
+ const limit = typeof input.limit === "number" ? Math.min(Math.max(Math.trunc(input.limit), 1), 50) : 15;
2338
+ const ghsaIds = uniqueStrings(input.ghsaIds ?? []);
2339
+ const keywords = uniqueStrings(input.keywords ?? []);
2340
+ const shared = { cacheDir: input.cacheDir, offline: input.offline, ttlMs: input.ttlMs };
2341
+ const advisoryTasks = [];
2342
+ for (const ghsaId of ghsaIds) {
2343
+ advisoryTasks.push({
2344
+ label: `lookupAdvisory(${ghsaId})`,
2345
+ run: async () => {
2346
+ const advisory = await lookupAdvisory({ ghsaId, ...shared }, opts);
2347
+ return advisory ? [advisory] : [];
2348
+ }
2349
+ });
2350
+ }
2351
+ if (input.ecosystem && input.packageName) {
2352
+ advisoryTasks.push({
2353
+ label: "searchAdvisories",
2354
+ run: async () => (await searchAdvisories({
2355
+ ecosystem: input.ecosystem,
2356
+ packageName: input.packageName,
2357
+ enrich: false,
2358
+ ...shared
2359
+ }, opts)).advisories
2360
+ });
2361
+ }
2362
+ const reportTasks = [];
2363
+ if (input.repository) {
2364
+ reportTasks.push({
2365
+ label: "searchPublicReports(broad)",
2366
+ run: () => searchPublicReports({ repository: input.repository, terms: BROAD_REPORT_TERMS, limit, ...shared }, opts)
2367
+ });
2368
+ if (keywords.length > 0) {
2369
+ reportTasks.push({
2370
+ label: "searchPublicReports(keywords)",
2371
+ run: () => searchPublicReports({ repository: input.repository, terms: keywords.join(" "), limit, ...shared }, opts)
2372
+ });
2373
+ }
2374
+ }
2375
+ const historyTask = input.includeTargetHistory && (input.repository || input.packageName) ? searchTargetHistory({ repository: input.repository, ecosystem: input.ecosystem, packageName: input.packageName, keywords, limit, ...shared }, opts) : Promise.resolve(null);
2376
+ const [advisoryResults, reportResults, historyResult] = await Promise.all([
2377
+ Promise.allSettled(advisoryTasks.map((task) => task.run())),
2378
+ Promise.allSettled(reportTasks.map((task) => task.run())),
2379
+ historyTask.catch((reason) => {
2380
+ console.warn("[intel] advisorySweep target-history failed", { reason: reason instanceof Error ? reason.message : String(reason) });
2381
+ return null;
2382
+ })
2383
+ ]);
2384
+ warnRejectedSources(advisoryTasks.map((task) => task.label), advisoryResults, { sweep: "advisories" });
2385
+ warnRejectedSources(reportTasks.map((task) => task.label), reportResults, { sweep: "reports" });
2386
+ const advisorySourceById = /* @__PURE__ */ new Map();
2387
+ const flatAdvisories = [];
2388
+ advisoryResults.forEach((result, idx) => {
2389
+ if (result.status !== "fulfilled")
2390
+ return;
2391
+ const source = idx < ghsaIds.length ? "github-advisory" : "package-advisory";
2392
+ for (const advisory of result.value) {
2393
+ flatAdvisories.push(advisory);
2394
+ advisorySourceById.set(advisory.id.toUpperCase(), source);
2395
+ }
2396
+ });
2397
+ const leads = [];
2398
+ const seen = /* @__PURE__ */ new Map();
2399
+ const addAdvisoryLead = (advisory, source, confidence) => {
2400
+ const key = `advisory:${advisory.id.toUpperCase()}`;
2401
+ const existing = seen.get(key);
2402
+ const html = advisory.references.find((ref) => ref.kind === "advisory")?.url ?? advisory.references[0]?.url;
2403
+ if (existing) {
2404
+ if (!existing.sources.includes(source))
2405
+ existing.sources.push(source);
2406
+ if (rankConfidence(confidence) > rankConfidence(existing.confidence))
2407
+ existing.confidence = confidence;
2408
+ return;
2409
+ }
2410
+ const lead = {
2411
+ id: advisory.id,
2412
+ kind: "advisory",
2413
+ confidence,
2414
+ sources: [source],
2415
+ title: advisory.summary ?? advisory.id,
2416
+ url: html,
2417
+ severity: advisory.severity,
2418
+ aliases: advisory.aliases
2419
+ };
2420
+ seen.set(key, lead);
2421
+ leads.push(lead);
2422
+ };
2423
+ for (const advisory of mergeIntel(flatAdvisories)) {
2424
+ addAdvisoryLead(advisory, advisorySourceById.get(advisory.id.toUpperCase()) ?? "package-advisory", "high");
2425
+ }
2426
+ reportResults.forEach((result) => {
2427
+ if (result.status !== "fulfilled")
2428
+ return;
2429
+ for (const report of result.value.reports) {
2430
+ const key = `report:${report.url}`;
2431
+ if (seen.has(key))
2432
+ continue;
2433
+ const lead = {
2434
+ id: report.url,
2435
+ kind: "public_report",
2436
+ confidence: "low",
2437
+ sources: ["github-issue"],
2438
+ title: report.title,
2439
+ url: report.url,
2440
+ state: report.state,
2441
+ isPullRequest: report.isPullRequest,
2442
+ createdAt: report.createdAt,
2443
+ snippet: report.bodySnippet
2444
+ };
2445
+ seen.set(key, lead);
2446
+ leads.push(lead);
2447
+ }
2448
+ });
2449
+ if (historyResult) {
2450
+ for (const advisory of historyResult.advisories) {
2451
+ addAdvisoryLead(advisory, "target-history", advisory.matchConfidence ?? "medium");
2452
+ }
2453
+ }
2454
+ leads.sort((a, b) => rankConfidence(b.confidence) - rankConfidence(a.confidence) || severityRank2(b.severity) - severityRank2(a.severity));
2455
+ const publicReports = leads.filter((lead) => lead.kind === "public_report").length;
2456
+ const advisories = leads.length - publicReports;
2457
+ const sources = uniqueStrings(leads.flatMap((lead) => lead.sources));
2458
+ return {
2459
+ query: { repository: input.repository, ecosystem: input.ecosystem, packageName: input.packageName, keywords, ghsaIds },
2460
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2461
+ counts: {
2462
+ total: leads.length,
2463
+ advisories,
2464
+ publicReports,
2465
+ high: leads.filter((lead) => lead.confidence === "high").length,
2466
+ medium: leads.filter((lead) => lead.confidence === "medium").length,
2467
+ low: leads.filter((lead) => lead.confidence === "low").length
2468
+ },
2469
+ leads,
2470
+ caveat: SWEEP_CAVEAT,
2471
+ provenance: { sources, offline: input.offline || void 0, unverified: true }
2472
+ };
2473
+ }
2474
+ function rankConfidence(confidence) {
2475
+ return { high: 3, medium: 2, low: 1 }[confidence];
2476
+ }
2477
+ function severityRank2(severity) {
2478
+ return { critical: 5, high: 4, medium: 3, low: 2, info: 1 }[severity ?? "info"];
2479
+ }
2480
+ async function goStdlibNvdFallback(input, opts) {
2481
+ if (!isGoStdlibSearch(input))
2482
+ return [];
2483
+ const keywords = goStdlibKeywords(input.packageName);
2484
+ if (keywords.length === 0)
2485
+ return [];
2486
+ const advisories = await searchNvdSimilar({
2487
+ keywords,
2488
+ limit: 20,
2489
+ cacheDir: input.cacheDir,
2490
+ offline: input.offline,
2491
+ ttlMs: input.ttlMs
2492
+ }, opts);
2493
+ return advisories.filter(isGoStdlibIntel);
2494
+ }
2495
+ function isGoStdlibSearch(input) {
2496
+ const ecosystem = input.ecosystem.trim().toLowerCase();
2497
+ if (ecosystem !== "go" && ecosystem !== "golang")
2498
+ return false;
2499
+ const name = input.packageName.trim().toLowerCase();
2500
+ return name === "stdlib" || name === "std" || name === "go" || name === "golang" || name === "github.com/golang/go" || name === "golang/go" || name.startsWith("std/") || name.startsWith("stdlib/");
2501
+ }
2502
+ function goStdlibKeywords(packageName) {
2503
+ const normalized = packageName.trim().toLowerCase();
2504
+ const pkg = normalized.replace(/^stdlib\//, "").replace(/^std\//, "");
2505
+ if (pkg && pkg !== normalized)
2506
+ return ["golang", pkg];
2507
+ return ["golang", "standard library"];
2508
+ }
2509
+ function isGoStdlibIntel(advisory) {
2510
+ const haystack = [
2511
+ advisory.summary,
2512
+ ...advisory.references.map((reference) => reference.url)
2513
+ ].join("\n").toLowerCase();
2514
+ return /\b(go|golang)\b/.test(haystack) && (haystack.includes("standard library") || haystack.includes("pkg.go.dev/std") || haystack.includes("github.com/golang/go") || haystack.includes("go.dev/issue") || haystack.includes("groups.google.com/g/golang-announce"));
2515
+ }
2516
+ async function enrichCveAliases(advisories, input, opts) {
2517
+ const cves = uniqueStrings(advisories.flatMap((advisory) => [advisory.id, ...advisory.aliases]).filter((id) => /^CVE-\d{4}-\d{4,}$/i.test(id)));
2518
+ const results = await Promise.allSettled(cves.slice(0, 10).map((cveId) => lookupCve({ cveId, cacheDir: input.cacheDir, offline: input.offline, ttlMs: input.ttlMs }, opts)));
2519
+ return results.flatMap((result) => result.status === "fulfilled" && result.value ? [result.value] : []);
2520
+ }
2521
+ function mergeSources(a, b) {
2522
+ return uniqueStrings([...a, ...b]);
2523
+ }
2524
+ function warnRejectedSources(sources, results, context) {
2525
+ for (const [idx, result] of results.entries()) {
2526
+ if (result.status !== "rejected")
2527
+ continue;
2528
+ const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
2529
+ console.warn(`[intel] ${sources[idx] ?? "source"} failed`, { reason, context });
2530
+ }
2531
+ }
2532
+
2533
+ export {
2534
+ classifyResponse,
2535
+ jitterFor,
2536
+ runEvasionCampaign,
2537
+ WafDetector,
2538
+ enumerateSubdomains,
2539
+ scanBody,
2540
+ scanJsArtifacts,
2541
+ defaultIntelCacheDir,
2542
+ IntelCache,
2543
+ mergeIntel,
2544
+ toGraphSnapshot,
2545
+ lookupKev,
2546
+ queryGitHubAdvisories,
2547
+ lookupGitHubAdvisory,
2548
+ normalizeGhsaId,
2549
+ parseGitHubAdvisories,
2550
+ buildPriorVulnerabilityAuditGraph,
2551
+ resolveTargetHistoryInput,
2552
+ inferTargetHistoryInputFromRepo,
2553
+ normalizeRepositoryHint,
2554
+ buildPublicReportQuery,
2555
+ searchGitHubIssues,
2556
+ lookupNvdCve,
2557
+ searchNvdSimilar,
2558
+ searchNvdTargetHistory,
2559
+ parseNvdResponse,
2560
+ toOsvEcosystem,
2561
+ queryOsvAdvisories,
2562
+ parseOsvResponse,
2563
+ formatTargetHistoryForPrompt,
2564
+ buildIntelDossier,
2565
+ searchAdvisories,
2566
+ lookupCve,
2567
+ searchSimilar,
2568
+ searchTargetHistory,
2569
+ lookupAdvisory,
2570
+ searchPublicReports,
2571
+ advisorySweep
2572
+ };