0sec-cli 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/0sec.js +40 -35
  2. package/README.md +9 -0
  3. package/chunks/{adapt-loop-ECQG7454.js → adapt-loop-SMH6V4PD.js} +4 -4
  4. package/chunks/{artifact-scraper-GJEZMOHF.js → artifact-scraper-HQ7HLP6V.js} +2 -2
  5. package/chunks/{assumption-mining-BVMMUZHB.js → assumption-mining-5SNIVVDN.js} +13 -13
  6. package/chunks/{chunk-SZJCPG2I.js → chunk-242TMK6G.js} +1 -1
  7. package/chunks/{chunk-HE7LCA7G.js → chunk-2JCCA2JL.js} +1 -1
  8. package/chunks/{chunk-SNKTC4BP.js → chunk-3KPWWJCI.js} +1 -1
  9. package/chunks/{chunk-IORC6MQX.js → chunk-5JI7L7KV.js} +1557 -145
  10. package/chunks/{chunk-KKGE5RQE.js → chunk-6L26OBWI.js} +1 -1
  11. package/chunks/{chunk-HT6P7RY3.js → chunk-72CISA2D.js} +1 -1
  12. package/chunks/{chunk-VGRDNSHA.js → chunk-AD7WXDH6.js} +3 -2
  13. package/chunks/{chunk-QOKTUOU2.js → chunk-ADOLI6DT.js} +1 -1
  14. package/chunks/{chunk-HDWV7PGI.js → chunk-AHTBZC3E.js} +2 -2
  15. package/chunks/{chunk-D6S3IIPG.js → chunk-ASUR522M.js} +3 -3
  16. package/chunks/{chunk-CLHCDHP4.js → chunk-ATATQACO.js} +1 -1
  17. package/chunks/{chunk-KTQDLNSR.js → chunk-BATRQBOR.js} +1 -1
  18. package/chunks/{chunk-K26SZ37E.js → chunk-BFR2CDV5.js} +7 -7
  19. package/chunks/{chunk-A6CLR72I.js → chunk-BFRB6HB2.js} +1 -1
  20. package/chunks/{chunk-H44E2CRN.js → chunk-BVAZO4WA.js} +1 -1
  21. package/chunks/{chunk-O462Y7P2.js → chunk-BZNEY2ZS.js} +334 -128
  22. package/chunks/{chunk-DN25OQQA.js → chunk-C2WXWFBB.js} +1 -1
  23. package/chunks/{chunk-QJQKHO7G.js → chunk-CMKXP5RR.js} +1 -1
  24. package/chunks/{chunk-2CJ776PV.js → chunk-GMT2AZKM.js} +23 -14
  25. package/chunks/{chunk-47TQJWDH.js → chunk-JQHOLLTD.js} +3 -3
  26. package/chunks/{chunk-MMLDQR4H.js → chunk-N7BOUYEV.js} +3 -3
  27. package/chunks/{chunk-QKICO43A.js → chunk-SVJEDVYK.js} +1 -1
  28. package/chunks/{chunk-3QFDYBZQ.js → chunk-TSK2AG6J.js} +18 -18
  29. package/chunks/{chunk-UJ4IK5PO.js → chunk-U5FLXGHT.js} +1 -1
  30. package/chunks/{chunk-SOG2U7B3.js → chunk-UCYRA73C.js} +1 -1
  31. package/chunks/{chunk-2SANI5RH.js → chunk-UR4ELX2Y.js} +61 -5
  32. package/chunks/{chunk-UM3ZNQIM.js → chunk-WEBMRWEG.js} +17 -9
  33. package/chunks/{chunk-RDZYQQLW.js → chunk-WXQ6BJ6A.js} +1 -1
  34. package/chunks/{chunk-4Y4KQJXE.js → chunk-XM5NLHYU.js} +6 -6
  35. package/chunks/{chunk-LOHTE223.js → chunk-YYRBQVFE.js} +1 -1
  36. package/chunks/{chunk-WKNNZVJS.js → chunk-Z5FA2XOX.js} +1 -1
  37. package/chunks/{commands-U3AMV5ZU.js → commands-TBC2F5CE.js} +635 -431
  38. package/chunks/{cost-ledger-ZCKMBJEN.js → cost-ledger-NU63MYZM.js} +2 -2
  39. package/chunks/{db-26NGQFKO.js → db-KFWOAUUB.js} +3 -3
  40. package/chunks/{disclose-A6AEIEDA.js → disclose-EKTWSCGW.js} +2 -2
  41. package/chunks/{dist-GL66KMUX.js → dist-4MAW5X6L.js} +2 -2
  42. package/chunks/{dist-FDKALV4R.js → dist-4YICS2J2.js} +29 -25
  43. package/chunks/{dist-JR67XKYO.js → dist-FMGSR3DW.js} +1 -1
  44. package/chunks/{eval-runner-DDLE5RQ3.js → eval-runner-7ZF472KV.js} +8 -8
  45. package/chunks/{exploit-agent-XP23ISN7.js → exploit-agent-45YQOHPJ.js} +1 -1
  46. package/chunks/{exploit-autoclimb-E6T7RRSC.js → exploit-autoclimb-UZGEMGYT.js} +1 -1
  47. package/chunks/{exploit-climb-2FAUP6XU.js → exploit-climb-SG23KB2G.js} +2 -2
  48. package/chunks/{harness-HWOYFKFY.js → harness-7ODOLOWU.js} +3 -3
  49. package/chunks/{hunt-scan-XPELSPQQ.js → hunt-scan-MMKUOETU.js} +11 -11
  50. package/chunks/{kernel-primitive-TENE3R7T.js → kernel-primitive-7U6CWHXD.js} +2 -2
  51. package/chunks/{kernel-vm-runner-4F6QSNFY.js → kernel-vm-runner-3JAU4O6J.js} +2 -2
  52. package/chunks/{native-loop-NTONUDP7.js → native-loop-RJCJPOUS.js} +7 -7
  53. package/chunks/{orchestrate-ZUWAUWBC.js → orchestrate-OC2VZN2X.js} +26 -26
  54. package/chunks/{pipeline-FCARFZU3.js → pipeline-BZK5BLJR.js} +2 -2
  55. package/chunks/{process-3Q7QJIOZ.js → process-LQRCS5OY.js} +3 -3
  56. package/chunks/{replay-runner-RRR4E2A7.js → replay-runner-45G3VU7X.js} +2 -2
  57. package/chunks/{run-ESNN4V5W.js → run-D56MPDKY.js} +324 -240
  58. package/chunks/{runtime-62ZQAH7H.js → runtime-G7YWIO3B.js} +5 -5
  59. package/chunks/{session-store-BCFQYDCE.js → session-store-GZ4A4KYT.js} +2 -2
  60. package/chunks/{variant-candidates-6KM6F6MD.js → variant-candidates-YERCYMPD.js} +3 -3
  61. package/chunks/{web-recon-prepass-DPVCBRZA.js → web-recon-prepass-JJGPEOLE.js} +3 -3
  62. package/package.json +1 -1
@@ -15,7 +15,7 @@ import {
15
15
  searchPublicReports,
16
16
  searchSimilar,
17
17
  searchTargetHistory
18
- } from "./chunk-LOHTE223.js";
18
+ } from "./chunk-YYRBQVFE.js";
19
19
  import {
20
20
  runWpFingerprint,
21
21
  summarizeWpFingerprint
@@ -34,7 +34,7 @@ import {
34
34
  loadJournal,
35
35
  rehydrateContext,
36
36
  renderSeedMessages
37
- } from "./chunk-SOG2U7B3.js";
37
+ } from "./chunk-UCYRA73C.js";
38
38
  import {
39
39
  applyPatchOps,
40
40
  parsePatch,
@@ -49,9 +49,10 @@ import {
49
49
  } from "./chunk-RRMJC3ZE.js";
50
50
  import {
51
51
  hasKnownMarkerText,
52
+ redactAuthHeaders,
52
53
  redactSensitiveHeaders,
53
54
  require_dist
54
- } from "./chunk-UM3ZNQIM.js";
55
+ } from "./chunk-WEBMRWEG.js";
55
56
  import {
56
57
  ScopePolicy,
57
58
  extractUrls,
@@ -62,11 +63,11 @@ import {
62
63
  diag,
63
64
  features,
64
65
  loadCloudCredentials
65
- } from "./chunk-2SANI5RH.js";
66
+ } from "./chunk-UR4ELX2Y.js";
66
67
  import {
67
68
  eventBus,
68
69
  isCloudEventSinkActive
69
- } from "./chunk-QOKTUOU2.js";
70
+ } from "./chunk-ADOLI6DT.js";
70
71
  import {
71
72
  DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
72
73
  DEFAULT_AUTONOMY_MODE,
@@ -77,7 +78,7 @@ import {
77
78
  getRates,
78
79
  homeStateDir,
79
80
  resolveIdentities
80
- } from "./chunk-KKGE5RQE.js";
81
+ } from "./chunk-6L26OBWI.js";
81
82
  import {
82
83
  external_exports
83
84
  } from "./chunk-YLMN3N25.js";
@@ -6631,7 +6632,7 @@ function redactAuthValues(value, secrets) {
6631
6632
  }
6632
6633
  return redacted;
6633
6634
  }
6634
- function redactAuthHeaders(headers, secrets) {
6635
+ function redactAuthHeaders2(headers, secrets) {
6635
6636
  const secretSet = new Set(secrets);
6636
6637
  return Object.fromEntries(Object.entries(headers).map(([name, value]) => [
6637
6638
  name,
@@ -13403,9 +13404,9 @@ async function executeDeepSourceReview(ctx, args) {
13403
13404
  const { statSync: statSync6 } = await import("node:fs");
13404
13405
  const { prepare } = await import("./prepare-MYY743TK.js");
13405
13406
  const { collectScopeFiles, countScopeFilesUpTo } = await import("./source-files-PWRQR6LY.js");
13406
- const { runHuntScan, makeMultiLensVerifier } = await import("./hunt-scan-XPELSPQQ.js");
13407
+ const { runHuntScan, makeMultiLensVerifier } = await import("./hunt-scan-MMKUOETU.js");
13407
13408
  const { loadAppsecFinderLenses } = await import("./appsec-catalog-CBWHGTEL.js");
13408
- const { ScanCostLedger } = await import("./cost-ledger-ZCKMBJEN.js");
13409
+ const { ScanCostLedger } = await import("./cost-ledger-NU63MYZM.js");
13409
13410
  let resolvedTarget;
13410
13411
  try {
13411
13412
  resolvedTarget = scopedPath(ctx, target);
@@ -13511,8 +13512,8 @@ async function executeFileSecurityReview(ctx, args) {
13511
13512
  if (!target)
13512
13513
  return errResult("`target` is required (a repo root path)");
13513
13514
  const { resolve: resolve15 } = await import("node:path");
13514
- const { createRuntime } = await import("./runtime-62ZQAH7H.js");
13515
- const { runFileReviewPipeline } = await import("./pipeline-FCARFZU3.js");
13515
+ const { createRuntime } = await import("./runtime-G7YWIO3B.js");
13516
+ const { runFileReviewPipeline } = await import("./pipeline-BZK5BLJR.js");
13516
13517
  let rootPath;
13517
13518
  try {
13518
13519
  rootPath = resolve15(scopedPath(ctx, target));
@@ -13547,8 +13548,8 @@ async function executeFileSecurityReview(ctx, args) {
13547
13548
  };
13548
13549
  }
13549
13550
  async function executeAssembleAdvisory(_ctx, args) {
13550
- const { osecDB } = await import("./dist-GL66KMUX.js");
13551
- const { renderAdvisoryMarkdown, assembleEvidencePack, decideFilingState, renderVendorNotificationMarkdown, EmptyPocError, UnreproducedFindingError } = await import("./disclose-A6AEIEDA.js");
13551
+ const { osecDB } = await import("./dist-4MAW5X6L.js");
13552
+ const { renderAdvisoryMarkdown, assembleEvidencePack, decideFilingState, renderVendorNotificationMarkdown, EmptyPocError, UnreproducedFindingError } = await import("./disclose-EKTWSCGW.js");
13552
13553
  const dbPath = typeof args.db_path === "string" && args.db_path.trim() ? args.db_path.trim() : void 0;
13553
13554
  const scanId = typeof args.scan_id === "string" && args.scan_id.trim() ? args.scan_id.trim() : void 0;
13554
13555
  const findingId = typeof args.finding_id === "string" && args.finding_id.trim() ? args.finding_id.trim() : void 0;
@@ -13654,7 +13655,7 @@ function rowToAdvisoryFinding(row) {
13654
13655
  return finding;
13655
13656
  }
13656
13657
  async function executeCveLookup(_ctx, args) {
13657
- const { findCveArtifacts, normaliseCveId } = await import("./artifact-scraper-GJEZMOHF.js");
13658
+ const { findCveArtifacts, normaliseCveId } = await import("./artifact-scraper-HQ7HLP6V.js");
13658
13659
  const rawCve = String(args.cve ?? args.cve_id ?? "").trim();
13659
13660
  if (!rawCve)
13660
13661
  return errResult("`cve` is required, e.g. CVE-2024-1086");
@@ -13861,8 +13862,8 @@ function refuseOutOfScope(ctx, url) {
13861
13862
  return null;
13862
13863
  }
13863
13864
  async function loadPersistedFinding(findingId, dbPathArg) {
13864
- const { osecDB, resolveOsecDbPath, listOsecRunDatabasePaths } = await import("./dist-GL66KMUX.js");
13865
- const { findingSchema, formatZodError } = await import("./dist-JR67XKYO.js");
13865
+ const { osecDB, resolveOsecDbPath, listOsecRunDatabasePaths } = await import("./dist-4MAW5X6L.js");
13866
+ const { findingSchema, formatZodError } = await import("./dist-FMGSR3DW.js");
13866
13867
  const { resolve: resolve15 } = await import("node:path");
13867
13868
  const { existsSync: existsSync9 } = await import("node:fs");
13868
13869
  const requestedId = findingId.trim();
@@ -13987,8 +13988,8 @@ async function executeVariantHunt(ctx, args) {
13987
13988
  return errResult2("`source` is required (a source tree path or git URL)");
13988
13989
  const { resolve: resolve15, join: join20 } = await import("node:path");
13989
13990
  const { prepare } = await import("./prepare-MYY743TK.js");
13990
- const { generateVariantCandidates } = await import("./variant-candidates-6KM6F6MD.js");
13991
- const { runHuntScan, makeSkepticVerifier } = await import("./hunt-scan-XPELSPQQ.js");
13991
+ const { generateVariantCandidates } = await import("./variant-candidates-YERCYMPD.js");
13992
+ const { runHuntScan, makeSkepticVerifier } = await import("./hunt-scan-MMKUOETU.js");
13992
13993
  let resolvedTarget;
13993
13994
  try {
13994
13995
  resolvedTarget = scopedPath2(ctx, target);
@@ -14054,8 +14055,8 @@ async function executeAssumptionHunt(ctx, args) {
14054
14055
  const { tmpdir: tmpdir5 } = await import("node:os");
14055
14056
  const { prepare } = await import("./prepare-MYY743TK.js");
14056
14057
  const { collectScopeFiles } = await import("./source-files-PWRQR6LY.js");
14057
- const { runAssumptionHunt } = await import("./assumption-mining-BVMMUZHB.js");
14058
- const { makeSkepticVerifier } = await import("./hunt-scan-XPELSPQQ.js");
14058
+ const { runAssumptionHunt } = await import("./assumption-mining-5SNIVVDN.js");
14059
+ const { makeSkepticVerifier } = await import("./hunt-scan-MMKUOETU.js");
14059
14060
  let resolvedTarget;
14060
14061
  try {
14061
14062
  resolvedTarget = scopedPath2(ctx, target);
@@ -14135,7 +14136,7 @@ async function executeGenerateFix(ctx, args) {
14135
14136
  return errResult2("`repo` is required (the repo root the patch applies to) \u2014 no scoped source path is set to default to.");
14136
14137
  const { resolve: resolve15 } = await import("node:path");
14137
14138
  const { runSourceFix } = await import("./fix-IG7LYXH3.js");
14138
- const { LlmApiRuntime: LlmApiRuntime2 } = await import("./runtime-62ZQAH7H.js");
14139
+ const { LlmApiRuntime: LlmApiRuntime2 } = await import("./runtime-G7YWIO3B.js");
14139
14140
  const loaded = await loadPersistedFinding(findingId, typeof args.db_path === "string" ? args.db_path : void 0);
14140
14141
  if ("error" in loaded)
14141
14142
  return errResult2(loaded.error);
@@ -14192,8 +14193,8 @@ async function executeVerifyFinding(ctx, args) {
14192
14193
  const loaded = await loadPersistedFinding(findingId, typeof args.db_path === "string" ? args.db_path : void 0);
14193
14194
  if ("error" in loaded)
14194
14195
  return errResult2(loaded.error);
14195
- const { runDeterministicReplay, LocalShellRunner } = await import("./replay-runner-RRR4E2A7.js");
14196
- const { VERSION: VERSION2 } = await import("./dist-JR67XKYO.js");
14196
+ const { runDeterministicReplay, LocalShellRunner } = await import("./replay-runner-45G3VU7X.js");
14197
+ const { VERSION: VERSION2 } = await import("./dist-FMGSR3DW.js");
14197
14198
  let outcome;
14198
14199
  try {
14199
14200
  outcome = await runDeterministicReplay(loaded.finding, {
@@ -14239,7 +14240,7 @@ async function executeProtocolConformance(ctx, args) {
14239
14240
  return errResult2(denial);
14240
14241
  const { runHttpConformanceCheck } = await import("./http-conformance-DU66MZIU.js");
14241
14242
  const { createLiveHttpSender } = await import("./http-sender-GWH2IYEA.js");
14242
- const { LlmApiRuntime: LlmApiRuntime2 } = await import("./runtime-62ZQAH7H.js");
14243
+ const { LlmApiRuntime: LlmApiRuntime2 } = await import("./runtime-G7YWIO3B.js");
14243
14244
  const llm = new LlmApiRuntime2({ type: "api", timeout: 6e4 });
14244
14245
  const send = createLiveHttpSender();
14245
14246
  const protocol = {
@@ -14378,7 +14379,7 @@ async function executeSafetyEval(ctx, args) {
14378
14379
  const denial = refuseOutOfScope(ctx, target);
14379
14380
  if (denial)
14380
14381
  return errResult2(denial);
14381
- const { runEval } = await import("./eval-runner-DDLE5RQ3.js");
14382
+ const { runEval } = await import("./eval-runner-7ZF472KV.js");
14382
14383
  const categories = Array.isArray(args.categories) ? args.categories.map((c) => String(c)).filter(Boolean) : void 0;
14383
14384
  const timeout = typeof args.timeout_ms === "number" && args.timeout_ms > 0 ? args.timeout_ms : void 0;
14384
14385
  let scorecard;
@@ -14557,14 +14558,14 @@ async function executeWeaponizeKernel(ctx, args) {
14557
14558
  if (!hasEngagementScope(ctx)) {
14558
14559
  return errResult2("weaponize_kernel requires an active engagement scope. It is not available for a no-scope session.");
14559
14560
  }
14560
- const { runWeaponization, kernelVmArtifactsReady } = await import("./harness-HWOYFKFY.js");
14561
+ const { runWeaponization, kernelVmArtifactsReady } = await import("./harness-7ODOLOWU.js");
14561
14562
  if (!kernelVmArtifactsReady()) {
14562
14563
  return errResult2("weaponize_kernel is denied: kernel-VM artifacts are not present. It only ever runs inside a DISPOSABLE kernel VM \u2014 set 0SEC_KERNEL_QEMU_KERNEL and 0SEC_KERNEL_QEMU_DISK to existing image paths. Refusing to run without the VM.");
14563
14564
  }
14564
14565
  const dmesg = typeof args.dmesg === "string" ? args.dmesg : "";
14565
14566
  if (!dmesg.trim())
14566
14567
  return errResult2("`dmesg` is required (the kernel crash log / KASAN splat to weaponize).");
14567
- const { classifyPrimitiveFromDmesg } = await import("./kernel-primitive-TENE3R7T.js");
14568
+ const { classifyPrimitiveFromDmesg } = await import("./kernel-primitive-7U6CWHXD.js");
14568
14569
  const crashType = typeof args.crash_type === "string" && args.crash_type.trim() ? args.crash_type.trim() : void 0;
14569
14570
  const primitive = classifyPrimitiveFromDmesg(dmesg, crashType);
14570
14571
  const reproducer = typeof args.reproducer === "string" && args.reproducer.trim() ? args.reproducer : void 0;
@@ -14599,12 +14600,12 @@ async function executeCveAdapt(ctx, args) {
14599
14600
  if (!hasEngagementScope(ctx)) {
14600
14601
  return errResult2("cve_adapt requires an active engagement scope. It is not available for a no-scope session.");
14601
14602
  }
14602
- const { kernelVmArtifactsReady } = await import("./harness-HWOYFKFY.js");
14603
+ const { kernelVmArtifactsReady } = await import("./harness-7ODOLOWU.js");
14603
14604
  if (!kernelVmArtifactsReady()) {
14604
14605
  return errResult2("cve_adapt is denied: its default verification boots a kernel VM and kernel-VM artifacts are not present. Set 0SEC_KERNEL_QEMU_KERNEL and 0SEC_KERNEL_QEMU_DISK to existing image paths. Refusing to run without the VM.");
14605
14606
  }
14606
- const { adaptAndVerify } = await import("./adapt-loop-ECQG7454.js");
14607
- const { findCveArtifacts, normaliseCveId } = await import("./artifact-scraper-GJEZMOHF.js");
14607
+ const { adaptAndVerify } = await import("./adapt-loop-SMH6V4PD.js");
14608
+ const { findCveArtifacts, normaliseCveId } = await import("./artifact-scraper-HQ7HLP6V.js");
14608
14609
  const rawCve = String(args.cve ?? "").trim();
14609
14610
  if (!rawCve)
14610
14611
  return errResult2("`cve` is required, e.g. CVE-2024-1086.");
@@ -19682,10 +19683,10 @@ var ToolExecutor = class _ToolExecutor {
19682
19683
  const responseHeaders = Object.fromEntries(chosen.res.headers.entries());
19683
19684
  const safeResponseBody = redactAuthValues(chosen.body, sensitiveValues);
19684
19685
  const safeRequestBody = body ? redactAuthValues(body, sensitiveValues) : void 0;
19685
- const safeSentHeaders = redactAuthHeaders(chosen.sentHeaders, sensitiveValues);
19686
+ const safeSentHeaders = redactAuthHeaders2(chosen.sentHeaders, sensitiveValues);
19686
19687
  const output = {
19687
19688
  status: chosen.res.status,
19688
- headers: redactAuthHeaders(responseHeaders, sensitiveValues),
19689
+ headers: redactAuthHeaders2(responseHeaders, sensitiveValues),
19689
19690
  body: safeResponseBody.slice(0, 1e4)
19690
19691
  // cap response size
19691
19692
  };
@@ -20091,11 +20092,11 @@ var ToolExecutor = class _ToolExecutor {
20091
20092
  const safeBody = redactAuthValues(text2, sensitiveValues);
20092
20093
  const output = {
20093
20094
  status: res.status,
20094
- headers: redactAuthHeaders(responseHeaders, sensitiveValues),
20095
+ headers: redactAuthHeaders2(responseHeaders, sensitiveValues),
20095
20096
  body: safeBody.slice(0, 1e4)
20096
20097
  };
20097
20098
  this.persistToolArtifact("submit_form", {
20098
- request: { url: fetchUrl, method, headers: redactAuthHeaders(sentHeaders, sensitiveValues), fields },
20099
+ request: { url: fetchUrl, method, headers: redactAuthHeaders2(sentHeaders, sensitiveValues), fields },
20099
20100
  response: { status: output.status, body: output.body.slice(0, 5e3) }
20100
20101
  });
20101
20102
  return { success: true, output };
@@ -21433,7 +21434,7 @@ var ToolExecutor = class _ToolExecutor {
21433
21434
  * inside `runNativeAgentLoop`), so this is effectively free.
21434
21435
  */
21435
21436
  async loadSubagentDeps() {
21436
- const { runNativeAgentLoop: runNativeAgentLoop2 } = await import("./native-loop-NTONUDP7.js");
21437
+ const { runNativeAgentLoop: runNativeAgentLoop2 } = await import("./native-loop-RJCJPOUS.js");
21437
21438
  return { runNativeAgentLoop: runNativeAgentLoop2 };
21438
21439
  }
21439
21440
  async runOneSubagent(task, maxTurns, base, deps, messagingOverride, selection, admittedLease, sharedContext) {
@@ -24912,7 +24913,7 @@ import { randomUUID as randomUUID14 } from "node:crypto";
24912
24913
  import { closeSync as closeSync7, fsyncSync as fsyncSync6, lstatSync as lstatSync5, openSync as openSync7, readFileSync as readFileSync13, renameSync as renameSync4, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7 } from "node:fs";
24913
24914
  import { chmod as chmod2, mkdir as mkdir4, mkdtemp as mkdtemp4, realpath as realpath2, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
24914
24915
  import { tmpdir as tmpdir4 } from "node:os";
24915
- import { dirname as dirname5, join as join15, resolve as resolve12 } from "node:path";
24916
+ import { dirname as dirname5, extname as extname2, join as join15, resolve as resolve12 } from "node:path";
24916
24917
 
24917
24918
  // packages/core/dist/improvement/loop.js
24918
24919
  import { randomUUID as randomUUID12 } from "node:crypto";
@@ -26952,7 +26953,7 @@ function resolveAnalyticsLevel(env = process.env) {
26952
26953
  }
26953
26954
  const raw = env[ANALYTICS_LEVEL_ENV];
26954
26955
  if (typeof raw !== "string")
26955
- return "off";
26956
+ return "full";
26956
26957
  const normalized = raw.trim().toLowerCase();
26957
26958
  return isAnalyticsLevel(normalized) ? normalized : "off";
26958
26959
  }
@@ -26993,46 +26994,145 @@ var REDACTED_SLACK = "<REDACTED-SLACK>";
26993
26994
  var REDACTED_JWT = "<REDACTED-JWT>";
26994
26995
  var REDACTED_PEM = "<REDACTED-PEM>";
26995
26996
  var REDACTED_SECRET = "<REDACTED-SECRET>";
26996
- var REDACTED_EMAIL = "<REDACTED-EMAIL>";
26997
26997
  var DEFAULT_REDACTION_CAP = 4e3;
26998
+ var MAX_CONTENT_BYTES = 262144;
26998
26999
  var PEM_RE = /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g;
26999
27000
  var OPENAI_RE = /\bsk-[A-Za-z0-9_-]{16,}/g;
27000
- var GH_RE = /\bgh[pousr]_[A-Za-z0-9]{20,}/g;
27001
+ var GH_RE = /\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})/g;
27001
27002
  var AWS_RE = /\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g;
27002
27003
  var GCP_RE = /\bAIza[0-9A-Za-z_-]{35}\b/g;
27003
27004
  var SLACK_RE = /\bxox[abprs]-[A-Za-z0-9-]{10,}/g;
27004
- var JWT_RE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+/g;
27005
- var CONN_STRING_RE = /\b([a-z][a-z0-9+.-]*):\/\/([^\s:/@]+):([^\s:/@]+)@/gi;
27006
- var ASSIGNMENT_RE = /\b(pass(?:word|wd)?|pwd|api[_-]?key|apikey|secret|token|credentials?)(\s*[:=]\s*)("[^"]*"|'[^']*'|\S+)/gi;
27007
- var ENV_SECRET_LINE_RE = /^([ \t]*(?:export[ \t]+)?[A-Za-z][A-Za-z0-9_]*(?:SECRET|TOKEN|KEY|PASSWORD|PASSWD|PWD|CREDENTIAL|APIKEY)[A-Za-z0-9_]*)([ \t]*=[ \t]*)(.+)$/gim;
27008
- var EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?\.[A-Za-z]{2,}/g;
27009
- var ENTROPY_RE = /(?=[A-Za-z0-9+/_-]{40,})(?=[A-Za-z0-9+/_-]*[a-z])(?=[A-Za-z0-9+/_-]*[A-Z])(?=[A-Za-z0-9+/_-]*[0-9])[A-Za-z0-9+/_-]{40,}={0,2}/g;
27005
+ var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
27006
+ var CONN_STRING_RE = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s:/?#"'`]+:[^\s/?#"'`]*@/gi;
27007
+ var CREDENTIAL_NAME = "password|passwd|pwd|secret|token|credentials?|api[_-]?key|access[_-]?key(?:[_-]?id)?|private[_-]?key|client[_-]?secret|(?:access|refresh|auth|session|bearer|api|csrf|xsrf)[_-]?token|session[_-]?cookie|authorization|proxy[_-]?authorization|cookie|set[_-]?cookie";
27008
+ var CREDENTIAL_KEY_RE = new RegExp(`(?:^|[^A-Za-z0-9])(?:${CREDENTIAL_NAME})$`, "i");
27009
+ var HEADER_RE = /^(?:authorization|proxy-authorization|cookie|set-cookie|x-auth-token|x-api-key|x-csrf-token|x-xsrf-token)$/i;
27010
+ var ASSIGNMENT_RE = /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\b[A-Za-z_][A-Za-z0-9_-]*)(\s*[:=]\s*)("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s"'\x60,;{}[\]&#\\]+)/g;
27011
+ var QUOTED_RE = /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g;
27012
+ function credentialKey(name) {
27013
+ return CREDENTIAL_KEY_RE.test(name.replace(/([a-z0-9])([A-Z])/g, "$1_$2"));
27014
+ }
27010
27015
  function truncate3(s, max) {
27011
- return s.length <= max ? s : `${s.slice(0, max)}...`;
27016
+ return s.length <= max ? s : `${s.slice(0, Math.max(0, max - 3))}${".".repeat(Math.min(3, max))}`;
27017
+ }
27018
+ function jsonValueEnd(text2, start) {
27019
+ let depth = 0;
27020
+ let quoted = false;
27021
+ for (let i = start; i < text2.length; i++) {
27022
+ const char = text2[i];
27023
+ if (quoted) {
27024
+ if (char === "\\")
27025
+ i++;
27026
+ else if (char === '"') {
27027
+ quoted = false;
27028
+ if (depth === 0)
27029
+ return i + 1;
27030
+ }
27031
+ } else if (char === '"')
27032
+ quoted = true;
27033
+ else if (char === "{" || char === "[")
27034
+ depth++;
27035
+ else if (char === "}" || char === "]") {
27036
+ if (depth === 0)
27037
+ return i;
27038
+ if (--depth === 0)
27039
+ return i + 1;
27040
+ } else if (depth === 0 && (char === "," || /\s/.test(char)))
27041
+ return i;
27042
+ }
27043
+ return text2.length;
27044
+ }
27045
+ function redactPlain(text2, ctx) {
27046
+ const secrets = ctx.authSecretValues ?? [];
27047
+ let out = secrets.length > 0 ? redactAuthValues(text2, secrets) : text2;
27048
+ out = redactAuthHeaders(out);
27049
+ out = out.replace(OPENAI_RE, REDACTED_OPENAI);
27050
+ out = out.replace(GH_RE, REDACTED_GH);
27051
+ out = out.replace(AWS_RE, REDACTED_AWS);
27052
+ out = out.replace(GCP_RE, REDACTED_GCP);
27053
+ out = out.replace(SLACK_RE, REDACTED_SLACK);
27054
+ out = out.replace(JWT_RE, REDACTED_JWT);
27055
+ return out.replace(CONN_STRING_RE, `$1${REDACTED_SECRET}@`);
27012
27056
  }
27013
27057
  function redactContent(text2, ctx = {}) {
27014
27058
  try {
27015
27059
  if (typeof text2 !== "string" || text2.length === 0)
27016
27060
  return "";
27017
- let out = text2;
27018
- const secrets = ctx.authSecretValues ?? [];
27019
- if (secrets.length > 0)
27020
- out = redactAuthValues(out, secrets);
27021
- out = redactSensitiveHeaders(out);
27022
- out = out.replace(PEM_RE, REDACTED_PEM);
27023
- out = out.replace(OPENAI_RE, REDACTED_OPENAI);
27024
- out = out.replace(GH_RE, REDACTED_GH);
27025
- out = out.replace(AWS_RE, REDACTED_AWS);
27026
- out = out.replace(GCP_RE, REDACTED_GCP);
27027
- out = out.replace(SLACK_RE, REDACTED_SLACK);
27028
- out = out.replace(JWT_RE, REDACTED_JWT);
27029
- out = out.replace(CONN_STRING_RE, `$1://${REDACTED_SECRET}@`);
27030
- out = out.replace(ASSIGNMENT_RE, `$1$2${REDACTED_SECRET}`);
27031
- out = out.replace(ENV_SECRET_LINE_RE, `$1$2${REDACTED_SECRET}`);
27032
- out = out.replace(EMAIL_RE, REDACTED_EMAIL);
27033
- out = out.replace(ENTROPY_RE, REDACTED_SECRET);
27034
27061
  const cap = ctx.maxChars ?? DEFAULT_REDACTION_CAP;
27035
- return truncate3(out, cap);
27062
+ const contentContext = ctx.maxChars === Number.POSITIVE_INFINITY ? ctx : { ...ctx, maxChars: Number.POSITIVE_INFINITY };
27063
+ if (/^\s*[[{]/.test(text2)) {
27064
+ let validJson = false;
27065
+ try {
27066
+ JSON.parse(text2);
27067
+ validJson = true;
27068
+ } catch (error) {
27069
+ if (!(error instanceof SyntaxError))
27070
+ throw error;
27071
+ }
27072
+ if (validJson) {
27073
+ let out2 = "";
27074
+ let cursor2 = 0;
27075
+ for (const match of text2.matchAll(QUOTED_RE)) {
27076
+ if (match.index < cursor2)
27077
+ continue;
27078
+ const token = match[0];
27079
+ const decoded = JSON.parse(token);
27080
+ const clean = redactContent(decoded, contentContext);
27081
+ const end = match.index + token.length;
27082
+ if (clean !== decoded) {
27083
+ out2 += text2.slice(cursor2, match.index) + JSON.stringify(clean);
27084
+ cursor2 = end;
27085
+ }
27086
+ let next = end;
27087
+ while (/\s/.test(text2[next] ?? ""))
27088
+ next++;
27089
+ if (text2[next] !== ":" || !credentialKey(decoded))
27090
+ continue;
27091
+ next++;
27092
+ while (/\s/.test(text2[next] ?? ""))
27093
+ next++;
27094
+ out2 += text2.slice(cursor2, next) + JSON.stringify(REDACTED_SECRET);
27095
+ cursor2 = jsonValueEnd(text2, next);
27096
+ }
27097
+ return truncate3(out2 + text2.slice(cursor2), cap);
27098
+ }
27099
+ }
27100
+ const assigned = text2.replace(PEM_RE, REDACTED_PEM).replace(ASSIGNMENT_RE, (match, key, separator, value) => {
27101
+ let name = key.startsWith('"') || key.startsWith("'") ? key.slice(1, -1) : key;
27102
+ if (key.startsWith('"')) {
27103
+ try {
27104
+ name = JSON.parse(key);
27105
+ } catch (error) {
27106
+ if (!(error instanceof SyntaxError))
27107
+ throw error;
27108
+ }
27109
+ }
27110
+ if (!credentialKey(name))
27111
+ return match;
27112
+ const quote = value[0] === '"' || value[0] === "'" ? value[0] : "";
27113
+ if (!quote && separator.includes(":") && HEADER_RE.test(name))
27114
+ return match;
27115
+ return `${key}${separator}${quote}${REDACTED_SECRET}${quote}`;
27116
+ });
27117
+ let out = "";
27118
+ let cursor = 0;
27119
+ for (const match of assigned.matchAll(QUOTED_RE)) {
27120
+ out += redactPlain(assigned.slice(cursor, match.index), ctx);
27121
+ const token = match[0];
27122
+ let decoded = token.slice(1, -1);
27123
+ if (token[0] === '"') {
27124
+ try {
27125
+ decoded = JSON.parse(token);
27126
+ } catch (error) {
27127
+ if (!(error instanceof SyntaxError))
27128
+ throw error;
27129
+ }
27130
+ }
27131
+ const clean = redactContent(decoded, contentContext);
27132
+ out += clean === decoded ? token : token[0] === '"' ? JSON.stringify(clean) : `'${clean}'`;
27133
+ cursor = match.index + token.length;
27134
+ }
27135
+ return truncate3(out + redactPlain(assigned.slice(cursor), ctx), cap);
27036
27136
  } catch {
27037
27137
  return "";
27038
27138
  }
@@ -27045,6 +27145,8 @@ var ANALYTICS_SENT_LOG_FILENAME = "analytics-sent.log";
27045
27145
  var TRANSMIT_TIMEOUT_MS = 4e3;
27046
27146
  var USAGE_FLUSH_DEBOUNCE_MS = 2e3;
27047
27147
  var TRANSMIT_DEBOUNCE_MS = 500;
27148
+ var MAX_RECORDS_PER_BATCH = 100;
27149
+ var MAX_BODY_BYTES = 1048576;
27048
27150
  var FINITE_PLATFORMS = /* @__PURE__ */ new Set([
27049
27151
  "darwin",
27050
27152
  "linux",
@@ -27106,6 +27208,10 @@ function classifyFailureText(text2) {
27106
27208
  return "stream-incomplete";
27107
27209
  return null;
27108
27210
  }
27211
+ var CONTENT_FIELDS = ["argsRedacted", "outputRedacted", "sourceRedacted"];
27212
+ var BODY_PREFIX = '{"records":[';
27213
+ var BODY_SUFFIX = "]}";
27214
+ var BODY_OVERHEAD_BYTES = BODY_PREFIX.length + BODY_SUFFIX.length;
27109
27215
  function redactRecordStrings(value) {
27110
27216
  try {
27111
27217
  if (typeof value === "string")
@@ -27113,16 +27219,16 @@ function redactRecordStrings(value) {
27113
27219
  if (Array.isArray(value))
27114
27220
  return value.map((v) => redactRecordStrings(v));
27115
27221
  if (value && typeof value === "object") {
27116
- const out = {};
27222
+ const out = /* @__PURE__ */ Object.create(null);
27117
27223
  for (const [k, v] of Object.entries(value)) {
27118
27224
  const rk = redactContent(k) || "<redacted-key>";
27119
- out[rk] = redactRecordStrings(v);
27225
+ out[rk] = typeof v === "string" && CONTENT_FIELDS.includes(k) ? redactContent(v, { maxChars: Number.POSITIVE_INFINITY }) : redactRecordStrings(v);
27120
27226
  }
27121
27227
  return out;
27122
27228
  }
27123
27229
  return value;
27124
27230
  } catch {
27125
- return value;
27231
+ return void 0;
27126
27232
  }
27127
27233
  }
27128
27234
  function toText(value) {
@@ -27162,12 +27268,19 @@ function bump(map, key) {
27162
27268
  }
27163
27269
  var AnalyticsPipeline = class {
27164
27270
  level = "off";
27271
+ observedLevel = "off";
27272
+ transmitting = null;
27165
27273
  homeDir;
27166
27274
  fetchImpl;
27167
27275
  installId = null;
27168
27276
  sessionId = newSessionId();
27169
27277
  acc = emptyAccumulator();
27170
27278
  accDirty = false;
27279
+ /**
27280
+ * Queued payload + the tier it was enqueued at. The tier is kept so on
27281
+ * downgrade we can purge records that are no longer permitted, and so
27282
+ * transmit can re-check each record against the live level.
27283
+ */
27171
27284
  batch = [];
27172
27285
  usageFlushTimer = null;
27173
27286
  transmitTimer = null;
@@ -27183,15 +27296,59 @@ var AnalyticsPipeline = class {
27183
27296
  if (opts.fetchImpl !== void 0)
27184
27297
  this.fetchImpl = opts.fetchImpl;
27185
27298
  }
27186
- /** Update the cached consent tier live (e.g. a /settings change). */
27299
+ /**
27300
+ * Update the cached consent tier live (e.g. a /settings change).
27301
+ *
27302
+ * Downgrading the level purges any queued records that are no longer
27303
+ * permitted. Switching to "off" also unsubscribes the bus sink and resets
27304
+ * the usage accumulator so events emitted while off are never queued.
27305
+ * Switching from "off" to a non-off level subscribes the sink with a fresh
27306
+ * accumulator.
27307
+ */
27187
27308
  setLevel(level) {
27309
+ const wasOff = this.level === "off";
27310
+ if (!levelAtLeast(level, this.level)) {
27311
+ this.batch = this.batch.filter((entry) => levelAtLeast(level, entry.requiredTier));
27312
+ }
27188
27313
  this.level = level;
27189
- if (level !== "off")
27314
+ this.effectiveLevel();
27315
+ if (level === "off") {
27316
+ this.unsubscribe?.();
27317
+ this.unsubscribe = null;
27318
+ this.acc = emptyAccumulator();
27319
+ this.accDirty = false;
27320
+ this.batch = [];
27321
+ } else {
27322
+ if (wasOff) {
27323
+ this.acc = emptyAccumulator();
27324
+ this.accDirty = false;
27325
+ }
27190
27326
  this.ensureSubscribed();
27327
+ }
27191
27328
  }
27192
- /** Current cached tier. */
27329
+ /** Current tier after live environment restrictions. */
27193
27330
  getLevel() {
27194
- return this.level;
27331
+ return this.effectiveLevel();
27332
+ }
27333
+ /**
27334
+ * The effective consent tier: minimum of the stored (saved/configured) tier
27335
+ * and the environment-resolved tier. This ensures an explicit env override
27336
+ * (e.g. `0SEC_ANALYTICS_LEVEL=off` in the parent shell) can never be
27337
+ * bypassed by a cached "full" pipeline. Also re-checks opt-out env vars
27338
+ * at every call, so a late-set `0SEC_OFFLINE` still takes effect.
27339
+ */
27340
+ effectiveLevel() {
27341
+ const envLevel = resolveAnalyticsLevel();
27342
+ const level = levelAtLeast(this.level, envLevel) ? envLevel : this.level;
27343
+ if (!levelAtLeast(level, this.observedLevel)) {
27344
+ this.batch = this.batch.filter((entry) => levelAtLeast(level, entry.requiredTier));
27345
+ if (level === "off") {
27346
+ this.acc = emptyAccumulator();
27347
+ this.accDirty = false;
27348
+ }
27349
+ }
27350
+ this.observedLevel = level;
27351
+ return level;
27195
27352
  }
27196
27353
  /** True iff the bus sink is currently subscribed. */
27197
27354
  isActive() {
@@ -27209,6 +27366,8 @@ var AnalyticsPipeline = class {
27209
27366
  // ── Bus-derived usage counters ────────────────────────────────────────
27210
27367
  onBusEvent(type, payload) {
27211
27368
  try {
27369
+ if (!levelAtLeast(this.effectiveLevel(), "usage"))
27370
+ return;
27212
27371
  switch (type) {
27213
27372
  case "tool_call_started": {
27214
27373
  const tool = typeof payload["tool"] === "string" ? payload["tool"] : "unknown";
@@ -27300,7 +27459,7 @@ var AnalyticsPipeline = class {
27300
27459
  */
27301
27460
  recordCommand(input) {
27302
27461
  try {
27303
- if (!levelAtLeast(this.level, "commands"))
27462
+ if (!levelAtLeast(this.effectiveLevel(), "commands"))
27304
27463
  return;
27305
27464
  const record = {
27306
27465
  tool: label(input.tool),
@@ -27320,7 +27479,7 @@ var AnalyticsPipeline = class {
27320
27479
  */
27321
27480
  recordCode(input) {
27322
27481
  try {
27323
- if (!levelAtLeast(this.level, "commands"))
27482
+ if (!levelAtLeast(this.effectiveLevel(), "commands"))
27324
27483
  return;
27325
27484
  const record = {
27326
27485
  lang: label(input.lang),
@@ -27337,7 +27496,7 @@ var AnalyticsPipeline = class {
27337
27496
  */
27338
27497
  recordScope(input) {
27339
27498
  try {
27340
- if (!levelAtLeast(this.level, "full"))
27499
+ if (!levelAtLeast(this.effectiveLevel(), "full"))
27341
27500
  return;
27342
27501
  const record = {
27343
27502
  targetRedacted: toText(input.target),
@@ -27353,7 +27512,7 @@ var AnalyticsPipeline = class {
27353
27512
  */
27354
27513
  recordFinding(input) {
27355
27514
  try {
27356
- if (!levelAtLeast(this.level, "full"))
27515
+ if (!levelAtLeast(this.effectiveLevel(), "full"))
27357
27516
  return;
27358
27517
  const record = {
27359
27518
  severity: label(input.severity),
@@ -27368,22 +27527,51 @@ var AnalyticsPipeline = class {
27368
27527
  }
27369
27528
  }
27370
27529
  // ── The choke point ───────────────────────────────────────────────────
27371
- /**
27372
- * The ONLY path toward transmission. Gate → redact → envelope → batch →
27373
- * transmit. Never throws; a below-tier record is silently dropped.
27374
- */
27530
+ /** The only path to transmission: consent, redaction, byte limits, envelope. */
27375
27531
  enqueue(record, requiredTier) {
27376
27532
  try {
27377
- if (!levelAtLeast(this.level, requiredTier))
27533
+ const level = this.effectiveLevel();
27534
+ if (level === "off" || !levelAtLeast(level, requiredTier))
27378
27535
  return;
27379
27536
  const redacted = redactRecordStrings(record);
27380
- const envelope = this.buildEnvelope();
27381
- const payload = { ...envelope, ...redacted };
27382
- this.batch.push(payload);
27537
+ if (!redacted)
27538
+ return;
27539
+ for (const field of CONTENT_FIELDS) {
27540
+ const content = redacted[field];
27541
+ if (typeof content !== "string")
27542
+ continue;
27543
+ const bytes2 = Buffer.byteLength(content, "utf8");
27544
+ if (bytes2 > MAX_CONTENT_BYTES) {
27545
+ this.reportOversize(field, bytes2, MAX_CONTENT_BYTES);
27546
+ return;
27547
+ }
27548
+ }
27549
+ const json = JSON.stringify({ ...this.buildEnvelope(), ...redacted });
27550
+ const bytes = Buffer.byteLength(json, "utf8");
27551
+ if (bytes + BODY_OVERHEAD_BYTES > MAX_BODY_BYTES) {
27552
+ this.reportOversize("record", bytes + BODY_OVERHEAD_BYTES, MAX_BODY_BYTES);
27553
+ return;
27554
+ }
27555
+ this.batch.push({ json, bytes, requiredTier });
27383
27556
  this.scheduleTransmit();
27384
27557
  } catch {
27385
27558
  }
27386
27559
  }
27560
+ reportOversize(field, bytes, maxBytes) {
27561
+ const filename = "analytics-outcomes.log";
27562
+ try {
27563
+ const dir = homeStateDir(this.homeDir);
27564
+ mkdirSync4(dir, { recursive: true, mode: 448 });
27565
+ appendFileSync(join14(dir, filename), `${JSON.stringify({ ts: Date.now(), outcome: "oversize", field, bytes, maxBytes })}
27566
+ `, { mode: 384 });
27567
+ } catch {
27568
+ }
27569
+ try {
27570
+ process.stderr.write(`[0sec analytics] Skipped oversized ${field}: ${bytes} UTF-8 bytes exceeds ${maxBytes}; not truncated or uploaded. Details: ${filename}.
27571
+ `);
27572
+ } catch {
27573
+ }
27574
+ }
27387
27575
  buildEnvelope() {
27388
27576
  if (this.installId === null) {
27389
27577
  try {
@@ -27396,7 +27584,7 @@ var AnalyticsPipeline = class {
27396
27584
  schemaVersion: ANALYTICS_SCHEMA_VERSION,
27397
27585
  installId: this.installId,
27398
27586
  sessionId: this.sessionId,
27399
- tier: this.level,
27587
+ tier: this.effectiveLevel(),
27400
27588
  ts: Date.now(),
27401
27589
  cliVersion: typeof VERSION === "string" ? VERSION : "unknown",
27402
27590
  platform: finitePlatform(),
@@ -27416,60 +27604,72 @@ var AnalyticsPipeline = class {
27416
27604
  } catch {
27417
27605
  }
27418
27606
  }
27419
- /**
27420
- * POST the pending batch. Fire-and-forget: resolves the credential set
27421
- * (silent no-op when the cloud is offline / unconfigured), writes the
27422
- * post-redaction payloads to the transparency log, then POSTs with a short
27423
- * timeout. Any failure is swallowed.
27424
- */
27425
- async transmit() {
27426
- const batch = this.batch.splice(0);
27427
- if (batch.length === 0)
27607
+ /** Serialize drains so consent changes can purge every still-pending record. */
27608
+ transmit() {
27609
+ if (!this.transmitting) {
27610
+ this.transmitting = Promise.resolve().then(() => this.drain()).finally(() => {
27611
+ this.transmitting = null;
27612
+ });
27613
+ }
27614
+ return this.transmitting;
27615
+ }
27616
+ async drain() {
27617
+ this.effectiveLevel();
27618
+ if (this.batch.length === 0)
27428
27619
  return;
27429
27620
  let creds;
27430
27621
  try {
27431
27622
  creds = loadCloudCredentials(this.homeDir ? { homeDir: this.homeDir } : {});
27432
27623
  } catch {
27624
+ this.batch = [];
27433
27625
  return;
27434
27626
  }
27435
- for (const p of batch)
27436
- this.appendTransparencyLog(p);
27437
- const controller = new AbortController();
27438
- let timer = null;
27439
- try {
27440
- timer = setTimeout(() => controller.abort(), TRANSMIT_TIMEOUT_MS);
27627
+ while (this.batch.length > 0) {
27628
+ const level = this.effectiveLevel();
27629
+ this.batch = this.batch.filter((entry) => levelAtLeast(level, entry.requiredTier));
27630
+ if (this.batch.length === 0)
27631
+ return;
27632
+ let bytes = BODY_OVERHEAD_BYTES;
27633
+ let count = 0;
27634
+ for (const entry of this.batch) {
27635
+ const nextBytes = bytes + entry.bytes + (count === 0 ? 0 : 1);
27636
+ if (count === MAX_RECORDS_PER_BATCH || nextBytes > MAX_BODY_BYTES)
27637
+ break;
27638
+ bytes = nextBytes;
27639
+ count++;
27640
+ }
27641
+ const chunk = this.batch.splice(0, count);
27642
+ const body = BODY_PREFIX + chunk.map((entry) => entry.json).join(",") + BODY_SUFFIX;
27643
+ for (const entry of chunk)
27644
+ this.appendTransparencyLog(entry.json);
27645
+ const controller = new AbortController();
27646
+ const timer = setTimeout(() => controller.abort(), TRANSMIT_TIMEOUT_MS);
27441
27647
  timer.unref?.();
27442
- } catch {
27443
- }
27444
- try {
27445
- const doFetch = this.fetchImpl ?? fetch;
27446
- await doFetch(`${creds.host}${ANALYTICS_ENDPOINT}`, {
27447
- method: "POST",
27448
- headers: {
27449
- Authorization: `Bearer ${creds.token}`,
27450
- "Content-Type": "application/json",
27451
- Accept: "application/json",
27452
- "User-Agent": `0sec-cli/${typeof VERSION === "string" ? VERSION : "unknown"}`
27453
- },
27454
- body: JSON.stringify({ records: batch }),
27455
- signal: controller.signal
27456
- });
27457
- } catch {
27458
- } finally {
27459
- if (timer) {
27460
- try {
27461
- clearTimeout(timer);
27462
- } catch {
27463
- }
27648
+ try {
27649
+ const doFetch = this.fetchImpl ?? fetch;
27650
+ await doFetch(`${creds.host}${ANALYTICS_ENDPOINT}`, {
27651
+ method: "POST",
27652
+ headers: {
27653
+ Authorization: `Bearer ${creds.token}`,
27654
+ "Content-Type": "application/json",
27655
+ Accept: "application/json",
27656
+ "User-Agent": `0sec-cli/${typeof VERSION === "string" ? VERSION : "unknown"}`
27657
+ },
27658
+ body,
27659
+ signal: controller.signal
27660
+ });
27661
+ } catch {
27662
+ } finally {
27663
+ clearTimeout(timer);
27464
27664
  }
27465
27665
  }
27466
27666
  }
27467
- appendTransparencyLog(payload) {
27667
+ appendTransparencyLog(json) {
27468
27668
  try {
27469
27669
  const dir = homeStateDir(this.homeDir);
27470
27670
  mkdirSync4(dir, { recursive: true, mode: 448 });
27471
27671
  const path = join14(dir, ANALYTICS_SENT_LOG_FILENAME);
27472
- appendFileSync(path, `${JSON.stringify(payload)}
27672
+ appendFileSync(path, `${json}
27473
27673
  `, { mode: 384 });
27474
27674
  } catch {
27475
27675
  }
@@ -27517,6 +27717,7 @@ var AnalyticsPipeline = class {
27517
27717
  this.usageFlushTimer = null;
27518
27718
  this.transmitTimer = null;
27519
27719
  this.level = "off";
27720
+ this.observedLevel = "off";
27520
27721
  this.homeDir = void 0;
27521
27722
  this.fetchImpl = void 0;
27522
27723
  this.installId = null;
@@ -27793,11 +27994,16 @@ var ExecutablePluginManager = class {
27793
27994
  throw new Error("Executable source exceeds size limit");
27794
27995
  }
27795
27996
  try {
27796
- analyticsPipeline.recordCode({
27797
- lang: "ts",
27798
- source: submission.files[submission.entry] ?? "",
27799
- origin: "executable-plugin"
27800
- });
27997
+ const level = analyticsPipeline.getLevel();
27998
+ if (level === "commands" || level === "full") {
27999
+ for (const name of names) {
28000
+ analyticsPipeline.recordCode({
28001
+ lang: extname2(name).slice(1) || "text",
28002
+ source: submission.files[name],
28003
+ origin: "executable-plugin"
28004
+ });
28005
+ }
28006
+ }
27801
28007
  } catch {
27802
28008
  }
27803
28009
  const before = this.readRegistry().plugins[manifest.id];