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,19 +1,19 @@
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
  VERSION,
6
- homeStateDir
7
- } from "./chunk-6L26OBWI.js";
6
+ cloudStateDir
7
+ } from "./chunk-PC6RCKQV.js";
8
8
 
9
9
  // packages/core/dist/agent/feature-presets.js
10
10
  var FP_MOAT_FLAGS = [
11
- "0SEC_FEATURE_REACHABILITY_GATE",
12
- "0SEC_FEATURE_MULTIMODAL",
13
- "0SEC_FEATURE_PUBLISHABILITY_GATE",
14
- "0SEC_FEATURE_POV_GATE",
15
- "0SEC_FEATURE_POC_GEN_STATIC",
16
- "0SEC_FEATURE_CONSENSUS_VERIFY"
11
+ "ZERO_FEATURE_REACHABILITY_GATE",
12
+ "ZERO_FEATURE_MULTIMODAL",
13
+ "ZERO_FEATURE_PUBLISHABILITY_GATE",
14
+ "ZERO_FEATURE_POV_GATE",
15
+ "ZERO_FEATURE_POC_GEN_STATIC",
16
+ "ZERO_FEATURE_CONSENSUS_VERIFY"
17
17
  ];
18
18
  var FEATURE_PRESETS = Object.freeze({
19
19
  "fp-moat": FP_MOAT_FLAGS
@@ -41,7 +41,7 @@ function applyFeaturePreset(preset, env2 = process.env) {
41
41
  return { preset, applied, preserved };
42
42
  }
43
43
  function applyFeaturePresetFromEnv(env2 = process.env) {
44
- const raw = env2["0SEC_FEATURE_PRESET"];
44
+ const raw = env2["ZERO_FEATURE_PRESET"];
45
45
  if (!raw)
46
46
  return void 0;
47
47
  const preset = resolveFeaturePreset(raw);
@@ -63,73 +63,73 @@ var features = {
63
63
  *
64
64
  * Turn count is also a cost PROXY, and cost is already bounded directly by
65
65
  * the token budget, so this never was the control that kept spend in check.
66
- * Enable with `0SEC_FEATURE_EARLY_STOP=1` for benchmark or A/B runs where a
66
+ * Enable with `ZERO_FEATURE_EARLY_STOP=1` for benchmark or A/B runs where a
67
67
  * fixed turn budget per attempt is the point.
68
68
  */
69
69
  get earlyStopRetry() {
70
- return env("0SEC_FEATURE_EARLY_STOP", false);
70
+ return env("ZERO_FEATURE_EARLY_STOP", false);
71
71
  },
72
72
  /** Detect A-A-A and A-B-A-B loop patterns, inject warning */
73
73
  get loopDetection() {
74
- return env("0SEC_FEATURE_LOOP_DETECTION", true);
74
+ return env("ZERO_FEATURE_LOOP_DETECTION", true);
75
75
  },
76
76
  /** Compress middle messages when context exceeds 30k tokens */
77
77
  get contextCompaction() {
78
- return env("0SEC_FEATURE_CONTEXT_COMPACTION", true);
78
+ return env("ZERO_FEATURE_CONTEXT_COMPACTION", true);
79
79
  },
80
80
  /**
81
81
  * Re-send the opaque, model-bound Responses output item array on the next
82
82
  * turn. Default ON; set to 0 only for matched retained-reasoning A/B runs.
83
83
  */
84
84
  get retainedReasoning() {
85
- return env("0SEC_FEATURE_RETAINED_REASONING", true);
85
+ return env("ZERO_FEATURE_RETAINED_REASONING", true);
86
86
  },
87
87
  /** Exploit script templates in shell prompt (blind SQLi, SSTI, auth chain) */
88
88
  get scriptTemplates() {
89
- return env("0SEC_FEATURE_SCRIPT_TEMPLATES", true);
89
+ return env("ZERO_FEATURE_SCRIPT_TEMPLATES", true);
90
90
  },
91
91
  /** Dynamic vulnerability playbooks injected after recon phase */
92
92
  get dynamicPlaybooks() {
93
- return env("0SEC_FEATURE_DYNAMIC_PLAYBOOKS", false);
93
+ return env("ZERO_FEATURE_DYNAMIC_PLAYBOOKS", false);
94
94
  },
95
95
  /** Just-in-time atomic DO/DON'T rules injected on a matching tool action */
96
96
  get ruleInjection() {
97
- return env("0SEC_FEATURE_RULE_INJECTION", false);
97
+ return env("ZERO_FEATURE_RULE_INJECTION", false);
98
98
  },
99
99
  /** Agent writes plan/creds to disk, injected at reflection checkpoints */
100
100
  get externalMemory() {
101
- return env("0SEC_FEATURE_EXTERNAL_MEMORY", false);
101
+ return env("ZERO_FEATURE_EXTERNAL_MEMORY", false);
102
102
  },
103
103
  /** Inject prior attempt findings when retrying (LLM-summarized progress handoff) */
104
104
  get progressHandoff() {
105
- return env("0SEC_FEATURE_PROGRESS_HANDOFF", true);
105
+ return env("ZERO_FEATURE_PROGRESS_HANDOFF", true);
106
106
  },
107
107
  /** Allow the agent to search the web for CVE details, docs, and technique references */
108
108
  get webSearch() {
109
- return env("0SEC_FEATURE_WEB_SEARCH", false);
109
+ return env("ZERO_FEATURE_WEB_SEARCH", false);
110
110
  },
111
111
  /** Interactive PTY sessions for exploits requiring interactivity (reverse shells, DB clients, SSH) */
112
112
  get ptySession() {
113
- return env("0SEC_FEATURE_PTY_SESSION", false);
113
+ return env("ZERO_FEATURE_PTY_SESSION", false);
114
114
  },
115
115
  /**
116
116
  * Persistent, COMPUTE-ONLY Python REPL (`python_exec`, Phase-0). A framed
117
117
  * python3 kernel keeps state across calls for payload/parse/crypto/encode
118
118
  * work; networking is blocked at the socket source whenever an engagement is
119
- * active. Default OFF — opt in via 0SEC_FEATURE_PYTHON_EXEC=1. Getter so
119
+ * active. Default OFF — opt in via ZERO_FEATURE_PYTHON_EXEC=1. Getter so
120
120
  * the CLI `--features` flag (set after this module is imported) is honored at
121
121
  * tool-dispatch time.
122
122
  */
123
123
  get pythonExec() {
124
- return env("0SEC_FEATURE_PYTHON_EXEC", false);
124
+ return env("ZERO_FEATURE_PYTHON_EXEC", false);
125
125
  },
126
126
  /**
127
127
  * Expose the path-confined `analyze_binary` bridge to 0verse. Default OFF:
128
128
  * a model may request a long-running binary analysis only after an operator
129
- * opts in with 0SEC_FEATURE_ZEROVERSE=1.
129
+ * opts in with ZERO_FEATURE_ZEROVERSE=1.
130
130
  */
131
131
  get zeroverse() {
132
- return env("0SEC_FEATURE_ZEROVERSE", false);
132
+ return env("ZERO_FEATURE_ZEROVERSE", false);
133
133
  },
134
134
  /**
135
135
  * EGATS specialist routing (#557, HPTSA-inspired). When ON, an EGATS branch
@@ -146,22 +146,22 @@ var features = {
146
146
  * benchmark harness. Implemented as a getter so the CLI `--features` flag
147
147
  * (which sets the env var inside the command action, AFTER this module has
148
148
  * been imported) is honored at routing time. Enable via
149
- * 0SEC_FEATURE_SPECIALIST_ROUTING=1.
149
+ * ZERO_FEATURE_SPECIALIST_ROUTING=1.
150
150
  */
151
151
  get specialistRouting() {
152
- return env("0SEC_FEATURE_SPECIALIST_ROUTING", false);
152
+ return env("ZERO_FEATURE_SPECIALIST_ROUTING", false);
153
153
  },
154
154
  /** Self-consistency voting: run the structured verify pipeline N times and take the majority vote */
155
155
  get selfConsistencyVerify() {
156
- return env("0SEC_FEATURE_CONSENSUS_VERIFY", false);
156
+ return env("ZERO_FEATURE_CONSENSUS_VERIFY", false);
157
157
  },
158
158
  /** Multi-modal agreement: cross-validate findings against foxguard (Rust pattern scanner) */
159
159
  get multiModalAgreement() {
160
- return env("0SEC_FEATURE_MULTIMODAL", false);
160
+ return env("ZERO_FEATURE_MULTIMODAL", false);
161
161
  },
162
162
  /** Reachability gate: suppress findings whose sink is not reachable from an application entry point */
163
163
  get reachabilityGate() {
164
- return env("0SEC_FEATURE_REACHABILITY_GATE", false);
164
+ return env("ZERO_FEATURE_REACHABILITY_GATE", false);
165
165
  },
166
166
  /**
167
167
  * Publishability / in-scope gate (issue #537 / #539). Decides
@@ -173,14 +173,14 @@ var features = {
173
173
  *
174
174
  * Default OFF: this gate can suppress reproducible findings, so it must be
175
175
  * explicitly opted into before any A/B claim. Disable/enable via
176
- * 0SEC_FEATURE_PUBLISHABILITY_GATE.
176
+ * ZERO_FEATURE_PUBLISHABILITY_GATE.
177
177
  */
178
178
  get publishabilityGate() {
179
- return env("0SEC_FEATURE_PUBLISHABILITY_GATE", false);
179
+ return env("ZERO_FEATURE_PUBLISHABILITY_GATE", false);
180
180
  },
181
181
  /** PoV gate: require a working, executable PoC per finding or downgrade to info */
182
182
  get povGate() {
183
- return env("0SEC_FEATURE_POV_GATE", false);
183
+ return env("ZERO_FEATURE_POV_GATE", false);
184
184
  },
185
185
  /**
186
186
  * Intra-scan semantic dedupe post-pass (anchored incremental LLM
@@ -188,10 +188,10 @@ var features = {
188
188
  * Marks duplicates with a canonical mapping + cluster reason instead of
189
189
  * dropping them. Default OFF: it spends an LLM call per ≤50-finding batch
190
190
  * after the scan, so it must be explicitly opted into before any A/B
191
- * claim. Toggle via 0SEC_FEATURE_SEMANTIC_DEDUPE.
191
+ * claim. Toggle via ZERO_FEATURE_SEMANTIC_DEDUPE.
192
192
  */
193
193
  get semanticDedupe() {
194
- return env("0SEC_FEATURE_SEMANTIC_DEDUPE", false);
194
+ return env("ZERO_FEATURE_SEMANTIC_DEDUPE", false);
195
195
  },
196
196
  /**
197
197
  * Finding-specific remediation written by the model
@@ -207,33 +207,17 @@ var features = {
207
207
  * degrade cost, never correctness.
208
208
  */
209
209
  get llmRemediation() {
210
- return env("0SEC_FEATURE_LLM_REMEDIATION", false);
211
- },
212
- /**
213
- * Per-finding impact assessment (`assessImpact`, `triage/impact-assessment.ts`)
214
- * written by the model: reachability tier, weaponizability, blast radius,
215
- * business-impact tier. Default OFF — one extra LLM call per non-false-positive
216
- * finding at report time.
217
- *
218
- * When on, the assessment feeds three things it is otherwise absent from:
219
- * a real CVSS exploitability vector (AV/PR/UI from the reachability tier
220
- * rather than the AV:N/severity-floor guess), the advisory's Impact +
221
- * attack-prerequisites section, and the vendor-notification impact line. When
222
- * off, all three fall back to today's category/severity heuristics — so this
223
- * flag strictly adds fidelity, never changes the no-assessment output.
224
- */
225
- get impactAssessment() {
226
- return env("0SEC_FEATURE_IMPACT_ASSESSMENT", false);
210
+ return env("ZERO_FEATURE_LLM_REMEDIATION", false);
227
211
  },
228
212
  /**
229
213
  * Incremental finding ranking post-pass (decimal-insertion between ranked
230
214
  * anchors, `triage/incremental-rank.ts`). Orders the report by comparative
231
215
  * promise (exploitability × impact × evidence strength). Default OFF: it
232
216
  * spends an LLM call per ≤50-finding batch; opt in before any A/B claim.
233
- * Toggle via 0SEC_FEATURE_INCREMENTAL_RANK.
217
+ * Toggle via ZERO_FEATURE_INCREMENTAL_RANK.
234
218
  */
235
219
  get incrementalRank() {
236
- return env("0SEC_FEATURE_INCREMENTAL_RANK", false);
220
+ return env("ZERO_FEATURE_INCREMENTAL_RANK", false);
237
221
  },
238
222
  /**
239
223
  * Static-finding PoC generation (#666 / EPIC #674 Part A). For findings that
@@ -247,10 +231,10 @@ var features = {
247
231
  *
248
232
  * Default OFF: it spends LLM + execution budget per static finding and must
249
233
  * be explicitly opted into before any A/B claim (A/B-able via the #656
250
- * harness). Toggle via 0SEC_FEATURE_POC_GEN_STATIC.
234
+ * harness). Toggle via ZERO_FEATURE_POC_GEN_STATIC.
251
235
  */
252
236
  get pocGenStatic() {
253
- return env("0SEC_FEATURE_POC_GEN_STATIC", false);
237
+ return env("ZERO_FEATURE_POC_GEN_STATIC", false);
254
238
  },
255
239
  /**
256
240
  * Inline validation / validate-on-save (#554). When ON, the native attack
@@ -268,10 +252,10 @@ var features = {
268
252
  * cost_per_flag claim. Implemented as a getter so the CLI `--features` flag
269
253
  * (which sets the env var inside the command action, AFTER this module is
270
254
  * imported) is honored at loop time. Enable via
271
- * 0SEC_FEATURE_INLINE_VALIDATION=1.
255
+ * ZERO_FEATURE_INLINE_VALIDATION=1.
272
256
  */
273
257
  get inlineValidation() {
274
- return env("0SEC_FEATURE_INLINE_VALIDATION", false);
258
+ return env("ZERO_FEATURE_INLINE_VALIDATION", false);
275
259
  },
276
260
  /**
277
261
  * WordPress plugin/theme fingerprinter + OSV CVE lookup.
@@ -286,7 +270,7 @@ var features = {
286
270
  * still honored at tool-dispatch time.
287
271
  */
288
272
  get wpFingerprint() {
289
- return env("0SEC_FEATURE_WP_FINGERPRINT", true);
273
+ return env("ZERO_FEATURE_WP_FINGERPRINT", true);
290
274
  },
291
275
  /**
292
276
  * MongoDB ObjectID forge tool. Exposes the `mongo_objectid` tool to the
@@ -296,23 +280,23 @@ var features = {
296
280
  *
297
281
  * Default ON — this is a pure-computation utility with no network or
298
282
  * filesystem side effects, so there's no reason to gate it off. Disable
299
- * via 0SEC_FEATURE_MONGO_OBJECTID_FORGE=0 or `--no-mongo-objectid-forge`
283
+ * via ZERO_FEATURE_MONGO_OBJECTID_FORGE=0 or `--no-mongo-objectid-forge`
300
284
  * for ablation. Implemented as a getter so the CLI `--features` flag
301
285
  * (which sets the env var inside the command action, AFTER this module
302
286
  * has been imported) is still honored at tool-dispatch time. Matches
303
287
  * the wpFingerprint pattern above. See packages/core/src/agent/objectid-forge.ts.
304
288
  */
305
289
  get mongoObjectIdForge() {
306
- return env("0SEC_FEATURE_MONGO_OBJECTID_FORGE", true);
290
+ return env("ZERO_FEATURE_MONGO_OBJECTID_FORGE", true);
307
291
  },
308
292
  /**
309
- * Live cloud-surface testing (0sec#925). Exposes `cloud_s3_probe` and
293
+ * Live cloud-surface testing (0#925). Exposes `cloud_s3_probe` and
310
294
  * `cloud_validate_credentials` to the attack agent so it can test S3 buckets
311
295
  * for public access + orphaned-bucket takeover and safely validate harvested
312
296
  * AWS credentials (read-only). All probes are anonymous or read/verify-only —
313
297
  * no writes, no data exfiltration beyond minimal proof.
314
298
  *
315
- * Default OFF (opt-in via 0SEC_FEATURE_CLOUD_SURFACE=1). Probing a target
299
+ * Default OFF (opt-in via ZERO_FEATURE_CLOUD_SURFACE=1). Probing a target
316
300
  * org's bucket-name space or validating its harvested credentials is recon
317
301
  * AGAINST THAT ORG, so it is deny-by-default at two layers: this enablement
318
302
  * flag, AND an engagement-scope check in the tool handlers (a configured
@@ -323,7 +307,7 @@ var features = {
323
307
  * mongoObjectIdForge pattern above. See packages/core/src/agent/cloud-surface.ts.
324
308
  */
325
309
  get cloudSurface() {
326
- return env("0SEC_FEATURE_CLOUD_SURFACE", false);
310
+ return env("ZERO_FEATURE_CLOUD_SURFACE", false);
327
311
  },
328
312
  /**
329
313
  * #978 (ADR-060) — agent fan-out. When ON, the agent gets the `start_scan`
@@ -331,11 +315,11 @@ var features = {
331
315
  * that run independently and report up the scan tree — the recursive
332
316
  * sub-agent orchestration. Default OFF: fan-out multiplies scans/cost, so it
333
317
  * stays opt-in even though the orchestrator enforces budget + a tree-level
334
- * cap (max children/depth). Enable with 0SEC_FEATURE_AGENT_FANOUT=1.
318
+ * cap (max children/depth). Enable with ZERO_FEATURE_AGENT_FANOUT=1.
335
319
  * Getter so the CLI `--features` flag is honored at dispatch time.
336
320
  */
337
321
  get agentFanout() {
338
- return env("0SEC_FEATURE_AGENT_FANOUT", false);
322
+ return env("ZERO_FEATURE_AGENT_FANOUT", false);
339
323
  },
340
324
  // ── Phase-2 offensive-engine feature flags (dev-live-engine-recovery) ──
341
325
  // Each gates a tool that RUNS/BUILDS untrusted code or WEAPONIZES. They are
@@ -347,21 +331,21 @@ var features = {
347
331
  /**
348
332
  * `memsafety_fuzz` — clones/builds/fuzzes a source tree (sanitizer builds +
349
333
  * a fuzz harness), executing attacker-adjacent build scripts and native
350
- * fuzz targets. Default OFF; opt in via 0SEC_FEATURE_MEMSAFETY=1. Building
334
+ * fuzz targets. Default OFF; opt in via ZERO_FEATURE_MEMSAFETY=1. Building
351
335
  * and running an untrusted tree is code execution, so it is deny-by-default
352
336
  * behind this flag AND an engagement scope.
353
337
  */
354
338
  get memsafetyFuzz() {
355
- return env("0SEC_FEATURE_MEMSAFETY", false);
339
+ return env("ZERO_FEATURE_MEMSAFETY", false);
356
340
  },
357
341
  /**
358
342
  * `npm_dynamic_discovery` — installs and RUNS untrusted npm packages under
359
343
  * instrumentation to observe malicious install/runtime behaviour. Executing
360
344
  * arbitrary package code is the whole point, so it is deny-by-default behind
361
- * this flag AND an engagement scope. Opt in via 0SEC_FEATURE_NPM_DISCOVERY=1.
345
+ * this flag AND an engagement scope. Opt in via ZERO_FEATURE_NPM_DISCOVERY=1.
362
346
  */
363
347
  get npmDynamicDiscovery() {
364
- return env("0SEC_FEATURE_NPM_DISCOVERY", false);
348
+ return env("ZERO_FEATURE_NPM_DISCOVERY", false);
365
349
  },
366
350
  /**
367
351
  * `weaponize_kernel` — the kernel-exploit weaponization ladder. It only ever
@@ -369,19 +353,19 @@ var features = {
369
353
  * present. Highest-caution capability: deny-by-default behind this flag AND
370
354
  * an engagement scope AND a runtime artifact-presence check (the handler
371
355
  * refuses when the kernel-VM assets are absent). Opt in via
372
- * 0SEC_FEATURE_KERNEL_WEAPONIZE=1.
356
+ * ZERO_FEATURE_KERNEL_WEAPONIZE=1.
373
357
  */
374
358
  get kernelWeaponize() {
375
- return env("0SEC_FEATURE_KERNEL_WEAPONIZE", false);
359
+ return env("ZERO_FEATURE_KERNEL_WEAPONIZE", false);
376
360
  },
377
361
  /**
378
362
  * `cve_adapt` — adapts a public CVE PoC to the target and RUNS it to confirm
379
363
  * exploitability. Running an adapted exploit is code execution against the
380
364
  * target, so it is deny-by-default behind this flag AND an engagement scope.
381
- * Opt in via 0SEC_FEATURE_CVE_ADAPT=1.
365
+ * Opt in via ZERO_FEATURE_CVE_ADAPT=1.
382
366
  */
383
367
  get cveAdapt() {
384
- return env("0SEC_FEATURE_CVE_ADAPT", false);
368
+ return env("ZERO_FEATURE_CVE_ADAPT", false);
385
369
  },
386
370
  /**
387
371
  * Anti-honeypot flag-shape validator. When the agent calls the `done`
@@ -392,7 +376,7 @@ var features = {
392
376
  *
393
377
  * Default ON because legitimate flags pass the shape check trivially
394
378
  * and the false-positive rate on real flags should be near zero. Turn
395
- * off via `0SEC_FEATURE_DECOY_DETECTION=0` or the CLI flag
379
+ * off via `ZERO_FEATURE_DECOY_DETECTION=0` or the CLI flag
396
380
  * `--no-decoy-detection` for ablation/testing.
397
381
  *
398
382
  * Implemented as a getter so the CLI flag (which flips the env var
@@ -402,7 +386,7 @@ var features = {
402
386
  * packages/core/src/agent/flag-validator.ts.
403
387
  */
404
388
  get decoyDetection() {
405
- return env("0SEC_FEATURE_DECOY_DETECTION", true);
389
+ return env("ZERO_FEATURE_DECOY_DETECTION", true);
406
390
  },
407
391
  // ── Always-on triage filters (default ON, ablatable for A/B testing) ──
408
392
  /**
@@ -411,13 +395,13 @@ var features = {
411
395
  * sink names and rejects findings that look like "the function did its job".
412
396
  *
413
397
  * Default ON because that's the existing v0.6.0 behavior. Can be disabled
414
- * via 0SEC_FEATURE_HOLDING_IT_WRONG=0 to test whether this filter is
398
+ * via ZERO_FEATURE_HOLDING_IT_WRONG=0 to test whether this filter is
415
399
  * suppressing real signal — the ceiling-analysis from 2026-04-06 identified
416
400
  * this as the strongest candidate for the unexplained XBOW finding-density
417
401
  * collapse from 14 → 4 between `features=none` and `features=all`.
418
402
  */
419
403
  get holdingItWrong() {
420
- return env("0SEC_FEATURE_HOLDING_IT_WRONG", true);
404
+ return env("ZERO_FEATURE_HOLDING_IT_WRONG", true);
421
405
  },
422
406
  /**
423
407
  * `evidence_completeness <= 0.5` reject (`packages/core/src/agentic-scanner.ts:591`).
@@ -425,10 +409,10 @@ var features = {
425
409
  * gather enough cross-source evidence (request + response + analysis + ...).
426
410
  *
427
411
  * Default ON because that's the existing v0.6.0 behavior. Can be disabled
428
- * via 0SEC_FEATURE_EVIDENCE_GATE=0 for ablation.
412
+ * via ZERO_FEATURE_EVIDENCE_GATE=0 for ablation.
429
413
  */
430
414
  get evidenceGate() {
431
- return env("0SEC_FEATURE_EVIDENCE_GATE", true);
415
+ return env("ZERO_FEATURE_EVIDENCE_GATE", true);
432
416
  },
433
417
  /**
434
418
  * Learned per-finding triage router (`packages/core/src/triage/learned-router.ts`).
@@ -439,17 +423,17 @@ var features = {
439
423
  * the scan's slice type (xbow-wb, xbow-bb, npm).
440
424
  *
441
425
  * Default OFF until the router is validated via A/B testing on xbow-bench
442
- * and npm-bench. See 0sec#113 for the design doc.
426
+ * and npm-bench. See 0#113 for the design doc.
443
427
  */
444
428
  get learnedRouter() {
445
- return env("0SEC_FEATURE_LEARNED_ROUTER", false);
429
+ return env("ZERO_FEATURE_LEARNED_ROUTER", false);
446
430
  },
447
431
  /**
448
432
  * Dynamic per-finding triage routing (`packages/core/src/triage/router/`).
449
433
  * When enabled, every finding is sent through a `RouterModel` that
450
434
  * decides which subset of the 11 triage layers to invoke for that
451
435
  * specific finding. v0 ships an explicit-rule router encoded from the
452
- * 0sec#72 per-profile ablation; a learned classifier replaces the
436
+ * 0#72 per-profile ablation; a learned classifier replaces the
453
437
  * rules in a follow-up PR without touching the dispatch site.
454
438
  *
455
439
  * Distinct from `learnedRouter` above: `learnedRouter` is the XGBoost
@@ -458,25 +442,25 @@ var features = {
458
442
  * the dispatch router gates which layers run AFTER the TP/FP score
459
443
  * model has spoken.
460
444
  *
461
- * Default OFF — opt in via 0SEC_FEATURE_DYNAMIC_TRIAGE=1. See
462
- * 0sec#113 for the design doc and 0sec#67 for the joint paper plan.
445
+ * Default OFF — opt in via ZERO_FEATURE_DYNAMIC_TRIAGE=1. See
446
+ * 0#113 for the design doc and 0#67 for the joint paper plan.
463
447
  */
464
448
  get dynamicTriageRouting() {
465
- return env("0SEC_FEATURE_DYNAMIC_TRIAGE", false);
449
+ return env("ZERO_FEATURE_DYNAMIC_TRIAGE", false);
466
450
  },
467
451
  /**
468
452
  * Opt-in cloud-sink webhook integration (`packages/core/src/cloud-sink.ts`).
469
- * When enabled AND the user has set 0SEC_CLOUD_SINK + 0SEC_CLOUD_SCAN_ID,
453
+ * When enabled AND the user has set ZERO_CLOUD_SINK + ZERO_CLOUD_SCAN_ID,
470
454
  * every finding and the final scan report are POSTed to the configured
471
455
  * remote endpoint in real time.
472
456
  *
473
457
  * Default ON so the env-var trio is sufficient to enable streaming, but the
474
458
  * flag exists so operators can force-disable the integration in environments
475
459
  * where outbound HTTP from the scanner is not desired (e.g. air-gapped CI).
476
- * Disable via 0SEC_FEATURE_CLOUD_SINK=0.
460
+ * Disable via ZERO_FEATURE_CLOUD_SINK=0.
477
461
  */
478
462
  get cloudSink() {
479
- return env("0SEC_FEATURE_CLOUD_SINK", true);
463
+ return env("ZERO_FEATURE_CLOUD_SINK", true);
480
464
  },
481
465
  /**
482
466
  * Pre-recon CVE check (`packages/core/src/pre-recon-cve.ts`).
@@ -487,10 +471,10 @@ var features = {
487
471
  * where the agent has source access but no concrete leads.
488
472
  *
489
473
  * Default ON in white-box mode (no-op in black-box). Disable via
490
- * 0SEC_FEATURE_PRE_RECON_CVE=0 for ablation.
474
+ * ZERO_FEATURE_PRE_RECON_CVE=0 for ablation.
491
475
  */
492
476
  get preReconCve() {
493
- return env("0SEC_FEATURE_PRE_RECON_CVE", true);
477
+ return env("ZERO_FEATURE_PRE_RECON_CVE", true);
494
478
  },
495
479
  /**
496
480
  * Deterministic web-recon pre-pass (`packages/core/src/stages/web-recon-prepass.ts`).
@@ -501,25 +485,25 @@ var features = {
501
485
  * checks. It EMITS findings directly for what it can prove and injects a
502
486
  * "pursue these leads" block into the system prompt for what it can only hint.
503
487
  *
504
- * Default ON (no-op in non-web modes). Gated behind 0SEC_FEATURE_WEB_RECON
488
+ * Default ON (no-op in non-web modes). Gated behind ZERO_FEATURE_WEB_RECON
505
489
  * so it can be disabled for ablation or offline runs. Implemented as a getter
506
490
  * so the CLI `--features` flag (which sets the env var inside the command
507
491
  * action, AFTER this module has been imported) is honored at stage time.
508
492
  */
509
493
  get webRecon() {
510
- return env("0SEC_FEATURE_WEB_RECON", true);
494
+ return env("ZERO_FEATURE_WEB_RECON", true);
511
495
  },
512
496
  /**
513
497
  * Best-effort target-history preflight for source review. When a local repo
514
- * path is known, 0sec infers repository/package/product hints, queries live
498
+ * path is known, 0 infers repository/package/product hints, queries live
515
499
  * prior-vulnerability intel, and injects a compact audit-graph summary into
516
500
  * the review prompt before the agent starts.
517
501
  *
518
502
  * Default ON for white-box/source-review modes. Disable via
519
- * 0SEC_FEATURE_TARGET_HISTORY_PRESEED=0 for offline or ablation runs.
503
+ * ZERO_FEATURE_TARGET_HISTORY_PRESEED=0 for offline or ablation runs.
520
504
  */
521
505
  get targetHistoryPreseed() {
522
- return env("0SEC_FEATURE_TARGET_HISTORY_PRESEED", true);
506
+ return env("ZERO_FEATURE_TARGET_HISTORY_PRESEED", true);
523
507
  },
524
508
  /**
525
509
  * Preserve credential / exploit-bearing messages verbatim during
@@ -534,15 +518,15 @@ var features = {
534
518
  * (a handful of extra messages preserved verbatim in the user
535
519
  * compaction-summary block) is small. BoxPwnr-inspired: see
536
520
  * `src/boxpwnr/solvers/single_loop_compactation.py` in 0ca/BoxPwnr,
537
- * and 0sec#229 for the design discussion.
521
+ * and 0#229 for the design discussion.
538
522
  *
539
523
  * Implemented as a getter so the CLI `--features` flag — which sets
540
524
  * the env var inside the command action AFTER this module is imported
541
525
  * — is still honored at compaction time. Disable via
542
- * 0SEC_FEATURE_PRESERVE_CRITICAL_MESSAGES=0 for ablation.
526
+ * ZERO_FEATURE_PRESERVE_CRITICAL_MESSAGES=0 for ablation.
543
527
  */
544
528
  get preserveCriticalMessages() {
545
- return env("0SEC_FEATURE_PRESERVE_CRITICAL_MESSAGES", true);
529
+ return env("ZERO_FEATURE_PRESERVE_CRITICAL_MESSAGES", true);
546
530
  },
547
531
  /**
548
532
  * Two-stage budget-warning injection in the agent loop (#408).
@@ -558,14 +542,14 @@ var features = {
558
542
  * single short user-message injection at two specific turn boundaries,
559
543
  * and the win on long benchmarks (clean handoff instead of stray
560
544
  * exploration on the last turn) is well-documented in Strix's
561
- * implementation. Disable via 0SEC_FEATURE_BUDGET_WARNINGS=0 for
545
+ * implementation. Disable via ZERO_FEATURE_BUDGET_WARNINGS=0 for
562
546
  * ablation. Implemented as a getter so the CLI `--features` flag —
563
547
  * which sets the env var inside the command action AFTER this module
564
548
  * is imported — is still honored at injection time (matches the
565
549
  * wpFingerprint / preserveCriticalMessages pattern).
566
550
  */
567
551
  get budgetWarnings() {
568
- return env("0SEC_FEATURE_BUDGET_WARNINGS", true);
552
+ return env("ZERO_FEATURE_BUDGET_WARNINGS", true);
569
553
  },
570
554
  /**
571
555
  * Per-file orchestration for the research and audit stages (#285).
@@ -579,14 +563,14 @@ var features = {
579
563
  * Trade-off: total token spend grows roughly N × per-file budget instead
580
564
  * of capped at a single session's budget. For a 50-file package, that
581
565
  * could be a 5-10× cost increase on research. Disable via
582
- * `0SEC_FEATURE_PER_ITEM_ORCHESTRATION=0` to revert to the shared-session
566
+ * `ZERO_FEATURE_PER_ITEM_ORCHESTRATION=0` to revert to the shared-session
583
567
  * behavior — useful for cost-bounded benchmarks.
584
568
  *
585
569
  * Implemented as a getter so the env var is honored at orchestration time
586
570
  * (matches the wpFingerprint / mongoObjectIdForge pattern).
587
571
  */
588
572
  get perItemOrchestration() {
589
- return env("0SEC_FEATURE_PER_ITEM_ORCHESTRATION", true);
573
+ return env("ZERO_FEATURE_PER_ITEM_ORCHESTRATION", true);
590
574
  },
591
575
  /**
592
576
  * JIT skill loading (`packages/core/src/agent/skills/`).
@@ -595,20 +579,21 @@ var features = {
595
579
  * them into working context mid-scan. Skills replace the monolithic
596
580
  * playbook injection with targeted, on-demand knowledge (#410, #457).
597
581
  *
598
- * Default OFF until the skill registry is validated via A/B testing.
582
+ * Default OFF unless an assigned cloud methodology manifest opts this run in.
583
+ * An explicit feature flag still takes precedence over that default.
599
584
  * Implemented as a getter so the CLI `--features` flag — which sets
600
585
  * the env var inside the command action, AFTER this module has been
601
586
  * imported — is still honored at tool-dispatch time.
602
587
  */
603
588
  get jitSkills() {
604
- return env("0SEC_FEATURE_JIT_SKILLS", false);
589
+ return env("ZERO_FEATURE_JIT_SKILLS", Boolean(process.env["ZERO_AUDIT_SKILLS_MANIFEST"]?.trim()));
605
590
  },
606
591
  /**
607
592
  * Execution-journal shadow mode (#494, first additive slice).
608
593
  *
609
594
  * When ON, the live agent loop ALSO writes append-only journal entries
610
595
  * (`tool_call`, `tool_result`, `finding`, `done`) to
611
- * `~/.0sec/runs/<scanId>/journal.jsonl` as it runs — a durable,
596
+ * `~/.0/runs/<scanId>/journal.jsonl` as it runs — a durable,
612
597
  * replayable trace alongside the existing in-memory conversation window.
613
598
  * This is strictly additive: the loop continues to drive off its own
614
599
  * conversation state, the journal is write-only here, and a failed
@@ -621,11 +606,11 @@ var features = {
621
606
  * moat-ablation harness before any A/B claim. Implemented as a getter so
622
607
  * the CLI `--features` flag (which sets the env var inside the command
623
608
  * action, AFTER this module has been imported) is honored at loop time.
624
- * Enable via 0SEC_FEATURE_EXECUTION_JOURNAL=1 or `--features
609
+ * Enable via ZERO_FEATURE_EXECUTION_JOURNAL=1 or `--features
625
610
  * execution-journal`.
626
611
  */
627
612
  get executionJournal() {
628
- return env("0SEC_FEATURE_EXECUTION_JOURNAL", false);
613
+ return env("ZERO_FEATURE_EXECUTION_JOURNAL", false);
629
614
  },
630
615
  /**
631
616
  * Execution-journal context routing (#494, slice 2).
@@ -641,7 +626,7 @@ var features = {
641
626
  * Independent of `executionJournal` (the shadow-WRITE flag) on purpose so
642
627
  * the moat-ablation harness can toggle write and route separately for a
643
628
  * clean A/B. Rehydrate is a READER, though, so it only does anything when
644
- * a journal was written for the run — it reads `~/.0sec/runs/<scanId>/
629
+ * a journal was written for the run — it reads `~/.0/runs/<scanId>/
645
630
  * journal.jsonl` regardless of how it got there (shadow mode this slice,
646
631
  * or specialists in a later slice). When the journal is missing, empty, or
647
632
  * corrupt the loop falls back to the existing DB-blob / fresh-prompt
@@ -654,11 +639,11 @@ var features = {
654
639
  * must be explicitly opted into before any A/B claim. Implemented as a
655
640
  * getter so the CLI `--features` flag (which sets the env var inside the
656
641
  * command action, AFTER this module has been imported) is honored at loop
657
- * time. Enable via 0SEC_FEATURE_JOURNAL_REHYDRATE=1 or `--features
642
+ * time. Enable via ZERO_FEATURE_JOURNAL_REHYDRATE=1 or `--features
658
643
  * journal-rehydrate`.
659
644
  */
660
645
  get journalRehydrate() {
661
- return env("0SEC_FEATURE_JOURNAL_REHYDRATE", false);
646
+ return env("ZERO_FEATURE_JOURNAL_REHYDRATE", false);
662
647
  },
663
648
  /**
664
649
  * Loot / foothold ledger for opportunistic exploit chaining (#567).
@@ -678,14 +663,14 @@ var features = {
678
663
  * tool), matches the `preserveCriticalMessages` rationale — recovering a
679
664
  * credential in turn 12 that's needed in turn 38 is a large win on long-tail
680
665
  * challenges — and the cost (a short, size-capped block per turn) is small.
681
- * Disable via 0SEC_FEATURE_LOOT_LEDGER=0 or `--no-loot-ledger` for
666
+ * Disable via ZERO_FEATURE_LOOT_LEDGER=0 or `--no-loot-ledger` for
682
667
  * ablation. Implemented as a getter so the CLI `--features` flag (which sets
683
668
  * the env var inside the command action, AFTER this module has been
684
669
  * imported) is honored at tool-dispatch / injection time — matches the
685
670
  * wpFingerprint / preserveCriticalMessages pattern.
686
671
  */
687
672
  get lootLedger() {
688
- return env("0SEC_FEATURE_LOOT_LEDGER", true);
673
+ return env("ZERO_FEATURE_LOOT_LEDGER", true);
689
674
  },
690
675
  /**
691
676
  * Typed TODO / plan ledger (`packages/core/src/agent/task-ledger.ts`).
@@ -704,13 +689,13 @@ var features = {
704
689
  * failure mode of an unused tool is a few hundred wasted schema tokens
705
690
  * rather than wrong behavior. Note for whoever publishes benchmark numbers
706
691
  * next: this DOES change the default tool list, so re-baseline before
707
- * quoting a figure across this change. Disable via 0SEC_FEATURE_AGENT_PLAN=0
692
+ * quoting a figure across this change. Disable via ZERO_FEATURE_AGENT_PLAN=0
708
693
  * or `--features no-agent-plan` for ablation. Getter so the CLI `--features`
709
694
  * flag (which sets the env var AFTER this module is imported) is honored at
710
695
  * tool-dispatch time.
711
696
  */
712
697
  get agentPlan() {
713
- return env("0SEC_FEATURE_AGENT_PLAN", false);
698
+ return env("ZERO_FEATURE_AGENT_PLAN", false);
714
699
  },
715
700
  /**
716
701
  * Task-drift detection (`packages/core/src/agent/drift.ts`).
@@ -733,10 +718,10 @@ var features = {
733
718
  * to a newly-discovered lead is lexically indistinguishable from a derail.
734
719
  * Repo convention is explicit that behavior-steering features stay opt-in
735
720
  * until A/B'd, and this is squarely one. Enable via
736
- * 0SEC_FEATURE_DRIFT_DETECTION=1 or `--features drift-detection`.
721
+ * ZERO_FEATURE_DRIFT_DETECTION=1 or `--features drift-detection`.
737
722
  */
738
723
  get driftDetection() {
739
- return env("0SEC_FEATURE_DRIFT_DETECTION", false);
724
+ return env("ZERO_FEATURE_DRIFT_DETECTION", false);
740
725
  },
741
726
  /**
742
727
  * OAST out-of-band interaction collaborator + oracle (#659).
@@ -748,12 +733,12 @@ var features = {
748
733
  * evidence and feeds the loot ledger.
749
734
  *
750
735
  * Default OFF — the tools are inert without a deployed collaborator. Enable
751
- * with 0SEC_FEATURE_OAST=1 AND point 0SEC_OAST_URL at the self-hosted
736
+ * with ZERO_FEATURE_OAST=1 AND point ZERO_OAST_URL at the self-hosted
752
737
  * collaborator server (see packages/core/src/oast/server.ts). Getter (not a
753
738
  * const) so the CLI `--features` flag is honored at tool-dispatch time.
754
739
  */
755
740
  get oastCollaborator() {
756
- return env("0SEC_FEATURE_OAST", false);
741
+ return env("ZERO_FEATURE_OAST", false);
757
742
  },
758
743
  /**
759
744
  * Anthropic prompt caching (`cache_control: {type: "ephemeral"}`) over the
@@ -776,17 +761,17 @@ var features = {
776
761
  * never see an Anthropic-shaped field regardless of this flag (see
777
762
  * `providerSupportsPromptCache`).
778
763
  *
779
- * Disable via 0SEC_FEATURE_PROMPT_CACHE=0 — worth doing only to isolate a
764
+ * Disable via ZERO_FEATURE_PROMPT_CACHE=0 — worth doing only to isolate a
780
765
  * suspected provider-side caching bug, or to measure the uncached baseline.
781
766
  * Implemented as a getter so a late env mutation (CLI `--features`, which
782
767
  * runs after this module is imported) is honoured at request-build time.
783
768
  */
784
769
  get promptCache() {
785
- return env("0SEC_FEATURE_PROMPT_CACHE", true);
770
+ return env("ZERO_FEATURE_PROMPT_CACHE", true);
786
771
  }
787
772
  };
788
773
  function presetRaisesDefault(key) {
789
- const raw = process.env["0SEC_FEATURE_PRESET"];
774
+ const raw = process.env["ZERO_FEATURE_PRESET"];
790
775
  if (!raw)
791
776
  return false;
792
777
  const preset = resolveFeaturePreset(raw);
@@ -884,7 +869,7 @@ function sanitizeFields(input) {
884
869
  var LEVEL_RANK = { info: 10, warn: 20, error: 30 };
885
870
  var OFF_RANK = Number.POSITIVE_INFINITY;
886
871
  function minimumRank() {
887
- const raw = process.env["0SEC_DIAG_LEVEL"];
872
+ const raw = process.env["ZERO_DIAG_LEVEL"];
888
873
  if (!raw)
889
874
  return LEVEL_RANK.info;
890
875
  switch (raw.trim().toLowerCase()) {
@@ -904,9 +889,9 @@ function minimumRank() {
904
889
  function formatDiagnosticLine(event) {
905
890
  const keys = Object.keys(event.fields);
906
891
  if (keys.length === 0)
907
- return `[0sec] ${event.message}`;
892
+ return `[0] ${event.message}`;
908
893
  const rendered = keys.map((k) => `${k}=${event.fields[k]}`).join(" ");
909
- return `[0sec] ${event.message} (${rendered})`;
894
+ return `[0] ${event.message} (${rendered})`;
910
895
  }
911
896
  var stderrDiagnosticSink = {
912
897
  emit(event) {
@@ -1087,19 +1072,22 @@ function loadCloudCredentials(opts = {}) {
1087
1072
  const env2 = opts.env ?? process.env;
1088
1073
  const warn = opts.warn ?? ((m) => process.stderr.write(`${m}
1089
1074
  `));
1090
- const envTok = env2["0SEC_CLOUD_TOKEN"]?.trim();
1075
+ const envTok = env2["ZERO_CLOUD_TOKEN"]?.trim() || env2["0SEC_CLOUD_TOKEN"]?.trim();
1091
1076
  if (envTok) {
1092
- const envHost = normaliseHost(env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
1077
+ const envHost = normaliseHost(env2["ZERO_CLOUD_HOST"]?.trim() ?? env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
1078
+ if (!env2["ZERO_CLOUD_TOKEN"]?.trim()) {
1079
+ warn("[0 cloud] using legacy 0SEC_CLOUD_* credentials; re-run `0 auth login` to migrate to ZERO_CLOUD_*.");
1080
+ }
1093
1081
  return { host: envHost, token: envTok, source: "env" };
1094
1082
  }
1095
- const path = join(homeStateDir(opts.homeDir), "cloud.env");
1083
+ const path = join(cloudStateDir(opts.homeDir, env2), "cloud.env");
1096
1084
  let raw;
1097
1085
  try {
1098
1086
  raw = readFileSync(path, "utf-8");
1099
1087
  } catch (err) {
1100
1088
  const code = err.code;
1101
1089
  if (code === "ENOENT") {
1102
- throw new CloudAuthMissingError(`0sec-cloud credentials not found. Run \`0sec auth login\` or set 0SEC_CLOUD_TOKEN in env, or create ${path} (chmod 600) with 0SEC_CLOUD_TOKEN=\u2026 (optionally 0SEC_CLOUD_HOST=\u2026).`);
1090
+ throw new CloudAuthMissingError(`0-cloud credentials not found. Run \`${env2["ZERO_DEV_SOURCE_ROOT"]?.trim() ? "0dev" : "0"} auth login\`.`);
1103
1091
  }
1104
1092
  throw err;
1105
1093
  }
@@ -1107,22 +1095,25 @@ function loadCloudCredentials(opts = {}) {
1107
1095
  const st = statSync(path);
1108
1096
  const mode = st.mode & 511;
1109
1097
  if (mode !== 384) {
1110
- warn(`[0sec cloud] WARNING: ${path} mode is ${mode.toString(8).padStart(3, "0")} (expected 600). Run: chmod 600 ${path}`);
1098
+ warn(`[0 cloud] WARNING: ${path} mode is ${mode.toString(8).padStart(3, "0")} (expected 600). Run: chmod 600 ${path}`);
1111
1099
  }
1112
1100
  } catch {
1113
1101
  }
1114
1102
  const parsed = parseEnvFile(raw);
1115
- const fileTok = parsed["0SEC_CLOUD_TOKEN"]?.trim();
1103
+ const fileTok = parsed["ZERO_CLOUD_TOKEN"]?.trim() || parsed["0SEC_CLOUD_TOKEN"]?.trim();
1116
1104
  if (!fileTok) {
1117
- throw new CloudAuthMissingError(`0sec-cloud credentials in ${path} are incomplete: 0SEC_CLOUD_TOKEN is required.`);
1105
+ throw new CloudAuthMissingError(`0-cloud credentials in ${path} are incomplete: ZERO_CLOUD_TOKEN is required.`);
1106
+ }
1107
+ if (!parsed["ZERO_CLOUD_TOKEN"]?.trim()) {
1108
+ warn("[0 cloud] using legacy 0SEC_CLOUD_* credentials; re-run `0 auth login` to migrate to ZERO_CLOUD_*.");
1118
1109
  }
1119
- const fileHost = normaliseHost(parsed["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
1110
+ const fileHost = normaliseHost(parsed["ZERO_CLOUD_HOST"]?.trim() ?? parsed["0SEC_CLOUD_HOST"]?.trim() ?? env2["ZERO_CLOUD_HOST"]?.trim() ?? env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
1120
1111
  return { host: fileHost, token: fileTok, source: "file" };
1121
1112
  }
1122
1113
  function normaliseHost(host) {
1123
1114
  let h = host;
1124
1115
  if (!/^https?:\/\//.test(h)) {
1125
- throw new CloudAuthMissingError(`0SEC_CLOUD_HOST must be an http(s) URL (got ${JSON.stringify(host)}).`);
1116
+ throw new CloudAuthMissingError(`ZERO_CLOUD_HOST must be an http(s) URL (got ${JSON.stringify(host)}).`);
1126
1117
  }
1127
1118
  while (h.endsWith("/"))
1128
1119
  h = h.slice(0, -1);
@@ -1167,26 +1158,92 @@ var CloudError = class extends Error {
1167
1158
  };
1168
1159
  var CloudUnauthorizedError = class extends CloudError {
1169
1160
  constructor(path) {
1170
- super(`0sec-cloud auth rejected (HTTP 401) on ${path}. Run \`0sec auth login\` to refresh.`, 401, path);
1161
+ super(`0-cloud auth rejected (HTTP 401) on ${path}. Run \`0 auth login\` to refresh.`, 401, path);
1171
1162
  this.name = "CloudUnauthorizedError";
1172
1163
  }
1173
1164
  };
1174
1165
  var CloudForbiddenError = class extends CloudError {
1175
1166
  constructor(path) {
1176
- super(`0sec-cloud forbidden (HTTP 403) on ${path}. Token lacks scope for this resource.`, 403, path);
1167
+ super(`0-cloud forbidden (HTTP 403) on ${path}. Token lacks scope for this resource.`, 403, path);
1177
1168
  this.name = "CloudForbiddenError";
1178
1169
  }
1179
1170
  };
1180
1171
  var CloudNetworkError = class extends CloudError {
1181
1172
  constructor(message, path) {
1182
- super(`0sec-cloud network error on ${path}: ${message}`, void 0, path);
1173
+ super(`0-cloud network error on ${path}: ${message}`, void 0, path);
1183
1174
  this.name = "CloudNetworkError";
1184
1175
  }
1185
1176
  };
1177
+ function isUsageAccount(raw) {
1178
+ if (!raw || typeof raw !== "object")
1179
+ return false;
1180
+ const obj = raw;
1181
+ if (obj.schemaVersion !== "usage-v2")
1182
+ return false;
1183
+ if (!isUtcDate(obj.snapshotAt))
1184
+ return false;
1185
+ if (!obj.scope || typeof obj.scope !== "object")
1186
+ return false;
1187
+ if (typeof obj.scope.orgId !== "string")
1188
+ return false;
1189
+ if (typeof obj.state !== "string")
1190
+ return false;
1191
+ if (!["ready", "disabled", "unavailable", "restricted"].includes(obj.state))
1192
+ return false;
1193
+ if (obj.reason !== null && typeof obj.reason !== "string")
1194
+ return false;
1195
+ const plan = obj.plan;
1196
+ if (!plan || typeof plan !== "object")
1197
+ return false;
1198
+ if (typeof plan.id !== "string" && plan.id !== null)
1199
+ return false;
1200
+ if (plan.id !== null && !["pro", "gold", "enterprise"].includes(plan.id))
1201
+ return false;
1202
+ if (typeof plan.name !== "string" && plan.name !== null)
1203
+ return false;
1204
+ if (!isUsd(plan.monthlyPriceUsd))
1205
+ return false;
1206
+ const included = obj.included;
1207
+ if (!included || typeof included !== "object")
1208
+ return false;
1209
+ if (typeof included.state !== "string")
1210
+ return false;
1211
+ if (!["active", "exhausted", "none", "unavailable"].includes(included.state))
1212
+ return false;
1213
+ if (included.usedPercent !== null && typeof included.usedPercent !== "number")
1214
+ return false;
1215
+ if (included.usedPercent !== null && (!Number.isFinite(included.usedPercent) || included.usedPercent < 0 || included.usedPercent > 100))
1216
+ return false;
1217
+ if (included.resetsAt !== null && !isUtcDate(included.resetsAt))
1218
+ return false;
1219
+ const prepaid = obj.prepaid;
1220
+ if (!prepaid || typeof prepaid !== "object")
1221
+ return false;
1222
+ if (!isUsd(prepaid.balanceUsd))
1223
+ return false;
1224
+ if (typeof prepaid.fallbackEnabled !== "boolean")
1225
+ return false;
1226
+ if (typeof obj.canManageBilling !== "boolean")
1227
+ return false;
1228
+ const admission = obj.admission;
1229
+ if (!admission || typeof admission !== "object")
1230
+ return false;
1231
+ if (typeof admission.eligible !== "boolean")
1232
+ return false;
1233
+ if (admission.reason !== null && typeof admission.reason !== "string")
1234
+ return false;
1235
+ return true;
1236
+ }
1237
+ function isUsd(value) {
1238
+ return value === null || typeof value === "string" && /^\d+(?:\.\d{1,9})?$/.test(value);
1239
+ }
1240
+ function isUtcDate(value) {
1241
+ return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/.test(value) && Number.isFinite(Date.parse(value));
1242
+ }
1186
1243
  function healthPath(host) {
1187
1244
  try {
1188
1245
  const hostname = new URL(host).hostname.toLowerCase();
1189
- if (hostname === "cloud.0sec.ai" || hostname === "cloud.0.security") {
1246
+ if (hostname === "cloud.0.ai" || hostname === "cloud.0.security") {
1190
1247
  return "/api/health";
1191
1248
  }
1192
1249
  } catch {
@@ -1219,25 +1276,46 @@ var CloudClient = class {
1219
1276
  return this.getJson("/api/inference/v1/models");
1220
1277
  }
1221
1278
  /**
1222
- * Fetch the organization's hosted inference credit availability. The service
1223
- * calculates the percentage from Autumn's current pool; holds reduce availability.
1224
- * Older gateways without percentage metadata remain explicitly unavailable.
1279
+ * Fetch the organization's credit account — usage-v2 shape including
1280
+ * plan, included allowance, prepaid balance, and admission status.
1281
+ *
1282
+ * Returns `null` when the response is a recognised HTTP 200 (customer is
1283
+ * authenticated) but the payload is missing, legacy, or structurally
1284
+ * unrecognised — not an auth failure. HTTP 401/403 still throw the
1285
+ * existing typed errors so the caller can distinguish a credential
1286
+ * problem from unsupported credit data.
1287
+ *
1288
+ * Monetary amounts are decimal strings (no Number coercion); the
1289
+ * caller preserves them for exact display.
1225
1290
  */
1226
1291
  async getInferenceAccount() {
1227
- const account = await this.getJson("/api/inference/account");
1228
- const credits = account.credits;
1229
- if (!credits || typeof credits !== "object" || typeof credits.featureId !== "string" || !credits.featureId.trim() || typeof credits.remaining !== "number" || !Number.isFinite(credits.remaining) || credits.remaining < 0 || credits.granted !== null && (typeof credits.granted !== "number" || !Number.isFinite(credits.granted) || credits.granted < 0)) {
1230
- return { ...account, credits: null };
1231
- }
1232
- const remainingPercent = typeof credits.remainingPercent === "number" && Number.isFinite(credits.remainingPercent) && credits.remainingPercent >= 0 && credits.remainingPercent <= 100 && credits.granted !== null && credits.granted > 0 && credits.remaining <= credits.granted ? credits.remainingPercent : null;
1233
- const nextResetAt = typeof credits.nextResetAt === "number" && Number.isSafeInteger(credits.nextResetAt) && credits.nextResetAt > 0 && credits.nextResetAt <= 864e13 ? credits.nextResetAt : null;
1234
- return { ...account, credits: {
1235
- featureId: credits.featureId,
1236
- granted: credits.granted,
1237
- remaining: credits.remaining,
1238
- remainingPercent,
1239
- nextResetAt
1240
- } };
1292
+ const raw = await this.getJson("/api/inference/account");
1293
+ if (!isUsageAccount(raw))
1294
+ return null;
1295
+ const { plan, included, prepaid, admission } = raw;
1296
+ return {
1297
+ schemaVersion: raw.schemaVersion,
1298
+ snapshotAt: raw.snapshotAt,
1299
+ scope: { orgId: raw.scope.orgId },
1300
+ state: raw.state,
1301
+ reason: raw.reason,
1302
+ plan: {
1303
+ id: plan.id,
1304
+ name: plan.name,
1305
+ monthlyPriceUsd: plan.monthlyPriceUsd
1306
+ },
1307
+ included: {
1308
+ state: included.state,
1309
+ usedPercent: included.usedPercent,
1310
+ resetsAt: included.resetsAt
1311
+ },
1312
+ prepaid: {
1313
+ balanceUsd: prepaid.balanceUsd,
1314
+ fallbackEnabled: prepaid.fallbackEnabled
1315
+ },
1316
+ canManageBilling: raw.canManageBilling,
1317
+ admission: { eligible: admission.eligible, reason: admission.reason }
1318
+ };
1241
1319
  }
1242
1320
  /**
1243
1321
  * Fetch request-level usage metadata for the operator's hosted
@@ -1247,9 +1325,87 @@ var CloudClient = class {
1247
1325
  async getInferenceUsage() {
1248
1326
  return this.getJson("/api/inference/usage");
1249
1327
  }
1328
+ // ── Audit-skills helpers (#audit-skills) ──
1329
+ /** List all audit skills for the authenticated organization. */
1330
+ async listAuditSkills() {
1331
+ return this.getJson("/api/audit-skills");
1332
+ }
1333
+ /**
1334
+ * Get a single audit skill with its revision history and project
1335
+ * assignments.
1336
+ */
1337
+ async getAuditSkill(id) {
1338
+ return this.getJson(`/api/audit-skills/${encodeURIComponent(id)}`);
1339
+ }
1340
+ /** Create a new markdown-based audit skill. */
1341
+ async createAuditSkill(input) {
1342
+ return this.postJson("/api/audit-skills", input);
1343
+ }
1344
+ /** Import an audit skill from a GitHub repository. */
1345
+ async importAuditSkillFromGithub(input) {
1346
+ return this.postJson("/api/audit-skills/import", input);
1347
+ }
1348
+ /** Create a new revision of an audit skill (CAS — 409 on stale expectedRevision). */
1349
+ async createAuditSkillRevision(id, input) {
1350
+ return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/revisions`, input);
1351
+ }
1352
+ /** Re-fetch the skill from its original GitHub source. */
1353
+ async syncAuditSkill(id, expectedRevision) {
1354
+ return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/sync`, { expectedRevision });
1355
+ }
1356
+ /** Pin an audit skill revision to a project for future scans. */
1357
+ async assignAuditSkill(id, projectId, revisionId) {
1358
+ return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/projects/${encodeURIComponent(projectId)}`, { revisionId });
1359
+ }
1360
+ /** Unpin an audit skill from a project (future scans no longer use it). */
1361
+ async unassignAuditSkill(id, projectId) {
1362
+ return this.deleteJson(`/api/audit-skills/${encodeURIComponent(id)}/projects/${encodeURIComponent(projectId)}`);
1363
+ }
1364
+ /** Archive an audit skill (disables future bindings, preserves history). */
1365
+ async archiveAuditSkill(id) {
1366
+ await this.deleteJson(`/api/audit-skills/${encodeURIComponent(id)}`);
1367
+ }
1368
+ /**
1369
+ * List audit skills available to a specific project, along with current
1370
+ * assignments for that project. The project must belong to the caller's org.
1371
+ */
1372
+ async listAuditSkillsByProject(projectId) {
1373
+ return this.getJson(`/api/audit-skills/by-project/${encodeURIComponent(projectId)}`);
1374
+ }
1375
+ /**
1376
+ * Generic JSON DELETE helper with the same error mapping as getJson/postJson.
1377
+ * Used by `0 service disconnect` to remove scan schedules.
1378
+ */
1379
+ async deleteJson(path) {
1380
+ const url = `${this.host}${path}`;
1381
+ let res;
1382
+ try {
1383
+ res = await this.fetchImpl(url, {
1384
+ method: "DELETE",
1385
+ headers: { ...this.headers(), "Content-Type": "application/json" }
1386
+ });
1387
+ } catch (err) {
1388
+ const msg = err instanceof Error ? err.message : String(err);
1389
+ throw new CloudNetworkError(this.scrub(msg), path);
1390
+ }
1391
+ if (!res.ok) {
1392
+ let code;
1393
+ try {
1394
+ const parsed = await res.json();
1395
+ const raw = typeof parsed?.error === "object" ? parsed.error?.code : void 0;
1396
+ if (typeof raw === "string" && raw.length > 0)
1397
+ code = raw;
1398
+ } catch {
1399
+ }
1400
+ this.throwForStatus(res.status, path, code);
1401
+ }
1402
+ if (res.status === 204)
1403
+ return void 0;
1404
+ return await res.json();
1405
+ }
1250
1406
  /**
1251
1407
  * Generic JSON POST helper with the same error mapping as getJson.
1252
- * Used by `0sec connect` to enqueue scans and schedules.
1408
+ * Used by `0 connect` to enqueue scans and schedules.
1253
1409
  */
1254
1410
  async postJson(path, body) {
1255
1411
  const url = `${this.host}${path}`;
@@ -1313,7 +1469,7 @@ var CloudClient = class {
1313
1469
  throw new CloudUnauthorizedError(path);
1314
1470
  if (status === 403)
1315
1471
  throw new CloudForbiddenError(path);
1316
- throw new CloudError(`0sec-cloud request failed (HTTP ${status}${code ? ` ${code}` : ""}) on ${path}.`, status, path, code);
1472
+ throw new CloudError(`0-cloud request failed (HTTP ${status}${code ? ` ${code}` : ""}) on ${path}.`, status, path, code);
1317
1473
  }
1318
1474
  /**
1319
1475
  * Throw a typed error for non-2xx responses. Public so direct callers
@@ -1326,14 +1482,14 @@ var CloudClient = class {
1326
1482
  throw new CloudUnauthorizedError(path);
1327
1483
  if (res.status === 403)
1328
1484
  throw new CloudForbiddenError(path);
1329
- throw new CloudError(`0sec-cloud request failed (HTTP ${res.status}) on ${path}.`, res.status, path);
1485
+ throw new CloudError(`0-cloud request failed (HTTP ${res.status}) on ${path}.`, res.status, path);
1330
1486
  }
1331
1487
  // ── internals ──
1332
1488
  headers() {
1333
1489
  return {
1334
1490
  Authorization: `Bearer ${this.token}`,
1335
1491
  Accept: "application/json",
1336
- "User-Agent": `0sec-cli/${VERSION}`
1492
+ "User-Agent": `@0/cli/${VERSION}`
1337
1493
  };
1338
1494
  }
1339
1495
  /**
@@ -1355,6 +1511,54 @@ import { appendFileSync, existsSync, readFileSync as readFileSync2, renameSync,
1355
1511
  import { homedir } from "node:os";
1356
1512
  import { join as join2 } from "node:path";
1357
1513
 
1514
+ // packages/core/dist/runtime/hosted-request-queue.js
1515
+ var MAX_IN_FLIGHT = 4;
1516
+ var endpoints = /* @__PURE__ */ new Map();
1517
+ function acquireHostedRequestSlot(endpoint, token, signal) {
1518
+ signal?.throwIfAborted();
1519
+ let accounts = endpoints.get(endpoint);
1520
+ if (!accounts)
1521
+ endpoints.set(endpoint, accounts = /* @__PURE__ */ new Map());
1522
+ let queue = accounts.get(token);
1523
+ if (!queue)
1524
+ accounts.set(token, queue = { active: 0, waiting: [] });
1525
+ const accountQueues = accounts;
1526
+ const current = queue;
1527
+ return new Promise((resolve, reject) => {
1528
+ const abort = () => {
1529
+ const index = current.waiting.indexOf(waiter);
1530
+ if (index !== -1)
1531
+ current.waiting.splice(index, 1);
1532
+ reject(signal.reason);
1533
+ };
1534
+ const waiter = { grant: () => {
1535
+ signal?.removeEventListener("abort", abort);
1536
+ current.active++;
1537
+ let released = false;
1538
+ resolve(() => {
1539
+ if (released)
1540
+ return;
1541
+ released = true;
1542
+ current.active--;
1543
+ const next = current.waiting.shift();
1544
+ if (next)
1545
+ next.grant();
1546
+ else if (current.active === 0) {
1547
+ accountQueues.delete(token);
1548
+ if (accountQueues.size === 0)
1549
+ endpoints.delete(endpoint);
1550
+ }
1551
+ });
1552
+ } };
1553
+ if (current.active < MAX_IN_FLIGHT)
1554
+ waiter.grant();
1555
+ else {
1556
+ current.waiting.push(waiter);
1557
+ signal?.addEventListener("abort", abort, { once: true });
1558
+ }
1559
+ });
1560
+ }
1561
+
1358
1562
  // packages/core/dist/runtime/prompt-cache.js
1359
1563
  var MAX_CACHE_BREAKPOINTS = 4;
1360
1564
  var MESSAGE_CACHE_BREAKPOINTS = MAX_CACHE_BREAKPOINTS - 1;
@@ -1369,7 +1573,7 @@ function providerSupportsPromptCache(provider) {
1369
1573
  return readExtraCacheProviders().has(provider);
1370
1574
  }
1371
1575
  function readExtraCacheProviders() {
1372
- const raw = process.env["0SEC_PROMPT_CACHE_EXTRA_PROVIDERS"];
1576
+ const raw = process.env["ZERO_PROMPT_CACHE_EXTRA_PROVIDERS"];
1373
1577
  if (!raw)
1374
1578
  return /* @__PURE__ */ new Set();
1375
1579
  return new Set(raw.split(",").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0));
@@ -1438,7 +1642,7 @@ function isWireBlockArray(blocks) {
1438
1642
  }
1439
1643
  var azureRegionCache = /* @__PURE__ */ new Map();
1440
1644
  async function probeAzureRegion(baseUrl, apiKey, fetchImpl = fetch) {
1441
- const override = process.env["0SEC_REGION_OVERRIDE"];
1645
+ const override = process.env["ZERO_REGION_OVERRIDE"];
1442
1646
  if (override && override.trim().length > 0) {
1443
1647
  return override.trim();
1444
1648
  }
@@ -1500,7 +1704,7 @@ function prettyRegion(code) {
1500
1704
  };
1501
1705
  return map[code.toLowerCase()] ?? code;
1502
1706
  }
1503
- var PROVIDER_BANNER_KEY = /* @__PURE__ */ Symbol.for("0sec.core.loggedProviderStartup");
1707
+ var PROVIDER_BANNER_KEY = /* @__PURE__ */ Symbol.for("0.core.loggedProviderStartup");
1504
1708
  var loggedProviderStartup = (() => {
1505
1709
  const g = globalThis;
1506
1710
  if (!g[PROVIDER_BANNER_KEY])
@@ -1508,7 +1712,7 @@ var loggedProviderStartup = (() => {
1508
1712
  return g[PROVIDER_BANNER_KEY];
1509
1713
  })();
1510
1714
  function appendNativeTrace(record) {
1511
- const file = process.env["0SEC_TRACE_NATIVE_RESPONSES"];
1715
+ const file = process.env["ZERO_TRACE_NATIVE_RESPONSES"];
1512
1716
  if (!file)
1513
1717
  return;
1514
1718
  try {
@@ -1518,7 +1722,7 @@ function appendNativeTrace(record) {
1518
1722
  }
1519
1723
  }
1520
1724
  function shouldLogProviderStartup() {
1521
- return process.env["0SEC_SUPPRESS_PROVIDER_STARTUP_LOG"] !== "1";
1725
+ return process.env["ZERO_SUPPRESS_PROVIDER_STARTUP_LOG"] !== "1";
1522
1726
  }
1523
1727
  function isRetryableHttpStatus(status) {
1524
1728
  return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
@@ -1528,7 +1732,7 @@ var TRANSIENT_STREAM_ERROR_PATTERNS = [
1528
1732
  "response stream failed"
1529
1733
  ];
1530
1734
  function llmStreamMaxAttempts() {
1531
- const raw = process.env["0SEC_LLM_STREAM_MAX_ATTEMPTS"];
1735
+ const raw = process.env["ZERO_LLM_STREAM_MAX_ATTEMPTS"];
1532
1736
  if (raw == null || raw.trim() === "")
1533
1737
  return 3;
1534
1738
  const n = Number.parseInt(raw, 10);
@@ -1575,28 +1779,28 @@ function isRetryableTransportCode(code) {
1575
1779
  ].includes(code);
1576
1780
  }
1577
1781
  function llmMaxRetries() {
1578
- const raw = process.env["0SEC_LLM_MAX_RETRIES"];
1782
+ const raw = process.env["ZERO_LLM_MAX_RETRIES"];
1579
1783
  if (raw == null || raw.trim() === "")
1580
1784
  return 6;
1581
1785
  const n = Number.parseInt(raw, 10);
1582
1786
  return Number.isFinite(n) && n >= 0 ? n : 6;
1583
1787
  }
1584
1788
  function llmMaxRetryWaitMs() {
1585
- const raw = process.env["0SEC_LLM_MAX_RETRY_WAIT_MS"];
1789
+ const raw = process.env["ZERO_LLM_MAX_RETRY_WAIT_MS"];
1586
1790
  if (raw == null || raw.trim() === "")
1587
1791
  return 6e4;
1588
1792
  const n = Number.parseInt(raw, 10);
1589
1793
  return Number.isFinite(n) && n > 0 ? n : 6e4;
1590
1794
  }
1591
1795
  function llm429MaxRetries() {
1592
- const raw = process.env["0SEC_LLM_429_MAX_RETRIES"] ?? process.env["0SEC_LLM_MAX_RETRIES"];
1796
+ const raw = process.env["ZERO_LLM_429_MAX_RETRIES"] ?? process.env["ZERO_LLM_MAX_RETRIES"];
1593
1797
  if (raw == null || raw.trim() === "")
1594
1798
  return 12;
1595
1799
  const n = Number.parseInt(raw, 10);
1596
1800
  return Number.isFinite(n) && n >= 0 ? n : 12;
1597
1801
  }
1598
1802
  function llm429MaxRetryWaitMs() {
1599
- const raw = process.env["0SEC_LLM_429_MAX_RETRY_WAIT_MS"] ?? process.env["0SEC_LLM_MAX_RETRY_WAIT_MS"];
1803
+ const raw = process.env["ZERO_LLM_429_MAX_RETRY_WAIT_MS"] ?? process.env["ZERO_LLM_MAX_RETRY_WAIT_MS"];
1600
1804
  if (raw == null || raw.trim() === "")
1601
1805
  return 3e5;
1602
1806
  const n = Number.parseInt(raw, 10);
@@ -1707,14 +1911,14 @@ function parseUsageLimitReached(body) {
1707
1911
  return details;
1708
1912
  }
1709
1913
  function llmStreamIdleTimeoutMs() {
1710
- const raw = process.env["0SEC_LLM_STREAM_IDLE_TIMEOUT_MS"];
1914
+ const raw = process.env["ZERO_LLM_STREAM_IDLE_TIMEOUT_MS"];
1711
1915
  if (raw == null || raw.trim() === "")
1712
1916
  return 12e4;
1713
1917
  const n = Number.parseInt(raw, 10);
1714
1918
  return Number.isFinite(n) && n > 0 ? n : 12e4;
1715
1919
  }
1716
1920
  function llmStreamEventIdleTimeoutMs() {
1717
- const raw = process.env["0SEC_LLM_STREAM_EVENT_IDLE_TIMEOUT_MS"];
1921
+ const raw = process.env["ZERO_LLM_STREAM_EVENT_IDLE_TIMEOUT_MS"];
1718
1922
  if (raw == null || raw.trim() === "")
1719
1923
  return 24e4;
1720
1924
  const n = Number.parseInt(raw, 10);
@@ -1864,7 +2068,7 @@ var AZURE_FOUNDRY_DEPLOYMENT_IDS = {
1864
2068
  "gpt-5.6-terra": true
1865
2069
  };
1866
2070
  function parseLlmFallbackChain(env2 = process.env) {
1867
- const raw = env2["0SEC_LLM_FALLBACK"];
2071
+ const raw = env2["ZERO_LLM_FALLBACK"];
1868
2072
  if (!raw || raw.trim().length === 0)
1869
2073
  return [];
1870
2074
  const entries = [];
@@ -1890,17 +2094,17 @@ function parseLlmFallbackChain(env2 = process.env) {
1890
2094
  continue;
1891
2095
  const colonIdx = trimmed.indexOf(":");
1892
2096
  if (colonIdx < 1 || colonIdx === trimmed.length - 1) {
1893
- diag.warn("fallback_chain_malformed_entry", `0SEC_LLM_FALLBACK: malformed entry "${trimmed}" (expected provider:model)`, { entry: trimmed, expected: "provider:model" });
2097
+ diag.warn("fallback_chain_malformed_entry", `ZERO_LLM_FALLBACK: malformed entry "${trimmed}" (expected provider:model)`, { entry: trimmed, expected: "provider:model" });
1894
2098
  continue;
1895
2099
  }
1896
2100
  const provider = trimmed.slice(0, colonIdx);
1897
2101
  const model = trimmed.slice(colonIdx + 1).trim();
1898
2102
  if (!VALID_PROVIDERS[provider]) {
1899
- diag.warn("fallback_chain_unknown_provider", `0SEC_LLM_FALLBACK: unknown provider "${provider}" in "${trimmed}"`, { entry: trimmed, provider });
2103
+ diag.warn("fallback_chain_unknown_provider", `ZERO_LLM_FALLBACK: unknown provider "${provider}" in "${trimmed}"`, { entry: trimmed, provider });
1900
2104
  continue;
1901
2105
  }
1902
2106
  if (!model) {
1903
- diag.warn("fallback_chain_empty_model", `0SEC_LLM_FALLBACK: empty model in "${trimmed}"`, { entry: trimmed, provider });
2107
+ diag.warn("fallback_chain_empty_model", `ZERO_LLM_FALLBACK: empty model in "${trimmed}"`, { entry: trimmed, provider });
1904
2108
  continue;
1905
2109
  }
1906
2110
  entries.push({ provider, model });
@@ -1943,7 +2147,7 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
1943
2147
  return { apiKey: key, baseUrl: env2.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com", wireApi: "chat_completions" };
1944
2148
  }
1945
2149
  case "chatgpt-codex": {
1946
- if (!env2["0SEC_CHATGPT_ACCESS_TOKEN"] && !env2["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"] && !readChatGptCodexAuthFile(env2))
2150
+ if (!env2["ZERO_CHATGPT_ACCESS_TOKEN"] && !env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"] && !readChatGptCodexAuthFile(env2))
1947
2151
  return void 0;
1948
2152
  return { apiKey: "", baseUrl: CODEX_API_ENDPOINT, wireApi: "responses" };
1949
2153
  }
@@ -1978,13 +2182,13 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
1978
2182
  return { apiKey: key, baseUrl: env2.OPENCODE_BASE_URL ?? OPENCODE_DEFAULT_BASE_URL, wireApi: opencodeWireApiForModel(model) };
1979
2183
  }
1980
2184
  case "copilot": {
1981
- const key = apiKey ?? env2["0SEC_COPILOT_GITHUB_TOKEN"];
2185
+ const key = apiKey ?? env2["ZERO_COPILOT_GITHUB_TOKEN"];
1982
2186
  if (!key)
1983
2187
  return void 0;
1984
2188
  return { apiKey: key, baseUrl: env2.COPILOT_BASE_URL ?? COPILOT_API_BASE, wireApi: "chat_completions" };
1985
2189
  }
1986
2190
  case "google": {
1987
- if (!env2["0SEC_GEMINI_ACCESS_TOKEN"] && !env2["0SEC_GEMINI_OAUTH_REFRESH_TOKEN"])
2191
+ if (!env2["ZERO_GEMINI_ACCESS_TOKEN"] && !env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"])
1988
2192
  return void 0;
1989
2193
  return { apiKey: "", baseUrl: CODE_ASSIST_ENDPOINT, wireApi: "google_generate_content" };
1990
2194
  }
@@ -2010,7 +2214,7 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
2010
2214
  }
2011
2215
  var fallbackChainCache;
2012
2216
  function getFallbackChain(env2) {
2013
- const raw = env2["0SEC_LLM_FALLBACK"];
2217
+ const raw = env2["ZERO_LLM_FALLBACK"];
2014
2218
  if (!fallbackChainCache || fallbackChainCache.raw !== raw) {
2015
2219
  fallbackChainCache = { raw, entries: parseLlmFallbackChain(env2) };
2016
2220
  }
@@ -2020,7 +2224,7 @@ var ZAI_DEFAULT_BASE_URL = "https://api.z.ai/api/anthropic";
2020
2224
  var ZAI_DEFAULT_MODEL = "glm-5.3";
2021
2225
  var ZAI_DEFAULT_THINKING_BUDGET = 2048;
2022
2226
  function zaiThinkingBudget() {
2023
- const raw = process.env["0SEC_ZAI_THINKING_BUDGET"];
2227
+ const raw = process.env["ZERO_ZAI_THINKING_BUDGET"];
2024
2228
  if (raw == null || raw.trim().length === 0)
2025
2229
  return ZAI_DEFAULT_THINKING_BUDGET;
2026
2230
  const n = Number.parseInt(raw, 10);
@@ -2035,18 +2239,18 @@ var CODEX_OAUTH_ISSUER = "https://auth.openai.com";
2035
2239
  var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
2036
2240
  var CODEX_DEFAULT_MODEL = "gpt-5.5";
2037
2241
  var LOOP_SERVER_COMPACTION_TOKENS = 15e4;
2038
- var PROCESS_SESSION_ID = `0sec-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;
2242
+ var PROCESS_SESSION_ID = `0-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;
2039
2243
  var chatGptCodexAuthStates = /* @__PURE__ */ new Map();
2040
2244
  function codexAuthStateKey(state) {
2041
2245
  return JSON.stringify([state.authFilePath, state.accountId, state.refreshToken || state.accessToken]);
2042
2246
  }
2043
2247
  function readChatGptCodexEnv(env2 = process.env) {
2044
- const access = env2["0SEC_CHATGPT_ACCESS_TOKEN"];
2045
- const refresh = env2["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"];
2248
+ const access = env2["ZERO_CHATGPT_ACCESS_TOKEN"];
2249
+ const refresh = env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"];
2046
2250
  if ((!access || access.length === 0) && (!refresh || refresh.length === 0)) {
2047
2251
  return void 0;
2048
2252
  }
2049
- const accountId = env2["0SEC_CHATGPT_ACCOUNT_ID"];
2253
+ const accountId = env2["ZERO_CHATGPT_ACCOUNT_ID"];
2050
2254
  return {
2051
2255
  accessToken: access && access.length > 0 ? access : void 0,
2052
2256
  refreshToken: refresh && refresh.length > 0 ? refresh : void 0,
@@ -2054,7 +2258,7 @@ function readChatGptCodexEnv(env2 = process.env) {
2054
2258
  };
2055
2259
  }
2056
2260
  function resolveChatGptCodexAuthPath(env2 = process.env) {
2057
- return env2["0SEC_CHATGPT_AUTH_FILE"] ?? join2(env2.HOME ?? homedir(), ".codex", "auth.json");
2261
+ return env2["ZERO_CHATGPT_AUTH_FILE"] ?? join2(env2.HOME ?? homedir(), ".codex", "auth.json");
2058
2262
  }
2059
2263
  function persistChatGptCodexAuthFile(authPath, tokens, usedRefreshToken) {
2060
2264
  try {
@@ -2083,7 +2287,7 @@ function persistChatGptCodexAuthFile(authPath, tokens, usedRefreshToken) {
2083
2287
  `, { mode: 384 });
2084
2288
  renameSync(tmp, authPath);
2085
2289
  } catch (err) {
2086
- process.stderr.write(`[0sec] warning: could not persist rotated Codex refresh token to ${authPath}: ${err instanceof Error ? err.message : String(err)}
2290
+ process.stderr.write(`[0] warning: could not persist rotated Codex refresh token to ${authPath}: ${err instanceof Error ? err.message : String(err)}
2087
2291
  `);
2088
2292
  }
2089
2293
  }
@@ -2119,6 +2323,7 @@ function accessTokenExpiryMs(accessToken) {
2119
2323
  }
2120
2324
  async function refreshChatGptCodexAccessToken(refreshToken) {
2121
2325
  const res = await fetch(`${CODEX_OAUTH_ISSUER}/oauth/token`, {
2326
+ signal: AbortSignal.timeout(3e4),
2122
2327
  method: "POST",
2123
2328
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
2124
2329
  body: new URLSearchParams({
@@ -2170,12 +2375,15 @@ function extractChatGptAccountId(tokens) {
2170
2375
  }
2171
2376
  return void 0;
2172
2377
  }
2378
+ async function getChatGptCodexAccessToken(env2 = process.env) {
2379
+ return refreshChatGptCodexAuthState(resolveChatGptCodexAuthState(env2));
2380
+ }
2173
2381
  function resolveChatGptCodexAuthState(env2) {
2174
2382
  const fromEnvOnly = readChatGptCodexEnv(env2);
2175
2383
  const fromFile = fromEnvOnly ? void 0 : readChatGptCodexAuthFile(env2);
2176
2384
  const tokens = fromEnvOnly ?? fromFile;
2177
2385
  if (!tokens) {
2178
- throw new Error("ChatGPT Codex auth: neither 0SEC_CHATGPT_ACCESS_TOKEN nor 0SEC_CHATGPT_OAUTH_REFRESH_TOKEN is set. Run `codex login` and either forward the access token via worker-controller (preferred for multi-sandbox dispatch \u2014 avoids the OAuth refresh-token rotation race) or keep a valid ~/.codex/auth.json on this host.");
2386
+ throw new Error("ChatGPT Codex auth: neither ZERO_CHATGPT_ACCESS_TOKEN nor ZERO_CHATGPT_OAUTH_REFRESH_TOKEN is set. Run `codex login` and either forward the access token via worker-controller (preferred for multi-sandbox dispatch \u2014 avoids the OAuth refresh-token rotation race) or keep a valid ~/.codex/auth.json on this host.");
2179
2387
  }
2180
2388
  const identity = {
2181
2389
  refreshToken: tokens.refreshToken ?? "",
@@ -2235,8 +2443,8 @@ function geminiAuthStateKey(refreshToken, accessToken) {
2235
2443
  return JSON.stringify([refreshToken, refreshToken ? void 0 : accessToken]);
2236
2444
  }
2237
2445
  function readGeminiCodeAssistEnv(env2 = process.env) {
2238
- const access = env2["0SEC_GEMINI_ACCESS_TOKEN"];
2239
- const refresh = env2["0SEC_GEMINI_OAUTH_REFRESH_TOKEN"];
2446
+ const access = env2["ZERO_GEMINI_ACCESS_TOKEN"];
2447
+ const refresh = env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"];
2240
2448
  if ((!access || access.length === 0) && (!refresh || refresh.length === 0))
2241
2449
  return void 0;
2242
2450
  return {
@@ -2247,7 +2455,7 @@ function readGeminiCodeAssistEnv(env2 = process.env) {
2247
2455
  function resolveGeminiCodeAssistAuthState(env2) {
2248
2456
  const tokens = readGeminiCodeAssistEnv(env2);
2249
2457
  if (!tokens) {
2250
- throw new Error("Google Gemini Code Assist auth: neither 0SEC_GEMINI_ACCESS_TOKEN nor 0SEC_GEMINI_OAUTH_REFRESH_TOKEN is set. Sign in with your Google account (0sec connect) or forward a fresh access token.");
2458
+ throw new Error("Google Gemini Code Assist auth: neither ZERO_GEMINI_ACCESS_TOKEN nor ZERO_GEMINI_OAUTH_REFRESH_TOKEN is set. Sign in with your Google account (0 connect) or forward a fresh access token.");
2251
2459
  }
2252
2460
  const key = geminiAuthStateKey(tokens.refreshToken ?? "", tokens.accessToken);
2253
2461
  const existing = geminiCodeAssistAuthStates.get(key);
@@ -2360,7 +2568,7 @@ async function resolveGeminiCodeAssistProject(state, env2, sleep = (ms) => new P
2360
2568
  return state.inflightProjectResolve;
2361
2569
  state.inflightProjectResolve = (async () => {
2362
2570
  try {
2363
- const override = firstNonEmptyEnv(env2, "GOOGLE_CLOUD_PROJECT", "0SEC_GEMINI_PROJECT");
2571
+ const override = firstNonEmptyEnv(env2, "GOOGLE_CLOUD_PROJECT", "ZERO_GEMINI_PROJECT");
2364
2572
  const accessToken = await refreshGeminiCodeAssistAuthState(state);
2365
2573
  let load;
2366
2574
  try {
@@ -2372,7 +2580,7 @@ async function resolveGeminiCodeAssistProject(state, env2, sleep = (ms) => new P
2372
2580
  if (err.securityPolicyViolated) {
2373
2581
  if (override)
2374
2582
  return override;
2375
- throw new Error("Google Gemini Code Assist: this account is behind a VPC Service Controls perimeter \u2014 set GOOGLE_CLOUD_PROJECT (or 0SEC_GEMINI_PROJECT).");
2583
+ throw new Error("Google Gemini Code Assist: this account is behind a VPC Service Controls perimeter \u2014 set GOOGLE_CLOUD_PROJECT (or ZERO_GEMINI_PROJECT).");
2376
2584
  }
2377
2585
  throw err;
2378
2586
  }
@@ -2484,13 +2692,13 @@ function providerForModel(model, env2) {
2484
2692
  return env2.OPENCODE_API_KEY ? "opencode" : void 0;
2485
2693
  }
2486
2694
  if (m.startsWith("copilot/")) {
2487
- return env2["0SEC_COPILOT_GITHUB_TOKEN"] ? "copilot" : void 0;
2695
+ return env2["ZERO_COPILOT_GITHUB_TOKEN"] ? "copilot" : void 0;
2488
2696
  }
2489
2697
  if (m.startsWith("gemini") || m.startsWith("google/")) {
2490
- return env2["0SEC_GEMINI_ACCESS_TOKEN"] || env2["0SEC_GEMINI_OAUTH_REFRESH_TOKEN"] ? "google" : void 0;
2698
+ return env2["ZERO_GEMINI_ACCESS_TOKEN"] || env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"] ? "google" : void 0;
2491
2699
  }
2492
2700
  if (/^gpt-|^o[1-4](?:[-_]|$)/.test(m)) {
2493
- if (env2["0SEC_CHATGPT_ACCESS_TOKEN"] || env2["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"])
2701
+ if (env2["ZERO_CHATGPT_ACCESS_TOKEN"] || env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"])
2494
2702
  return "chatgpt-codex";
2495
2703
  if (env2.OPENAI_API_KEY)
2496
2704
  return "openai";
@@ -2526,21 +2734,21 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2526
2734
  if (configProvider !== void 0 && !Object.hasOwn(DEFAULT_PROVIDER_MODELS, configProvider)) {
2527
2735
  throw new Error(`RuntimeConfig.provider is unsupported: ${configProvider}`);
2528
2736
  }
2529
- const selectedProviderRaw = configProvider ?? env2["0SEC_SELECTED_PROVIDER"]?.trim();
2530
- const forcedProviderRaw = env2["0SEC_FORCE_PROVIDER"]?.trim() || void 0;
2737
+ const selectedProviderRaw = configProvider ?? env2["ZERO_SELECTED_PROVIDER"]?.trim();
2738
+ const forcedProviderRaw = env2["ZERO_FORCE_PROVIDER"]?.trim() || void 0;
2531
2739
  if (selectedProviderRaw && forcedProviderRaw && selectedProviderRaw !== forcedProviderRaw) {
2532
- throw new Error(`${configProvider !== void 0 ? "RuntimeConfig.provider" : "0SEC_SELECTED_PROVIDER"} conflicts with 0SEC_FORCE_PROVIDER`);
2740
+ throw new Error(`${configProvider !== void 0 ? "RuntimeConfig.provider" : "ZERO_SELECTED_PROVIDER"} conflicts with ZERO_FORCE_PROVIDER`);
2533
2741
  }
2534
- const primaryModel = env2["0SEC_MODEL"]?.trim();
2742
+ const primaryModel = env2["ZERO_MODEL"]?.trim();
2535
2743
  const selectedProviderApplies = configProvider !== void 0 || !preferredModel || !primaryModel || preferredModel === primaryModel;
2536
2744
  const pinnedProviderRaw = forcedProviderRaw ?? (selectedProviderApplies ? selectedProviderRaw : void 0);
2537
2745
  if (pinnedProviderRaw) {
2538
- const source = pinnedProviderRaw === forcedProviderRaw ? "0SEC_FORCE_PROVIDER" : configProvider !== void 0 ? "RuntimeConfig.provider" : "0SEC_SELECTED_PROVIDER";
2746
+ const source = pinnedProviderRaw === forcedProviderRaw ? "ZERO_FORCE_PROVIDER" : configProvider !== void 0 ? "RuntimeConfig.provider" : "ZERO_SELECTED_PROVIDER";
2539
2747
  if (!Object.hasOwn(DEFAULT_PROVIDER_MODELS, pinnedProviderRaw)) {
2540
2748
  throw new Error(`${source} is unsupported: ${pinnedProviderRaw}`);
2541
2749
  }
2542
2750
  const provider = pinnedProviderRaw;
2543
- const model = preferredModel ?? env2["0SEC_MODEL"] ?? (configProvider !== void 0 || provider === "hosted" ? DEFAULT_PROVIDER_MODELS[provider] : void 0);
2751
+ const model = preferredModel ?? env2["ZERO_MODEL"] ?? (configProvider !== void 0 || provider === "hosted" ? DEFAULT_PROVIDER_MODELS[provider] : void 0);
2544
2752
  if (model === void 0 || model === "" && provider !== "hosted") {
2545
2753
  throw new Error(`${source} requires an explicit model`);
2546
2754
  }
@@ -2650,7 +2858,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2650
2858
  case "copilot":
2651
2859
  return {
2652
2860
  provider: "copilot",
2653
- apiKey: env2["0SEC_COPILOT_GITHUB_TOKEN"],
2861
+ apiKey: env2["ZERO_COPILOT_GITHUB_TOKEN"],
2654
2862
  baseUrl: env2.COPILOT_BASE_URL ?? COPILOT_API_BASE,
2655
2863
  defaultModel: preferredModel ?? COPILOT_DEFAULT_MODEL,
2656
2864
  wireApi: "chat_completions"
@@ -2668,7 +2876,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2668
2876
  provider: "chatgpt-codex",
2669
2877
  apiKey: "",
2670
2878
  baseUrl: CODEX_API_ENDPOINT,
2671
- defaultModel: env2["0SEC_MODEL"] ?? CODEX_DEFAULT_MODEL,
2879
+ defaultModel: env2["ZERO_MODEL"] ?? CODEX_DEFAULT_MODEL,
2672
2880
  wireApi: "responses"
2673
2881
  };
2674
2882
  case "anthropic":
@@ -2698,8 +2906,23 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2698
2906
  default:
2699
2907
  break;
2700
2908
  }
2701
- const chatGptAccess = env2["0SEC_CHATGPT_ACCESS_TOKEN"];
2702
- const chatGptRefresh = env2["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"];
2909
+ try {
2910
+ const hostedCreds = loadCloudCredentials({
2911
+ env: env2,
2912
+ warn: () => {
2913
+ }
2914
+ });
2915
+ return {
2916
+ provider: "hosted",
2917
+ apiKey: hostedCreds.token,
2918
+ baseUrl: `${hostedCreds.host}/api/inference/v1`,
2919
+ defaultModel: "",
2920
+ wireApi: "chat_completions"
2921
+ };
2922
+ } catch {
2923
+ }
2924
+ const chatGptAccess = env2["ZERO_CHATGPT_ACCESS_TOKEN"];
2925
+ const chatGptRefresh = env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"];
2703
2926
  const chatGptAuthFile = !chatGptAccess && !chatGptRefresh ? readChatGptCodexAuthFile(env2) : void 0;
2704
2927
  if (chatGptAccess && chatGptAccess.length > 0 || chatGptRefresh && chatGptRefresh.length > 0 || !!chatGptAuthFile) {
2705
2928
  return {
@@ -2712,7 +2935,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2712
2935
  // baseUrl is informational only — the runtime hardcodes
2713
2936
  // CODEX_API_ENDPOINT for this provider.
2714
2937
  baseUrl: CODEX_API_ENDPOINT,
2715
- defaultModel: env2["0SEC_MODEL"] ?? CODEX_DEFAULT_MODEL,
2938
+ defaultModel: env2["ZERO_MODEL"] ?? CODEX_DEFAULT_MODEL,
2716
2939
  wireApi: "responses"
2717
2940
  };
2718
2941
  }
@@ -2808,7 +3031,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2808
3031
  wireApi: opencodeWireApiForModel(preferredModel)
2809
3032
  };
2810
3033
  }
2811
- const copilotToken = env2["0SEC_COPILOT_GITHUB_TOKEN"];
3034
+ const copilotToken = env2["ZERO_COPILOT_GITHUB_TOKEN"];
2812
3035
  if (copilotToken) {
2813
3036
  return {
2814
3037
  provider: "copilot",
@@ -2818,14 +3041,14 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2818
3041
  wireApi: "chat_completions"
2819
3042
  };
2820
3043
  }
2821
- const geminiAccess = env2["0SEC_GEMINI_ACCESS_TOKEN"];
2822
- const geminiRefresh = env2["0SEC_GEMINI_OAUTH_REFRESH_TOKEN"];
3044
+ const geminiAccess = env2["ZERO_GEMINI_ACCESS_TOKEN"];
3045
+ const geminiRefresh = env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"];
2823
3046
  if (geminiAccess && geminiAccess.length > 0 || geminiRefresh && geminiRefresh.length > 0) {
2824
3047
  return {
2825
3048
  provider: "google",
2826
3049
  apiKey: "",
2827
3050
  baseUrl: CODE_ASSIST_ENDPOINT,
2828
- defaultModel: env2["0SEC_MODEL"] ?? GEMINI_DEFAULT_MODEL,
3051
+ defaultModel: env2["ZERO_MODEL"] ?? GEMINI_DEFAULT_MODEL,
2829
3052
  wireApi: "google_generate_content"
2830
3053
  };
2831
3054
  }
@@ -2839,21 +3062,6 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2839
3062
  wireApi: "chat_completions"
2840
3063
  };
2841
3064
  }
2842
- try {
2843
- const hostedCreds = loadCloudCredentials({
2844
- env: env2,
2845
- warn: () => {
2846
- }
2847
- });
2848
- return {
2849
- provider: "hosted",
2850
- apiKey: hostedCreds.token,
2851
- baseUrl: `${hostedCreds.host}/api/inference/v1`,
2852
- defaultModel: "",
2853
- wireApi: "chat_completions"
2854
- };
2855
- } catch {
2856
- }
2857
3065
  return {
2858
3066
  provider: "anthropic",
2859
3067
  apiKey: "",
@@ -2880,7 +3088,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
2880
3088
  reasoningEffort;
2881
3089
  azureConfig;
2882
3090
  serverCompactionTokens;
2883
- /** Ordered fallback chain (0SEC_LLM_FALLBACK). Empty = no failover. */
3091
+ /** Ordered fallback chain (ZERO_LLM_FALLBACK). Empty = no failover. */
2884
3092
  fallbackChain;
2885
3093
  /** Index into fallbackChain — which entry to try next. */
2886
3094
  fallbackIndex;
@@ -2956,7 +3164,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
2956
3164
  credentials: resolveFailoverProvider(entry.provider, entry.model, this.env)
2957
3165
  }));
2958
3166
  this.fallbackIndex = 0;
2959
- const detected = detectProvider(config.apiKey, config.model ?? this.env["0SEC_MODEL"], this.env, config.provider);
3167
+ const detected = detectProvider(config.apiKey, config.model ?? this.env["ZERO_MODEL"], this.env, config.provider);
2960
3168
  this.provider = detected.provider;
2961
3169
  this.apiKey = detected.apiKey;
2962
3170
  this.baseUrl = detected.baseUrl;
@@ -2973,9 +3181,9 @@ var LlmApiRuntime = class _LlmApiRuntime {
2973
3181
  this.geminiAuthState = resolveGeminiCodeAssistAuthState(this.env);
2974
3182
  }
2975
3183
  }
2976
- this.reasoningEffort = this.env["0SEC_REASONING_EFFORT"] ?? detected.reasoningEffort;
3184
+ this.reasoningEffort = this.env["ZERO_REASONING_EFFORT"] ?? detected.reasoningEffort;
2977
3185
  this.serverCompactionTokens = config.serverCompactionTokens !== void 0 ? Math.max(1e3, config.serverCompactionTokens) : void 0;
2978
- const requestedModel = config.model ?? this.env["0SEC_MODEL"];
3186
+ const requestedModel = config.model ?? this.env["ZERO_MODEL"];
2979
3187
  if (requestedModel === "free" && this.provider === "openrouter") {
2980
3188
  this.model = FREE_OPENROUTER_MODEL;
2981
3189
  } else {
@@ -2988,11 +3196,50 @@ var LlmApiRuntime = class _LlmApiRuntime {
2988
3196
  this.model = copilotModelId(this.model);
2989
3197
  }
2990
3198
  this.applyModelWireApi();
2991
- if (this.apiKey && !this.env["0SEC_SKIP_PROVIDER_BANNER"]) {
3199
+ if (this.apiKey && !this.env["ZERO_SKIP_PROVIDER_BANNER"]) {
2992
3200
  void logProviderStartup(this.provider, this.providerLabel, this.baseUrl, this.model, this.wireApi, this.apiKey).catch(() => {
2993
3201
  });
2994
3202
  }
2995
3203
  }
3204
+ /** Discover models using this runtime's captured account, including after a separate login changes. */
3205
+ async codexModelCatalog(signal) {
3206
+ const state = this.codexAuthState;
3207
+ if (this.provider !== "chatgpt-codex" || !state)
3208
+ throw new Error("No active Codex subscription");
3209
+ const { loadCodexModelCatalog } = await import("./codex-models-TYYV4JNN.js");
3210
+ return loadCodexModelCatalog({ signal, resolveCredentials: () => refreshChatGptCodexAuthState(state) });
3211
+ }
3212
+ /** Check account admission and resolve the service model without inference.
3213
+ * Each explicit check refreshes admission; failed discovery is never cached.
3214
+ */
3215
+ async prepare() {
3216
+ while (this.provider === "hosted") {
3217
+ const config = this.config;
3218
+ const client = new CloudClient({
3219
+ host: this.baseUrl.replace(/\/api\/inference\/v1$/, ""),
3220
+ token: this.apiKey
3221
+ });
3222
+ try {
3223
+ const account = await client.getInferenceAccount();
3224
+ if (this.config !== config)
3225
+ continue;
3226
+ if (!account) {
3227
+ throw new CloudError("0cloud account availability could not be read. Check again or review your account in /connect.", void 0, "/api/inference/account", "unsupported_account_data");
3228
+ }
3229
+ if (!account.admission.eligible) {
3230
+ const reason = account.admission.reason ?? account.reason ?? "account_restricted";
3231
+ throw new CloudError(account.state === "unavailable" ? `0cloud account availability could not be checked (${reason}). Check again or review your account in /connect.` : `0cloud account access is restricted (${reason}). Review your account in /connect or contact your organization owner.`, void 0, "/api/inference/account", reason);
3232
+ }
3233
+ await this.ensureHostedModel();
3234
+ if (this.config === config)
3235
+ return;
3236
+ } catch (error) {
3237
+ if (this.config !== config)
3238
+ continue;
3239
+ throw error;
3240
+ }
3241
+ }
3242
+ }
2996
3243
  /**
2997
3244
  * Mutate the live selection in place so the NEXT turn (the engine reads
2998
3245
  * `config.runtime` per turn) and the NEXT `forkForSubagent` pick up the new
@@ -3001,11 +3248,12 @@ var LlmApiRuntime = class _LlmApiRuntime {
3001
3248
  */
3002
3249
  reconfigure(sel) {
3003
3250
  const providerChanged = sel.provider !== void 0 && sel.provider !== this.provider;
3004
- if (providerChanged) {
3251
+ if (providerChanged || this.provider === "hosted" && (sel.env !== void 0 || sel.provider === "hosted")) {
3005
3252
  const merged = {
3006
3253
  ...this.config,
3254
+ ...providerChanged && sel.provider === "hosted" && sel.model === void 0 ? { model: "" } : {},
3007
3255
  apiKey: void 0,
3008
- provider: sel.provider,
3256
+ provider: sel.provider ?? this.provider,
3009
3257
  ...sel.model !== void 0 ? { model: sel.model } : {},
3010
3258
  ...sel.agentModels !== void 0 ? { agentModels: sel.agentModels } : {},
3011
3259
  ...sel.singleModel !== void 0 ? { singleModel: sel.singleModel } : {},
@@ -3035,6 +3283,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
3035
3283
  this.wireApi = openAICompatibleWireApi(this.env, "AZURE_OPENAI_WIRE_API", this.azureConfig.wireApi);
3036
3284
  this.applyModelWireApi();
3037
3285
  this.reasoningEffort = void 0;
3286
+ this.hostedCatalogPromise = null;
3287
+ this.hostedMaxOutputTokens = void 0;
3038
3288
  }
3039
3289
  }
3040
3290
  }
@@ -3119,25 +3369,50 @@ var LlmApiRuntime = class _LlmApiRuntime {
3119
3369
  }
3120
3370
  /** The server catalog is authoritative even when a model was selected explicitly. */
3121
3371
  async ensureHostedModel() {
3122
- if (this.provider !== "hosted")
3123
- return;
3124
- if (!this.hostedCatalogPromise) {
3125
- this.hostedCatalogPromise = (async () => {
3372
+ while (this.provider === "hosted") {
3373
+ let pending = this.hostedCatalogPromise;
3374
+ if (!pending) {
3375
+ const requestedModel = this.model;
3126
3376
  const client = new CloudClient({
3127
3377
  host: this.baseUrl.replace(/\/api\/inference\/v1$/, ""),
3128
3378
  token: this.apiKey
3129
3379
  });
3130
- const catalog = await client.getInferenceModels();
3131
- const selected = this.model ? catalog.data.find((model) => model.id === this.model) : catalog.data[0];
3132
- if (!selected) {
3133
- throw new Error(this.model ? `Hosted model "${this.model}" is unavailable. Run \`0sec models\` for available models.` : "No hosted models are available. Run `0sec models` to check service availability.");
3134
- }
3135
- this.model = selected.id;
3136
- this.wireApi = selected.wire_api;
3137
- this.hostedMaxOutputTokens = selected.max_output_tokens;
3138
- })();
3380
+ const request = client.getInferenceModels().then((catalog) => {
3381
+ if (this.hostedCatalogPromise !== request)
3382
+ return;
3383
+ const selected = requestedModel ? catalog.data.find((model) => model.id === requestedModel) : catalog.data[0];
3384
+ if (!selected) {
3385
+ throw new Error(requestedModel ? `Hosted model "${requestedModel}" is unavailable. Run \`0 models\` for available models.` : "No hosted models are available. Run `0 models` to check service availability.");
3386
+ }
3387
+ this.model = selected.id;
3388
+ this.wireApi = selected.wire_api;
3389
+ this.hostedMaxOutputTokens = selected.max_output_tokens;
3390
+ });
3391
+ this.hostedCatalogPromise = pending = request;
3392
+ }
3393
+ try {
3394
+ await pending;
3395
+ } catch (error) {
3396
+ if (this.hostedCatalogPromise !== pending)
3397
+ continue;
3398
+ this.hostedCatalogPromise = null;
3399
+ throw error;
3400
+ }
3401
+ if (this.hostedCatalogPromise === pending)
3402
+ return;
3139
3403
  }
3140
- await this.hostedCatalogPromise;
3404
+ }
3405
+ /** All audits and nested workers on this hosted credential share admission. */
3406
+ async acquireHostedSlot(signal) {
3407
+ while (this.provider === "hosted") {
3408
+ const config = this.config;
3409
+ const release = await acquireHostedRequestSlot(this.baseUrl, this.apiKey, signal);
3410
+ if (this.config === config)
3411
+ return release;
3412
+ release();
3413
+ await this.ensureHostedModel();
3414
+ }
3415
+ return void 0;
3141
3416
  }
3142
3417
  /**
3143
3418
  * A hard dollar ceiling needs a provider-enforced bound on the next response.
@@ -3215,8 +3490,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
3215
3490
  if (this.provider === "chatgpt-codex") {
3216
3491
  return {
3217
3492
  "Content-Type": "application/json",
3218
- originator: "0sec",
3219
- "User-Agent": `0sec/${VERSION}`
3493
+ originator: "0",
3494
+ "User-Agent": `0/${VERSION}`
3220
3495
  };
3221
3496
  }
3222
3497
  if (this.isGeminiCodeAssist) {
@@ -3248,8 +3523,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
3248
3523
  headers["Authorization"] = `Bearer ${this.apiKey}`;
3249
3524
  }
3250
3525
  if (this.provider === "openrouter") {
3251
- headers["HTTP-Referer"] = "https://0sec.ai";
3252
- headers["X-Title"] = "0sec Security Scanner";
3526
+ headers["HTTP-Referer"] = "https://0.security";
3527
+ headers["X-Title"] = "0 Security Scanner";
3253
3528
  }
3254
3529
  return headers;
3255
3530
  }
@@ -3273,7 +3548,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3273
3548
  * happens.
3274
3549
  *
3275
3550
  * session_id is process-stable (PROCESS_SESSION_ID, randomised
3276
- * once at module load). A 0sec-cli invocation = one scan = one
3551
+ * once at module load). A @0/cli invocation = one scan = one
3277
3552
  * session, so the process-lifetime constant is the right
3278
3553
  * granularity. If we ever want per-scan ids inside a long-lived
3279
3554
  * controller process, add a setter on the runtime; for now this
@@ -3427,7 +3702,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3427
3702
  /**
3428
3703
  * Per-turn prompt-cache accounting line, so a run can be shown to actually
3429
3704
  * be hitting cache rather than assumed to be. Off unless
3430
- * `0SEC_DEBUG_PROMPT_CACHE` is set — this fires once per agent turn, and an
3705
+ * `ZERO_DEBUG_PROMPT_CACHE` is set — this fires once per agent turn, and an
3431
3706
  * unconditional line would interleave with the TUI on every scan.
3432
3707
  *
3433
3708
  * The same numbers reach the cloud without this flag: `cachedInputTokens`
@@ -3435,7 +3710,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3435
3710
  * is the durable, queryable proof. This is the local fast path.
3436
3711
  */
3437
3712
  logCacheUsage(usage) {
3438
- if (!usage || !process.env["0SEC_DEBUG_PROMPT_CACHE"])
3713
+ if (!usage || !process.env["ZERO_DEBUG_PROMPT_CACHE"])
3439
3714
  return;
3440
3715
  const read = usage.cachedInputTokens ?? 0;
3441
3716
  const write = usage.cacheWriteTokens ?? 0;
@@ -3479,11 +3754,11 @@ var LlmApiRuntime = class _LlmApiRuntime {
3479
3754
  case "google":
3480
3755
  return "Google Gemini (Code Assist)";
3481
3756
  case "hosted":
3482
- return "0sec Cloud";
3757
+ return "0.security Cloud";
3483
3758
  }
3484
3759
  }
3485
3760
  noKeyError() {
3486
- return "No provider credential found. Set one of:\n env 0SEC_CHATGPT_OAUTH_REFRESH_TOKEN=... 0sec <command> (ChatGPT Codex subscription auth)\n export OPENROUTER_API_KEY=sk-or-... (OpenRouter \u2014 many models, one key)\n export DEEPSEEK_API_KEY=... (DeepSeek \u2014 direct Flash 0731 inference)\n export ANTHROPIC_API_KEY=sk-ant-... (Anthropic \u2014 direct Claude access)\n export AZURE_OPENAI_API_KEY=... (Azure OpenAI \u2014 reuse your Codex Azure provider)\n export OPENAI_API_KEY=sk-... (OpenAI \u2014 direct GPT access)\n export Z_AI_API_KEY=... (Z.ai GLM \u2014 flat-rate Coding Plan, Anthropic-compatible)\n export KIMI_API_KEY=... (Moonshot Kimi K3 \u2014 flat-rate coding, Anthropic-compatible)\n export QWEN_API_KEY=... (Alibaba Qwen \u2014 Token Plan sub, OpenAI-compatible)\n export XAI_API_KEY=... (xAI Grok \u2014 OpenAI-compatible)\n export OPENCODE_API_KEY=... (OpenCode Zen \u2014 multi-wire gateway)\n export 0SEC_COPILOT_GITHUB_TOKEN=... (GitHub Copilot \u2014 device-code OAuth token)\n Run `0sec login` (0sec hosted inference)";
3761
+ return "No provider credential found. Set one of:\n env ZERO_CHATGPT_OAUTH_REFRESH_TOKEN=... 0 <command> (ChatGPT Codex subscription auth)\n export OPENROUTER_API_KEY=sk-or-... (OpenRouter \u2014 many models, one key)\n export DEEPSEEK_API_KEY=... (DeepSeek \u2014 direct Flash 0731 inference)\n export ANTHROPIC_API_KEY=sk-ant-... (Anthropic \u2014 direct Claude access)\n export AZURE_OPENAI_API_KEY=... (Azure OpenAI \u2014 reuse your Codex Azure provider)\n export OPENAI_API_KEY=sk-... (OpenAI \u2014 direct GPT access)\n export Z_AI_API_KEY=... (Z.ai GLM \u2014 flat-rate Coding Plan, Anthropic-compatible)\n export KIMI_API_KEY=... (Moonshot Kimi K3 \u2014 flat-rate coding, Anthropic-compatible)\n export QWEN_API_KEY=... (Alibaba Qwen \u2014 Token Plan sub, OpenAI-compatible)\n export XAI_API_KEY=... (xAI Grok \u2014 OpenAI-compatible)\n export OPENCODE_API_KEY=... (OpenCode Zen \u2014 multi-wire gateway)\n export ZERO_COPILOT_GITHUB_TOKEN=... (GitHub Copilot \u2014 device-code OAuth token)\n Run `0 login` (0 hosted inference)";
3487
3762
  }
3488
3763
  getConfigurationDiagnostics() {
3489
3764
  if (!this.apiKey && this.provider !== "chatgpt-codex") {
@@ -3503,7 +3778,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3503
3778
  };
3504
3779
  }
3505
3780
  const hasConfiguredBaseUrl = !!(this.env.AZURE_OPENAI_BASE_URL || this.env.OPENAI_BASE_URL || this.azureConfig.baseUrl);
3506
- const hasConfiguredModel = !!(this.config.model || this.env["0SEC_MODEL"] || this.env.AZURE_OPENAI_MODEL || this.azureConfig.model);
3781
+ const hasConfiguredModel = !!(this.config.model || this.env["ZERO_MODEL"] || this.env.AZURE_OPENAI_MODEL || this.azureConfig.model);
3507
3782
  const missing = [];
3508
3783
  if (!hasConfiguredBaseUrl) {
3509
3784
  missing.push("AZURE_OPENAI_BASE_URL (or [model_providers.azure].base_url in ~/.codex/config.toml)");
@@ -3519,7 +3794,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3519
3794
  reason: "invalid_config",
3520
3795
  fatalError: `Azure OpenAI runtime is selected, but the configuration is incomplete.
3521
3796
  Missing: ${missing.join("; ")}
3522
- 0sec will not guess Azure defaults because that can silently route to the wrong endpoint or deployment.`
3797
+ 0 will not guess Azure defaults because that can silently route to the wrong endpoint or deployment.`
3523
3798
  };
3524
3799
  }
3525
3800
  return {
@@ -3537,15 +3812,15 @@ Missing: ${missing.join("; ")}
3537
3812
  *
3538
3813
  * Two 429 classes are handled differently:
3539
3814
  * - per-minute rate limit → retry with the wider 429 budget
3540
- * (0SEC_LLM_429_MAX_RETRIES attempts / 0SEC_LLM_429_MAX_RETRY_WAIT_MS
3815
+ * (ZERO_LLM_429_MAX_RETRIES attempts / ZERO_LLM_429_MAX_RETRY_WAIT_MS
3541
3816
  * cumulative, defaults 12 / 5min) since the limiter resets every ~60s;
3542
3817
  * `Retry-After` / `retry-after-ms` headers are honored up to a 120s cap.
3543
3818
  * - plan-quota exhaustion (`usage_limit_reached`, resets in hours/days) →
3544
- * skips retries and immediately advances `0SEC_LLM_FALLBACK`; if no
3819
+ * skips retries and immediately advances `ZERO_LLM_FALLBACK`; if no
3545
3820
  * configured fallback has credentials, it throws QuotaExhaustedError.
3546
3821
  *
3547
3822
  * Other retryable statuses (transient 5xx) keep the generic budget:
3548
- * 0SEC_LLM_MAX_RETRIES (attempts) and 0SEC_LLM_MAX_RETRY_WAIT_MS
3823
+ * ZERO_LLM_MAX_RETRIES (attempts) and ZERO_LLM_MAX_RETRY_WAIT_MS
3549
3824
  * (cumulative backoff). On exhaustion it returns the last still-failing
3550
3825
  * Response with its body intact, so the caller's existing `!res.ok` branch
3551
3826
  * surfaces the clear "API error <status>" message — a rate-limit never
@@ -3556,7 +3831,7 @@ Missing: ${missing.join("; ")}
3556
3831
  * is fixed across attempts.
3557
3832
  */
3558
3833
  /**
3559
- * Try the next fallback provider in the chain (0SEC_LLM_FALLBACK).
3834
+ * Try the next fallback provider in the chain (ZERO_LLM_FALLBACK).
3560
3835
  * Updates `this.provider`, `this.model`, `this.apiKey`, `this.baseUrl`,
3561
3836
  * `this.wireApi` to match the next valid provider. Returns `true` when a
3562
3837
  * valid next provider was found and switched to, `false` when the chain is
@@ -3568,7 +3843,7 @@ Missing: ${missing.join("; ")}
3568
3843
  this.fallbackIndex++;
3569
3844
  const cfg = entry.credentials;
3570
3845
  if (!cfg) {
3571
- diag.warn("failover_provider_skipped", `0SEC_LLM_FALLBACK: skipping ${entry.provider} (auth env missing)`, { provider: entry.provider, model: entry.model, cause: "auth-env-missing" });
3846
+ diag.warn("failover_provider_skipped", `ZERO_LLM_FALLBACK: skipping ${entry.provider} (auth env missing)`, { provider: entry.provider, model: entry.model, cause: "auth-env-missing" });
3572
3847
  continue;
3573
3848
  }
3574
3849
  this.provider = entry.provider;
@@ -3614,7 +3889,7 @@ Missing: ${missing.join("; ")}
3614
3889
  } catch (error) {
3615
3890
  abort?.throwIfCancelled();
3616
3891
  if (this.provider === "hosted") {
3617
- throw new Error("0sec hosted request outcome is unknown. Automatic replay is disabled; check your inference usage before retrying.", { cause: error });
3892
+ throw new Error("0 hosted request outcome is unknown. Automatic replay is disabled; check your inference usage before retrying.", { cause: error });
3618
3893
  }
3619
3894
  const cause = error instanceof Error ? error.cause : void 0;
3620
3895
  const causeCode = cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string" ? cause.code : "unknown";
@@ -3642,12 +3917,12 @@ Missing: ${missing.join("; ")}
3642
3917
  if (res.ok || !isRetryableHttpStatus(res.status)) {
3643
3918
  return res;
3644
3919
  }
3645
- if (this.provider === "hosted" && res.status === 429 && res.headers.get("x-0sec-retry-safe") !== "1") {
3920
+ if (this.provider === "hosted" && res.status === 429 && res.headers.get("x-0-retry-safe") !== "1") {
3646
3921
  return res;
3647
3922
  }
3648
3923
  if (this.provider === "hosted" && res.status >= 500) {
3649
3924
  await res.body?.cancel();
3650
- throw new Error(`0sec hosted request returned HTTP ${res.status}; its outcome may be unknown. Automatic replay is disabled; check your inference usage before retrying.`);
3925
+ throw new Error(`0 hosted request returned HTTP ${res.status}; its outcome may be unknown. Automatic replay is disabled; check your inference usage before retrying.`);
3651
3926
  }
3652
3927
  abort?.throwIfCancelled();
3653
3928
  const is429 = res.status === 429;
@@ -3751,11 +4026,15 @@ Missing: ${missing.join("; ")}
3751
4026
  };
3752
4027
  }
3753
4028
  const systemPrompt = context?.systemPrompt ?? "";
4029
+ let releaseHostedSlot = this.provider === "hosted" ? await this.acquireHostedSlot() : void 0;
3754
4030
  const controller = new AbortController();
3755
4031
  const timer = setTimeout(() => controller.abort(), this.config.timeout || 12e4);
3756
4032
  try {
3757
4033
  let res;
3758
4034
  do {
4035
+ if (this.provider === "hosted" && !releaseHostedSlot) {
4036
+ releaseHostedSlot = await this.acquireHostedSlot(controller.signal);
4037
+ }
3759
4038
  if (this.isOpenAICompat && this.wireApi === "chat_completions") {
3760
4039
  const messages = [];
3761
4040
  if (systemPrompt) {
@@ -3883,6 +4162,8 @@ Missing: ${missing.join("; ")}
3883
4162
  durationMs: Date.now() - start,
3884
4163
  error: timedOut ? `${this.providerLabel} API request timed out` : `${this.providerLabel} API error: ${msg}`
3885
4164
  };
4165
+ } finally {
4166
+ releaseHostedSlot?.();
3886
4167
  }
3887
4168
  }
3888
4169
  // ── Native Runtime interface (structured messages + tool_use) ──
@@ -3936,12 +4217,25 @@ Missing: ${missing.join("; ")}
3936
4217
  }
3937
4218
  if (signal?.aborted)
3938
4219
  return this.cancelledResult(start);
4220
+ let releaseHostedSlot;
4221
+ if (this.provider === "hosted") {
4222
+ try {
4223
+ releaseHostedSlot = await this.acquireHostedSlot(signal);
4224
+ } catch (error) {
4225
+ if (signal?.aborted)
4226
+ return this.cancelledResult(start);
4227
+ throw error;
4228
+ }
4229
+ }
3939
4230
  const controller = new AbortController();
3940
4231
  const timer = setTimeout(() => controller.abort(), this.config.timeout || 12e4);
3941
4232
  const call = composeCallAbort(controller.signal, signal);
3942
4233
  try {
3943
4234
  let res;
3944
4235
  do {
4236
+ if (this.provider === "hosted" && !releaseHostedSlot) {
4237
+ releaseHostedSlot = await this.acquireHostedSlot(call.signal);
4238
+ }
3945
4239
  if (this.isOpenAICompat && this.wireApi === "chat_completions") {
3946
4240
  const chatMessages = [];
3947
4241
  chatMessages.push({ role: "system", content: system });
@@ -4440,6 +4734,7 @@ Missing: ${missing.join("; ")}
4440
4734
  error: timedOut ? `${this.providerLabel} API request timed out` : `${this.providerLabel} API error: ${msg}`
4441
4735
  };
4442
4736
  } finally {
4737
+ releaseHostedSlot?.();
4443
4738
  call.dispose();
4444
4739
  }
4445
4740
  }
@@ -4507,9 +4802,14 @@ Missing: ${missing.join("; ")}
4507
4802
  };
4508
4803
  const decoder = new TextDecoder();
4509
4804
  let buffer = "";
4805
+ let trailingCR = false;
4806
+ let receivedBytes = 0;
4807
+ let malformedEvents = 0;
4808
+ const eventTypes = /* @__PURE__ */ new Set();
4809
+ const identifier = (value) => typeof value === "string" && /^[A-Za-z0-9_.:-]{1,96}$/.test(value) ? value : null;
4510
4810
  let completedResponse = null;
4511
- let openRouterStreamFailed = false;
4512
- let openRouterUsage;
4811
+ let streamFailure;
4812
+ let responseUsage;
4513
4813
  const streamedOutputItems = [];
4514
4814
  let thinkingText = "";
4515
4815
  let lastThinkingEmit = 0;
@@ -4532,7 +4832,7 @@ Missing: ${missing.join("; ")}
4532
4832
  lastThinkingLength = thinkingText.length;
4533
4833
  callbacks.onThinking(thinkingText);
4534
4834
  };
4535
- while (true) {
4835
+ responses: while (true) {
4536
4836
  let chunk;
4537
4837
  try {
4538
4838
  chunk = await readBounded();
@@ -4565,9 +4865,18 @@ Missing: ${missing.join("; ")}
4565
4865
  throw err;
4566
4866
  }
4567
4867
  const { done, value } = chunk;
4568
- if (done)
4868
+ if (done) {
4869
+ buffer += decoder.decode();
4569
4870
  break;
4570
- buffer += decoder.decode(value, { stream: true });
4871
+ }
4872
+ receivedBytes += value.byteLength;
4873
+ let decoded = decoder.decode(value, { stream: true });
4874
+ if (decoded) {
4875
+ if (trailingCR && decoded.startsWith("\n"))
4876
+ decoded = decoded.slice(1);
4877
+ trailingCR = decoded.endsWith("\r");
4878
+ buffer += decoded.replace(/\r\n?/g, "\n");
4879
+ }
4571
4880
  let boundary = buffer.indexOf("\n\n");
4572
4881
  while (boundary >= 0) {
4573
4882
  const rawChunk = buffer.slice(0, boundary);
@@ -4580,24 +4889,42 @@ Missing: ${missing.join("; ")}
4580
4889
  let event;
4581
4890
  try {
4582
4891
  event = JSON.parse(payload);
4892
+ if (!event || typeof event !== "object" || Array.isArray(event)) {
4893
+ malformedEvents++;
4894
+ continue;
4895
+ }
4583
4896
  } catch {
4897
+ malformedEvents++;
4584
4898
  continue;
4585
4899
  }
4586
4900
  const type = String(event.type ?? "");
4587
- if (this.provider === "openrouter" && (type === "response.done" || type === "response.failed" || type === "response.completed" || type === "response.incomplete")) {
4588
- const response = event.response;
4589
- const usage2 = response?.usage;
4590
- if (usage2 && typeof usage2.input_tokens === "number" && Number.isFinite(usage2.input_tokens) && usage2.input_tokens >= 0 && typeof usage2.output_tokens === "number" && Number.isFinite(usage2.output_tokens) && usage2.output_tokens >= 0) {
4591
- openRouterUsage = {
4592
- inputTokens: usage2.input_tokens,
4593
- outputTokens: usage2.output_tokens,
4594
- ...readResponsesCachedTokens(usage2)
4901
+ if (eventTypes.size < 32)
4902
+ eventTypes.add(identifier(type) ?? "unrecognized");
4903
+ const terminal = type === "response.completed" || this.provider === "openrouter" && type === "response.done";
4904
+ if (terminal || type === "response.failed" || type === "response.incomplete" || type === "error") {
4905
+ const response = event.response && typeof event.response === "object" && !Array.isArray(event.response) ? event.response : void 0;
4906
+ const usage = response?.usage;
4907
+ if (usage && typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens) && usage.input_tokens >= 0 && typeof usage.output_tokens === "number" && Number.isFinite(usage.output_tokens) && usage.output_tokens >= 0) {
4908
+ responseUsage = {
4909
+ inputTokens: usage.input_tokens,
4910
+ outputTokens: usage.output_tokens,
4911
+ ...readResponsesCachedTokens(usage)
4595
4912
  };
4596
- callbacks?.onUsage?.(openRouterUsage);
4913
+ callbacks?.onUsage?.(responseUsage);
4597
4914
  }
4598
- }
4599
- if (this.provider === "openrouter" && (type === "error" || type === "response.failed" || type === "response.incomplete")) {
4600
- openRouterStreamFailed = true;
4915
+ if (!terminal || !response || response.error != null || (type === "response.done" || response.status !== void 0) && response.status !== "completed") {
4916
+ const error = response?.error ?? event.error;
4917
+ const incomplete = response?.incomplete_details;
4918
+ streamFailure = {
4919
+ event: type,
4920
+ status: identifier(response?.status),
4921
+ code: identifier(error?.code ?? event.code),
4922
+ errorType: identifier(error?.type),
4923
+ reason: identifier(incomplete?.reason)
4924
+ };
4925
+ break responses;
4926
+ }
4927
+ completedResponse = response;
4601
4928
  continue;
4602
4929
  }
4603
4930
  if (type === "response.output_text.delta" || this.provider === "openrouter" && type === "response.content_part.delta") {
@@ -4631,33 +4958,33 @@ Missing: ${missing.join("; ")}
4631
4958
  }
4632
4959
  continue;
4633
4960
  }
4634
- if (type === "response.completed" || type === "response.incomplete" || this.provider === "openrouter" && type === "response.done") {
4635
- const response = event.response;
4636
- if (response) {
4637
- if (this.provider === "openrouter" && (openRouterStreamFailed || response.error != null || (type === "response.done" || response.status !== void 0) && response.status !== "completed")) {
4638
- openRouterStreamFailed = true;
4639
- continue;
4640
- }
4641
- completedResponse = response;
4642
- const usage2 = response.usage;
4643
- if (usage2 && this.provider !== "openrouter") {
4644
- callbacks?.onUsage?.({
4645
- inputTokens: Number(usage2.input_tokens ?? 0),
4646
- outputTokens: Number(usage2.output_tokens ?? 0)
4647
- });
4648
- }
4649
- }
4650
- }
4651
4961
  }
4652
4962
  }
4653
4963
  emitThinking(true);
4654
- if (!completedResponse || openRouterStreamFailed) {
4964
+ if (!completedResponse || streamFailure) {
4965
+ if (streamFailure) {
4966
+ void reader.cancel().catch(() => {
4967
+ });
4968
+ }
4969
+ appendNativeTrace({
4970
+ kind: "native-response-stream-error",
4971
+ provider: this.providerLabel,
4972
+ wireApi: this.wireApi,
4973
+ httpStatus: res.status,
4974
+ eventStreamContentType: /^text\/event-stream(?:\s*;|$)/i.test(res.headers.get("content-type") ?? ""),
4975
+ terminalFailure: streamFailure ?? null,
4976
+ eventTypes: [...eventTypes],
4977
+ receivedBytes,
4978
+ malformedEvents,
4979
+ trailingCharacters: buffer.length,
4980
+ usage: responseUsage ?? null
4981
+ });
4655
4982
  return {
4656
4983
  content: thinkingText ? [{ type: "text", text: thinkingText }] : [{ type: "text", text: "" }],
4657
4984
  stopReason: "error",
4658
4985
  durationMs: Date.now() - start,
4659
- ...openRouterUsage ? { usage: openRouterUsage } : {},
4660
- error: `${this.providerLabel} API error: ${openRouterStreamFailed ? "response stream failed" : "stream completed without final response"}`
4986
+ ...responseUsage ? { usage: responseUsage } : {},
4987
+ error: `${this.providerLabel} API error: ${streamFailure ? `Responses terminal failure ${JSON.stringify(streamFailure)}` : `stream completed without final response (HTTP ${res.status}; events=${[...eventTypes].join(",") || "none"}; malformed=${malformedEvents}; trailing=${buffer.length})`}`
4661
4988
  };
4662
4989
  }
4663
4990
  appendNativeTrace({
@@ -4710,21 +5037,10 @@ Missing: ${missing.join("; ")}
4710
5037
  thinkingText = reasoningSummaries.join("\n");
4711
5038
  emitThinking(true);
4712
5039
  }
4713
- const usageRecord = completedResponse.usage;
4714
- const usage = usageRecord ? {
4715
- inputTokens: Number(usageRecord.input_tokens ?? 0),
4716
- outputTokens: Number(usageRecord.output_tokens ?? 0),
4717
- // Responses `input_tokens` already INCLUDES the cached span (unlike
4718
- // Anthropic, which subtracts it), so no normalisation is needed —
4719
- // this is purely so cache behaviour becomes observable. Without it
4720
- // the Codex cache hit rate is unmeasurable: `prompt-cache.ts`
4721
- // instruments the Anthropic path only.
4722
- ...readResponsesCachedTokens(usageRecord)
4723
- } : void 0;
4724
5040
  return {
4725
5041
  content,
4726
5042
  stopReason: content.some((item) => item.type === "tool_use") ? "tool_use" : "end_turn",
4727
- usage,
5043
+ usage: responseUsage,
4728
5044
  durationMs: Date.now() - start,
4729
5045
  // `outputItems` is the complete, correctly-ordered response array —
4730
5046
  // reasoning items with their `encrypted_content` still attached, each
@@ -4780,5 +5096,6 @@ export {
4780
5096
  OperatorAbortError,
4781
5097
  parseUsageLimitReached,
4782
5098
  LOOP_SERVER_COMPACTION_TOKENS,
5099
+ getChatGptCodexAccessToken,
4783
5100
  LlmApiRuntime
4784
5101
  };