0sec-cli 0.18.0 → 0.21.4

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 (171) hide show
  1. package/{0sec.js → 0.js} +84 -76
  2. package/LICENSE +1 -1
  3. package/README.md +74 -43
  4. package/chunks/adapt-loop-IGBRQAXO.js +18 -0
  5. package/chunks/{adgraph-JLGA6RYI.js → adgraph-AUDQTBYA.js} +5 -5
  6. package/chunks/agent/skills/frameworks/entra-id.yaml +2 -2
  7. package/chunks/agent/skills/techniques/assumption-mining.yaml +2 -2
  8. package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +2 -2
  9. package/chunks/agent/skills/techniques/entra-attack-paths.yaml +1 -1
  10. package/chunks/agent/skills/techniques/kernel-weaponization.yaml +1 -1
  11. package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +2 -2
  12. package/chunks/agent/skills/techniques/npm-ecosystem.yaml +1 -1
  13. package/chunks/agent/skills/techniques/poc-verification.yaml +1 -1
  14. package/chunks/agent/skills/techniques/seedless-depth-review.yaml +20 -38
  15. package/chunks/{artifact-scraper-HQ7HLP6V.js → artifact-scraper-LNQJUORS.js} +6 -6
  16. package/chunks/{assumption-mining-5SNIVVDN.js → assumption-mining-4MJTP6H5.js} +24 -26
  17. package/chunks/{chunk-P6WKNFWX.js → chunk-2OQOQ2FZ.js} +8 -8
  18. package/chunks/{chunk-5G3ZXFBW.js → chunk-2WNCI664.js} +3 -3
  19. package/chunks/{chunk-AHTBZC3E.js → chunk-2WSXFFJZ.js} +4 -4
  20. package/chunks/{chunk-DW5UWPFY.js → chunk-4VGLO2YS.js} +6 -5
  21. package/chunks/{chunk-3KPWWJCI.js → chunk-5T6D5BVY.js} +88 -88
  22. package/chunks/{chunk-OEFNRYI2.js → chunk-6GHSR47F.js} +3 -6
  23. package/chunks/chunk-6YNKRUMC.js +4474 -0
  24. package/chunks/{chunk-BZNEY2ZS.js → chunk-ANDKY54G.js} +22390 -19895
  25. package/chunks/{chunk-53G27VPS.js → chunk-AZ7L3HFD.js} +7 -2
  26. package/chunks/{chunk-UR4ELX2Y.js → chunk-BACXRTQ4.js} +633 -316
  27. package/chunks/{chunk-UCYRA73C.js → chunk-CAJJRUTV.js} +4 -4
  28. package/chunks/{chunk-57ZENEX2.js → chunk-CGHCEN7W.js} +3 -3
  29. package/chunks/{chunk-AD7WXDH6.js → chunk-CL5KAUM6.js} +16 -16
  30. package/chunks/{chunk-ADOLI6DT.js → chunk-CXKDWFNO.js} +6 -6
  31. package/chunks/{chunk-RWONANDA.js → chunk-DQTNI3KY.js} +5 -5
  32. package/chunks/{chunk-BFR2CDV5.js → chunk-E2UOY5PJ.js} +288 -100
  33. package/chunks/{chunk-ATATQACO.js → chunk-ER3SUMYJ.js} +40 -18
  34. package/chunks/{chunk-5JI7L7KV.js → chunk-FAH6V4QM.js} +30515 -32242
  35. package/chunks/{chunk-6LKRLK2R.js → chunk-FHC2B7LN.js} +3 -3
  36. package/chunks/{chunk-XM5NLHYU.js → chunk-FWAYJ2IV.js} +16 -16
  37. package/chunks/{chunk-WVTBZEQO.js → chunk-G34QP2XW.js} +8 -8
  38. package/chunks/{chunk-MYVT64FN.js → chunk-GHWODZMR.js} +2 -2
  39. package/chunks/{chunk-F3WBKITT.js → chunk-GIQKESMN.js} +10 -10
  40. package/chunks/{chunk-2RMLOJVB.js → chunk-GVD6SJGH.js} +2 -2
  41. package/chunks/{chunk-WXQ6BJ6A.js → chunk-HBKCGJAV.js} +7 -7
  42. package/chunks/{chunk-SF4KZ4O3.js → chunk-HLFS6WV2.js} +2 -2
  43. package/chunks/chunk-HPQZHOIU.js +67 -0
  44. package/chunks/{chunk-TSK2AG6J.js → chunk-IMGWPNQM.js} +231 -1308
  45. package/chunks/{chunk-3MOLBTLS.js → chunk-K2PWUPUI.js} +2 -2
  46. package/chunks/{chunk-N7BOUYEV.js → chunk-NDJU5ZZ7.js} +14 -14
  47. package/chunks/{chunk-BVAZO4WA.js → chunk-NOPBXZUV.js} +3 -3
  48. package/chunks/{chunk-2JCCA2JL.js → chunk-O6SD4A36.js} +3 -3
  49. package/chunks/{chunk-SAFFWQW4.js → chunk-P6YG6TEZ.js} +6 -6
  50. package/chunks/{chunk-6L26OBWI.js → chunk-PC6RCKQV.js} +198 -11
  51. package/chunks/{chunk-LP3HHYQU.js → chunk-PD2BBBKT.js} +2 -2
  52. package/chunks/{chunk-BKZFDZ23.js → chunk-QCUFBVIZ.js} +3 -3
  53. package/chunks/{chunk-KLTNTE2Z.js → chunk-QZVG3BG4.js} +14 -14
  54. package/chunks/{chunk-ASUR522M.js → chunk-RF5QGKLR.js} +9 -9
  55. package/chunks/{chunk-YYRBQVFE.js → chunk-SBMB4OL3.js} +9 -9
  56. package/chunks/{chunk-IOL7D5YV.js → chunk-SQCBHFWN.js} +3 -3
  57. package/chunks/{chunk-BFRB6HB2.js → chunk-TF46UWFU.js} +3 -3
  58. package/chunks/chunk-TH2LW437.js +7829 -0
  59. package/chunks/chunk-TRH2PJSN.js +547 -0
  60. package/chunks/{chunk-RJWOYEOG.js → chunk-UEX7PFSE.js} +5 -5
  61. package/chunks/{chunk-VQG4FT5D.js → chunk-UFHN6RIU.js} +2 -2
  62. package/chunks/{chunk-WU6AFRAZ.js → chunk-V36HVTUL.js} +12 -12
  63. package/chunks/{chunk-IR537GON.js → chunk-VCSFCHAJ.js} +2 -2
  64. package/chunks/{chunk-YLMN3N25.js → chunk-VIT5ALXF.js} +3 -3
  65. package/chunks/{chunk-U5FLXGHT.js → chunk-VOAXLO4A.js} +10 -10
  66. package/chunks/{chunk-WEBMRWEG.js → chunk-W4JOXE4N.js} +83 -75
  67. package/chunks/{chunk-C2WXWFBB.js → chunk-WF5W75WY.js} +25 -25
  68. package/chunks/{chunk-SVJEDVYK.js → chunk-WPQLBN4S.js} +5 -5
  69. package/chunks/{chunk-72CISA2D.js → chunk-WZISK5VC.js} +37 -35
  70. package/chunks/{chunk-CMKXP5RR.js → chunk-Y5H6E24H.js} +3 -3
  71. package/chunks/{chunk-BATRQBOR.js → chunk-YA4PUM4H.js} +41 -41
  72. package/chunks/{chunk-JQHOLLTD.js → chunk-YHWWF4QY.js} +9 -9
  73. package/chunks/{chunk-GMT2AZKM.js → chunk-YPOA3W4O.js} +28 -28
  74. package/chunks/{chunk-7DQEV5QI.js → chunk-Z3IKZX3X.js} +4 -4
  75. package/chunks/codex-models-TYYV4JNN.js +15 -0
  76. package/chunks/{commands-TBC2F5CE.js → commands-WF5RG4I2.js} +3834 -931
  77. package/chunks/corpus-v1.json +2 -2
  78. package/chunks/data/appsec-archetypes.json +2 -2
  79. package/chunks/data/chromium-archetypes.json +1 -1
  80. package/chunks/data/freebsd-archetypes.json +1 -1
  81. package/chunks/data/kernel-archetypes.json +2 -2
  82. package/chunks/db-U3WQZVAO.js +16 -0
  83. package/chunks/{disclose-EKTWSCGW.js → disclose-67GZCDBO.js} +5 -5
  84. package/chunks/{dist-FMGSR3DW.js → dist-ACC3DZUZ.js} +11 -5
  85. package/chunks/{dist-4MAW5X6L.js → dist-EVTN3UTT.js} +6 -6
  86. package/chunks/{dist-4YICS2J2.js → dist-F6X62TQK.js} +286 -327
  87. package/chunks/dist-KEULE5IQ.js +21914 -0
  88. package/chunks/dist-XZB53PS5.js +53 -0
  89. package/chunks/eval-runner-SA6OUGHZ.js +26 -0
  90. package/chunks/example-manifest.json +2 -2
  91. package/chunks/{exploit-agent-45YQOHPJ.js → exploit-agent-36Q7XQFP.js} +4 -4
  92. package/chunks/{exploit-autoclimb-UZGEMGYT.js → exploit-autoclimb-J4OTSTZ6.js} +6 -6
  93. package/chunks/{exploit-climb-SG23KB2G.js → exploit-climb-PYEJDIC4.js} +12 -12
  94. package/chunks/fix-J2KBRJDV.js +12 -0
  95. package/chunks/{github-issues-MJ6OYOOU.js → github-issues-IGTTWM7V.js} +7 -7
  96. package/chunks/harness-GDUMKGSE.js +22 -0
  97. package/chunks/http-conformance-U5F6GKYG.js +11 -0
  98. package/chunks/http-sender-I5XFFY35.js +10 -0
  99. package/chunks/hunt-scan-UDJSCISU.js +36 -0
  100. package/chunks/{identity-6ZAIIWOR.js → identity-JTIVJSV7.js} +5 -5
  101. package/chunks/{kernel-primitive-7U6CWHXD.js → kernel-primitive-F2IXUFAX.js} +6 -6
  102. package/chunks/{kernel-vm-runner-3JAU4O6J.js → kernel-vm-runner-276LHIOV.js} +6 -6
  103. package/chunks/memsafety-scan-NHUOGTEX.js +16 -0
  104. package/chunks/{native-loop-RJCJPOUS.js → native-loop-3BSYVDUI.js} +17 -18
  105. package/chunks/{npm-detectors-KB5Y5ZFX.js → npm-detectors-VIYJXKFQ.js} +6 -6
  106. package/chunks/npm-dynamic-discovery-5J7FT3XM.js +13 -0
  107. package/chunks/orchestrate-DHBFRTSI.js +59 -0
  108. package/chunks/{pre-recon-cve-66EB6G4M.js → pre-recon-cve-X2K2P5YQ.js} +4 -4
  109. package/chunks/prepare-KYHGFWCJ.js +13 -0
  110. package/chunks/process-PNMC36JI.js +15 -0
  111. package/chunks/{replay-runner-45G3VU7X.js → replay-runner-LK2XFIVG.js} +7 -7
  112. package/chunks/{run-D56MPDKY.js → run-CBGE6DRQ.js} +2201 -1694
  113. package/chunks/{runtime-G7YWIO3B.js → runtime-PXO5G6UV.js} +11 -11
  114. package/chunks/runtime-XFWJTBUO.js +12 -0
  115. package/chunks/{scan-stream-FHI2FYZE.js → scan-stream-BMOCPKQX.js} +7 -7
  116. package/chunks/{scope-BI7BF4ZY.js → scope-NJHIDDTN.js} +4 -4
  117. package/chunks/{session-store-GZ4A4KYT.js → session-store-46WBZM22.js} +6 -6
  118. package/chunks/source-files-REHOM44I.js +12 -0
  119. package/chunks/{specdrift-WCLH6TTQ.js → specdrift-AFS54WEJ.js} +5 -5
  120. package/chunks/token-KRWP5JYA.js +72 -0
  121. package/chunks/token-util-DWTL33S3.js +8 -0
  122. package/chunks/variant-candidates-UH2WC2HA.js +13 -0
  123. package/chunks/{web-recon-prepass-JJGPEOLE.js → web-recon-prepass-SFXU44SS.js} +9 -9
  124. package/dashboard/assets/desktop-BT7RkC4q.js +32 -0
  125. package/dashboard/assets/desktop-OaBI0E0e.css +1 -0
  126. package/dashboard/assets/{findings-page-CACW9nw2.js → findings-page-B4rwF6fZ.js} +2 -2
  127. package/dashboard/assets/{format-PoISqsES.js → format-LNQSSpn6.js} +1 -1
  128. package/dashboard/assets/live-page-DoNwpPAO.js +1 -0
  129. package/dashboard/assets/{meta-tile-CcskIV_o.js → meta-tile-BMtBC-Qq.js} +1 -1
  130. package/dashboard/assets/operations-CMFVrXEe.css +2 -0
  131. package/dashboard/assets/operations-app-ER0gg7GN.js +2 -0
  132. package/dashboard/assets/{operations-D8nUP5_m.js → operations-e2CGYKB1.js} +2 -2
  133. package/dashboard/assets/{overview-page-DTGS8dPo.js → overview-page-CmeWH17A.js} +1 -1
  134. package/dashboard/assets/{page-header-MFVvEtZW.js → page-header-CIJyycfz.js} +1 -1
  135. package/dashboard/assets/scans-page-CuhzWhHx.js +1 -0
  136. package/dashboard/assets/{table-BYS-nIVA.js → table-BgsSBdke.js} +1 -1
  137. package/dashboard/assets/{tabs-DivxbhQy.js → tabs-vn6v7Bk6.js} +1 -1
  138. package/dashboard/desktop.html +3 -3
  139. package/dashboard/index.html +3 -3
  140. package/package.json +11 -11
  141. package/chunks/adapt-loop-SMH6V4PD.js +0 -18
  142. package/chunks/appsec-catalog-CBWHGTEL.js +0 -24
  143. package/chunks/chunk-242TMK6G.js +0 -110
  144. package/chunks/chunk-H2FFLZNK.js +0 -3
  145. package/chunks/chunk-QI233I24.js +0 -333
  146. package/chunks/chunk-RRMJC3ZE.js +0 -105
  147. package/chunks/chunk-Z5FA2XOX.js +0 -2798
  148. package/chunks/cost-ledger-NU63MYZM.js +0 -13
  149. package/chunks/db-KFWOAUUB.js +0 -16
  150. package/chunks/eval-runner-7ZF472KV.js +0 -27
  151. package/chunks/fix-IG7LYXH3.js +0 -12
  152. package/chunks/harness-7ODOLOWU.js +0 -22
  153. package/chunks/http-conformance-DU66MZIU.js +0 -11
  154. package/chunks/http-sender-GWH2IYEA.js +0 -10
  155. package/chunks/hunt-scan-MMKUOETU.js +0 -38
  156. package/chunks/memsafety-scan-J7SECV5W.js +0 -16
  157. package/chunks/npm-dynamic-discovery-AHROOZDB.js +0 -13
  158. package/chunks/orchestrate-OC2VZN2X.js +0 -62
  159. package/chunks/pipeline-BZK5BLJR.js +0 -14
  160. package/chunks/prepare-MYY743TK.js +0 -13
  161. package/chunks/process-LQRCS5OY.js +0 -13
  162. package/chunks/runtime-J7PLXZNM.js +0 -12
  163. package/chunks/source-files-PWRQR6LY.js +0 -12
  164. package/chunks/variant-candidates-YERCYMPD.js +0 -13
  165. package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
  166. package/dashboard/assets/desktop-CXTQonNm.js +0 -32
  167. package/dashboard/assets/desktop-NOekk4QS.css +0 -1
  168. package/dashboard/assets/live-page-DrgQqQK5.js +0 -1
  169. package/dashboard/assets/operations-BKE8T-vY.css +0 -2
  170. package/dashboard/assets/operations-app-CQQhG54n.js +0 -2
  171. package/dashboard/assets/scans-page-ChVtq8Us.js +0 -1
@@ -1,55 +1,46 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire as __0secCreateRequire } from "node:module";
3
- const require = __0secCreateRequire(import.meta.url);
2
+ import { createRequire as __0CreateRequire } from "node:module";
3
+ const require = __0CreateRequire(import.meta.url);
4
4
  import {
5
5
  isValidSessionId,
6
6
  listSessions,
7
7
  loadSession
8
- } from "./chunk-BVAZO4WA.js";
8
+ } from "./chunk-NOPBXZUV.js";
9
9
  import {
10
10
  consolePresentationOutput,
11
11
  getSettings,
12
12
  maybeLoadCodexAuth
13
- } from "./chunk-GMT2AZKM.js";
13
+ } from "./chunk-YPOA3W4O.js";
14
14
  import {
15
- captureObservation,
15
+ source_default
16
+ } from "./chunk-HLFS6WV2.js";
17
+ import {
18
+ createConsoleJevRuntime,
16
19
  createConsoleSession,
17
- createEvolvedFinder,
18
20
  inspectLensRegistry,
19
21
  lastGreen,
20
22
  loadLedger,
21
23
  loadManifest,
22
24
  makeFinderLensProbe,
23
25
  retireArchetype,
24
- runLensSynthesisLoop
25
- } from "./chunk-5JI7L7KV.js";
26
- import {
27
- source_default
28
- } from "./chunk-SF4KZ4O3.js";
29
- import {
30
- stampDeploymentContext
31
- } from "./chunk-BFR2CDV5.js";
26
+ runLensSynthesisLoop,
27
+ toToolContextJevRuntime
28
+ } from "./chunk-FAH6V4QM.js";
32
29
  import {
33
30
  harvestMissesFromLedgerEntry,
34
- loadEvolutionConfigFile,
35
31
  mergeHarvestedMisses
36
- } from "./chunk-BZNEY2ZS.js";
32
+ } from "./chunk-ANDKY54G.js";
33
+ import {
34
+ eventBus
35
+ } from "./chunk-CXKDWFNO.js";
37
36
  import {
38
37
  listOsecRunDatabasePaths,
39
38
  osecDB,
40
- resolveOsecDbPath,
41
- resolveOsecRunStorage,
42
- writeOsecRunReport
43
- } from "./chunk-72CISA2D.js";
44
- import {
45
- loadAppsecFinderLenses
46
- } from "./chunk-QI233I24.js";
47
- import {
48
- ScanCostLedger
49
- } from "./chunk-242TMK6G.js";
39
+ resolveOsecDbPath
40
+ } from "./chunk-WZISK5VC.js";
50
41
  import {
51
42
  redactSensitiveHeaders
52
- } from "./chunk-WEBMRWEG.js";
43
+ } from "./chunk-W4JOXE4N.js";
53
44
  import {
54
45
  CloudAuthMissingError,
55
46
  CloudClient,
@@ -59,13 +50,11 @@ import {
59
50
  CloudUnauthorizedError,
60
51
  DEFAULT_CLOUD_HOST,
61
52
  loadCloudCredentials
62
- } from "./chunk-UR4ELX2Y.js";
63
- import {
64
- eventBus
65
- } from "./chunk-ADOLI6DT.js";
53
+ } from "./chunk-BACXRTQ4.js";
66
54
  import {
67
55
  SEVERITY_RANK,
68
56
  VERSION,
57
+ cloudStateDir,
69
58
  createAuditReportDocument,
70
59
  createReviewReportDocument,
71
60
  createScanReportDocument,
@@ -73,12 +62,12 @@ import {
73
62
  formatZodError,
74
63
  homeStateDir,
75
64
  severityRank
76
- } from "./chunk-6L26OBWI.js";
65
+ } from "./chunk-PC6RCKQV.js";
77
66
 
78
67
  // packages/cli/src/utils.ts
79
68
  import { gzipSync } from "zlib";
80
69
  async function getRuntimeAvailability() {
81
- const { detectAvailableRuntimes, LlmApiRuntime } = await import("./dist-4YICS2J2.js");
70
+ const { detectAvailableRuntimes, LlmApiRuntime } = await import("./dist-F6X62TQK.js");
82
71
  const apiRuntimeDiagnostics = new LlmApiRuntime({ type: "api", timeout: 5e3 }).getConfigurationDiagnostics();
83
72
  const hasApiKey = apiRuntimeDiagnostics.valid;
84
73
  const availableRuntimes = [...await detectAvailableRuntimes()];
@@ -110,1094 +99,14 @@ async function checkRuntimeAvailability(runtime) {
110
99
  } else {
111
100
  console.log(source_default.yellow(" Warning: No API key or local agent runtime detected. AI analysis will be skipped."));
112
101
  }
113
- console.log(source_default.gray(" Provider credentials: 0SEC_CHATGPT_ACCESS_TOKEN | 0SEC_CHATGPT_OAUTH_REFRESH_TOKEN, OPENROUTER_API_KEY, ANTHROPIC_API_KEY, AZURE_OPENAI_API_KEY, OPENAI_API_KEY"));
102
+ console.log(source_default.gray(" Provider credentials: ZERO_CHATGPT_ACCESS_TOKEN | ZERO_CHATGPT_OAUTH_REFRESH_TOKEN, OPENROUTER_API_KEY, ANTHROPIC_API_KEY, AZURE_OPENAI_API_KEY, OPENAI_API_KEY"));
114
103
  console.log("");
115
104
  }
116
105
  function buildShareUrl(report) {
117
106
  const json = JSON.stringify(report);
118
107
  const compressed = gzipSync(Buffer.from(json, "utf-8"));
119
108
  const b64 = compressed.toString("base64url");
120
- return `https://0sec.ai/r#${b64}`;
121
- }
122
-
123
- // packages/cli/src/commands/hunt.ts
124
- import { readFileSync, writeFileSync } from "node:fs";
125
- import { resolve } from "node:path";
126
- function leadToCandidateFinding(finding, bugClass, seedRef, candidatePath) {
127
- const evidence = finding.evidence ?? {};
128
- const provenance = `Variant-hunt LEAD (bug class: ${bugClass}; seed: ${seedRef}). Surfaced by the recency hunt and gated by the adversarial skeptic \u2014 a HYPOTHESIS, not a confirmed bug. Verify the real sink + upstream-fix status (novelty) before any disclosure.`;
129
- if (candidatePath) stampDeploymentContext(finding, candidatePath);
130
- return {
131
- ...finding,
132
- // LEADS are never confirmed/sendable: force candidate status so the cloud
133
- // ingests them as verify candidates, never as confirmed findings.
134
- status: "discovered",
135
- templateId: "recency-hunt-lead",
136
- evidence: {
137
- request: evidence.request ?? "",
138
- response: evidence.response ?? "",
139
- analysis: evidence.analysis ? `${evidence.analysis}
140
-
141
- ${provenance}` : provenance
142
- }
143
- };
144
- }
145
- function parsePositive(flag, raw, dflt) {
146
- if (raw === void 0) return dflt;
147
- const n = parseInt(raw, 10);
148
- if (!Number.isFinite(n) || n <= 0) throw new Error(`invalid ${flag} '${raw}' (expected positive integer)`);
149
- return n;
150
- }
151
- var IMPACT_CEILINGS = ["dos-only", "info-leak", "oob-write", "uaf-control"];
152
- function parseCeiling(raw) {
153
- const v = raw.trim();
154
- if (!IMPACT_CEILINGS.includes(v)) {
155
- throw new Error(`invalid --prove-min-ceiling '${raw}' (expected one of: ${IMPACT_CEILINGS.join(", ")})`);
156
- }
157
- return v;
158
- }
159
- function parseNonNegative(flag, raw, dflt) {
160
- if (raw === void 0) return dflt;
161
- const n = parseInt(raw, 10);
162
- if (!Number.isFinite(n) || n < 0) throw new Error(`invalid ${flag} '${raw}' (expected non-negative integer)`);
163
- return n;
164
- }
165
- function parseEnvBestOfN(raw) {
166
- if (raw === void 0 || raw.trim() === "") return void 0;
167
- const n = Number.parseInt(raw, 10);
168
- return Number.isFinite(n) && n > 1 ? n : void 0;
169
- }
170
- function parseEnvPositiveInt(raw) {
171
- if (raw === void 0 || raw.trim() === "") return void 0;
172
- const n = Number.parseInt(raw, 10);
173
- return Number.isFinite(n) && n > 0 ? n : void 0;
174
- }
175
- async function runHunt(opts) {
176
- const {
177
- generateVariantCandidates,
178
- runHuntScan,
179
- makeSkepticVerifier,
180
- loadKnownNegativesFromEnv,
181
- buildInvariantHuntContext,
182
- buildGraphSliceHuntContext,
183
- localMirrors,
184
- syncLoreMirror,
185
- makeLloreJudge,
186
- makeHuntProveStage,
187
- prepare,
188
- getCloudSinkConfig,
189
- postFinding
190
- } = await import("./dist-4YICS2J2.js");
191
- const log = opts.log ?? (() => {
192
- });
193
- const runtime = opts.runtime ?? "api";
194
- const seedDiff = readFileSync(resolve(opts.seedPath), "utf8");
195
- const prepared = await prepare(opts.sourceRoot, "source-code", { ...opts.timeoutMs ? { timeout: opts.timeoutMs } : {} }, (e) => {
196
- if (e.message) log(`[hunt:source] ${e.message}`);
197
- });
198
- const sourceRoot = resolve(prepared.resolvedTarget);
199
- const noveltyRoot = opts.novelty?.rootDir ?? process.env["0SEC_LORE_MIRROR_ROOT"] ?? "/root/lore-mirror";
200
- const noveltyLists = opts.novelty?.lists ?? (process.env["0SEC_LORE_LISTS"] ?? "linux-media").split(",").map((s) => s.trim()).filter(Boolean);
201
- const noveltyRecentEpochs = opts.novelty?.recentEpochs ?? 1;
202
- const noveltyWarnings = [];
203
- const sinkCfg = getCloudSinkConfig();
204
- const savedCloudSink = process.env["0SEC_CLOUD_SINK"];
205
- if (sinkCfg) delete process.env["0SEC_CLOUD_SINK"];
206
- try {
207
- let noveltyMirrors = [];
208
- if (opts.novelty && noveltyLists.length > 0) {
209
- try {
210
- noveltyMirrors = opts.novelty.sync ? await syncLoreMirror({
211
- rootDir: noveltyRoot,
212
- lists: noveltyLists,
213
- recentEpochs: noveltyRecentEpochs,
214
- log
215
- }) : localMirrors(noveltyRoot, noveltyLists);
216
- } catch (err) {
217
- const reason = err instanceof Error ? err.message : String(err);
218
- noveltyWarnings.push(`hunt: novelty sync failed: ${reason.slice(0, 160)}`);
219
- log(`[hunt] ${noveltyWarnings[noveltyWarnings.length - 1]}`);
220
- }
221
- }
222
- if (opts.novelty && noveltyMirrors.length === 0) {
223
- const message = `novelty requested but no lore mirrors found under ${noveltyRoot} for ${noveltyLists.join(",") || "(no lists)"}`;
224
- return {
225
- exitCode: 3,
226
- result: {
227
- mode: "hunt",
228
- seed: opts.ref ?? opts.seedPath,
229
- candidates: 0,
230
- novelty: { enabled: true, required: true, mirrors: [] },
231
- warnings: [...noveltyWarnings, `hunt: ${message}; aborting fail-closed`],
232
- note: "Discovery did not run because requested novelty evidence was unavailable."
233
- }
234
- };
235
- }
236
- const skipCandidates = opts.skipCandidates ?? 0;
237
- const maxCandidates = opts.maxCandidates ?? 40;
238
- const reachableOnly = opts.reachableOnly ?? process.env.HUNT_REACHABLE_ONLY === "1";
239
- const reachablePrefer = opts.reachablePrefer ?? (opts.methodology ? true : process.env.HUNT_REACHABLE_PREFER === "1");
240
- let invariantCtx = null;
241
- const invariantWarnings = [];
242
- if (opts.invariant) {
243
- try {
244
- invariantCtx = await buildInvariantHuntContext({ sourceRoot, seedDiff, runtime, log });
245
- if (!invariantCtx) {
246
- invariantWarnings.push("hunt: --invariant set but no subsystem scope/files derivable from the seed \u2014 continuing without invariant context");
247
- log(`[hunt] ${invariantWarnings[invariantWarnings.length - 1]}`);
248
- }
249
- } catch (err) {
250
- const reason = err instanceof Error ? err.message : String(err);
251
- invariantWarnings.push(`hunt: invariant-model stage failed, continuing without it: ${reason.slice(0, 160)}`);
252
- log(`[hunt] ${invariantWarnings[invariantWarnings.length - 1]}`);
253
- }
254
- }
255
- let graphSliceCtx = null;
256
- const graphSliceWarnings = [];
257
- if (opts.graphSlice) {
258
- try {
259
- graphSliceCtx = buildGraphSliceHuntContext({
260
- sourceRoot,
261
- seedDiff,
262
- ...opts.cpgPath ? { cpgPath: opts.cpgPath } : {},
263
- log,
264
- ...opts.opsHarvestSourceFiles !== void 0 ? { opsHarvestSourceFiles: opts.opsHarvestSourceFiles } : {},
265
- ...opts.graphSliceHops !== void 0 ? { hops: opts.graphSliceHops } : {}
266
- });
267
- if (!graphSliceCtx) {
268
- graphSliceWarnings.push("hunt: --graph-slice set but no CPG/scope/slice derivable \u2014 continuing with the flat-text finder");
269
- log(`[hunt] ${graphSliceWarnings[graphSliceWarnings.length - 1]}`);
270
- }
271
- } catch (err) {
272
- const reason = err instanceof Error ? err.message : String(err);
273
- graphSliceWarnings.push(`hunt: graph-slice stage failed, continuing without it: ${reason.slice(0, 160)}`);
274
- log(`[hunt] ${graphSliceWarnings[graphSliceWarnings.length - 1]}`);
275
- }
276
- }
277
- const plan = await generateVariantCandidates({
278
- sourceRoot,
279
- fix: { diff: seedDiff, reference: opts.ref ?? opts.seedPath },
280
- runtime,
281
- maxCandidates: skipCandidates + maxCandidates,
282
- ...opts.models ? { models: opts.models } : {},
283
- ...reachableOnly ? { reachableOnly } : {},
284
- ...reachablePrefer ? { reachablePrefer } : {},
285
- log
286
- });
287
- const selectedCandidates = plan.candidates.slice(skipCandidates, skipCandidates + maxCandidates);
288
- if (selectedCandidates.length === 0) {
289
- return {
290
- exitCode: 2,
291
- result: {
292
- mode: "hunt",
293
- seed: opts.ref ?? opts.seedPath,
294
- bug_class: plan.brief.bugClass,
295
- grep_patterns: plan.grepPatterns,
296
- candidates: 0,
297
- skipped_candidates: Math.min(skipCandidates, plan.candidates.length),
298
- warnings: [...noveltyWarnings, ...plan.warnings],
299
- note: plan.candidates.length === 0 ? "no candidate sites generated \u2014 seed too narrow or surface already clean" : "no candidate sites left after --skip-candidates"
300
- }
301
- };
302
- }
303
- const candidates = selectedCandidates.map((c) => ({ ...c, path: `${sourceRoot}/${c.path}` }));
304
- const attemptsPerCandidate = opts.attemptsPerCandidate ?? parseEnvBestOfN(process.env.HUNT_BEST_OF_N) ?? (opts.methodology ? 4 : void 0);
305
- const judgeTopK = opts.judgeTopK ?? parseEnvPositiveInt(process.env.HUNT_JUDGE_TOP_K) ?? (opts.methodology ? 2 : void 0);
306
- const judgeModel = opts.judgeModel ?? process.env.HUNT_JUDGE_MODEL;
307
- const baseBrief = opts.methodology ? {
308
- ...plan.brief,
309
- pattern: `${plan.brief.pattern}
310
-
311
- METHODOLOGY LENSES: map the full object lifecycle across handlers, callbacks, error/teardown paths and concurrent sessions; trace buffer/page provenance across zero-copy and in-place subsystem boundaries; look for a fix applied to one sibling path but bypassed by another. Refute capability, configuration and state-machine reachability before claiming impact. Treat source reasoning as a hypothesis until a sanitizer-backed reproducer proves it.`
312
- } : plan.brief;
313
- let huntPattern = baseBrief.pattern;
314
- if (invariantCtx) huntPattern = `${huntPattern}
315
-
316
- ${invariantCtx.promptBlock}`;
317
- if (graphSliceCtx) huntPattern = `${huntPattern}
318
-
319
- ${graphSliceCtx.promptBlock}`;
320
- const huntBrief = huntPattern === baseBrief.pattern ? baseBrief : { ...baseBrief, pattern: huntPattern };
321
- const res = await runHuntScan({
322
- sourceRoot,
323
- candidates,
324
- brief: huntBrief,
325
- runtime,
326
- concurrency: opts.concurrency ?? 4,
327
- ...opts.models ? { models: opts.models } : {},
328
- ...attemptsPerCandidate ? { attemptsPerCandidate } : {},
329
- ...judgeTopK ? { judgeTopK } : {},
330
- ...judgeModel ? { judgeModel } : {},
331
- // The skeptic is handed `models[0]` — i.e. by default the refute pass runs
332
- // on the SAME MODEL that found the bug, which is the correlated-error
333
- // problem in #661. `finderModels` lets the cross-family selector avoid
334
- // every family in the finder fan-out (not just the first) and swap the
335
- // refuter to a different family when one is configured; when only one
336
- // provider is configured it passes through to exactly this model. `log`
337
- // makes that decision visible in the run log instead of implicit.
338
- ...opts.verify === false ? {} : {
339
- verify: makeSkepticVerifier({
340
- sourceRoot,
341
- runtime,
342
- ...opts.models?.[0] ? { model: opts.models[0] } : {},
343
- ...opts.models && opts.models.length > 0 ? { finderModels: opts.models } : {},
344
- negatives: loadKnownNegativesFromEnv(),
345
- log
346
- })
347
- },
348
- // PROVE stage (#1119) — the terminal gate. Only wired on --exploitability,
349
- // because it boots REAL QEMU per confirmed finding. runHuntScan itself
350
- // ignores it (with a warning) when `verify` is absent, so --no-verify
351
- // --exploitability degrades safely instead of booting anything.
352
- ...opts.exploitability ? {
353
- exploitability: makeHuntProveStage({
354
- // Cheap VM-free pre-filter: never spend a QEMU slot on a dos-only bug.
355
- escalation: opts.proveMinCeiling ? { minCeiling: opts.proveMinCeiling } : {},
356
- onVerdict: (f, _c, v) => log(
357
- `[hunt:prove] ${f.title}: maxObserved=${v.maxObservedClass} upgraded=${v.upgraded} reachesPrivesc=${v.reachesPrivesc} proven=${v.provenExploitability}`
358
- ),
359
- onEscalation: (f, _c, v) => log(`[hunt:prove] ${f.title}: impact ceiling ${v.ceiling} (${v.basis}, conf ${v.confidence})`),
360
- log
361
- })
362
- } : {},
363
- ...opts.novelty && noveltyMirrors.length > 0 ? {
364
- novelty: {
365
- mirrors: noveltyMirrors,
366
- ...opts.novelty.model ? { judge: makeLloreJudge({ model: opts.novelty.model }) } : {}
367
- }
368
- } : {},
369
- log
370
- });
371
- const gated = opts.verify !== false;
372
- const leads = gated ? res.confirmed : res.findings;
373
- let ingested = 0;
374
- if (sinkCfg) {
375
- const seedRef = opts.ref ?? opts.seedPath;
376
- const pathForId = /* @__PURE__ */ new Map();
377
- for (const rec of res.records) pathForId.set(rec.finding.id, rec.candidatePath);
378
- for (const lead of leads) {
379
- const candidatePath = pathForId.get(lead.id);
380
- await postFinding(leadToCandidateFinding(lead, plan.brief.bugClass, seedRef, candidatePath), sinkCfg);
381
- ingested++;
382
- }
383
- log(`[hunt] posted ${ingested} lead(s) to the cloud-sink as candidate findings`);
384
- }
385
- return {
386
- exitCode: leads.length > 0 ? 0 : 1,
387
- result: {
388
- mode: "hunt",
389
- seed: opts.ref ?? opts.seedPath,
390
- bug_class: plan.brief.bugClass,
391
- source: sourceRoot,
392
- candidate_sites: selectedCandidates.map((c) => c.path),
393
- skipped_candidates: skipCandidates,
394
- scanned: res.scanned,
395
- findings: res.findings.length,
396
- confirmed: gated ? res.confirmed.length : null,
397
- novelty: opts.novelty ? {
398
- enabled: true,
399
- required: true,
400
- root: noveltyRoot,
401
- lists: noveltyLists,
402
- mirrors: noveltyMirrors.map((m) => ({ list: m.list, epoch: m.epoch, dir: m.dir })),
403
- duplicates: res.duplicates.map((d) => ({
404
- title: d.finding.title,
405
- matches: d.novelty.duplicates
406
- }))
407
- } : { enabled: false },
408
- leads: leads.map((f) => ({
409
- title: f.title,
410
- severity: f.severity,
411
- analysis: f.evidence.analysis ?? ""
412
- })),
413
- dropped: res.dropped.map((d) => ({
414
- title: d.finding.title,
415
- severity: d.finding.severity,
416
- candidatePath: d.candidatePath,
417
- lensId: d.lensId,
418
- dropReason: d.dropReason,
419
- detail: d.detail
420
- })),
421
- invariant: invariantCtx ? {
422
- enabled: true,
423
- subsystem: invariantCtx.subsystem,
424
- model_path: invariantCtx.modelPath,
425
- model_loaded: invariantCtx.modelLoaded,
426
- objects: invariantCtx.model.objects.length,
427
- violations: invariantCtx.violations.length
428
- } : { enabled: false },
429
- graph_slice: graphSliceCtx ? {
430
- enabled: true,
431
- subsystem: graphSliceCtx.subsystem,
432
- cpg_path: graphSliceCtx.cpgPath,
433
- target_functions: graphSliceCtx.targetFunctions,
434
- resolved_targets: graphSliceCtx.resolvedTargets,
435
- ops_edges: graphSliceCtx.opsEdges,
436
- functions: graphSliceCtx.stats.functions,
437
- files: graphSliceCtx.stats.files.length,
438
- call_edges: graphSliceCtx.stats.callEdges,
439
- slice_chars: graphSliceCtx.stats.chars
440
- } : { enabled: false },
441
- ingested: sinkCfg ? ingested : null,
442
- gated,
443
- methodology: opts.methodology === true,
444
- warnings: [...invariantWarnings, ...graphSliceWarnings, ...noveltyWarnings, ...plan.warnings, ...res.warnings].slice(0, 10),
445
- note: opts.novelty ? "LEADS, not confirmed 0-days. Novelty-duplicate leads were dropped when lore mirrors matched; still verify the real sink before disclosure." : "LEADS, not confirmed 0-days. Verify the real sink + upstream-fix (novelty) before disclosure."
446
- }
447
- };
448
- } finally {
449
- if (savedCloudSink !== void 0) process.env["0SEC_CLOUD_SINK"] = savedCloudSink;
450
- prepared.cleanup();
451
- }
452
- }
453
- async function huntAction(opts) {
454
- if (!opts.source) throw new Error("missing required flag: --source <kernel/src tree>");
455
- if (!opts.seed) throw new Error("missing required flag: --seed <fix diff/patch to hunt variants of>");
456
- const outcome = await runHunt({
457
- sourceRoot: opts.source,
458
- seedPath: opts.seed,
459
- ...opts.ref ? { ref: opts.ref } : {},
460
- concurrency: parsePositive("--concurrency", opts.concurrency, 4),
461
- skipCandidates: parseNonNegative("--skip-candidates", opts.skipCandidates, 0),
462
- ...opts.models ? { models: opts.models.split(",").map((s) => s.trim()).filter(Boolean) } : {},
463
- ...opts.reachableOnly ? { reachableOnly: true } : {},
464
- ...opts.reachablePrefer ? { reachablePrefer: true } : {},
465
- verify: opts.verify,
466
- ...opts.novelty || opts.noveltyRequired || opts.methodology ? {
467
- novelty: {
468
- ...opts.noveltyRoot ? { rootDir: opts.noveltyRoot } : {},
469
- ...opts.noveltyLists ? { lists: opts.noveltyLists.split(",").map((s) => s.trim()).filter(Boolean) } : {},
470
- recentEpochs: parsePositive("--novelty-recent-epochs", opts.noveltyRecentEpochs, 1),
471
- sync: opts.noveltySync === true || opts.methodology === true,
472
- ...opts.noveltyModel ? { model: opts.noveltyModel } : {},
473
- required: opts.noveltyRequired === true || opts.methodology === true
474
- }
475
- } : {},
476
- methodology: opts.methodology === true,
477
- invariant: opts.invariant === true,
478
- graphSlice: opts.graphSlice === true,
479
- ...opts.cpg ? { cpgPath: opts.cpg } : {},
480
- ...opts.opsHarvest !== void 0 ? {
481
- opsHarvestSourceFiles: opts.opsHarvest.split(",").map((path) => path.trim()).filter(Boolean)
482
- } : {},
483
- ...opts.graphSliceHops ? { graphSliceHops: parsePositive("--graph-slice-hops", opts.graphSliceHops, 3) } : {},
484
- exploitability: opts.exploitability === true,
485
- ...opts.proveMinCeiling ? { proveMinCeiling: parseCeiling(opts.proveMinCeiling) } : {},
486
- ...opts.runtime ? { runtime: opts.runtime } : {},
487
- timeoutMs: parsePositive("--timeout", opts.timeout, 6e5),
488
- log: (m) => process.stderr.write(m + "\n")
489
- });
490
- const json = JSON.stringify(outcome.result, null, 2);
491
- if (opts.output) writeFileSync(resolve(opts.output), json + "\n", "utf8");
492
- else process.stdout.write(json + "\n");
493
- process.exitCode = outcome.exitCode;
494
- }
495
- function registerHuntCommand(program) {
496
- program.command("hunt").description(
497
- "Hunt a bug CLASS across a source tree, seeded by a proven fix: generate variant candidate sites from the fix, fan finders out over them, and gate each finding through an adversarial skeptic. Emits LEADS to verify (not confirmed 0-days). Exit 0=lead(s), 1=none, 2=no candidates, 3=error."
498
- ).requiredOption("--source <path>", "Source tree to hunt in (e.g. a linux checkout)").requiredOption("--seed <path>", "Fix diff / .patch whose bug class to hunt variants of").option("--ref <name>", "Provenance label for the seed (e.g. the CVE / commit)").option("--concurrency <N>", "Max finders in flight (default 4)").option("--max-candidates <N>", "Cap candidate sites hunted (default 40)").option("--skip-candidates <N>", "Skip the first N ranked candidate sites before hunting (default 0)").option("--models <a,b>", "Comma-separated finder models for diversity (default: provider default)").option("--reachable-only", "Restrict candidates to paths built + zero-cap reachable on the kernelCTF COS target (default: HUNT_REACHABLE_ONLY env)").option("--reachable-prefer", "Sort kernelCTF-reachable candidates first, without dropping any (default: HUNT_REACHABLE_PREFER env)").option("--no-verify", "Skip the skeptic gate (emit all raw findings \u2014 triage only, never disclosure)").option("--novelty", "Require lore.kernel.org duplicate suppression; abort before discovery when evidence is unavailable").option("--novelty-root <path>", "Lore mirror root (default: 0SEC_LORE_MIRROR_ROOT or /root/lore-mirror)").option("--novelty-lists <a,b>", "Comma-separated lore lists to search (default: 0SEC_LORE_LISTS or linux-media)").option("--novelty-recent-epochs <N>", "Newest public-inbox epochs to sync per list when --novelty-sync is set (default 1)").option("--novelty-sync", "Clone/fetch lore mirrors before running the novelty gate").option("--novelty-model <model>", "Optional model override for the lore duplicate judge").option("--novelty-required", "Legacy alias; --novelty already aborts when evidence is unavailable").option("--methodology", "Use the kernel-LPE methodology preset: lifecycle/provenance lenses, best-of-4, top-2 skeptic gate, reachable-first").option("--invariant", "Engine A: build (or load) the seed-touched subsystem's stored invariant model and inject its rules + deterministic violation hypotheses into every finder prompt").option("--graph-slice", "Load the seed-touched subsystem's pre-exported Joern CPG and inject a compact interprocedural reachability slice around the fix site into every finder prompt (needs scripts/provision-cpg.sh; fail-open to flat-text)").option("--cpg <path>", "Explicit CPG graphson JSON path for --graph-slice (default: <source>/.0sec/cpg/<subsystem>.json)").option(
499
- "--ops-harvest <paths>",
500
- "[--graph-slice] Comma-separated repo-relative C files to harvest static ops-struct initializers from; overrides a precomputed .ops.json"
501
- ).option(
502
- "--graph-slice-hops <N>",
503
- "[--graph-slice] Call-graph radius around the seed functions (default 3; use 8 for the exp527 known answer)"
504
- ).option(
505
- "--exploitability",
506
- "PROVE stage: after the skeptic+prover gate, run the execution-verified exploitability oracle on each confirmed finding (GREBE diversify + SCAVY differential). BOOTS REAL QEMU VMs \u2014 requires staged kernel-VM artifacts and is ignored under --no-verify. Never rejects a finding; it stamps a proven verdict and gates the weaponize budget."
507
- ).option(
508
- "--prove-min-ceiling <ceiling>",
509
- "[--exploitability] Minimum assessed impact ceiling worth a VM slot: dos-only|info-leak|oob-write|uaf-control (default info-leak \u2014 filters out dos-only before QEMU is touched)"
510
- ).option("--output <path>", "Write the hunt result JSON to this path instead of stdout").option("--runtime <mode>", "Engine runtime (default api)").option("--timeout <ms>", "Accepted cloud agent timeout budget in milliseconds", "600000").action(async (opts) => {
511
- try {
512
- await huntAction(opts);
513
- } catch (err) {
514
- const reason = err instanceof Error ? err.message : String(err);
515
- const json = JSON.stringify({ mode: "hunt", error: reason }, null, 2);
516
- if (opts.output) {
517
- try {
518
- writeFileSync(resolve(opts.output), json + "\n", "utf8");
519
- } catch {
520
- process.stderr.write(json + "\n");
521
- }
522
- } else process.stdout.write(json + "\n");
523
- process.exitCode = 3;
524
- }
525
- });
526
- }
527
-
528
- // packages/cli/src/commands/deep-review.ts
529
- import { readFileSync as readFileSync2, realpathSync, statSync } from "node:fs";
530
- import { resolve as resolve2, join, sep, relative } from "node:path";
531
- import { createHash, randomUUID } from "node:crypto";
532
- var DEEP_REVIEW_FILE_CAP = 5e3;
533
- var DEFAULT_MAX_CANDIDATES = 8;
534
- function defaultMaxCandidates() {
535
- const raw = process.env["0SEC_DEEP_REVIEW_MAX_CANDIDATES"];
536
- if (!raw) return DEFAULT_MAX_CANDIDATES;
537
- const n = Number.parseInt(raw, 10);
538
- return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_CANDIDATES;
539
- }
540
- function scaleMaxCandidates(fileCount) {
541
- if (fileCount > 2e3) return 20;
542
- if (fileCount > 500) return 16;
543
- if (fileCount > 200) return 12;
544
- return DEFAULT_MAX_CANDIDATES;
545
- }
546
- var DEFAULT_ATTEMPTS = 1;
547
- function defaultAttempts() {
548
- const raw = process.env["0SEC_DEEP_REVIEW_ATTEMPTS"];
549
- if (!raw) return DEFAULT_ATTEMPTS;
550
- const n = Number.parseInt(raw, 10);
551
- return Number.isFinite(n) && n > 0 ? n : DEFAULT_ATTEMPTS;
552
- }
553
- function defaultModels() {
554
- const raw = process.env["0SEC_DEEP_REVIEW_MODELS"];
555
- if (!raw) return void 0;
556
- const models = raw.split(",").map((s) => s.trim()).filter(Boolean);
557
- return models.length > 0 ? models : void 0;
558
- }
559
- var DEFAULT_CONCURRENCY = 8;
560
- var genericFinderLenses = [
561
- {
562
- id: "memory-safety",
563
- challengeHint: "Hunt MEMORY-SAFETY bugs only: out-of-bounds read/write, use-after-free, double-free, uninitialized read, integer overflow feeding an allocation or index, and unchecked length/size arithmetic before a copy. Cite the exact unguarded sink (file:line) and the attacker-controlled path that reaches it."
564
- },
565
- {
566
- id: "input-validation",
567
- challengeHint: "Hunt INPUT-VALIDATION and injection bugs only: untrusted input reaching a dangerous sink without validation \u2014 command/SQL/path/template injection, unsafe deserialization, SSRF, unchecked redirects, or a parser that trusts attacker-supplied structure. Trace the taint from the entry point to the sink."
568
- },
569
- {
570
- id: "auth-logic",
571
- challengeHint: "Hunt AUTHZ / LOGIC bugs only: a missing or wrong permission/ownership check, a broken state machine, a TOCTOU race, an off-by-one or boundary error in a security-relevant decision, or a check that can be bypassed. Prove the guard is absent or mis-scoped, not merely that the keywords appear."
572
- },
573
- {
574
- id: "secrets-crypto",
575
- challengeHint: "Hunt SECRETS / CRYPTO misuse only: hardcoded credentials, a weak/predictable RNG used for security, a broken or misused cryptographic primitive (ECB, static IV/nonce, missing MAC verification, non-constant-time compare), or a signature/token check that can be forged or replayed. Cite the exact misuse."
576
- },
577
- {
578
- id: "cross-component",
579
- challengeHint: "Hunt CROSS-COMPONENT bugs only: a bug whose exploit path spans two or more subsystems touching DIFFERENT trust boundaries \u2014 e.g. an API layer that passes unvalidated user input to a kernel driver, a storage layer whose consistency assumptions are violated by a separate async path, or a UX component whose state bleeds into an auth decision in a sibling module. Trace the path ACROSS the boundary and prove the attacker can reach BOTH sides. Ignore bugs fully contained within a single component."
580
- }
581
- ];
582
- function lensSetDigest(lenses) {
583
- return createHash("sha256").update(JSON.stringify(lenses.map(({ id, challengeHint }) => ({ id, challengeHint })))).digest("hex");
584
- }
585
- function createDefaultFinderLenses() {
586
- return [...genericFinderLenses, ...loadAppsecFinderLenses()];
587
- }
588
- var defaultFinderLenses = createDefaultFinderLenses();
589
- var defaultVerifyLenses = [
590
- {
591
- id: "reachability",
592
- challengeHint: "REACHABILITY: is the vulnerable code actually reachable by an attacker from a real entry point (exported/public API, request handler, CLI, parser) with no gate it cannot pass? Trace the concrete path from the entry to the sink. If the path is dead code, disabled, or privileged-only, refute it."
593
- },
594
- {
595
- id: "completeness",
596
- challengeHint: "COMPLETENESS: is the 'missing' check actually enforced elsewhere on the path \u2014 a validation earlier in the flow, a guard in the only caller, a wrapper that sanitizes first? Read the whole call path and the full file/module. If the guard is present AND correctly scoped, refute it; keep it only if genuinely absent or mis-scoped."
597
- },
598
- {
599
- id: "novelty-known-issue",
600
- challengeHint: "NOVELTY / KNOWN-GUARD: is the standard guard for this class present and correct (a framework escaper, a safe API, a bounds check, a working auth middleware)? Is this a well-known already-mitigated pattern in this codebase or its dependencies? If a correct standard guard covers it, refute it."
601
- },
602
- {
603
- id: "scope",
604
- challengeHint: "SCOPE / IMPACT: does exploiting this actually corrupt memory, leak secrets, escalate privilege, execute code, or move value / break a security invariant? A cosmetic missing check with no real impact is info/low, not a high-severity finding \u2014 refute it as such."
605
- },
606
- {
607
- id: "deployment-context",
608
- challengeHint: "DEPLOYMENT CONTEXT: is this finding's vulnerable code path actually deployed to a production runtime, or is it restricted to development, test, or build-only surfaces? \n\nYour job is to examine the finding's evidence AND your own reading of the candidate source to decide: can an attacker reach this code from a production entry point? \n\n- If the code is in a test file, a dev-only config (seeds, .dev.vars, dev-server), or build scaffolding (webpack, CI, vendored deps) with NO trust-boundary crossing from prod, refute it as dev/test/build-only.\n- If you find a trust-boundary bypass \u2014 a prod-routed entry point that reaches this code despite it being in a dev/test/build file \u2014 DO NOT refute; the finding is valid per the HackerOne rule.\n- If you are UNCERTAIN (the evidence is ambiguous or you cannot assess the deployment topology), ABSTAIN by returning an error. Never falsely refute a finding with weak evidence.\nPRIORITY: the mechanical path heuristic (file path classification) is the DETERMINISTIC FLOOR and overrides this lens on conflict. If the heuristic already tagged the finding as dev_only/test_only/build_only, the severity cap has already been applied \u2014 this lens exists to catch cases the heuristic misses (ambiguous paths, model-extracted evidence, cross-boundary reachability)."
609
- }
610
- ];
611
- function selectProfileLenses(profile, sets, defaultFinderSnapshot = defaultFinderLenses) {
612
- switch ((profile ?? "").trim().toLowerCase()) {
613
- case "evm-onchain":
614
- return { finderLenses: sets.evmFinderLenses, verifyLenses: sets.evmVerifyLenses, matchedProfile: "evm-onchain" };
615
- case "solana-onchain":
616
- return { finderLenses: sets.solanaFinderLenses, verifyLenses: sets.solanaVerifyLenses, matchedProfile: "solana-onchain" };
617
- case "cardano-onchain":
618
- return { finderLenses: sets.cardanoFinderLenses, verifyLenses: sets.cardanoVerifyLenses, matchedProfile: "cardano-onchain" };
619
- case "cairo-onchain":
620
- return { finderLenses: sets.cairoFinderLenses, verifyLenses: sets.cairoVerifyLenses, matchedProfile: "cairo-onchain" };
621
- case "move-onchain":
622
- return { finderLenses: sets.moveFinderLenses, verifyLenses: sets.moveVerifyLenses, matchedProfile: "move-onchain" };
623
- default:
624
- return { finderLenses: defaultFinderSnapshot, verifyLenses: defaultVerifyLenses, matchedProfile: "default" };
625
- }
626
- }
627
- var NATIVE_SOURCE_EXTS = /* @__PURE__ */ new Set([
628
- "c",
629
- "h",
630
- "cc",
631
- "cpp",
632
- "cxx",
633
- "hpp",
634
- "hh",
635
- "hxx",
636
- "rs"
637
- ]);
638
- var MANAGED_WEB_EXTS = /* @__PURE__ */ new Set([
639
- "cs",
640
- "csproj",
641
- "sln",
642
- "vbproj",
643
- "fsproj",
644
- // .NET / C#
645
- "ts",
646
- "tsx",
647
- "js",
648
- "jsx",
649
- "mjs",
650
- "cjs",
651
- "vue",
652
- "svelte",
653
- // JS/TS front+back
654
- "java",
655
- "kt",
656
- "kts",
657
- "scala",
658
- // JVM
659
- "py",
660
- "rb",
661
- "php",
662
- "go",
663
- // scripting / Go
664
- "html",
665
- "htm",
666
- "cshtml",
667
- "razor",
668
- "aspx",
669
- "jsp"
670
- // web templates
671
- ]);
672
- function fileExt(path) {
673
- const base = path.split(/[/\\]+/).pop() ?? path;
674
- const dot = base.lastIndexOf(".");
675
- return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
676
- }
677
- function detectStack(files) {
678
- let native = 0;
679
- let managed = 0;
680
- for (const f of files) {
681
- const ext = fileExt(f);
682
- if (NATIVE_SOURCE_EXTS.has(ext)) native++;
683
- else if (MANAGED_WEB_EXTS.has(ext)) managed++;
684
- }
685
- if (native === 0 && managed === 0) return "unknown";
686
- if (native === 0) return "managed-web";
687
- if (managed === 0) return "native";
688
- return "mixed";
689
- }
690
- var MEMORY_SAFETY_LENS_ID = "memory-safety";
691
- function selectDefaultFinderLensesForStack(files, defaultFinder) {
692
- if (detectStack(files) === "managed-web") {
693
- return defaultFinder.filter((l) => l.id !== MEMORY_SAFETY_LENS_ID);
694
- }
695
- return defaultFinder;
696
- }
697
- var EVM_EXCLUDE_DIR_SEGMENTS = /* @__PURE__ */ new Set([
698
- "test",
699
- "tests",
700
- "mock",
701
- "mocks",
702
- "script",
703
- "scripts",
704
- // Foundry deploy scripts
705
- "lib",
706
- // Foundry vendored deps (forge-std / openzeppelin / solmate …)
707
- "node_modules"
708
- // Hardhat/JS vendored deps (also skipped by the core walker)
709
- ]);
710
- function isNonProtocolEvmPath(absPath, scopeRoot) {
711
- const rel = relative(scopeRoot, absPath);
712
- if (rel === "" || rel.startsWith("..")) return false;
713
- const segments = rel.split(/[/\\]+/).filter(Boolean);
714
- if (segments.length === 0) return false;
715
- for (let i = 0; i < segments.length - 1; i++) {
716
- if (EVM_EXCLUDE_DIR_SEGMENTS.has(segments[i].toLowerCase())) return true;
717
- }
718
- const fileName = segments[segments.length - 1].toLowerCase();
719
- if (fileName.endsWith(".t.sol")) return true;
720
- if (fileName.includes(".test.") || fileName.includes(".spec.")) return true;
721
- return false;
722
- }
723
- function topLevelModule(absPath, scopeRoot) {
724
- const rel = relative(scopeRoot, absPath);
725
- if (rel === "" || rel.startsWith("..")) return ".";
726
- const segments = rel.split(/[/\\]+/).filter(Boolean);
727
- return segments.length > 1 ? segments[0] : ".";
728
- }
729
- function selectCandidatesModuleSpread(entries, scopeRoot, maxCandidates) {
730
- const cmp = (a, b) => b.size - a.size || a.p.localeCompare(b.p);
731
- const byModule = /* @__PURE__ */ new Map();
732
- for (const e of entries) {
733
- const key = topLevelModule(e.p, scopeRoot);
734
- let group = byModule.get(key);
735
- if (!group) byModule.set(key, group = []);
736
- group.push(e);
737
- }
738
- const groups = [...byModule.values()];
739
- for (const g of groups) g.sort(cmp);
740
- groups.sort((a, b) => cmp(a[0], b[0]));
741
- const cap = Math.max(1, maxCandidates);
742
- const out = [];
743
- for (let round = 0; out.length < cap; round++) {
744
- let advanced = false;
745
- for (const g of groups) {
746
- if (round >= g.length) continue;
747
- out.push(g[round].p);
748
- advanced = true;
749
- if (out.length >= cap) break;
750
- }
751
- if (!advanced) break;
752
- }
753
- return out;
754
- }
755
- function enumerateDeepReviewCandidates(scopeRoot, helpers, opts) {
756
- const fileCap = opts.fileCap ?? DEEP_REVIEW_FILE_CAP;
757
- const exclude = opts.exclude ?? (() => false);
758
- const fileSize = helpers.fileSize ?? ((p) => {
759
- try {
760
- return statSync(p).size;
761
- } catch {
762
- return 0;
763
- }
764
- });
765
- const totalFiles = helpers.countScopeFilesUpTo(scopeRoot, fileCap);
766
- const overCap = totalFiles > fileCap;
767
- const files = helpers.collectScopeFiles(scopeRoot, { maxFiles: fileCap });
768
- const entries = files.filter((p) => !exclude(p)).map((p) => ({ p, size: fileSize(p) }));
769
- const candidates = selectCandidatesModuleSpread(entries, scopeRoot, opts.maxCandidates);
770
- return { candidates, totalFiles, overCap };
771
- }
772
- async function runDeepReview(opts) {
773
- const defaultFinderSnapshot = createDefaultFinderLenses();
774
- const evolvedFinder = opts.evolutionConfig ? await createEvolvedFinder(opts.evolutionConfig, `deep-review-${randomUUID()}`) : void 0;
775
- const {
776
- runHuntScan,
777
- makeMultiLensVerifier,
778
- prepare,
779
- collectScopeFiles,
780
- countScopeFilesUpTo,
781
- getCloudSinkConfig,
782
- postFinding,
783
- evmFinderLenses,
784
- evmVerifyLenses,
785
- solanaFinderLenses,
786
- solanaVerifyLenses,
787
- cardanoFinderLenses,
788
- cardanoVerifyLenses,
789
- cairoFinderLenses,
790
- cairoVerifyLenses,
791
- moveFinderLenses,
792
- moveVerifyLenses
793
- } = await import("./dist-4YICS2J2.js");
794
- const costLedger = new ScanCostLedger();
795
- const scanStartedAt = Date.now();
796
- const log = opts.log ?? (() => {
797
- });
798
- const runtime = opts.runtime ?? "api";
799
- const explicitMaxCandidates = opts.maxCandidates;
800
- const hasEnvMaxOverride = !!process.env["0SEC_DEEP_REVIEW_MAX_CANDIDATES"];
801
- const baseMaxCandidates = explicitMaxCandidates ?? defaultMaxCandidates();
802
- if (evolvedFinder && opts.models?.length) throw new Error("--models selects native finders and cannot be combined with --evolution-config");
803
- const models = evolvedFinder ? ["evolved-source"] : opts.models ?? defaultModels();
804
- const attemptsPerCandidate = opts.attemptsPerCandidate ?? defaultAttempts();
805
- const prepared = await prepare(opts.target, "source-code", { ...opts.timeoutMs ? { timeout: opts.timeoutMs } : {} }, (e) => {
806
- if (e.message) log(`[deep-review:source] ${e.message}`);
807
- });
808
- const sourceRoot = resolve2(prepared.resolvedTarget);
809
- let scopeRoot = sourceRoot;
810
- if (opts.subsystem && opts.subsystem.trim() !== "") {
811
- const scoped = resolve2(join(sourceRoot, opts.subsystem));
812
- if (scoped !== sourceRoot && !scoped.startsWith(sourceRoot + sep)) {
813
- prepared.cleanup();
814
- throw new Error(`--subsystem '${opts.subsystem}' escapes the source tree`);
815
- }
816
- try {
817
- const realRoot = realpathSync(sourceRoot);
818
- const realScoped = realpathSync(scoped);
819
- if (realScoped !== realRoot && !realScoped.startsWith(realRoot + sep)) {
820
- throw new Error(`--subsystem '${opts.subsystem}' escapes the source tree`);
821
- }
822
- scopeRoot = scoped;
823
- } catch (error) {
824
- prepared.cleanup();
825
- throw error;
826
- }
827
- }
828
- const sinkCfg = getCloudSinkConfig();
829
- const savedCloudSink = process.env["0SEC_CLOUD_SINK"];
830
- if (sinkCfg) delete process.env["0SEC_CLOUD_SINK"];
831
- try {
832
- const isEvmProfile = (opts.profile ?? "").trim().toLowerCase() === "evm-onchain";
833
- const totalFiles = countScopeFilesUpTo(scopeRoot, DEEP_REVIEW_FILE_CAP);
834
- const maxCandidates = explicitMaxCandidates ?? (hasEnvMaxOverride ? baseMaxCandidates : scaleMaxCandidates(totalFiles));
835
- const overCap = totalFiles > DEEP_REVIEW_FILE_CAP;
836
- if (explicitMaxCandidates === void 0 && maxCandidates !== baseMaxCandidates) {
837
- log(`[deep-review] auto-scaled candidate cap from ${baseMaxCandidates} to ${maxCandidates} (${totalFiles} scope files)`);
838
- }
839
- let lanes = null;
840
- let allocateAcrossLanes;
841
- if (opts.useThreatModel) {
842
- const threatModule = await import("./dist-4YICS2J2.js");
843
- allocateAcrossLanes = threatModule.allocateCandidatesAcrossLanes;
844
- const rtType = runtime !== "auto" ? runtime : "api";
845
- const plannerRuntime = threatModule.createRuntime({
846
- type: rtType,
847
- timeout: opts.timeoutMs ?? 6e5,
848
- model: models?.[0]
849
- });
850
- lanes = await threatModule.runThreatModelPlanner(sourceRoot, plannerRuntime, log);
851
- if (!lanes || lanes.length === 0) {
852
- log("[threat-model] no lanes produced \u2014 continuing with default selection");
853
- } else {
854
- log(`[threat-model] planner returned ${lanes.length} lane(s) \u2014 will allocate candidates across boundaries`);
855
- }
856
- }
857
- if (overCap) {
858
- return {
859
- exitCode: 2,
860
- result: {
861
- mode: "deep_review",
862
- source: sourceRoot,
863
- subsystem: opts.subsystem ?? null,
864
- scope_files: totalFiles,
865
- note: `scope exceeds the ${DEEP_REVIEW_FILE_CAP}-file review cap \u2014 narrow it with --subsystem <path> to a specific directory.`
866
- }
867
- };
868
- }
869
- const { candidates: initialCandidates } = enumerateDeepReviewCandidates(
870
- scopeRoot,
871
- { collectScopeFiles, countScopeFilesUpTo },
872
- {
873
- maxCandidates,
874
- fileCap: DEEP_REVIEW_FILE_CAP,
875
- ...isEvmProfile ? { exclude: (p) => isNonProtocolEvmPath(p, scopeRoot) } : {}
876
- }
877
- );
878
- const candidatePaths = initialCandidates.slice();
879
- if (lanes && lanes.length > 0 && allocateAcrossLanes) {
880
- const allFiles = collectScopeFiles(scopeRoot, { maxFiles: DEEP_REVIEW_FILE_CAP });
881
- const exclude = isEvmProfile ? (p) => isNonProtocolEvmPath(p, scopeRoot) : () => false;
882
- const entries = allFiles.filter((p) => !exclude(p)).map((p) => {
883
- try {
884
- return { p, size: statSync(p).size };
885
- } catch {
886
- return { p, size: 0 };
887
- }
888
- });
889
- const laneCandidates = allocateAcrossLanes(entries, lanes, scopeRoot, maxCandidates, log);
890
- if (laneCandidates.length > 0) {
891
- const prevModules = new Set(initialCandidates.map((p) => relative(scopeRoot, p).split(/[/\\]+/)[0]));
892
- const laneModules = new Set(laneCandidates.map((p) => relative(scopeRoot, p).split(/[/\\]+/)[0]));
893
- log(`[deep-review] threat-model re-allocated ${initialCandidates.length} \u2192 ${laneCandidates.length} candidate(s); modules ${prevModules.size} \u2192 ${laneModules.size}`);
894
- candidatePaths.length = 0;
895
- candidatePaths.push(...laneCandidates);
896
- }
897
- }
898
- if (candidatePaths.length === 0) {
899
- return {
900
- exitCode: 2,
901
- result: {
902
- mode: "deep_review",
903
- source: sourceRoot,
904
- subsystem: opts.subsystem ?? null,
905
- scope_files: totalFiles,
906
- candidates: 0,
907
- note: "no reviewable source files found under the scope (empty tree or all filtered)."
908
- }
909
- };
910
- }
911
- const { finderLenses: selectedFinderLenses, verifyLenses, matchedProfile } = selectProfileLenses(opts.profile, {
912
- evmFinderLenses,
913
- evmVerifyLenses,
914
- solanaFinderLenses,
915
- solanaVerifyLenses,
916
- cardanoFinderLenses,
917
- cardanoVerifyLenses,
918
- cairoFinderLenses,
919
- cairoVerifyLenses,
920
- moveFinderLenses,
921
- moveVerifyLenses
922
- }, defaultFinderSnapshot);
923
- const finderLenses = matchedProfile === "default" ? selectDefaultFinderLensesForStack(candidatePaths, selectedFinderLenses) : selectedFinderLenses;
924
- const lensDigest = lensSetDigest(finderLenses);
925
- const scanId = randomUUID();
926
- let sourceRevisionDigest = null;
927
- let sourceFingerprintWarning;
928
- try {
929
- const files = [...candidatePaths].sort().map((path) => ({
930
- path: relative(sourceRoot, path),
931
- digest: createHash("sha256").update(readFileSync2(path)).digest("hex")
932
- }));
933
- sourceRevisionDigest = `sha256:${createHash("sha256").update(JSON.stringify(files)).digest("hex")}`;
934
- } catch (error) {
935
- sourceFingerprintWarning = `learning feedback skipped: reviewed inputs could not be fingerprinted (${error instanceof Error ? error.message : String(error)})`;
936
- log(`[deep-review] ${sourceFingerprintWarning}`);
937
- }
938
- log(
939
- `[deep-review] ${candidatePaths.length} candidate file(s) (of ${totalFiles}${overCap ? "+" : ""}) \xD7 ${finderLenses.length} finder-lens(es); verify quorum over ${verifyLenses.length} lens(es); profile=${matchedProfile}; lens-snapshot=${lensDigest.slice(0, 12)}`
940
- );
941
- const verify = makeMultiLensVerifier(verifyLenses, {
942
- sourceRoot,
943
- runtime,
944
- ...!evolvedFinder && models?.[0] ? { model: models[0] } : {},
945
- // Every verify lens is a refute pass on `models[0]` — the same model the
946
- // finders ran on. Passing the full finder set lets the cross-family
947
- // selector move the refutation off ALL finder families when a second
948
- // provider is configured (#661), and `log` records what it decided —
949
- // including "it could not", which is the case worth knowing about.
950
- ...opts.costCeilingUsd !== void 0 ? { costCeilingUsd: opts.costCeilingUsd } : {},
951
- costLedger,
952
- ...!evolvedFinder && models && models.length > 0 ? { finderModels: models } : {},
953
- ...opts.quorum ? { quorum: opts.quorum } : {},
954
- log
955
- });
956
- const postedIds = /* @__PURE__ */ new Set();
957
- let ingested = 0;
958
- const provenance = `deep_review (${matchedProfile} lenses)`;
959
- const postLead = async (lead) => {
960
- if (!sinkCfg || postedIds.has(lead.id)) return;
961
- postedIds.add(lead.id);
962
- await postFinding(leadToCandidateFinding(lead, `deep-review:${matchedProfile}`, provenance), sinkCfg);
963
- ingested++;
964
- };
965
- const res = await runHuntScan({
966
- sourceRoot,
967
- candidates: candidatePaths.map((path) => ({ path })),
968
- lenses: finderLenses,
969
- runtime,
970
- ...evolvedFinder ? { finder: (input) => evolvedFinder.find(sourceRoot, input) } : {},
971
- concurrency: opts.concurrency ?? DEFAULT_CONCURRENCY,
972
- ...opts.costCeilingUsd !== void 0 ? { costCeilingUsd: opts.costCeilingUsd } : {},
973
- costLedger,
974
- attemptsPerCandidate,
975
- ...models ? { models } : {},
976
- verify,
977
- ...sinkCfg ? { onConfirmed: postLead } : {},
978
- log
979
- });
980
- const leads = res.confirmed;
981
- if (sourceFingerprintWarning) res.warnings.push(sourceFingerprintWarning);
982
- if (sourceRevisionDigest) {
983
- try {
984
- for (const gap of res.incompleteCoverage ?? []) {
985
- captureObservation({
986
- classHint: gap.lensId || "generic-finder-timeout",
987
- sinkPattern: "",
988
- exampleFileLine: gap.file,
989
- whyMissed: `finder ${gap.reason}; budget ${gap.budgetMs}ms`,
990
- source: "incomplete-coverage",
991
- scanId,
992
- sourceRevisionDigest,
993
- consent: false,
994
- detectorLensId: gap.lensId || void 0,
995
- detectorLensVersionDigest: gap.lensVersionDigest
996
- });
997
- }
998
- for (const dropped of res.dropped ?? []) {
999
- if (dropped.dropReason === "verify_refuted" || dropped.dropReason === "novelty_duplicate" || dropped.dropReason === "judge_truncation" || dropped.dropReason === "late_resolution") continue;
1000
- captureObservation({
1001
- classHint: dropped.lensId || "unknown-lens",
1002
- sinkPattern: "",
1003
- exampleFileLine: dropped.candidatePath,
1004
- whyMissed: `dropped: ${dropped.dropReason}`,
1005
- source: "incomplete-coverage",
1006
- scanId,
1007
- sourceRevisionDigest,
1008
- consent: false,
1009
- detectorLensId: dropped.lensId || void 0,
1010
- detectorLensVersionDigest: dropped.lensVersionDigest
1011
- });
1012
- }
1013
- } catch (error) {
1014
- const warning = `learning feedback was not persisted: ${error instanceof Error ? error.message : String(error)}`;
1015
- res.warnings.push(warning);
1016
- log(`[deep-review] ${warning}`);
1017
- }
1018
- }
1019
- if (sinkCfg) {
1020
- for (const lead of leads) await postLead(lead);
1021
- log(`[deep-review] posted ${ingested} lead(s) to the cloud-sink as candidate findings (incremental)`);
1022
- }
1023
- const sweptNothing = res.scanned > 0 && typeof res.finderCompleted === "number" && res.finderCompleted === 0;
1024
- const cost = costLedger.costBreakdown();
1025
- eventBus.emit("scan_completed", {
1026
- exit_reason: sweptNothing ? "failed" : res.costCeilingExceeded ? "cost_exceeded" : "completed",
1027
- findings: res.findings.length,
1028
- findings_count: res.findings.length,
1029
- duration_ms: Date.now() - scanStartedAt,
1030
- summary: res.costCeilingExceeded ? `Deep review stopped at the $${opts.costCeilingUsd?.toFixed(2) ?? "configured"} cost ceiling.` : `Deep review completed: ${res.scanned} finder runs, ${res.findings.length} findings, ${leads.length} leads.`,
1031
- ...cost ? { cost_usd: cost.costUsd, cost_breakdown: cost.breakdown } : {}
1032
- });
1033
- if (sweptNothing) {
1034
- return {
1035
- exitCode: 3,
1036
- result: {
1037
- mode: "deep_review",
1038
- profile: matchedProfile,
1039
- scan_id: scanId,
1040
- source_revision_digest: sourceRevisionDigest,
1041
- lens_snapshot_digest: lensDigest,
1042
- ...evolvedFinder ? { evolution_version_id: evolvedFinder.versionId, evolution_compute_cost_usd: evolvedFinder.computeCostUsd } : {},
1043
- source: sourceRoot,
1044
- subsystem: opts.subsystem ?? null,
1045
- scope_files: totalFiles,
1046
- candidates: candidatePaths.length,
1047
- scanned: res.scanned,
1048
- finder_completed: res.finderCompleted,
1049
- finder_timed_out: res.finderTimedOut ?? null,
1050
- finder_errored: res.finderErrored ?? null,
1051
- error: `every finder run failed (0 of ${res.scanned} completed) \u2014 no coverage was produced; treating as a backend/LLM failure, not a clean 0-finding result.`,
1052
- warnings: res.warnings.slice(0, 10)
1053
- }
1054
- };
1055
- }
1056
- const completedAt = /* @__PURE__ */ new Date();
1057
- const candidateFindings = leads.map((lead) => ({
1058
- ...lead,
1059
- status: "discovered"
1060
- }));
1061
- const report = {
1062
- target: opts.target,
1063
- scanDepth: "deep",
1064
- startedAt: new Date(scanStartedAt).toISOString(),
1065
- completedAt: completedAt.toISOString(),
1066
- durationMs: completedAt.getTime() - scanStartedAt,
1067
- summary: {
1068
- totalAttacks: res.scanned,
1069
- totalFindings: candidateFindings.length,
1070
- critical: candidateFindings.filter((finding) => finding.severity === "critical").length,
1071
- high: candidateFindings.filter((finding) => finding.severity === "high").length,
1072
- medium: candidateFindings.filter((finding) => finding.severity === "medium").length,
1073
- low: candidateFindings.filter((finding) => finding.severity === "low").length,
1074
- info: candidateFindings.filter((finding) => finding.severity === "info").length
1075
- },
1076
- findings: candidateFindings,
1077
- warnings: res.warnings.slice(0, 10).map((message) => ({
1078
- stage: "attack",
1079
- message
1080
- })),
1081
- ...res.costCeilingExceeded ? { costCeilingExceeded: true, exitReason: "cost_ceiling_exceeded" } : {}
1082
- };
1083
- return {
1084
- exitCode: 0,
1085
- report,
1086
- result: {
1087
- mode: "deep_review",
1088
- profile: matchedProfile,
1089
- scan_id: scanId,
1090
- source_revision_digest: sourceRevisionDigest,
1091
- source: sourceRoot,
1092
- subsystem: opts.subsystem ?? null,
1093
- scope_files: totalFiles,
1094
- lens_snapshot_digest: lensDigest,
1095
- ...evolvedFinder ? { evolution_version_id: evolvedFinder.versionId, evolution_compute_cost_usd: evolvedFinder.computeCostUsd } : {},
1096
- candidates: candidatePaths.length,
1097
- finder_lenses: finderLenses.map((l) => l.id),
1098
- verify_lenses: verifyLenses.map((l) => l.id),
1099
- scanned: res.scanned,
1100
- findings: res.findings.length,
1101
- confirmed: leads.length,
1102
- leads: leads.map((f) => ({
1103
- title: f.title,
1104
- severity: f.severity,
1105
- analysis: f.evidence.analysis ?? ""
1106
- })),
1107
- dropped: res.dropped.map((d) => ({
1108
- title: d.finding.title,
1109
- severity: d.finding.severity,
1110
- candidatePath: d.candidatePath,
1111
- lensId: d.lensId,
1112
- dropReason: d.dropReason,
1113
- detail: d.detail
1114
- })),
1115
- ingested: sinkCfg ? ingested : null,
1116
- warnings: res.warnings.slice(0, 10),
1117
- note: "LEADS, not confirmed bugs. Each survived the multi-lens refute quorum; verify the real sink + impact before disclosure."
1118
- }
1119
- };
1120
- } catch (error) {
1121
- const cost = costLedger.costBreakdown();
1122
- eventBus.emit("scan_completed", {
1123
- exit_reason: "failed",
1124
- findings: 0,
1125
- findings_count: 0,
1126
- duration_ms: Date.now() - scanStartedAt,
1127
- summary: `Deep review failed: ${error instanceof Error ? error.message : String(error)}`,
1128
- ...cost ? { cost_usd: cost.costUsd, cost_breakdown: cost.breakdown } : {}
1129
- });
1130
- throw error;
1131
- } finally {
1132
- if (savedCloudSink !== void 0) process.env["0SEC_CLOUD_SINK"] = savedCloudSink;
1133
- prepared.cleanup();
1134
- }
1135
- }
1136
- function parsePositive2(flag, raw, dflt) {
1137
- if (raw === void 0) return dflt;
1138
- const n = parseInt(raw, 10);
1139
- if (!Number.isFinite(n) || n <= 0) throw new Error(`invalid ${flag} '${raw}' (expected positive integer)`);
1140
- return n;
1141
- }
1142
- async function deepReviewAction(target, opts) {
1143
- if (!target || target.trim() === "") throw new Error("missing required argument: <target> (source tree path or git URL)");
1144
- const ceilingSource = opts.costCeiling ?? process.env["0SEC_COST_CEILING_USD"];
1145
- let costCeilingUsd;
1146
- if (ceilingSource !== void 0 && ceilingSource !== "") {
1147
- const parsed = Number(ceilingSource);
1148
- if (!Number.isFinite(parsed) || parsed <= 0) {
1149
- throw new Error(`invalid --cost-ceiling '${ceilingSource}' (expected positive USD amount)`);
1150
- }
1151
- costCeilingUsd = parsed;
1152
- }
1153
- const { writeFileSync: writeFileSync3 } = await import("node:fs");
1154
- const outcome = await runDeepReview({
1155
- target,
1156
- ...costCeilingUsd !== void 0 ? { costCeilingUsd } : {},
1157
- ...opts.profile ? { profile: opts.profile } : {},
1158
- ...opts.subsystem ? { subsystem: opts.subsystem } : {},
1159
- // Flag > env > single provider-default model. Only pass `models` when the
1160
- // flag is set; otherwise let runDeepReview resolve $0SEC_DEEP_REVIEW_MODELS.
1161
- ...opts.models ? { models: opts.models.split(",").map((s) => s.trim()).filter(Boolean) } : {},
1162
- ...opts.attempts ? { attemptsPerCandidate: parsePositive2("--attempts", opts.attempts, defaultAttempts()) } : {},
1163
- concurrency: parsePositive2("--concurrency", opts.concurrency, DEFAULT_CONCURRENCY),
1164
- maxCandidates: parsePositive2("--max-candidates", opts.maxCandidates, defaultMaxCandidates()),
1165
- ...opts.quorum ? { quorum: parsePositive2("--quorum", opts.quorum, 1) } : {},
1166
- ...opts.threatModel ? { useThreatModel: true } : {},
1167
- ...opts.runtime ? { runtime: opts.runtime } : {},
1168
- ...opts.evolutionConfig ? { evolutionConfig: loadEvolutionConfigFile(opts.evolutionConfig) } : {},
1169
- timeoutMs: parsePositive2("--timeout", opts.timeout, 6e5),
1170
- log: (m) => process.stderr.write(m + "\n")
1171
- });
1172
- if (outcome.report) {
1173
- const storage = resolveOsecRunStorage();
1174
- writeOsecRunReport(storage, outcome.report);
1175
- }
1176
- const json = JSON.stringify(outcome.result, null, 2);
1177
- if (opts.output) writeFileSync3(resolve2(opts.output), json + "\n", "utf8");
1178
- else process.stdout.write(json + "\n");
1179
- process.exitCode = outcome.exitCode;
1180
- }
1181
- function registerDeepReviewCommand(program) {
1182
- program.command("deep-review").description(
1183
- "Seedless DEPTH review of a source tree: enumerate candidate files, re-hunt each through the profile's specialized finder lenses, and gate survivors through the multi-lens verify quorum. Emits LEADS to verify (not confirmed bugs). Exit 0=sweep completed (with or without leads), 2=skipped (no files / over the review cap), 3=error (bad flags / unreadable target / all finders failed)."
1184
- ).argument("<target>", "Source tree to review (a local path or a git URL)").option("--profile <p>", "Lens profile: evm-onchain | solana-onchain | cardano-onchain | cairo-onchain | move-onchain (else a generic default lens set)").option("--subsystem <path>", "Narrow the review scope to a subdirectory (respects the 5000-file review cap)").option("--evolution-config <path>", "Use the active source finder with private local execution receipts").option("--models <a,b>", "Comma-separated finder models for diversity (default: single provider model, or $0SEC_DEEP_REVIEW_MODELS)").option("--attempts <N>", `Finder attempts per candidate\xD7lens\xD7model, best-of-N (default ${DEFAULT_ATTEMPTS}, or $0SEC_DEEP_REVIEW_ATTEMPTS)`).option("--concurrency <N>", `Max finders in flight (default ${DEFAULT_CONCURRENCY})`).option("--cost-ceiling <usd>", "Hard scan-wide USD ceiling. Overrides $0SEC_COST_CEILING_USD.").option("--max-candidates <N>", `Cap candidate files hunted, largest-first (default ${DEFAULT_MAX_CANDIDATES}, or $0SEC_DEEP_REVIEW_MAX_CANDIDATES)`).option("--threat-model", "Enable pre-selection threat-model planner pass (trust-boundary lanes); default OFF").option("--quorum <N>", "Multi-lens verify quorum (default: majority of the verify-lens count)").option("--format <fmt>", "Output format (json)", "json").option("--output <path>", "Write the result JSON to this path instead of stdout").option("--runtime <mode>", "Engine runtime (default api)").option("--timeout <ms>", "Cloud agent timeout budget in milliseconds", "600000").action(async (target, opts) => {
1185
- try {
1186
- await deepReviewAction(target, opts);
1187
- } catch (err) {
1188
- const reason = err instanceof Error ? err.message : String(err);
1189
- const json = JSON.stringify({ mode: "deep_review", error: reason }, null, 2);
1190
- if (opts.output) {
1191
- const { writeFileSync: writeFileSync3 } = await import("node:fs");
1192
- try {
1193
- writeFileSync3(resolve2(opts.output), json + "\n", "utf8");
1194
- } catch {
1195
- process.stderr.write(json + "\n");
1196
- }
1197
- } else process.stdout.write(json + "\n");
1198
- process.exitCode = 3;
1199
- }
1200
- });
109
+ return `https://0.security/r#${b64}`;
1201
110
  }
1202
111
 
1203
112
  // packages/cli/src/finding-handoff.ts
@@ -1285,7 +194,7 @@ function shellQuote(value) {
1285
194
  }
1286
195
  function buildFindingConsoleCommand(finding, dbPath, intent = "investigate") {
1287
196
  const args = [
1288
- "0sec console",
197
+ "0 console",
1289
198
  "--finding",
1290
199
  shellQuote(finding.id),
1291
200
  "--finding-intent",
@@ -1296,7 +205,7 @@ function buildFindingConsoleCommand(finding, dbPath, intent = "investigate") {
1296
205
  }
1297
206
 
1298
207
  // packages/cli/src/finding-focus.ts
1299
- import { resolve as resolve3 } from "node:path";
208
+ import { resolve } from "node:path";
1300
209
  import { existsSync } from "node:fs";
1301
210
  function parseJson(value) {
1302
211
  if (value == null) return void 0;
@@ -1308,11 +217,11 @@ function parseJson(value) {
1308
217
  }
1309
218
  }
1310
219
  function databasePaths(dbPath) {
1311
- if (dbPath?.trim()) return [resolve3(dbPath)];
220
+ if (dbPath?.trim()) return [resolve(dbPath)];
1312
221
  return [...new Set([
1313
222
  ...listOsecRunDatabasePaths(),
1314
223
  resolveOsecDbPath()
1315
- ].map((path) => resolve3(path)))];
224
+ ].map((path) => resolve(path)))];
1316
225
  }
1317
226
  function findingFromRow(row, reviewFields) {
1318
227
  const id = typeof row.id === "string" ? row.id : "";
@@ -1408,9 +317,9 @@ function loadFindingFocus(id, options = {}) {
1408
317
  }
1409
318
 
1410
319
  // packages/cli/src/commands/lens-synth.ts
1411
- import { createHash as createHash2 } from "node:crypto";
1412
- import { readFileSync as readFileSync3 } from "node:fs";
1413
- import { resolve as resolve4 } from "node:path";
320
+ import { createHash } from "node:crypto";
321
+ import { readFileSync } from "node:fs";
322
+ import { resolve as resolve2 } from "node:path";
1414
323
  function isRecord(value) {
1415
324
  return typeof value === "object" && value !== null && !Array.isArray(value);
1416
325
  }
@@ -1466,10 +375,10 @@ function parseMissInputFile(raw) {
1466
375
  return { misses, corpus };
1467
376
  }
1468
377
  async function harvestBenchMisses(fromBench, manifestPath) {
1469
- const ledger = await loadLedger(resolve4(fromBench));
378
+ const ledger = await loadLedger(resolve2(fromBench));
1470
379
  const entry = lastGreen(ledger) ?? ledger.entries[ledger.entries.length - 1];
1471
380
  if (!entry) throw new Error(`--from-bench: ledger ${fromBench} has no entries to harvest`);
1472
- const manifest = await loadManifest(resolve4(manifestPath));
381
+ const manifest = await loadManifest(resolve2(manifestPath));
1473
382
  const result = harvestMissesFromLedgerEntry(entry, manifest);
1474
383
  return { misses: result.misses, result };
1475
384
  }
@@ -1487,7 +396,7 @@ async function runLensSynthesisInput(input, opts, deps = {}) {
1487
396
  ...deps.model ? { model: deps.model } : {},
1488
397
  ...opts.model ? { modelId: opts.model } : {},
1489
398
  probe,
1490
- ...opts.registry ? { registryPath: resolve4(opts.registry) } : {},
399
+ ...opts.registry ? { registryPath: resolve2(opts.registry) } : {},
1491
400
  maxRegistrations: opts.maxRegister ?? 1,
1492
401
  dryRun: !opts.promote,
1493
402
  trials,
@@ -1496,7 +405,7 @@ async function runLensSynthesisInput(input, opts, deps = {}) {
1496
405
  });
1497
406
  }
1498
407
  async function runLensSynthCommand(opts, deps = {}) {
1499
- const raw = JSON.parse(readFileSync3(resolve4(opts.missInput), "utf8"));
408
+ const raw = JSON.parse(readFileSync(resolve2(opts.missInput), "utf8"));
1500
409
  let input = parseMissInputFile(raw);
1501
410
  if (opts.fromBench) {
1502
411
  if (!opts.manifest) throw new Error("--from-bench requires --manifest");
@@ -1507,7 +416,7 @@ async function runLensSynthCommand(opts, deps = {}) {
1507
416
  return runLensSynthesisInput(input, opts, deps);
1508
417
  }
1509
418
  async function watchLensSynthCommand(opts, deps = {}) {
1510
- const inputPath = resolve4(opts.missInput);
419
+ const inputPath = resolve2(opts.missInput);
1511
420
  const log = deps.log ?? (() => {
1512
421
  });
1513
422
  const requestedInterval = opts.pollIntervalMs ?? 2e3;
@@ -1516,8 +425,8 @@ async function watchLensSynthCommand(opts, deps = {}) {
1516
425
  let previousRevision;
1517
426
  while (!deps.signal?.aborted) {
1518
427
  try {
1519
- const bytes = readFileSync3(inputPath);
1520
- const revision = createHash2("sha256").update(bytes).digest("hex");
428
+ const bytes = readFileSync(inputPath);
429
+ const revision = createHash("sha256").update(bytes).digest("hex");
1521
430
  if (revision !== previousRevision) {
1522
431
  previousRevision = revision;
1523
432
  const input = parseMissInputFile(JSON.parse(bytes.toString("utf8")));
@@ -1584,9 +493,9 @@ function formatLensRegistryStatus(status) {
1584
493
  return lines.join("\n");
1585
494
  }
1586
495
  function registerLensSynthCommand(program) {
1587
- program.command("lens-synth").description("Evolve appsec finder coverage from curated misses; promotion is corpus-gated and active reviews stay pinned").option("--miss-input <path>", "curated miss-input JSON ({ misses, corpus })").option("--registry <path>", "durable overlay path (default: ~/.0sec/lenses/appsec-archetypes.json)").option("--max-register <n>", "cap promoted champions per input revision", (value) => Number.parseInt(value, 10)).option("-m, --model <id>", "synthesis model override").option("--promote", "persist a validated champion to the durable overlay", false).option("--trials <n>", "repeated validation trials (2\u201310; default 2)", Number).option("--from-bench <ledger>", "harvest the champion's false-negatives from a benchmark ledger into the curated misses (requires --manifest)").option("--manifest <path>", "bench manifest path (ground-truth vuln class + sink); required with --from-bench").option("--watch", "poll the miss-input and process each new content revision", false).option("--poll-interval <ms>", "watch polling interval (minimum 100ms)", "2000").option("--status", "show the active durable overlay and promotion ledger", false).option("--rollback <lens-id>", "retire one previously promoted overlay lens").option("--json", "print machine-readable output", false).action(async (opts) => {
496
+ program.command("lens-synth").description("Evolve appsec finder coverage from curated misses; promotion is corpus-gated and active reviews stay pinned").option("--miss-input <path>", "curated miss-input JSON ({ misses, corpus })").option("--registry <path>", "durable overlay path (default: ~/.0/lenses/appsec-archetypes.json)").option("--max-register <n>", "cap promoted champions per input revision", (value) => Number.parseInt(value, 10)).option("-m, --model <id>", "synthesis model override").option("--promote", "persist a validated champion to the durable overlay", false).option("--trials <n>", "repeated validation trials (2\u201310; default 2)", Number).option("--from-bench <ledger>", "harvest the champion's false-negatives from a benchmark ledger into the curated misses (requires --manifest)").option("--manifest <path>", "bench manifest path (ground-truth vuln class + sink); required with --from-bench").option("--watch", "poll the miss-input and process each new content revision", false).option("--poll-interval <ms>", "watch polling interval (minimum 100ms)", "2000").option("--status", "show the active durable overlay and promotion ledger", false).option("--rollback <lens-id>", "retire one previously promoted overlay lens").option("--json", "print machine-readable output", false).action(async (opts) => {
1588
497
  try {
1589
- const registry = opts.registry ? resolve4(opts.registry) : void 0;
498
+ const registry = opts.registry ? resolve2(opts.registry) : void 0;
1590
499
  if (opts.status && opts.rollback) throw new Error("--status and --rollback cannot be combined");
1591
500
  if (opts.status) {
1592
501
  const status = inspectLensRegistry(registry);
@@ -1625,7 +534,7 @@ function registerLensSynthCommand(program) {
1625
534
  promote: Boolean(opts.promote),
1626
535
  trials: trialCount(opts.trials),
1627
536
  ...opts.fromBench ? { fromBench: String(opts.fromBench) } : {},
1628
- ...opts.manifest ? { manifest: resolve4(String(opts.manifest)) } : {}
537
+ ...opts.manifest ? { manifest: resolve2(String(opts.manifest)) } : {}
1629
538
  };
1630
539
  const printResult = (result) => {
1631
540
  process.stdout.write(
@@ -1671,9 +580,9 @@ function registerLensSynthCommand(program) {
1671
580
 
1672
581
  // packages/cli/src/commands/auth.ts
1673
582
  import { spawn } from "node:child_process";
1674
- import { mkdirSync, writeFileSync as writeFileSync2, chmodSync, unlinkSync, existsSync as existsSync2 } from "node:fs";
583
+ import { mkdirSync, writeFileSync, chmodSync, unlinkSync, existsSync as existsSync2 } from "node:fs";
1675
584
  import { homedir, platform } from "node:os";
1676
- import { join as join2 } from "node:path";
585
+ import { join } from "node:path";
1677
586
  import { randomBytes } from "node:crypto";
1678
587
  import { setTimeout as delay } from "node:timers/promises";
1679
588
  var EXIT_OK = 0;
@@ -1681,19 +590,19 @@ var EXIT_USER_ERROR = 1;
1681
590
  var EXIT_AUTH = 2;
1682
591
  var EXIT_NET = 3;
1683
592
  function registerAuthCommand(program) {
1684
- const auth = program.command("auth").description("0sec-cloud authentication");
1685
- auth.command("login").description("Log in to 0sec-cloud (opens browser; --token to paste directly)").option("--host <url>", `Cloud host (default ${DEFAULT_CLOUD_HOST})`).option("--token <value>", "Skip the browser flow and persist this token directly").action(async (opts) => {
593
+ const auth = program.command("auth").description("0-cloud authentication");
594
+ auth.command("login").description("Log in to 0-cloud (opens browser; --token to paste directly)").option("--host <url>", "Cloud host (defaults to ZERO_CLOUD_HOST or production)").option("--token <value>", "Skip the browser flow and persist this token directly").action(async (opts) => {
1686
595
  await runLogin(opts);
1687
596
  });
1688
- auth.command("logout").description("Delete ~/.0sec/cloud.env").action(() => {
597
+ auth.command("logout").description("Delete credentials for the current Cloud profile").action(() => {
1689
598
  runLogout({});
1690
599
  });
1691
- auth.command("status").description("Verify 0sec-cloud credentials against /health").action(async () => {
600
+ auth.command("status").description("Verify 0-cloud credentials and account access").action(async () => {
1692
601
  await runStatus({});
1693
602
  });
1694
603
  }
1695
604
  async function hostedBrowserLoginFlow(opts = {}) {
1696
- const host = normaliseHostArg(opts.host ?? DEFAULT_CLOUD_HOST);
605
+ const host = normaliseHostArg(opts.host ?? process.env["ZERO_CLOUD_HOST"] ?? DEFAULT_CLOUD_HOST);
1697
606
  if (host === null) {
1698
607
  return { ok: false, error: "Cloud host must be an http(s) URL without credentials, query or fragment." };
1699
608
  }
@@ -1718,7 +627,7 @@ async function hostedBrowserLoginFlow(opts = {}) {
1718
627
  return { ok: false, error: "Sign-in timed out. Try again.", recoverable: true };
1719
628
  };
1720
629
  try {
1721
- opts.onStatus?.("opening", "Opening browser for 0sec Cloud sign-in.", loginUrl);
630
+ opts.onStatus?.("opening", "Opening browser for 0cloud sign-in.", loginUrl);
1722
631
  signal.throwIfAborted();
1723
632
  try {
1724
633
  await awaitLoginStep(Promise.resolve().then(() => {
@@ -1776,9 +685,9 @@ async function hostedBrowserLoginFlow(opts = {}) {
1776
685
  try {
1777
686
  persistCredentials(host, token, opts.homeDir);
1778
687
  } catch {
1779
- return { ok: false, error: "Could not save 0sec Cloud credentials." };
688
+ return { ok: false, error: "Could not save 0cloud credentials." };
1780
689
  }
1781
- opts.onStatus?.("ready", "Signed in to 0sec Cloud.");
690
+ opts.onStatus?.("ready", "Signed in to 0cloud.");
1782
691
  return { ok: true, host };
1783
692
  }
1784
693
  if (status === "pending") continue;
@@ -1789,7 +698,7 @@ async function hostedBrowserLoginFlow(opts = {}) {
1789
698
  if (res.status === 410) return { ok: false, error: "Sign-in request expired. Try again.", recoverable: true };
1790
699
  return {
1791
700
  ok: false,
1792
- error: `0sec Cloud sign-in unavailable (HTTP ${res.status}). Use your own provider or try again later.`,
701
+ error: `0cloud sign-in unavailable (HTTP ${res.status}). Use your own provider or try again later.`,
1793
702
  recoverable: res.status === 429 || res.status >= 500
1794
703
  };
1795
704
  } finally {
@@ -1807,7 +716,7 @@ async function hostedBrowserLoginFlow(opts = {}) {
1807
716
  }
1808
717
  }
1809
718
  async function awaitLoginStep(operation, signal) {
1810
- return new Promise((resolve6, reject) => {
719
+ return new Promise((resolve4, reject) => {
1811
720
  const onAbort = () => {
1812
721
  signal.removeEventListener("abort", onAbort);
1813
722
  reject(signal.reason);
@@ -1815,7 +724,7 @@ async function awaitLoginStep(operation, signal) {
1815
724
  operation.then(
1816
725
  (value) => {
1817
726
  signal.removeEventListener("abort", onAbort);
1818
- resolve6(value);
727
+ resolve4(value);
1819
728
  },
1820
729
  (error) => {
1821
730
  signal.removeEventListener("abort", onAbort);
@@ -1827,7 +736,7 @@ async function awaitLoginStep(operation, signal) {
1827
736
  });
1828
737
  }
1829
738
  async function runLogin(opts) {
1830
- const host = normaliseHostArg(opts.host ?? DEFAULT_CLOUD_HOST);
739
+ const host = normaliseHostArg(opts.host ?? process.env["ZERO_CLOUD_HOST"] ?? DEFAULT_CLOUD_HOST);
1831
740
  if (host === null) {
1832
741
  consolePresentationOutput.stderr(source_default.red("Error: Cloud host must be an http(s) URL without credentials, query or fragment."), "auth.login.host-error");
1833
742
  process.exitCode = EXIT_USER_ERROR;
@@ -1843,7 +752,7 @@ async function runLogin(opts) {
1843
752
  try {
1844
753
  persistCredentials(host, tok, opts.homeDir);
1845
754
  } catch {
1846
- consolePresentationOutput.stderr(source_default.red("Could not save 0sec Cloud credentials."), "auth.login.save-error");
755
+ consolePresentationOutput.stderr(source_default.red("Could not save 0cloud credentials."), "auth.login.save-error");
1847
756
  process.exitCode = EXIT_USER_ERROR;
1848
757
  return;
1849
758
  }
@@ -1882,8 +791,9 @@ async function runLogin(opts) {
1882
791
  }
1883
792
  function runLogout(opts) {
1884
793
  const home = opts.homeDir ?? homedir();
1885
- const osecPath = join2(homeStateDir(opts.homeDir), "cloud.env");
1886
- const cloudCredsPath = join2(home, ".0cloud", "credentials.json");
794
+ const stateDir = cloudStateDir(opts.homeDir);
795
+ const osecPath = join(stateDir, "cloud.env");
796
+ const cloudCredsPath = join(home, ".0cloud", "credentials.json");
1887
797
  let deletedAny = false;
1888
798
  try {
1889
799
  unlinkSync(osecPath);
@@ -1896,15 +806,17 @@ function runLogout(opts) {
1896
806
  return;
1897
807
  }
1898
808
  }
1899
- try {
1900
- unlinkSync(cloudCredsPath);
1901
- deletedAny = true;
1902
- } catch (err) {
1903
- const code = err.code;
1904
- if (code !== "ENOENT") {
1905
- consolePresentationOutput.stderr(source_default.red(`Could not delete ${cloudCredsPath}: ${err instanceof Error ? err.message : String(err)}`), "auth.logout.delete-error");
1906
- process.exitCode = EXIT_USER_ERROR;
1907
- return;
809
+ if (stateDir === homeStateDir(opts.homeDir)) {
810
+ try {
811
+ unlinkSync(cloudCredsPath);
812
+ deletedAny = true;
813
+ } catch (err) {
814
+ const code = err !== null && typeof err === "object" && "code" in err ? err.code : void 0;
815
+ if (code !== "ENOENT") {
816
+ consolePresentationOutput.stderr(source_default.red(`Could not delete ${cloudCredsPath}: ${err instanceof Error ? err.message : String(err)}`), "auth.logout.delete-error");
817
+ process.exitCode = EXIT_USER_ERROR;
818
+ return;
819
+ }
1908
820
  }
1909
821
  }
1910
822
  if (deletedAny) {
@@ -1931,7 +843,7 @@ async function runStatus(opts) {
1931
843
  }
1932
844
  const client = new CloudClient({ ...creds, fetchImpl: opts.fetchImpl });
1933
845
  try {
1934
- await client.pingHealth();
846
+ await client.getInferenceAccount();
1935
847
  consolePresentationOutput.stdout(`OK (host=${creds.host})`, "auth.status.ok");
1936
848
  process.exitCode = EXIT_OK;
1937
849
  } catch (err) {
@@ -1975,23 +887,24 @@ function normaliseHostArg(host) {
1975
887
  function persistCredentials(host, token, homeDirOverride) {
1976
888
  if (!validCloudToken(token)) throw new Error("Invalid Cloud credential");
1977
889
  const home = homeDirOverride ?? homedir();
1978
- const dir = homeStateDir(homeDirOverride);
890
+ const dir = cloudStateDir(homeDirOverride);
1979
891
  mkdirSync(dir, { recursive: true, mode: 448 });
1980
- const path = join2(dir, "cloud.env");
1981
- const body = `# 0sec-cloud credentials. Managed by \`0sec auth\`.
892
+ const path = join(dir, "cloud.env");
893
+ const body = `# 0-cloud credentials. Managed by \`0 auth\`.
1982
894
  # DO NOT commit this file or share its contents.
1983
- 0SEC_CLOUD_HOST=${host}
1984
- 0SEC_CLOUD_TOKEN=${token}
895
+ ZERO_CLOUD_HOST=${host}
896
+ ZERO_CLOUD_TOKEN=${token}
1985
897
  `;
1986
- writeFileSync2(path, body, { mode: 384 });
898
+ writeFileSync(path, body, { mode: 384 });
1987
899
  chmodSync(path, 384);
1988
900
  if (!existsSync2(path)) {
1989
901
  return;
1990
902
  }
903
+ if (dir !== homeStateDir(homeDirOverride)) return;
1991
904
  try {
1992
- const cloudDir = join2(home, ".0cloud");
905
+ const cloudDir = join(home, ".0cloud");
1993
906
  mkdirSync(cloudDir, { recursive: true });
1994
- const cloudCredsPath = join2(cloudDir, "credentials.json");
907
+ const cloudCredsPath = join(cloudDir, "credentials.json");
1995
908
  const trimmedHost = host.replace(/\/+$/, "");
1996
909
  const apiEndpoint = trimmedHost.endsWith("/api") ? trimmedHost : `${trimmedHost}/api`;
1997
910
  const cloudCreds = JSON.stringify(
@@ -2005,7 +918,7 @@ function persistCredentials(host, token, homeDirOverride) {
2005
918
  null,
2006
919
  2
2007
920
  ) + "\n";
2008
- writeFileSync2(cloudCredsPath, cloudCreds, { mode: 384 });
921
+ writeFileSync(cloudCredsPath, cloudCreds, { mode: 384 });
2009
922
  chmodSync(cloudCredsPath, 384);
2010
923
  } catch {
2011
924
  }
@@ -2029,7 +942,7 @@ function extractToken(body) {
2029
942
  }
2030
943
  function defaultOpenBrowser(url) {
2031
944
  const plat = platform();
2032
- return new Promise((resolve6, reject) => {
945
+ return new Promise((resolve4, reject) => {
2033
946
  const options = { detached: true, stdio: "ignore", shell: false };
2034
947
  const child = plat === "darwin" ? spawn("open", [url], options) : plat === "win32" ? spawn("powershell.exe", [
2035
948
  "-NoProfile",
@@ -2040,14 +953,14 @@ function defaultOpenBrowser(url) {
2040
953
  child.once("error", reject);
2041
954
  child.once("spawn", () => {
2042
955
  child.unref();
2043
- resolve6();
956
+ resolve4();
2044
957
  });
2045
958
  });
2046
959
  }
2047
960
 
2048
961
  // packages/cli/src/commands/run.ts
2049
962
  import { writeFile } from "node:fs/promises";
2050
- import { join as join3, resolve as resolve5 } from "node:path";
963
+ import { join as join2, resolve as resolve3 } from "node:path";
2051
964
  import { tmpdir } from "node:os";
2052
965
  import { execFile } from "node:child_process";
2053
966
  import { existsSync as existsSync3 } from "node:fs";
@@ -2177,7 +1090,7 @@ var EVIDENCE_BLOCK_LIMIT = 4e3;
2177
1090
  var POC_STEP_LIMIT = 20;
2178
1091
  function formatMarkdown(report) {
2179
1092
  const lines = [];
2180
- lines.push("# 0sec Scan Report");
1093
+ lines.push("# 0 Scan Report");
2181
1094
  lines.push("");
2182
1095
  lines.push(`| Field | Value |`);
2183
1096
  lines.push(`|-------|-------|`);
@@ -2434,7 +1347,7 @@ function formatHtml(report) {
2434
1347
  <head>
2435
1348
  <meta charset="UTF-8">
2436
1349
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2437
- <title>0sec Report \u2014 ${escapeHtml(report.target)}</title>
1350
+ <title>0 Report \u2014 ${escapeHtml(report.target)}</title>
2438
1351
  <style>
2439
1352
  * { margin: 0; padding: 0; box-sizing: border-box; }
2440
1353
  body {
@@ -2698,7 +1611,7 @@ function formatHtml(report) {
2698
1611
  <div class="header">
2699
1612
  <div class="header-top">
2700
1613
  <div>
2701
- <div class="brand">0SEC</div>
1614
+ <div class="brand">ZERO</div>
2702
1615
  <div class="brand-sub">Security Scan Report</div>
2703
1616
  </div>
2704
1617
  <div class="risk-badge" style="background:${risk.color};color:#fff">${risk.label} RISK</div>
@@ -2739,7 +1652,7 @@ function formatHtml(report) {
2739
1652
  </div>
2740
1653
 
2741
1654
  <div class="footer">
2742
- Generated by 0sec &middot; ${escapeHtml((/* @__PURE__ */ new Date()).toISOString())}
1655
+ Generated by 0 &middot; ${escapeHtml((/* @__PURE__ */ new Date()).toISOString())}
2743
1656
  </div>
2744
1657
  </body>
2745
1658
  </html>`;
@@ -2875,9 +1788,9 @@ function formatSarif(report) {
2875
1788
  {
2876
1789
  tool: {
2877
1790
  driver: {
2878
- name: "0sec",
1791
+ name: "0",
2879
1792
  version: VERSION,
2880
- informationUri: "https://github.com/0sec-labs/0sec",
1793
+ informationUri: "https://github.com/0sec-labs/0",
2881
1794
  rules: Array.from(rulesMap.values())
2882
1795
  }
2883
1796
  },
@@ -2953,13 +1866,13 @@ function truncate(text, maxLen) {
2953
1866
  async function generatePdfReport(report, outputPath) {
2954
1867
  const require_ = createRequire(import.meta.url);
2955
1868
  const PDFDocument = require_("pdfkit");
2956
- return new Promise((resolve6, reject) => {
1869
+ return new Promise((resolve4, reject) => {
2957
1870
  const doc = new PDFDocument({
2958
1871
  size: "LETTER",
2959
1872
  margins: { top: PAGE_MARGIN, bottom: PAGE_MARGIN, left: PAGE_MARGIN, right: PAGE_MARGIN },
2960
1873
  info: {
2961
- Title: `0sec Pentest Report - ${report.target}`,
2962
- Author: "0sec",
1874
+ Title: `0 Pentest Report - ${report.target}`,
1875
+ Author: "0",
2963
1876
  Subject: "Automated Penetration Test Report",
2964
1877
  CreationDate: /* @__PURE__ */ new Date()
2965
1878
  },
@@ -2967,7 +1880,7 @@ async function generatePdfReport(report, outputPath) {
2967
1880
  });
2968
1881
  const stream = createWriteStream(outputPath);
2969
1882
  stream.on("error", reject);
2970
- stream.on("finish", resolve6);
1883
+ stream.on("finish", resolve4);
2971
1884
  doc.pipe(stream);
2972
1885
  renderCoverPage(doc, report);
2973
1886
  renderExecutiveSummary(doc, report);
@@ -2980,7 +1893,7 @@ async function generatePdfReport(report, outputPath) {
2980
1893
  }
2981
1894
  function renderCoverPage(doc, report) {
2982
1895
  doc.rect(0, 0, 612, 792).fill(COLORS.primary);
2983
- doc.fontSize(48).font("Helvetica-Bold").fillColor(COLORS.accent).text("0sec", PAGE_MARGIN, 200, { align: "center" });
1896
+ doc.fontSize(48).font("Helvetica-Bold").fillColor(COLORS.accent).text("0", PAGE_MARGIN, 200, { align: "center" });
2984
1897
  doc.fontSize(14).font("Helvetica").fillColor(COLORS.white).text("Automated Penetration Test Report", PAGE_MARGIN, 260, { align: "center" });
2985
1898
  doc.moveTo(200, 300).lineTo(412, 300).strokeColor(COLORS.accent).lineWidth(2).stroke();
2986
1899
  doc.fontSize(18).font("Helvetica-Bold").fillColor(COLORS.white).text(report.target, PAGE_MARGIN, 330, { align: "center" });
@@ -3107,7 +2020,7 @@ function renderMethodology(doc, report) {
3107
2020
  doc.addPage();
3108
2021
  sectionTitle(doc, "Methodology");
3109
2022
  doc.fontSize(10).font("Helvetica").fillColor(COLORS.text).text(
3110
- "This report was generated by 0sec, a fully autonomous agentic pentesting framework. The tool uses AI-driven attack generation and multi-agent consensus verification to identify security vulnerabilities.",
2023
+ "This report was generated by 0, a fully autonomous agentic pentesting framework. The tool uses AI-driven attack generation and multi-agent consensus verification to identify security vulnerabilities.",
3111
2024
  PAGE_MARGIN,
3112
2025
  doc.y + 10,
3113
2026
  { width: CONTENT_WIDTH }
@@ -3184,7 +2097,7 @@ function renderFooters(doc) {
3184
2097
  doc.switchToPage(i);
3185
2098
  if (i === 0) continue;
3186
2099
  doc.fontSize(8).font("Helvetica").fillColor(COLORS.textLight).text(
3187
- `0sec Pentest Report | Page ${i + 1} of ${pageCount} | CONFIDENTIAL`,
2100
+ `0 Pentest Report | Page ${i + 1} of ${pageCount} | CONFIDENTIAL`,
3188
2101
  PAGE_MARGIN,
3189
2102
  760,
3190
2103
  { width: CONTENT_WIDTH, align: "center" }
@@ -3194,7 +2107,7 @@ function renderFooters(doc) {
3194
2107
 
3195
2108
  // packages/cli/src/formatters/replay.ts
3196
2109
  function sleep(ms) {
3197
- return new Promise((resolve6) => setTimeout(resolve6, ms));
2110
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
3198
2111
  }
3199
2112
  function stripAnsi(s) {
3200
2113
  return s.replace(/\x1b\[[0-9;]*m/g, "");
@@ -3383,7 +2296,7 @@ function buildReplayLines(data) {
3383
2296
  const duration = data.durationMs < 1e3 ? `${data.durationMs}ms` : `${(data.durationMs / 1e3).toFixed(1)}s`;
3384
2297
  lines.push({
3385
2298
  text: boxRow(
3386
- source_default.white(`Completed in ${duration}`) + source_default.dim(" \u2192 ") + source_default.gray("./0sec-report.json")
2299
+ source_default.white(`Completed in ${duration}`) + source_default.dim(" \u2192 ") + source_default.gray("./0-report.json")
3387
2300
  ),
3388
2301
  delay: MED
3389
2302
  });
@@ -3394,7 +2307,7 @@ async function renderReplay(data) {
3394
2307
  const lines = buildReplayLines(data);
3395
2308
  process.stdout.write("\n");
3396
2309
  process.stdout.write(
3397
- source_default.red.bold(" \u25C6 0sec") + source_default.gray(" attack replay") + "\n"
2310
+ source_default.red.bold(" \u25C6 0") + source_default.gray(" attack replay") + "\n"
3398
2311
  );
3399
2312
  process.stdout.write(
3400
2313
  source_default.dim(" " + "\u2500".repeat(BOX_WIDTH + 1)) + "\n"
@@ -3620,7 +2533,7 @@ async function loadCoreModule() {
3620
2533
  if (!coreModulePromise) {
3621
2534
  const srcUrl = new URL("../../../core/src/index.ts", import.meta.url);
3622
2535
  const srcExists = process.versions.bun && existsSync3(fileURLToPath(srcUrl));
3623
- coreModulePromise = srcExists ? import(srcUrl.href) : import("./dist-4YICS2J2.js");
2536
+ coreModulePromise = srcExists ? import(srcUrl.href) : import("./dist-F6X62TQK.js");
3624
2537
  }
3625
2538
  return coreModulePromise;
3626
2539
  }
@@ -3664,15 +2577,15 @@ function getTargetType(report, opts) {
3664
2577
  return report?.targetType ?? opts.targetType;
3665
2578
  }
3666
2579
  function emitResultLine(payload) {
3667
- if (process.env["0SEC_EMIT_RESULT_LINE"] !== "1" && !process.env["0SEC_CLOUD_SINK"]) return;
3668
- console.log(`0SEC_RESULT=${JSON.stringify(payload)}`);
2580
+ if (process.env["ZERO_EMIT_RESULT_LINE"] !== "1" && !process.env["ZERO_CLOUD_SINK"]) return;
2581
+ console.log(`ZERO_RESULT=${JSON.stringify(payload)}`);
3669
2582
  }
3670
2583
  function getCloudFinalSinkConfig() {
3671
- if (process.env["0SEC_FEATURE_CLOUD_SINK"] === "0") return null;
3672
- const sinkUrl = process.env["0SEC_CLOUD_SINK"]?.trim();
3673
- const scanId = process.env["0SEC_CLOUD_SCAN_ID"]?.trim();
2584
+ if (process.env["ZERO_FEATURE_CLOUD_SINK"] === "0") return null;
2585
+ const sinkUrl = process.env["ZERO_CLOUD_SINK"]?.trim();
2586
+ const scanId = process.env["ZERO_CLOUD_SCAN_ID"]?.trim();
3674
2587
  if (!sinkUrl || !scanId) return null;
3675
- const token = process.env["0SEC_CLOUD_TOKEN"]?.trim() || void 0;
2588
+ const token = process.env["ZERO_CLOUD_TOKEN"]?.trim() || void 0;
3676
2589
  return { sinkUrl, scanId, token };
3677
2590
  }
3678
2591
  function isCostBreakdownEntry(value) {
@@ -3752,7 +2665,7 @@ async function postFinalResultToCloud(report) {
3752
2665
  const url = `${config.sinkUrl.replace(/\/+$/, "")}/scans/${encodeURIComponent(config.scanId)}/findings`;
3753
2666
  const headers = {
3754
2667
  "Content-Type": "application/json",
3755
- "X-0sec-Scan-Id": config.scanId
2668
+ "X-0-Scan-Id": config.scanId
3756
2669
  };
3757
2670
  if (config.token) headers.Authorization = `Bearer ${config.token}`;
3758
2671
  try {
@@ -3764,37 +2677,16 @@ async function postFinalResultToCloud(report) {
3764
2677
  if (!res.ok) {
3765
2678
  const text = await res.text().catch(() => "");
3766
2679
  process.stderr.write(
3767
- `[0sec cloud-sink] report POST ${url} returned ${res.status}: ${text.slice(0, 200)}
2680
+ `[0 cloud-sink] report POST ${url} returned ${res.status}: ${text.slice(0, 200)}
3768
2681
  `
3769
2682
  );
3770
2683
  }
3771
2684
  } catch (err) {
3772
2685
  const msg = err instanceof Error ? err.message : String(err);
3773
- process.stderr.write(`[0sec cloud-sink] report POST ${url} failed: ${msg}
2686
+ process.stderr.write(`[0 cloud-sink] report POST ${url} failed: ${msg}
3774
2687
  `);
3775
2688
  }
3776
2689
  }
3777
- function skippedLensReviewReport(target, message) {
3778
- const now = (/* @__PURE__ */ new Date()).toISOString();
3779
- return {
3780
- target,
3781
- scanDepth: "deep",
3782
- startedAt: now,
3783
- completedAt: now,
3784
- durationMs: 0,
3785
- summary: {
3786
- totalAttacks: 0,
3787
- totalFindings: 0,
3788
- critical: 0,
3789
- high: 0,
3790
- medium: 0,
3791
- low: 0,
3792
- info: 0
3793
- },
3794
- findings: [],
3795
- warnings: [{ stage: "attack", message }]
3796
- };
3797
- }
3798
2690
  async function runUnified(opts) {
3799
2691
  const { target, depth, format, runtime, timeout } = opts;
3800
2692
  const core = await loadCoreModule();
@@ -3804,7 +2696,7 @@ async function runUnified(opts) {
3804
2696
  console.error(source_default.red("--branch-from requires --resume <run-id>"));
3805
2697
  process.exit(2);
3806
2698
  }
3807
- const { branchJournal } = await import("./dist-4YICS2J2.js");
2699
+ const { branchJournal } = await import("./dist-F6X62TQK.js");
3808
2700
  const result = branchJournal({
3809
2701
  runId: effectiveResumeScanId,
3810
2702
  fromEntry: opts.branchFromEntry
@@ -3840,7 +2732,7 @@ async function runUnified(opts) {
3840
2732
  console.error(source_default.red(`Unknown runtime '${runtime}'. Valid: ${validRuntimes.join(", ")}`));
3841
2733
  process.exit(2);
3842
2734
  }
3843
- const directCodexProviderConfigured = runtime === "codex" && (!!process.env["0SEC_CHATGPT_ACCESS_TOKEN"]?.trim() || !!process.env["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"]?.trim());
2735
+ const directCodexProviderConfigured = runtime === "codex" && (!!process.env["ZERO_CHATGPT_ACCESS_TOKEN"]?.trim() || !!process.env["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"]?.trim());
3844
2736
  if (runtime !== "api" && runtime !== "auto" && !directCodexProviderConfigured) {
3845
2737
  const rt = core.createRuntime({ type: runtime, timeout });
3846
2738
  const available = await rt.isAvailable();
@@ -3859,9 +2751,9 @@ async function runUnified(opts) {
3859
2751
  if (opts.sessionUiFactory) {
3860
2752
  inkUI = await opts.sessionUiFactory({ target, depth, mode });
3861
2753
  } else {
3862
- const { isBunRuntime } = await import("./runtime-J7PLXZNM.js");
2754
+ const { isBunRuntime } = await import("./runtime-XFWJTBUO.js");
3863
2755
  if (isBunRuntime()) {
3864
- const { createOpenTuiSession } = await import("./run-D56MPDKY.js");
2756
+ const { createOpenTuiSession } = await import("./run-CBGE6DRQ.js");
3865
2757
  const availability = await getRuntimeAvailability();
3866
2758
  inkUI = await createOpenTuiSession({
3867
2759
  target,
@@ -3875,7 +2767,7 @@ async function runUnified(opts) {
3875
2767
  model: opts.model
3876
2768
  });
3877
2769
  } else {
3878
- const { renderScanStream } = await import("./scan-stream-FHI2FYZE.js");
2770
+ const { renderScanStream } = await import("./scan-stream-BMOCPKQX.js");
3879
2771
  inkUI = renderScanStream({ version: VERSION, target, depth, mode });
3880
2772
  }
3881
2773
  }
@@ -3883,44 +2775,8 @@ async function runUnified(opts) {
3883
2775
  getPendingUserMessages = inkUI.getPendingUserMessages;
3884
2776
  }
3885
2777
  try {
3886
- let runnerExitCode = 0;
3887
2778
  let report;
3888
- if (opts.targetType === "source-code" && opts.reviewStrategy === "lenses") {
3889
- eventHandler({
3890
- type: "stage:start",
3891
- stage: "source-analysis",
3892
- message: "capturing the validated finder-lens snapshot"
3893
- });
3894
- const outcome = await runDeepReview({
3895
- target,
3896
- profile: opts.reviewProfile,
3897
- subsystem: opts.subsystem,
3898
- models: opts.model ? [opts.model] : void 0,
3899
- runtime,
3900
- timeoutMs: timeout,
3901
- costCeilingUsd: opts.costCeilingUsd,
3902
- log: (message) => eventHandler({
3903
- type: "stage:start",
3904
- stage: "attack",
3905
- message
3906
- })
3907
- });
3908
- runnerExitCode = outcome.exitCode;
3909
- const lensReport = outcome.report ?? skippedLensReviewReport(
3910
- target,
3911
- typeof outcome.result === "object" && outcome.result !== null && "note" in outcome.result ? String(outcome.result.note) : `lens review ended with exit code ${outcome.exitCode}`
3912
- );
3913
- report = lensReport;
3914
- writeOsecRunReport(
3915
- resolveOsecRunStorage({ ...opts.dbPath ? { dbPath: opts.dbPath } : {} }),
3916
- lensReport
3917
- );
3918
- eventHandler({
3919
- type: "stage:end",
3920
- stage: "attack",
3921
- message: outcome.exitCode === 0 ? "validated finder-lens review completed" : `validated finder-lens review ended with exit code ${outcome.exitCode}`
3922
- });
3923
- } else if (opts.targetType === "url" || opts.targetType === "web-app") {
2779
+ if (opts.targetType === "url" || opts.targetType === "web-app") {
3924
2780
  report = await core.agenticScan({
3925
2781
  config: {
3926
2782
  target,
@@ -3995,7 +2851,7 @@ async function runUnified(opts) {
3995
2851
  } else {
3996
2852
  if (format === "html" || format === "pdf") {
3997
2853
  const extension = format === "pdf" ? "pdf" : "html";
3998
- const filePath = opts.reportPath ? resolve5(opts.reportPath) : join3(tmpdir(), `0sec-report-${Date.now()}.${extension}`);
2854
+ const filePath = opts.reportPath ? resolve3(opts.reportPath) : join2(tmpdir(), `0-report-${Date.now()}.${extension}`);
3999
2855
  if (format === "pdf") {
4000
2856
  await generatePdfReport(toScanReport(reportAny), filePath);
4001
2857
  } else {
@@ -4015,7 +2871,7 @@ async function runUnified(opts) {
4015
2871
  console.log("");
4016
2872
  console.log(source_default.gray(" --tui post-scan view is no longer bundled in the npm package."));
4017
2873
  console.log(source_default.gray(" Install the standalone binary for the full OpenTUI experience:"));
4018
- console.log(source_default.gray(" curl -fsSL https://raw.githubusercontent.com/0sec-labs/0sec/main/install.sh | bash"));
2874
+ console.log(source_default.gray(" curl -fsSL https://raw.githubusercontent.com/0sec-labs/0/main/install.sh | bash"));
4019
2875
  console.log("");
4020
2876
  }
4021
2877
  if (crossValidatedLeads) {
@@ -4025,14 +2881,14 @@ async function runUnified(opts) {
4025
2881
  printCostSummary(scanCompletedCost);
4026
2882
  }
4027
2883
  unsubscribeCost();
4028
- let exitCode = runnerExitCode;
2884
+ let exitCode = 0;
4029
2885
  const estimatedCostUsd = getEstimatedCost(reportAny);
4030
2886
  const usage = getUsage(reportAny);
4031
2887
  if (opts.emit === "pr") {
4032
2888
  const findings = report.findings ?? [];
4033
2889
  const core2 = await loadCoreModule();
4034
- const repoRoot = opts.targetType === "source-code" && existsSync3(target) ? resolve5(target) : process.cwd();
4035
- const outDir = opts.emitOutDir ?? join3(tmpdir(), `0sec-emit-${Date.now()}`);
2890
+ const repoRoot = opts.targetType === "source-code" && existsSync3(target) ? resolve3(target) : process.cwd();
2891
+ const outDir = opts.emitOutDir ?? join2(tmpdir(), `0-emit-${Date.now()}`);
4036
2892
  console.log(source_default.blue(`[emit pr] ${findings.length} finding(s); repo=${repoRoot} base=${opts.emitPrBase ?? "main"}${opts.emitPrDryRun ? " (dry-run)" : ""}`));
4037
2893
  const emitReport = await core2.emitFindingsAsPRs(findings, {
4038
2894
  repoRoot,
@@ -4063,7 +2919,7 @@ async function runUnified(opts) {
4063
2919
  if (findings.length === 0) {
4064
2920
  console.log(source_default.yellow("No findings to export."));
4065
2921
  } else {
4066
- const { exportToGitHubIssues } = await import("./github-issues-MJ6OYOOU.js");
2922
+ const { exportToGitHubIssues } = await import("./github-issues-IGTTWM7V.js");
4067
2923
  console.log(source_default.blue(`Exporting ${findings.length} finding(s) to GitHub Issues on ${repo}...`));
4068
2924
  const result = await exportToGitHubIssues(findings, repo);
4069
2925
  console.log(
@@ -4071,7 +2927,7 @@ async function runUnified(opts) {
4071
2927
  );
4072
2928
  }
4073
2929
  }
4074
- const ceilingRaw = process.env["0SEC_COST_CEILING_USD"]?.trim();
2930
+ const ceilingRaw = process.env["ZERO_COST_CEILING_USD"]?.trim();
4075
2931
  if (ceilingRaw) {
4076
2932
  const ceiling = Number(ceilingRaw);
4077
2933
  if (Number.isFinite(ceiling) && estimatedCostUsd !== void 0 && estimatedCostUsd > ceiling) {
@@ -4389,14 +3245,6 @@ var SLASH_COMMANDS = [
4389
3245
  description: "Replay a previous turn or session",
4390
3246
  tuiOnly: true
4391
3247
  },
4392
- // ── mode ────────────────────────────────────────────────────────────────
4393
- {
4394
- name: "mode",
4395
- aliases: [],
4396
- category: "mode",
4397
- description: "Set Standard (approve actions), Co-pilot (in scope), YOLO (public-network autonomy), or Recon (passive)",
4398
- usage: "/mode [standard|copilot|yolo|recon]"
4399
- },
4400
3248
  {
4401
3249
  name: "resume",
4402
3250
  aliases: ["sessions"],
@@ -4408,7 +3256,7 @@ var SLASH_COMMANDS = [
4408
3256
  name: "providers",
4409
3257
  aliases: [],
4410
3258
  category: "system",
4411
- description: "Connection: 0sec Cloud, API keys, or provider subscriptions",
3259
+ description: "Connection: 0cloud, API keys, or provider subscriptions",
4412
3260
  usage: "/providers",
4413
3261
  tuiOnly: true
4414
3262
  },
@@ -4423,7 +3271,7 @@ var SLASH_COMMANDS = [
4423
3271
  name: "feedback",
4424
3272
  aliases: [],
4425
3273
  category: "system",
4426
- description: "Record feedback about 0sec to a local file you control, with optional HTTPS submission",
3274
+ description: "Record feedback about 0 to a local file you control, with optional HTTPS submission",
4427
3275
  usage: "/feedback <message> | /feedback submit <message> | /feedback send | /feedback cancel"
4428
3276
  },
4429
3277
  {
@@ -4452,7 +3300,7 @@ var SLASH_COMMANDS = [
4452
3300
  {
4453
3301
  name: "model",
4454
3302
  aliases: ["models"],
4455
- category: "mode",
3303
+ category: "session",
4456
3304
  description: "Show or select a model (TUI: applies to New chat)",
4457
3305
  usage: "/model [id]"
4458
3306
  },
@@ -4503,7 +3351,7 @@ var SLASH_COMMANDS = [
4503
3351
  name: "connect",
4504
3352
  aliases: ["login", "auth"],
4505
3353
  category: "navigation",
4506
- description: "Connect to 0sec Cloud, or use your own API key or provider subscription",
3354
+ description: "Connect to 0cloud, or use your own API key or provider subscription",
4507
3355
  usage: "/connect",
4508
3356
  tuiOnly: true
4509
3357
  },
@@ -4605,7 +3453,7 @@ function filterCommands(query) {
4605
3453
  }
4606
3454
 
4607
3455
  // packages/cli/src/console-session.ts
4608
- import { randomUUID as randomUUID2 } from "node:crypto";
3456
+ import { randomUUID } from "node:crypto";
4609
3457
 
4610
3458
  // packages/cli/src/conversation-history.ts
4611
3459
  var MAX_PAGE_CHARS = 16e3;
@@ -4677,10 +3525,10 @@ function createConversationHistory(options = {}) {
4677
3525
 
4678
3526
  // packages/cli/src/dev-engine-updates.ts
4679
3527
  import { execFile as execFile2 } from "node:child_process";
4680
- import { createHash as createHash3 } from "node:crypto";
4681
- import { appendFileSync, readFileSync as readFileSync4, readdirSync, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
3528
+ import { createHash as createHash2 } from "node:crypto";
3529
+ import { appendFileSync, readFileSync as readFileSync2, readdirSync, realpathSync, statSync } from "node:fs";
4682
3530
  import { mkdir, mkdtemp, rm } from "node:fs/promises";
4683
- import { isAbsolute, join as join4, relative as relative2, sep as sep2 } from "node:path";
3531
+ import { isAbsolute, join as join3, relative, sep } from "node:path";
4684
3532
  import { pathToFileURL } from "node:url";
4685
3533
  import { isDeepStrictEqual, promisify } from "node:util";
4686
3534
  var execute = promisify(execFile2);
@@ -4708,12 +3556,12 @@ function assertSessionContract(session) {
4708
3556
  }
4709
3557
  }
4710
3558
  function sourceDigest(root) {
4711
- const hash = createHash3("sha256");
3559
+ const hash = createHash2("sha256");
4712
3560
  const visit = (directory) => {
4713
3561
  const entries = readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
4714
3562
  for (const entry of entries) {
4715
3563
  if (entry.name.startsWith(".") || Object.hasOwn(ignoredSourceNames, entry.name)) continue;
4716
- const path = join4(directory, entry.name);
3564
+ const path = join3(directory, entry.name);
4717
3565
  if (entry.isDirectory()) {
4718
3566
  visit(path);
4719
3567
  continue;
@@ -4721,23 +3569,23 @@ function sourceDigest(root) {
4721
3569
  if (!entry.isFile() && !entry.isSymbolicLink() || /\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name) || entry.name.endsWith(".d.ts")) continue;
4722
3570
  let readable = path;
4723
3571
  if (entry.isSymbolicLink()) {
4724
- readable = realpathSync2(path);
4725
- const target = relative2(root, readable);
4726
- if (target === ".." || target.startsWith(`..${sep2}`) || isAbsolute(target) || !statSync2(readable).isFile()) {
3572
+ readable = realpathSync(path);
3573
+ const target = relative(root, readable);
3574
+ if (target === ".." || target.startsWith(`..${sep}`) || isAbsolute(target) || !statSync(readable).isFile()) {
4727
3575
  throw new Error(`Source links must resolve to files inside the core source tree: ${path}`);
4728
3576
  }
4729
3577
  }
4730
- hash.update(relative2(root, path)).update("\0").update(readFileSync4(readable)).update("\0");
3578
+ hash.update(relative(root, path)).update("\0").update(readFileSync2(readable)).update("\0");
4731
3579
  }
4732
3580
  };
4733
3581
  visit(root);
4734
3582
  return hash.digest("hex");
4735
3583
  }
4736
3584
  function withDevEngineUpdates(initial, config, closeStore) {
4737
- const requestedRoot = process.env["0SEC_DEV_SOURCE_ROOT"];
4738
- const root = requestedRoot ? realpathSync2(requestedRoot) : void 0;
4739
- if (root && JSON.parse(readFileSync4(join4(root, "packages/core/package.json"), "utf8")).name !== "@0sec/core") {
4740
- throw new Error("0SEC_DEV_SOURCE_ROOT must identify a 0sec development checkout");
3585
+ const requestedRoot = process.env["ZERO_DEV_SOURCE_ROOT"];
3586
+ const root = requestedRoot ? realpathSync(requestedRoot) : void 0;
3587
+ if (root && JSON.parse(readFileSync2(join3(root, "packages/core/package.json"), "utf8")).name !== "@0/core") {
3588
+ throw new Error("ZERO_DEV_SOURCE_ROOT must identify a 0 development checkout");
4741
3589
  }
4742
3590
  let current = initial;
4743
3591
  let activeDigest;
@@ -4763,15 +3611,15 @@ function withDevEngineUpdates(initial, config, closeStore) {
4763
3611
  let committed = false;
4764
3612
  let pendingHarnessSnapshot;
4765
3613
  try {
4766
- const source = join4(root, "packages/core/src");
3614
+ const source = join3(root, "packages/core/src");
4767
3615
  const digest = sourceDigest(source);
4768
3616
  if (digest === activeDigest || digest === rejectedDigest) return;
4769
3617
  rejectedDigest = digest;
4770
3618
  notify("Building changed development engine source; the current session remains active until handoff.");
4771
- const generationRoot = join4(root, "packages/core/.0sec/dev-engines");
3619
+ const generationRoot = join3(root, "packages/core/.0/dev-engines");
4772
3620
  await mkdir(generationRoot, { recursive: true, mode: 448 });
4773
- directory = await mkdtemp(join4(generationRoot, "generation-"));
4774
- const result = await execute(process.execPath, [join4(root, "scripts/build-dev-engine.mjs"), source, directory], {
3621
+ directory = await mkdtemp(join3(generationRoot, "generation-"));
3622
+ const result = await execute(process.execPath, [join3(root, "scripts/build-dev-engine.mjs"), source, directory], {
4775
3623
  cwd: root,
4776
3624
  timeout: 12e4,
4777
3625
  maxBuffer: 2 * 1024 * 1024,
@@ -4801,7 +3649,7 @@ function withDevEngineUpdates(initial, config, closeStore) {
4801
3649
  if (!isDeepStrictEqual(comparableCheckpoint(candidate.exportCheckpoint()), comparableCheckpoint(checkpoint))) {
4802
3650
  throw new Error("Candidate engine did not preserve the session checkpoint");
4803
3651
  }
4804
- const candidateBus = await import(pathToFileURL(join4(directory, "runtime/events/bus.js")).href);
3652
+ const candidateBus = await import(pathToFileURL(join3(directory, "runtime/events/bus.js")).href);
4805
3653
  signal?.throwIfAborted();
4806
3654
  if (closingStarted || !getSettings().allowDevSourceUpdates) throw new Error("Development engine update cancelled before handoff");
4807
3655
  if (!isDeepStrictEqual(current.exportCheckpoint(), checkpoint)) throw new Error("Session changed during candidate preparation; keeping the current engine");
@@ -4879,7 +3727,7 @@ function withDevEngineUpdates(initial, config, closeStore) {
4879
3727
  };
4880
3728
  const devlog = (o) => {
4881
3729
  try {
4882
- appendFileSync(process.env["0SEC_TUI_LOG"] ?? "/tmp/0sec-tui.log", JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), kind: "dev-engine", ...o }) + "\n");
3730
+ appendFileSync(process.env["ZERO_TUI_LOG"] ?? "/tmp/0-tui.log", JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), kind: "dev-engine", ...o }) + "\n");
4883
3731
  } catch {
4884
3732
  }
4885
3733
  };
@@ -4946,9 +3794,25 @@ function withDevEngineUpdates(initial, config, closeStore) {
4946
3794
  }
4947
3795
 
4948
3796
  // packages/cli/src/console-session.ts
3797
+ var CONSOLE_JEV_FEATURES = /* @__PURE__ */ new Set([
3798
+ "browser",
3799
+ "memory",
3800
+ "dedupe",
3801
+ "redteam",
3802
+ "kernel",
3803
+ "crash",
3804
+ "radar",
3805
+ "foxguard"
3806
+ ]);
3807
+ function consoleJevRuntimeFromEnvironment() {
3808
+ const features = [...new Set(
3809
+ (process.env["ZERO_JEV_FEATURES"] ?? "").split(",").map((feature) => feature.trim()).filter((feature) => CONSOLE_JEV_FEATURES.has(feature))
3810
+ )];
3811
+ return toToolContextJevRuntime(createConsoleJevRuntime({ provider: "direct", features }));
3812
+ }
4949
3813
  function createLocalConsoleSession(config, dbPath) {
4950
3814
  const db = new osecDB(dbPath);
4951
- const scanId = config.scanId ?? `console-${randomUUID2()}`;
3815
+ const scanId = config.scanId ?? `console-${randomUUID()}`;
4952
3816
  let ownsScan = false;
4953
3817
  try {
4954
3818
  if (!db.getScan(scanId)) {
@@ -4964,7 +3828,8 @@ function createLocalConsoleSession(config, dbPath) {
4964
3828
  ...config,
4965
3829
  scanId,
4966
3830
  db,
4967
- conversationHistory: config.conversationHistory ?? createConversationHistory()
3831
+ conversationHistory: config.conversationHistory ?? createConversationHistory(),
3832
+ jevRuntime: config.jevRuntime ?? consoleJevRuntimeFromEnvironment()
4968
3833
  };
4969
3834
  const session = createConsoleSession(engineConfig);
4970
3835
  return withDevEngineUpdates(session, engineConfig, (completed) => {
@@ -4984,6 +3849,46 @@ function createLocalConsoleSession(config, dbPath) {
4984
3849
  }
4985
3850
  }
4986
3851
 
3852
+ // packages/cli/src/tui/hosted-balance.ts
3853
+ function fmtUsd(usd) {
3854
+ return usd === null ? "unavailable" : `$${usd}`;
3855
+ }
3856
+ function hostedBalanceState(account) {
3857
+ if (!account) return { status: "unavailable" };
3858
+ const { included, prepaid } = account;
3859
+ const parts = [included.state === "none" ? "no included allowance" : included.state !== "unavailable" && included.usedPercent !== null ? `${included.usedPercent}% used` : "unavailable"];
3860
+ if (included.state !== "unavailable" && included.resetsAt) {
3861
+ parts.push(`resets ${included.resetsAt.slice(0, 10)}`);
3862
+ }
3863
+ if (prepaid.fallbackEnabled) parts.push(`prepaid ${fmtUsd(prepaid.balanceUsd)}`);
3864
+ if (account.state !== "ready") {
3865
+ parts.push(`${account.state}${account.reason ? `: ${account.reason}` : ""}`);
3866
+ } else if (!account.admission.eligible) {
3867
+ parts.push("access blocked");
3868
+ }
3869
+ return { status: "ready", display: parts.join(" \xB7 ") };
3870
+ }
3871
+ function formatHostedBalance(state) {
3872
+ if (state.status === "loading") return "Usage: Loading\u2026";
3873
+ if (state.status === "unavailable") return "Usage: unavailable";
3874
+ return `Usage: ${state.display}`;
3875
+ }
3876
+ function formatBalanceDetail(account) {
3877
+ if (!account) return " Usage unavailable (unsupported account data).\n";
3878
+ const { included, prepaid } = account;
3879
+ const lines = [included.state === "none" ? " Usage: no included allowance" : included.state !== "unavailable" && included.usedPercent !== null ? ` Usage: ${included.usedPercent}% used` : " Usage: unavailable"];
3880
+ if (included.state !== "unavailable" && included.resetsAt) {
3881
+ lines.push(` Resets: ${included.resetsAt}`);
3882
+ }
3883
+ if (prepaid.fallbackEnabled) lines.push(` Prepaid balance: ${fmtUsd(prepaid.balanceUsd)}`);
3884
+ if (account.state !== "ready" || !account.admission.eligible) {
3885
+ lines.push(` Access: ${account.state === "ready" ? "blocked" : account.state}`);
3886
+ const reason = account.admission.reason ?? account.reason;
3887
+ if (reason) lines.push(` Reason: ${reason}`);
3888
+ }
3889
+ return lines.join("\n") + "\n";
3890
+ }
3891
+
4987
3892
  // packages/cli/src/tui/codex-device-auth.ts
4988
3893
  import { spawn as spawn2 } from "node:child_process";
4989
3894
  var MAX_VISIBLE_LINES = 8;
@@ -5001,22 +3906,39 @@ function defaultSpawnCodexDeviceAuth(command, args, options) {
5001
3906
  function startCodexDeviceAuth(options) {
5002
3907
  const env = options.env ?? process.env;
5003
3908
  const launch = options.spawn ?? defaultSpawnCodexDeviceAuth;
3909
+ const openBrowser = options.openBrowser ?? defaultOpenBrowser;
5004
3910
  const lines = [];
5005
3911
  let pending = "";
5006
3912
  let settled = false;
5007
3913
  let cancelled = false;
3914
+ let browserOpened = false;
5008
3915
  const publish = (phase, message) => {
5009
3916
  options.onUpdate({ phase, lines: [...lines], message });
5010
3917
  };
3918
+ const tryOpenBrowser = (text) => {
3919
+ if (browserOpened) return;
3920
+ const rawUrl = text.match(/https:\/\/auth\.openai\.com\/codex\/device[^\s\x1b]*/)?.[0];
3921
+ if (!rawUrl) return;
3922
+ const url = rawUrl.replace(/[),.;]+$/, "");
3923
+ browserOpened = true;
3924
+ void Promise.resolve(openBrowser(url)).catch(() => {
3925
+ lines.push("Could not open a browser automatically; open the URL above.");
3926
+ if (lines.length > MAX_VISIBLE_LINES) lines.shift();
3927
+ publish("running", "Complete the ChatGPT Codex device sign-in in your browser.");
3928
+ });
3929
+ };
5011
3930
  const pushOutput = (chunk) => {
5012
- const normalized = sanitizeTuiText(`${pending}${chunk.toString("utf8")}`);
5013
- const parts = normalized.split("\n");
3931
+ const text = chunk.toString("utf8");
3932
+ tryOpenBrowser(text);
3933
+ const raw = `${pending}${text}`;
3934
+ const parts = raw.split(/\r?\n/);
5014
3935
  pending = parts.pop() ?? "";
5015
3936
  for (const line of parts) {
5016
- const value = line.trim();
3937
+ const value = sanitizeTuiText(line);
5017
3938
  if (value.length === 0) continue;
5018
3939
  lines.push(value);
5019
3940
  if (lines.length > MAX_VISIBLE_LINES) lines.shift();
3941
+ tryOpenBrowser(value);
5020
3942
  }
5021
3943
  publish("running", "Complete the ChatGPT Codex device sign-in in your browser.");
5022
3944
  };
@@ -5052,7 +3974,7 @@ function startCodexDeviceAuth(options) {
5052
3974
  return;
5053
3975
  }
5054
3976
  maybeLoadCodexAuth({ env, home: options.homeDir, force: true });
5055
- if (!env["0SEC_CHATGPT_ACCESS_TOKEN"] && !env["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"]) {
3977
+ if (!env["ZERO_CHATGPT_ACCESS_TOKEN"] && !env["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"]) {
5056
3978
  finish("failed", "Codex completed without a readable ChatGPT subscription credential.");
5057
3979
  return;
5058
3980
  }
@@ -5077,8 +3999,6 @@ export {
5077
3999
  TIMELINE_FORMATS,
5078
4000
  isTimelineFormat,
5079
4001
  formatTimeline,
5080
- registerHuntCommand,
5081
- registerDeepReviewCommand,
5082
4002
  resolveFindingChatIntent,
5083
4003
  buildFindingChatPrompt,
5084
4004
  buildFindingConsoleCommand,
@@ -5099,10 +4019,13 @@ export {
5099
4019
  watchLensSynthCommand,
5100
4020
  registerLensSynthCommand,
5101
4021
  createLocalConsoleSession,
5102
- startCodexDeviceAuth,
4022
+ hostedBalanceState,
4023
+ formatHostedBalance,
4024
+ formatBalanceDetail,
5103
4025
  registerAuthCommand,
5104
4026
  hostedBrowserLoginFlow,
5105
4027
  runLogin,
5106
4028
  defaultOpenBrowser,
4029
+ startCodexDeviceAuth,
5107
4030
  runUnified
5108
4031
  };