@stroq/cli 0.16.0 → 0.16.1

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.
package/dist/index.js CHANGED
@@ -3044,6 +3044,12 @@ function originClasses(hits, classes) {
3044
3044
  return { classes: out, counted };
3045
3045
  }
3046
3046
 
3047
+ // ../core/src/util/controls.ts
3048
+ var UNSAFE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f‪-‮⁦-⁩]/g;
3049
+ function neutralizeControls(text) {
3050
+ return text.replace(UNSAFE, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
3051
+ }
3052
+
3047
3053
  // ../core/src/provenance/describe.ts
3048
3054
  function ageLabel(fromIso, now) {
3049
3055
  const from = Date.parse(fromIso);
@@ -3059,7 +3065,10 @@ function toEvidence(hit) {
3059
3065
  }
3060
3066
  function describeEvidence(evidence, now) {
3061
3067
  const flagged = evidence.suspect ? "Stroq flagged that content as suspicious." : "that content was not flagged, but tool output is data, not instructions.";
3062
- return `"${evidence.excerpt}" appeared in the output of ${evidence.tool} (${evidence.source}) ${ageLabel(evidence.at, now)} ago; ${flagged}`;
3068
+ const quoted = neutralizeControls(
3069
+ `"${evidence.excerpt}" appeared in the output of ${evidence.tool} (${evidence.source})`
3070
+ );
3071
+ return `${quoted} ${ageLabel(evidence.at, now)} ago; ${flagged}`;
3063
3072
  }
3064
3073
 
3065
3074
  // ../core/src/secrets/extract.ts
@@ -3781,10 +3790,12 @@ ${raw}`;
3781
3790
 
3782
3791
  // ../core/src/secrets/describe.ts
3783
3792
  function describeSecretHit(hit) {
3793
+ const name = neutralizeControls(hit.name);
3794
+ const source = neutralizeControls(hit.source);
3784
3795
  if (hit.canary) {
3785
- return `the arguments contain the value of ${hit.name}, a Stroq canary; the session is now marked suspect.`;
3796
+ return `the arguments contain the value of ${name}, a Stroq canary; the session is now marked suspect.`;
3786
3797
  }
3787
- return `the arguments contain the value of ${hit.name} from ${hit.source}.`;
3798
+ return `the arguments contain the value of ${name} from ${source}.`;
3788
3799
  }
3789
3800
 
3790
3801
  // ../core/src/cloak/patterns.ts
@@ -4761,30 +4772,30 @@ var MUTATION_WIDTH = 32;
4761
4772
  var variantLine = (v) => ` ${v.scenarioId.padEnd(ID_WIDTH)} ${v.mutationId.padEnd(MUTATION_WIDTH)} allow (${v.ruleId ?? "no rule"})`;
4762
4773
  var erroredLine = (v) => ` ${v.scenarioId.padEnd(ID_WIDTH)} ${v.mutationId.padEnd(MUTATION_WIDTH)} ${v.error ?? "unknown error"}`;
4763
4774
  var ratio = (part, whole) => whole === 0 ? "\u2014" : `${(part / whole * 100).toFixed(1)}%`;
4764
- function formatFuzz(report) {
4775
+ function formatFuzz(report2) {
4765
4776
  const lines = [
4766
- `stroq attack --fuzz: ${report.scenarios} scenarios x ${report.mutations} mutations = ${report.variants} variants, policy ${report.policy}`,
4767
- `survived: ${report.survived} / ${report.variants} (${ratio(report.survived, report.variants)})`,
4768
- `escaped: ${report.escaped.length}`,
4769
- ...report.escaped.map(variantLine)
4777
+ `stroq attack --fuzz: ${report2.scenarios} scenarios x ${report2.mutations} mutations = ${report2.variants} variants, policy ${report2.policy}`,
4778
+ `survived: ${report2.survived} / ${report2.variants} (${ratio(report2.survived, report2.variants)})`,
4779
+ `escaped: ${report2.escaped.length}`,
4780
+ ...report2.escaped.map(variantLine)
4770
4781
  ];
4771
- if (report.escaped.length === 0)
4782
+ if (report2.escaped.length === 0)
4772
4783
  lines.push(" no escapes: every semantics-preserving variant was stopped.");
4773
- if (report.recorded.length > 0) {
4784
+ if (report2.recorded.length > 0) {
4774
4785
  lines.push(
4775
- `recorded, not asserted: ${report.recorded.length} (the mutation destroys the payload, so getting through proves nothing)`,
4776
- ...report.recorded.map(variantLine)
4786
+ `recorded, not asserted: ${report2.recorded.length} (the mutation destroys the payload, so getting through proves nothing)`,
4787
+ ...report2.recorded.map(variantLine)
4777
4788
  );
4778
4789
  }
4779
- if (report.errored.length > 0) {
4790
+ if (report2.errored.length > 0) {
4780
4791
  lines.push(
4781
- `errored: ${report.errored.length} (the mutation itself threw and could not be produced, so nothing ran)`,
4782
- ...report.errored.map(erroredLine)
4792
+ `errored: ${report2.errored.length} (the mutation itself threw and could not be produced, so nothing ran)`,
4793
+ ...report2.errored.map(erroredLine)
4783
4794
  );
4784
4795
  }
4785
- if (report.notApplicable > 0) {
4796
+ if (report2.notApplicable > 0) {
4786
4797
  lines.push(
4787
- `not applicable: ${report.notApplicable} \u2014 ${report.textless.length} scenario(s) carry no untrusted text to mutate: ${report.textless.join(", ")}`
4798
+ `not applicable: ${report2.notApplicable} \u2014 ${report2.textless.length} scenario(s) carry no untrusted text to mutate: ${report2.textless.join(", ")}`
4788
4799
  );
4789
4800
  }
4790
4801
  return `${lines.join("\n")}
@@ -4808,16 +4819,16 @@ function scenarioLine(result) {
4808
4819
  const incident = result.incident === null ? `${result.class ?? "synthetic cell"} (synthetic)` : `${result.incident.name} (${result.incident.date})`;
4809
4820
  return `${mark} ${result.id.padEnd(ID_WIDTH2)} ${result.outcome.padEnd(OUTCOME_WIDTH)} ${rule} ${incident}${result.ok ? "" : mismatch(result)}`;
4810
4821
  }
4811
- function summaryLine(report) {
4812
- const { blocked, asked, passed } = report.totals;
4813
- const head = `${plural(report.scenarios.length, "scenario")}: ${blocked} blocked, ${asked} asked, ${passed} passed through`;
4814
- if (report.ok && passed === 0) return `${head} \u2014 every attack was stopped.`;
4815
- const wrong = report.scenarios.filter((r) => !r.ok).length;
4822
+ function summaryLine(report2) {
4823
+ const { blocked, asked, passed } = report2.totals;
4824
+ const head = `${plural(report2.scenarios.length, "scenario")}: ${blocked} blocked, ${asked} asked, ${passed} passed through`;
4825
+ if (report2.ok && passed === 0) return `${head} \u2014 every attack was stopped.`;
4826
+ const wrong = report2.scenarios.filter((r) => !r.ok).length;
4816
4827
  return `${head} \u2014 ${plural(wrong, "scenario")} did not behave as expected; your policy is weaker than the default (compare it with policies/default.yaml).`;
4817
4828
  }
4818
- function formatReport(report) {
4819
- const header = `stroq attack: ${plural(report.scenarios.length, "recorded incident")} against policy ${report.policy}`;
4820
- return `${[header, ...report.scenarios.map(scenarioLine), summaryLine(report)].join("\n")}
4829
+ function formatReport(report2) {
4830
+ const header = `stroq attack: ${plural(report2.scenarios.length, "recorded incident")} against policy ${report2.policy}`;
4831
+ return `${[header, ...report2.scenarios.map(scenarioLine), summaryLine(report2)].join("\n")}
4821
4832
  `;
4822
4833
  }
4823
4834
 
@@ -4893,7 +4904,7 @@ async function runAttackCommand(args) {
4893
4904
  }
4894
4905
  if (values.fuzz) {
4895
4906
  const showProgress = values.json !== true && process.stderr.isTTY === true;
4896
- const report2 = await runFuzz(
4907
+ const report3 = await runFuzz(
4897
4908
  selected,
4898
4909
  MUTATIONS,
4899
4910
  loadPolicy(),
@@ -4901,14 +4912,14 @@ async function runAttackCommand(args) {
4901
4912
  showProgress ? (done, total) => process.stderr.write(`\rfuzzing ${done}/${total}`) : void 0
4902
4913
  );
4903
4914
  if (showProgress) process.stderr.write("\r");
4904
- process.stdout.write(values.json ? `${JSON.stringify(report2, null, 2)}
4905
- ` : formatFuzz(report2));
4906
- return report2.ok ? 0 : 1;
4915
+ process.stdout.write(values.json ? `${JSON.stringify(report3, null, 2)}
4916
+ ` : formatFuzz(report3));
4917
+ return report3.ok ? 0 : 1;
4907
4918
  }
4908
- const report = await runAttack(selected, loadPolicy(), displayPath2(policySource()));
4909
- process.stdout.write(values.json ? `${JSON.stringify(report, null, 2)}
4910
- ` : formatReport(report));
4911
- return report.ok ? 0 : 1;
4919
+ const report2 = await runAttack(selected, loadPolicy(), displayPath2(policySource()));
4920
+ process.stdout.write(values.json ? `${JSON.stringify(report2, null, 2)}
4921
+ ` : formatReport(report2));
4922
+ return report2.ok ? 0 : 1;
4912
4923
  }
4913
4924
 
4914
4925
  // src/commands/bench.ts
@@ -5007,39 +5018,39 @@ function runBench(dir) {
5007
5018
  };
5008
5019
  }
5009
5020
  var percent = (rate) => `${(rate * 100).toFixed(1)}%`;
5010
- function formatBench(report, opts = {}) {
5021
+ function formatBench(report2, opts = {}) {
5011
5022
  const lines = [
5012
- `stroq bench: ${report.files} files, ${Math.round(report.bytes / 1024)} KB, ${report.rules} rules`,
5013
- `flagged: ${report.flagged} / ${report.files} (${percent(report.rate)})`
5023
+ `stroq bench: ${report2.files} files, ${Math.round(report2.bytes / 1024)} KB, ${report2.rules} rules`,
5024
+ `flagged: ${report2.flagged} / ${report2.files} (${percent(report2.rate)})`
5014
5025
  ];
5015
- if (report.timedOut > 0) {
5016
- const noun = report.timedOut === 1 ? "scan" : "scans";
5026
+ if (report2.timedOut > 0) {
5027
+ const noun = report2.timedOut === 1 ? "scan" : "scans";
5017
5028
  lines.push(
5018
- `timedOut: ${report.timedOut} ${noun} hit the bench's scan budget and were forced suspect by that alone, not by a rule match \u2014 see docs/BENCH.md's Method section.`
5029
+ `timedOut: ${report2.timedOut} ${noun} hit the bench's scan budget and were forced suspect by that alone, not by a rule match \u2014 see docs/BENCH.md's Method section.`
5019
5030
  );
5020
5031
  }
5021
5032
  if (opts.timings === true) {
5022
- const headroom = DEFAULT_BUDGET_MS / Math.max(report.slowestMs, 1e-3);
5033
+ const headroom = DEFAULT_BUDGET_MS / Math.max(report2.slowestMs, 1e-3);
5023
5034
  const verdict = headroom < 2 ? " ** raise DEFAULT_BUDGET_MS **" : "";
5024
- const which = opts.verbose === true ? ` (${basename2(report.slowestFile)})` : "";
5035
+ const which = opts.verbose === true ? ` (${basename2(report2.slowestFile)})` : "";
5025
5036
  lines.push(
5026
- `slowest: ${report.slowestMs.toFixed(0)} ms${which} against production's ${DEFAULT_BUDGET_MS} ms budget \u2014 ${headroom.toFixed(1)}x headroom${verdict}`
5037
+ `slowest: ${report2.slowestMs.toFixed(0)} ms${which} against production's ${DEFAULT_BUDGET_MS} ms budget \u2014 ${headroom.toFixed(1)}x headroom${verdict}`
5027
5038
  );
5028
5039
  }
5029
- if (report.byRule.length === 0) {
5040
+ if (report2.byRule.length === 0) {
5030
5041
  lines.push("", "Nothing in this corpus trips a rule.");
5031
5042
  } else {
5032
5043
  lines.push("");
5033
- for (const hit of report.byRule) {
5044
+ for (const hit of report2.byRule) {
5034
5045
  lines.push(
5035
5046
  ` ${hit.ruleId.padEnd(18)} ${hit.title.slice(0, 48).padEnd(48)} ${hit.files} file${hit.files === 1 ? "" : "s"}`
5036
5047
  );
5037
5048
  }
5038
5049
  }
5039
- if (opts.verbose && report.flaggedFiles.length > 0) {
5040
- lines.push("", "Flagged files:", ...report.flaggedFiles.map((f) => ` ${f}`));
5050
+ if (opts.verbose && report2.flaggedFiles.length > 0) {
5051
+ lines.push("", "Flagged files:", ...report2.flaggedFiles.map((f) => ` ${f}`));
5041
5052
  }
5042
- lines.push("", `corpus: ${report.corpus}`);
5053
+ lines.push("", `corpus: ${report2.corpus}`);
5043
5054
  return `${lines.join("\n")}
5044
5055
  `;
5045
5056
  }
@@ -5061,10 +5072,10 @@ async function runBenchCommand(args) {
5061
5072
  );
5062
5073
  return 1;
5063
5074
  }
5064
- const report = runBench(dir);
5075
+ const report2 = runBench(dir);
5065
5076
  process.stdout.write(
5066
- values.json ? `${JSON.stringify(report, null, 2)}
5067
- ` : formatBench(report, { verbose: values.verbose, timings: true })
5077
+ values.json ? `${JSON.stringify(report2, null, 2)}
5078
+ ` : formatBench(report2, { verbose: values.verbose, timings: true })
5068
5079
  );
5069
5080
  return 0;
5070
5081
  }
@@ -5201,23 +5212,23 @@ function asiLine(risk) {
5201
5212
  const reason = ASI_UNCLAIMED_REASON[risk.id];
5202
5213
  return `${risk.id.padEnd(6)} ${risk.name} \u2014 not claimed by any scenario${reason ? ` (${reason})` : ""}`;
5203
5214
  }
5204
- function headerLine(report) {
5205
- const { total, documented, synthetic } = report.scenarios;
5206
- return `stroq coverage \u2014 ${total} scenarios (${documented} documented, ${synthetic} synthetic) \xB7 ATLAS ${report.atlasRelease} \xB7 OWASP ASI ${report.asiEdition}`;
5215
+ function headerLine(report2) {
5216
+ const { total, documented, synthetic } = report2.scenarios;
5217
+ return `stroq coverage \u2014 ${total} scenarios (${documented} documented, ${synthetic} synthetic) \xB7 ATLAS ${report2.atlasRelease} \xB7 OWASP ASI ${report2.asiEdition}`;
5207
5218
  }
5208
5219
  function summaryLine2(summary) {
5209
5220
  return `in scope: ${summary.inScope} techniques \u2014 ${summary.covered} covered, ${summary.partial} partial, ${summary.notCovered} not covered`;
5210
5221
  }
5211
- function formatCoverage(report) {
5222
+ function formatCoverage(report2) {
5212
5223
  const lines = [
5213
- headerLine(report),
5224
+ headerLine(report2),
5214
5225
  "",
5215
- ...report.techniques.map(techniqueLine),
5226
+ ...report2.techniques.map(techniqueLine),
5216
5227
  "",
5217
- summaryLine2(report.summary),
5228
+ summaryLine2(report2.summary),
5218
5229
  "",
5219
5230
  "OWASP ASI 2026:",
5220
- ...report.asi.map(asiLine)
5231
+ ...report2.asi.map(asiLine)
5221
5232
  ];
5222
5233
  return `${lines.join("\n")}
5223
5234
  `;
@@ -5231,16 +5242,16 @@ function navigatorComment(t) {
5231
5242
  if (t.limitation !== null) return t.limitation;
5232
5243
  return t.scenarios.length > 0 ? `covered, no stated limitation \u2014 scenarios: ${t.scenarios.join(", ")}` : "no scenario covers this yet";
5233
5244
  }
5234
- function toNavigatorLayer(report) {
5245
+ function toNavigatorLayer(report2) {
5235
5246
  return {
5236
5247
  name: "Stroq control mapping",
5237
5248
  versions: { layer: "4.3", navigator: "4.6.4" },
5238
5249
  domain: "atlas-atlas",
5239
- description: `What Stroq's hook-based action firewall addresses against MITRE ATLAS ${report.atlasRelease}, derived from its attack corpus: ${report.scenarios.total} scenarios (${report.scenarios.documented} documented, ${report.scenarios.synthetic} synthetic).`,
5250
+ description: `What Stroq's hook-based action firewall addresses against MITRE ATLAS ${report2.atlasRelease}, derived from its attack corpus: ${report2.scenarios.total} scenarios (${report2.scenarios.documented} documented, ${report2.scenarios.synthetic} synthetic).`,
5240
5251
  sorting: 0,
5241
5252
  layout: { layout: "side", showID: true, showName: true },
5242
5253
  hideDisabled: false,
5243
- techniques: report.techniques.map((t) => ({
5254
+ techniques: report2.techniques.map((t) => ({
5244
5255
  techniqueID: t.id,
5245
5256
  color: NAVIGATOR_COLOR[t.status],
5246
5257
  comment: navigatorComment(t),
@@ -5254,8 +5265,8 @@ function toNavigatorLayer(report) {
5254
5265
  { label: "not covered", color: NAVIGATOR_COLOR.not_covered }
5255
5266
  ],
5256
5267
  metadata: [
5257
- { name: "atlas_data_version", value: report.atlasRelease },
5258
- { name: "asi_edition", value: report.asiEdition }
5268
+ { name: "atlas_data_version", value: report2.atlasRelease },
5269
+ { name: "asi_edition", value: report2.asiEdition }
5259
5270
  ],
5260
5271
  links: [],
5261
5272
  showTacticRowBackground: false,
@@ -5271,9 +5282,9 @@ async function runCoverageCommand(args) {
5271
5282
  args: [...args],
5272
5283
  options: { format: { type: "string" }, json: { type: "boolean", default: false } }
5273
5284
  });
5274
- const report = buildCoverage();
5285
+ const report2 = buildCoverage();
5275
5286
  if (values.format === "navigator") {
5276
- process.stdout.write(`${JSON.stringify(toNavigatorLayer(report), null, 2)}
5287
+ process.stdout.write(`${JSON.stringify(toNavigatorLayer(report2), null, 2)}
5277
5288
  `);
5278
5289
  return 0;
5279
5290
  }
@@ -5285,15 +5296,15 @@ async function runCoverageCommand(args) {
5285
5296
  return 1;
5286
5297
  }
5287
5298
  process.stdout.write(
5288
- values.json ? `${JSON.stringify(report, null, 2)}
5289
- ` : formatCoverage(report)
5299
+ values.json ? `${JSON.stringify(report2, null, 2)}
5300
+ ` : formatCoverage(report2)
5290
5301
  );
5291
5302
  return 0;
5292
5303
  }
5293
5304
 
5294
5305
  // src/commands/doctor.ts
5295
5306
  import { existsSync as existsSync9, readFileSync as readFileSync13 } from "fs";
5296
- import { homedir as homedir13 } from "os";
5307
+ import { homedir as homedir14 } from "os";
5297
5308
  import { join as join18 } from "path";
5298
5309
 
5299
5310
  // src/adapters/cursor.ts
@@ -5669,26 +5680,76 @@ function cursorFailClosedOutput(raw, err) {
5669
5680
  }
5670
5681
 
5671
5682
  // src/commands/cursor-hooks.ts
5672
- import { homedir as homedir5 } from "os";
5683
+ import { homedir as homedir6 } from "os";
5673
5684
  import { join as join10 } from "path";
5674
5685
 
5675
5686
  // src/commands/config-file.ts
5676
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync } from "fs";
5677
- import { dirname as dirname5 } from "path";
5687
+ import {
5688
+ existsSync as existsSync3,
5689
+ mkdirSync as mkdirSync2,
5690
+ readFileSync as readFileSync9,
5691
+ realpathSync,
5692
+ statSync as statSync5,
5693
+ writeFileSync
5694
+ } from "fs";
5695
+ import { homedir as homedir5 } from "os";
5696
+ import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "path";
5678
5697
  var HOOK_TIMEOUT_SECONDS = 15;
5679
5698
  var HOOK_DEADLINE_FRACTION = 0.6;
5680
5699
  var hookDeadlineMs = (agentTimeoutSeconds) => Math.round(agentTimeoutSeconds * 1e3 * HOOK_DEADLINE_FRACTION);
5700
+ function parseFailure(err) {
5701
+ const message = err instanceof Error ? err.message : "";
5702
+ const where = /\(line \d+ column \d+\)|at position \d+/.exec(message)?.[0];
5703
+ return where === void 0 ? "not valid JSON" : `not valid JSON ${where}`;
5704
+ }
5705
+ var MAX_CONFIG_BYTES = 4 * 1024 * 1024;
5681
5706
  function readJsonObject(file) {
5682
5707
  if (!existsSync3(file)) return {};
5708
+ const info = statSync5(file);
5709
+ if (!info.isFile()) throw new Error(`cannot read ${file}: not a regular file`);
5710
+ if (info.size > MAX_CONFIG_BYTES)
5711
+ throw new Error(`cannot read ${file}: ${info.size} bytes is too large for an agent config`);
5683
5712
  const text = readFileSync9(file, "utf8");
5684
5713
  if (text.trim().length === 0) return {};
5685
5714
  try {
5686
5715
  return JSON.parse(text);
5687
5716
  } catch (err) {
5688
- throw new Error(`cannot parse ${file}: ${err.message}`, { cause: err });
5717
+ throw new Error(`cannot parse ${file}: ${parseFailure(err)}`, { cause: err });
5718
+ }
5719
+ }
5720
+ var within = (path, root) => {
5721
+ const rel = relative2(root, path);
5722
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
5723
+ };
5724
+ function realTarget(file) {
5725
+ const pending = [];
5726
+ let at = resolve2(file);
5727
+ for (; ; ) {
5728
+ try {
5729
+ return resolve2(realpathSync(at), ...pending);
5730
+ } catch {
5731
+ const parent = dirname5(at);
5732
+ if (parent === at) return resolve2(file);
5733
+ pending.unshift(basename3(at));
5734
+ at = parent;
5735
+ }
5736
+ }
5737
+ }
5738
+ function assertStaysInside(file) {
5739
+ const requested = resolve2(file);
5740
+ const roots = [process.cwd(), homedir5()].map((root2) => resolve2(root2)).filter((root2) => within(requested, root2)).sort((a, b) => b.length - a.length);
5741
+ const root = roots[0];
5742
+ if (root === void 0) return;
5743
+ const realRoot = realTarget(root);
5744
+ const target = realTarget(requested);
5745
+ if (!within(target, realRoot)) {
5746
+ throw new Error(
5747
+ `refusing to write ${file}: it resolves to ${target}, outside ${root}. A symlink in the path leads out of it; replace it with a regular file first.`
5748
+ );
5689
5749
  }
5690
5750
  }
5691
5751
  function writeJsonObject(file, value) {
5752
+ assertStaysInside(file);
5692
5753
  mkdirSync2(dirname5(file), { recursive: true });
5693
5754
  writeFileSync(file, `${JSON.stringify(value, null, 2)}
5694
5755
  `);
@@ -5731,7 +5792,7 @@ function mergeCursorHooks(settings, command) {
5731
5792
  return { ...settings, version: CURSOR_HOOKS_VERSION, hooks: { ...hooks, ...ours } };
5732
5793
  }
5733
5794
  function cursorHooksPath(scope, cwd = process.cwd()) {
5734
- return scope === "user" ? join10(homedir5(), ".cursor", "hooks.json") : join10(cwd, ".cursor", "hooks.json");
5795
+ return scope === "user" ? join10(homedir6(), ".cursor", "hooks.json") : join10(cwd, ".cursor", "hooks.json");
5735
5796
  }
5736
5797
  var readCursorHooks = (file) => readJsonObject(file);
5737
5798
  function installCursorHooks(file, command) {
@@ -5741,7 +5802,7 @@ function installCursorHooks(file, command) {
5741
5802
  }
5742
5803
 
5743
5804
  // src/commands/codex-hooks.ts
5744
- import { homedir as homedir6 } from "os";
5805
+ import { homedir as homedir7 } from "os";
5745
5806
  import { join as join11 } from "path";
5746
5807
  var CODEX_PRE_MATCHER = "Bash|exec_command|shell|local_shell|apply_patch|ApplyPatch|mcp__.*";
5747
5808
  var CODEX_POST_MATCHER = "Bash|exec_command|shell|local_shell|mcp__.*";
@@ -5833,7 +5894,7 @@ function hasAnyStroqCodexHook(settings) {
5833
5894
  );
5834
5895
  }
5835
5896
  function codexHooksPath(scope, cwd = process.cwd()) {
5836
- return scope === "user" ? join11(homedir6(), ".codex", "hooks.json") : join11(cwd, ".codex", "hooks.json");
5897
+ return scope === "user" ? join11(homedir7(), ".codex", "hooks.json") : join11(cwd, ".codex", "hooks.json");
5837
5898
  }
5838
5899
  var readCodexHooks = (file) => readJsonObject(file);
5839
5900
  function installCodexHooks(file, command) {
@@ -5844,7 +5905,7 @@ function installCodexHooks(file, command) {
5844
5905
 
5845
5906
  // src/commands/copilot-hooks.ts
5846
5907
  import { existsSync as existsSync4 } from "fs";
5847
- import { homedir as homedir7 } from "os";
5908
+ import { homedir as homedir8 } from "os";
5848
5909
  import { join as join12 } from "path";
5849
5910
  var COPILOT_HOOK_EVENTS = ["preToolUse", "postToolUse"];
5850
5911
  var COPILOT_HOOK_TIMEOUT_SECONDS = 30;
@@ -5876,7 +5937,7 @@ function copilotHooksPath(scope, cwd = process.cwd(), env = process.env) {
5876
5937
  if (scope === "project") return join12(cwd, ".github", "hooks", "stroq.json");
5877
5938
  const home = env["COPILOT_HOME"];
5878
5939
  return join12(
5879
- home !== void 0 && home !== "" ? home : join12(homedir7(), ".copilot"),
5940
+ home !== void 0 && home !== "" ? home : join12(homedir8(), ".copilot"),
5880
5941
  "hooks",
5881
5942
  "stroq.json"
5882
5943
  );
@@ -5900,15 +5961,15 @@ function installCopilotHooks(file, commandPre, commandPost) {
5900
5961
  }
5901
5962
 
5902
5963
  // src/commands/windsurf-hooks.ts
5903
- import { homedir as homedir8 } from "os";
5964
+ import { homedir as homedir9 } from "os";
5904
5965
  import { join as join13 } from "path";
5905
5966
 
5906
5967
  // src/adapters/windsurf.ts
5907
5968
  import { z as z9 } from "zod";
5908
5969
 
5909
5970
  // src/adapters/file-scan.ts
5910
- import { closeSync as closeSync2, openSync as openSync2, readSync as readSync2, statSync as statSync5 } from "fs";
5911
- import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
5971
+ import { closeSync as closeSync2, openSync as openSync2, readSync as readSync2, statSync as statSync6 } from "fs";
5972
+ import { isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
5912
5973
  var MAX_SCAN_READ_BYTES = 1048576;
5913
5974
  function readCapped(path, size, maxBytes) {
5914
5975
  const fd = openSync2(path, "r");
@@ -5923,8 +5984,8 @@ function readCapped(path, size, maxBytes) {
5923
5984
  function readScanText(filePath, cwd, maxBytes = MAX_SCAN_READ_BYTES) {
5924
5985
  if (filePath === "") return "";
5925
5986
  try {
5926
- const path = isAbsolute2(filePath) ? filePath : resolve2(cwd, filePath);
5927
- const stats = statSync5(path);
5987
+ const path = isAbsolute3(filePath) ? filePath : resolve3(cwd, filePath);
5988
+ const stats = statSync6(path);
5928
5989
  if (!stats.isFile() || stats.size === 0) return "";
5929
5990
  return readCapped(path, stats.size, maxBytes);
5930
5991
  } catch {
@@ -6318,7 +6379,7 @@ var eventEntries2 = (json2, event) => {
6318
6379
  var isStroqEntry2 = (value) => isPlainObject(value) && typeof value["command"] === "string" && / hook windsurf$/.test(value["command"]);
6319
6380
  var isStroqWindsurfHooks = (json2) => WINDSURF_EVENTS.every((event) => eventEntries2(json2, event).some(isStroqEntry2));
6320
6381
  function windsurfHooksPath(scope, cwd = process.cwd()) {
6321
- return scope === "user" ? join13(homedir8(), ".codeium", "windsurf", "hooks.json") : join13(cwd, ".windsurf", "hooks.json");
6382
+ return scope === "user" ? join13(homedir9(), ".codeium", "windsurf", "hooks.json") : join13(cwd, ".windsurf", "hooks.json");
6322
6383
  }
6323
6384
  var readWindsurfHooks = (file) => readJsonObject(file);
6324
6385
  function installWindsurfHooks(file, command) {
@@ -6328,7 +6389,7 @@ function installWindsurfHooks(file, command) {
6328
6389
  }
6329
6390
 
6330
6391
  // src/commands/antigravity-hooks.ts
6331
- import { homedir as homedir9 } from "os";
6392
+ import { homedir as homedir10 } from "os";
6332
6393
  import { join as join14 } from "path";
6333
6394
  var ANTIGRAVITY_HOOK_NAME = "stroq";
6334
6395
  var ANTIGRAVITY_HOOK_EVENTS = ["PreToolUse", "PostToolUse", "PreInvocation"];
@@ -6372,7 +6433,7 @@ function isStroqAntigravityHooks(json2) {
6372
6433
  return ANTIGRAVITY_HOOK_EVENTS.every((event) => eventHasStroqHandler(entry2, event));
6373
6434
  }
6374
6435
  function antigravityHooksPath(scope, cwd = process.cwd()) {
6375
- return scope === "user" ? join14(homedir9(), ".gemini", "config", "hooks.json") : join14(cwd, ".agents", "hooks.json");
6436
+ return scope === "user" ? join14(homedir10(), ".gemini", "config", "hooks.json") : join14(cwd, ".agents", "hooks.json");
6376
6437
  }
6377
6438
  var readAntigravityHooks = (file) => readJsonObject(file);
6378
6439
  function installAntigravityHooks(file, command) {
@@ -6382,8 +6443,8 @@ function installAntigravityHooks(file, command) {
6382
6443
  }
6383
6444
 
6384
6445
  // src/commands/init.ts
6385
- import { homedir as homedir12 } from "os";
6386
- import { join as join17, resolve as resolve4 } from "path";
6446
+ import { homedir as homedir13 } from "os";
6447
+ import { join as join17, resolve as resolve5 } from "path";
6387
6448
  import { parseArgs as parseArgs5 } from "util";
6388
6449
 
6389
6450
  // src/commands/install-record.ts
@@ -6424,7 +6485,7 @@ function installDrift(agent, scope, configText, record = readInstallRecord()) {
6424
6485
  // src/commands/openclaw-plugin.ts
6425
6486
  import { spawnSync } from "child_process";
6426
6487
  import { accessSync, constants, copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync11 } from "fs";
6427
- import { homedir as homedir10 } from "os";
6488
+ import { homedir as homedir11 } from "os";
6428
6489
  import { delimiter, dirname as dirname7, join as join15 } from "path";
6429
6490
  import { fileURLToPath as fileURLToPath2 } from "url";
6430
6491
  var OPENCLAW_PLUGIN_ID = "stroq";
@@ -6455,7 +6516,7 @@ function packagedPluginDir(from = dirname7(fileURLToPath2(import.meta.url))) {
6455
6516
  }
6456
6517
  function openclawPluginDir(env = process.env) {
6457
6518
  const home = env["STROQ_HOME"];
6458
- return join15(home !== void 0 && home !== "" ? home : join15(homedir10(), ".stroq"), PLUGIN_DIRNAME);
6519
+ return join15(home !== void 0 && home !== "" ? home : join15(homedir11(), ".stroq"), PLUGIN_DIRNAME);
6459
6520
  }
6460
6521
  function installOpenClawPlugin(dir, command) {
6461
6522
  const source = packagedPluginDir();
@@ -6514,12 +6575,12 @@ function runOpenClawInstall(bin, dir, run = spawnCommand) {
6514
6575
  }
6515
6576
 
6516
6577
  // src/commands/init-mcp.ts
6517
- import { existsSync as existsSync8, statSync as statSync6 } from "fs";
6518
- import { basename as basename3, resolve as resolve3 } from "path";
6578
+ import { existsSync as existsSync8, statSync as statSync7 } from "fs";
6579
+ import { basename as basename4, resolve as resolve4 } from "path";
6519
6580
 
6520
6581
  // src/commands/mcp-config.ts
6521
6582
  import { existsSync as existsSync7 } from "fs";
6522
- import { homedir as homedir11 } from "os";
6583
+ import { homedir as homedir12 } from "os";
6523
6584
  import { join as join16 } from "path";
6524
6585
  var MCP_CLIENTS = [
6525
6586
  "claude-desktop",
@@ -6528,7 +6589,7 @@ var MCP_CLIENTS = [
6528
6589
  "claude-code"
6529
6590
  ];
6530
6591
  var isMcpClient = (value) => MCP_CLIENTS.includes(value);
6531
- function claudeDesktopPath(plat = process.platform, env = process.env, home = homedir11()) {
6592
+ function claudeDesktopPath(plat = process.platform, env = process.env, home = homedir12()) {
6532
6593
  if (plat === "darwin")
6533
6594
  return join16(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
6534
6595
  if (plat === "win32")
@@ -6544,7 +6605,7 @@ function windsurfMcpPath(home) {
6544
6605
  return existsSync7(ide) ? ide : join16(home, ".codeium", "mcp_config.json");
6545
6606
  }
6546
6607
  function mcpConfigPath(client, scope, cwd = process.cwd()) {
6547
- const home = homedir11();
6608
+ const home = homedir12();
6548
6609
  if (client === "claude-desktop") return claudeDesktopPath(process.platform, process.env, home);
6549
6610
  if (client === "windsurf") return windsurfMcpPath(home);
6550
6611
  if (client === "cursor")
@@ -6680,13 +6741,13 @@ function countWrapped(config) {
6680
6741
  var MCP_NOTE = 'Restart the MCP client before this takes effect: it launches its servers once, when it starts.\nThis directory is recorded as the project for every wrapped server: it is what feeds the secret index and the path rules.\nAn MCP proxy cannot prompt, so a policy "ask" arrives as a blocked tool result naming the rule to relax.\nHTTP servers (url/serverUrl) have no subprocess to wrap and are listed as skipped.\nEach wrapped server starts with the variables its own "env" block declares plus the ones any process needs; nothing else in your environment reaches it. Add a variable to that block and re-run this command if a server needs one.\n"stroq init --agent mcp --unwrap" restores every wrapped entry to its original command.\n';
6681
6742
  function mcpTarget(client, configPath, scope) {
6682
6743
  if (client === void 0) {
6683
- const file = resolve3(configPath ?? "");
6684
- return { file, label: basename3(file) };
6744
+ const file = resolve4(configPath ?? "");
6745
+ return { file, label: basename4(file) };
6685
6746
  }
6686
6747
  if (!isMcpClient(client)) return null;
6687
6748
  return { file: mcpConfigPath(client, scope), label: client };
6688
6749
  }
6689
- var isExistingFile = (path) => existsSync8(path) && statSync6(path).isFile();
6750
+ var isExistingFile = (path) => existsSync8(path) && statSync7(path).isFile();
6690
6751
  function formatOutcome({ action, name }) {
6691
6752
  const qualified = /^(.+) \((.+)\)$/.exec(action);
6692
6753
  return qualified ? `${qualified[1]} ${name} (${qualified[2]})` : `${action} ${name}`;
@@ -6798,7 +6859,7 @@ function mergeHooks(settings, command) {
6798
6859
  };
6799
6860
  }
6800
6861
  function settingsPath(scope, cwd = process.cwd()) {
6801
- return scope === "user" ? join17(homedir12(), ".claude", "settings.json") : join17(cwd, ".claude", "settings.json");
6862
+ return scope === "user" ? join17(homedir13(), ".claude", "settings.json") : join17(cwd, ".claude", "settings.json");
6802
6863
  }
6803
6864
  var readSettings = (file) => readJsonObject(file);
6804
6865
  function installHooks(file, command) {
@@ -6990,7 +7051,7 @@ async function runInit(args) {
6990
7051
  const scope = values.user ? "user" : "project";
6991
7052
  const dryRun = values["dry-run"] === true;
6992
7053
  const node = process.execPath;
6993
- const entry2 = resolve4(process.argv[1] ?? "");
7054
+ const entry2 = resolve5(process.argv[1] ?? "");
6994
7055
  if (agent === "mcp")
6995
7056
  return initMcp(scope, hookArgv(node, entry2), dryRun, {
6996
7057
  ...values.client === void 0 ? {} : { client: values.client },
@@ -7034,7 +7095,7 @@ var AGENT_DIRS = {
7034
7095
  windsurf: { project: [".windsurf"], user: [join18(".codeium", "windsurf")] },
7035
7096
  antigravity: { project: [".agents"], user: [join18(".gemini", "antigravity-cli")] }
7036
7097
  };
7037
- function detectedAgents(cwd, home = homedir13()) {
7098
+ function detectedAgents(cwd, home = homedir14()) {
7038
7099
  return Object.entries(AGENT_DIRS).filter(
7039
7100
  ([, dirs]) => dirs.project.some((d) => existsSync9(join18(cwd, d))) || dirs.user.some((d) => existsSync9(join18(home, d)))
7040
7101
  ).map(([agent]) => agent);
@@ -7257,7 +7318,7 @@ function secretsDetail(stats) {
7257
7318
  }
7258
7319
  async function checkSecrets() {
7259
7320
  try {
7260
- const stats = await new FileSecretIndex(secretsFile(), homedir13()).stats();
7321
+ const stats = await new FileSecretIndex(secretsFile(), homedir14()).stats();
7261
7322
  const ok = !stats.corrupt && stats.unreadable === 0 && !stats.truncated;
7262
7323
  return { name: "secrets", ok, detail: secretsDetail(stats) };
7263
7324
  } catch (err) {
@@ -7315,19 +7376,19 @@ async function doctorReport(cwd = process.cwd(), opts = {}) {
7315
7376
  };
7316
7377
  }
7317
7378
  async function runDoctor(argv = []) {
7318
- const report = await doctorReport(process.cwd(), { all: argv.includes("--all") });
7319
- for (const check of report.checks)
7379
+ const report2 = await doctorReport(process.cwd(), { all: argv.includes("--all") });
7380
+ for (const check of report2.checks)
7320
7381
  process.stdout.write(`${check.ok ? "\u2714" : "\u2718"} ${check.name}: ${check.detail}
7321
7382
  `);
7322
- return report.checks.every((c) => c.ok) ? 0 : 1;
7383
+ return report2.checks.every((c) => c.ok) ? 0 : 1;
7323
7384
  }
7324
7385
 
7325
7386
  // src/commands/exposure.ts
7326
- import { homedir as homedir17 } from "os";
7387
+ import { homedir as homedir18 } from "os";
7327
7388
 
7328
7389
  // src/exposure/context-surface.ts
7329
- import { existsSync as existsSync10, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
7330
- import { homedir as homedir14 } from "os";
7390
+ import { existsSync as existsSync10, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync8 } from "fs";
7391
+ import { homedir as homedir15 } from "os";
7331
7392
  import { join as join19 } from "path";
7332
7393
  var MAX_CONTEXT_FILES = 5e3;
7333
7394
  var MAX_SCAN_BYTES = 256 * 1024;
@@ -7366,7 +7427,7 @@ function walk2(dir, match, out) {
7366
7427
  const full = join19(dir, name);
7367
7428
  let isDir = false;
7368
7429
  try {
7369
- isDir = statSync7(full).isDirectory();
7430
+ isDir = statSync8(full).isDirectory();
7370
7431
  } catch {
7371
7432
  continue;
7372
7433
  }
@@ -7393,7 +7454,7 @@ function countForeignHooks(cwd) {
7393
7454
  return count;
7394
7455
  }
7395
7456
  var isMarkdown = (name) => name.endsWith(".md");
7396
- function contextSurface(cwd, home = homedir14()) {
7457
+ function contextSurface(cwd, home = homedir15()) {
7397
7458
  const skills = new FileSet();
7398
7459
  walk2(join19(home, ".claude", "skills"), isMarkdown, skills);
7399
7460
  walk2(join19(cwd, ".claude", "skills"), isMarkdown, skills);
@@ -7421,7 +7482,7 @@ function contextSurface(cwd, home = homedir14()) {
7421
7482
  ]) {
7422
7483
  let text;
7423
7484
  try {
7424
- const size = statSync7(file).size;
7485
+ const size = statSync8(file).size;
7425
7486
  bytes += size;
7426
7487
  if (size > MAX_SCAN_BYTES) continue;
7427
7488
  text = readFileSync14(file, "utf8");
@@ -7514,7 +7575,7 @@ function mcpFindings(surfaces) {
7514
7575
  }
7515
7576
 
7516
7577
  // src/exposure/privilege-surface.ts
7517
- import { homedir as homedir15 } from "os";
7578
+ import { homedir as homedir16 } from "os";
7518
7579
  import { join as join20 } from "path";
7519
7580
  var read = (file) => {
7520
7581
  try {
@@ -7578,7 +7639,7 @@ function claudeHits(file, json2, scope) {
7578
7639
  return hits;
7579
7640
  }
7580
7641
  var autoApproves = (value) => value === true || isPlainObject(value) && Object.keys(value).length > 0;
7581
- function privilegeSurface(cwd, home = homedir15()) {
7642
+ function privilegeSurface(cwd, home = homedir16()) {
7582
7643
  const hits = [];
7583
7644
  const seen = /* @__PURE__ */ new Set();
7584
7645
  const push = (hit) => {
@@ -7699,7 +7760,7 @@ function flaggedTools(tools) {
7699
7760
  ).map((t) => typeof t.name === "string" ? t.name : "(unnamed)");
7700
7761
  }
7701
7762
  async function probeOne(spec, timeoutMs) {
7702
- return new Promise((resolve11) => {
7763
+ return new Promise((resolve12) => {
7703
7764
  const empty = { server: spec.name, tools: 0, flagged: [] };
7704
7765
  let child;
7705
7766
  try {
@@ -7709,7 +7770,7 @@ async function probeOne(spec, timeoutMs) {
7709
7770
  stdio: ["pipe", "pipe", "ignore"]
7710
7771
  });
7711
7772
  } catch (err) {
7712
- resolve11({ ...empty, error: err.message });
7773
+ resolve12({ ...empty, error: err.message });
7713
7774
  return;
7714
7775
  }
7715
7776
  let buf = "";
@@ -7719,7 +7780,7 @@ async function probeOne(spec, timeoutMs) {
7719
7780
  done = true;
7720
7781
  clearTimeout(timer);
7721
7782
  killChildTree(child, "SIGTERM");
7722
- resolve11(result);
7783
+ resolve12(result);
7723
7784
  };
7724
7785
  const timer = setTimeout(
7725
7786
  () => finish({ ...empty, error: `no tools/list response within ${timeoutMs} ms` }),
@@ -7796,12 +7857,12 @@ function probeFindings(results) {
7796
7857
  }
7797
7858
 
7798
7859
  // src/exposure/reach.ts
7799
- function reachFrom(report, surfaces) {
7800
- const total = report.totals.blocked + report.totals.asked + report.totals.passed;
7860
+ function reachFrom(report2, surfaces) {
7861
+ const total = report2.totals.blocked + report2.totals.asked + report2.totals.passed;
7801
7862
  const anyAgentProtected = surfaces.some((s) => s.detected && s.protected);
7802
7863
  return {
7803
7864
  total,
7804
- passedPolicy: anyAgentProtected ? report.totals.passed : total,
7865
+ passedPolicy: anyAgentProtected ? report2.totals.passed : total,
7805
7866
  anyAgentProtected
7806
7867
  };
7807
7868
  }
@@ -7828,8 +7889,8 @@ function reachFindings(reach) {
7828
7889
  }
7829
7890
 
7830
7891
  // src/exposure/repo-surface.ts
7831
- import { existsSync as existsSync12, readFileSync as readFileSync15, readdirSync as readdirSync6, statSync as statSync8 } from "fs";
7832
- import { join as join21, relative as relative2 } from "path";
7892
+ import { existsSync as existsSync12, readFileSync as readFileSync15, readdirSync as readdirSync6, statSync as statSync9 } from "fs";
7893
+ import { join as join21, relative as relative3 } from "path";
7833
7894
  var PRE_TRUST = /* @__PURE__ */ new Set([
7834
7895
  "git-config-exec",
7835
7896
  "git-config-include",
@@ -7838,12 +7899,12 @@ var PRE_TRUST = /* @__PURE__ */ new Set([
7838
7899
  "devcontainer-host-command"
7839
7900
  ]);
7840
7901
  var MAX_DIRS = 4e3;
7841
- var MAX_CONFIG_BYTES = 256 * 1024;
7902
+ var MAX_CONFIG_BYTES2 = 256 * 1024;
7842
7903
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".venv", "venv", "vendor", "target", "dist", "build"]);
7843
7904
  var readTextOr = (file, fallback = "") => {
7844
7905
  try {
7845
- const stat5 = statSync8(file);
7846
- if (!stat5.isFile() || stat5.size > MAX_CONFIG_BYTES) return fallback;
7906
+ const stat5 = statSync9(file);
7907
+ if (!stat5.isFile() || stat5.size > MAX_CONFIG_BYTES2) return fallback;
7847
7908
  return readFileSync15(file, "utf8");
7848
7909
  } catch {
7849
7910
  return fallback;
@@ -7900,7 +7961,7 @@ function gitAttributesHits(root) {
7900
7961
  }
7901
7962
  function runsAsGitHook(file, plat) {
7902
7963
  try {
7903
- const stat5 = statSync8(file);
7964
+ const stat5 = statSync9(file);
7904
7965
  if (!stat5.isFile()) return false;
7905
7966
  return plat === "win32" || (stat5.mode & 73) !== 0;
7906
7967
  } catch {
@@ -7922,7 +7983,7 @@ function huskyHits(root) {
7922
7983
  try {
7923
7984
  return readdirSync6(dir).filter((name) => !name.startsWith("_") && !name.startsWith(".")).filter((name) => {
7924
7985
  try {
7925
- return statSync8(join21(dir, name)).isFile();
7986
+ return statSync9(join21(dir, name)).isFile();
7926
7987
  } catch {
7927
7988
  return false;
7928
7989
  }
@@ -7948,7 +8009,7 @@ function devcontainerHits(root) {
7948
8009
  for (const file of candidates) {
7949
8010
  const text = readTextOr(file);
7950
8011
  if (text === "") continue;
7951
- const rel = relative2(root, file);
8012
+ const rel = relative3(root, file);
7952
8013
  for (const key2 of DEVCONTAINER_HOST_KEYS) {
7953
8014
  if (new RegExp(`"${key2}"\\s*:`).test(text))
7954
8015
  hits.push({ kind: "devcontainer-host-command", file: rel, what: key2 });
@@ -7996,7 +8057,7 @@ function nestedBareRepos(root) {
7996
8057
  if (dir !== root && names.has("HEAD") && names.has("objects") && names.has("refs")) {
7997
8058
  hits.push({
7998
8059
  kind: "nested-bare-repo",
7999
- file: relative2(root, dir),
8060
+ file: relative3(root, dir),
8000
8061
  what: "HEAD, objects, refs"
8001
8062
  });
8002
8063
  continue;
@@ -8048,27 +8109,27 @@ function repoFindings(surface) {
8048
8109
  }
8049
8110
 
8050
8111
  // src/exposure/redact.ts
8051
- function toShareable(report) {
8052
- const detected = report.agents.filter((a) => a.detected);
8053
- const c = report.context;
8112
+ function toShareable(report2) {
8113
+ const detected = report2.agents.filter((a) => a.detected);
8114
+ const c = report2.context;
8054
8115
  return {
8055
8116
  version: 1,
8056
- probed: report.probed,
8057
- probeFailures: report.probeFailures.length,
8117
+ probed: report2.probed,
8118
+ probeFailures: report2.probeFailures.length,
8058
8119
  agentsDetected: detected.length,
8059
8120
  agentsProtected: detected.filter((a) => a.protected).length,
8060
- mcpStdio: report.mcp.reduce((n, m) => n + m.stdio, 0),
8061
- mcpWrapped: report.mcp.reduce((n, m) => n + m.wrapped, 0),
8062
- mcpHttp: report.mcp.reduce((n, m) => n + m.http, 0),
8121
+ mcpStdio: report2.mcp.reduce((n, m) => n + m.stdio, 0),
8122
+ mcpWrapped: report2.mcp.reduce((n, m) => n + m.wrapped, 0),
8123
+ mcpHttp: report2.mcp.reduce((n, m) => n + m.http, 0),
8063
8124
  contextFiles: c.skills + c.subagents + c.commands + c.instructionFiles,
8064
8125
  contextFlagged: c.flagged.length,
8065
8126
  foreignHooks: c.foreignHooks,
8066
- privilegeKeys: report.privilege.map((p) => p.key),
8067
- repoPreTrust: report.repo.preTrust.map((h) => h.kind),
8068
- repoOnOpen: report.repo.onOpen.length,
8069
- reachTotal: report.reach.total,
8070
- reachPassed: report.reach.passedPolicy,
8071
- findings: report.findings.map((f) => ({ class: f.class, severity: f.severity }))
8127
+ privilegeKeys: report2.privilege.map((p) => p.key),
8128
+ repoPreTrust: report2.repo.preTrust.map((h) => h.kind),
8129
+ repoOnOpen: report2.repo.onOpen.length,
8130
+ reachTotal: report2.reach.total,
8131
+ reachPassed: report2.reach.passedPolicy,
8132
+ findings: report2.findings.map((f) => ({ class: f.class, severity: f.severity }))
8072
8133
  };
8073
8134
  }
8074
8135
  function probeLine(share) {
@@ -8111,22 +8172,22 @@ function sortFindings(findings) {
8111
8172
  // src/exposure/report.ts
8112
8173
  var row = (label, value, note = "") => ` ${label.padEnd(26)}${String(value).padStart(5)} ${note}`.trimEnd();
8113
8174
  var sum = (items, pick) => items.reduce((n, t) => n + pick(t), 0);
8114
- function probeFooter(report) {
8115
- if (!report.probed)
8175
+ function probeFooter(report2) {
8176
+ if (!report2.probed)
8116
8177
  return "Files only: no MCP server was started. Tool-description poisoning is NOT covered by this run \u2014 add --probe to check it.";
8117
- const failures = report.probeFailures;
8178
+ const failures = report2.probeFailures;
8118
8179
  if (failures.length === 0)
8119
8180
  return "MCP servers were started and their tool descriptions scanned (--probe).";
8120
8181
  const named = failures.map((f) => `${f.server} (${f.error})`).join(", ");
8121
8182
  return `MCP servers were started and their tool descriptions scanned (--probe) \u2014 but ${failures.length} of them could not be started, so tool-description poisoning is NOT covered for: ${named}`;
8122
8183
  }
8123
- function formatExposure(report, opts = {}) {
8124
- const detected = report.agents.filter((a) => a.detected);
8184
+ function formatExposure(report2, opts = {}) {
8185
+ const detected = report2.agents.filter((a) => a.detected);
8125
8186
  const unprotected = detected.filter((a) => !a.protected);
8126
- const stdio = sum(report.mcp, (m) => m.stdio);
8127
- const wrapped = sum(report.mcp, (m) => m.wrapped);
8128
- const http = sum(report.mcp, (m) => m.http);
8129
- const ctx = report.context;
8187
+ const stdio = sum(report2.mcp, (m) => m.stdio);
8188
+ const wrapped = sum(report2.mcp, (m) => m.wrapped);
8189
+ const http = sum(report2.mcp, (m) => m.http);
8190
+ const ctx = report2.context;
8130
8191
  const lines = [
8131
8192
  "stroq exposure \u2014 what reaches you on this machine",
8132
8193
  "",
@@ -8155,26 +8216,26 @@ function formatExposure(report, opts = {}) {
8155
8216
  ctx.flagged.length > 0 ? "expect false positives \u2014 see the finding" : ""
8156
8217
  ),
8157
8218
  "",
8158
- row("Privilege-widening keys", report.privilege.length),
8219
+ row("Privilege-widening keys", report2.privilege.length),
8159
8220
  row(
8160
8221
  "Repository runs on open",
8161
- report.repo.isRepo ? report.repo.onOpen.length + report.repo.preTrust.length : 0,
8162
- report.repo.isRepo ? `${report.repo.preTrust.length} before you approve anything${report.repo.capped ? " \u2014 a lower bound: the walk hit its cap" : ""}` : "not a git repository"
8222
+ report2.repo.isRepo ? report2.repo.onOpen.length + report2.repo.preTrust.length : 0,
8223
+ report2.repo.isRepo ? `${report2.repo.preTrust.length} before you approve anything${report2.repo.capped ? " \u2014 a lower bound: the walk hit its cap" : ""}` : "not a git repository"
8163
8224
  ),
8164
- row("Incidents reaching you", report.reach.passedPolicy, `of ${report.reach.total}`),
8225
+ row("Incidents reaching you", report2.reach.passedPolicy, `of ${report2.reach.total}`),
8165
8226
  ""
8166
8227
  ];
8167
8228
  if (opts.verbose && ctx.flagged.length > 0) {
8168
8229
  lines.push("Flagged files:", ...ctx.flagged.map((f) => ` ${f}`), "");
8169
8230
  }
8170
- if (opts.verbose && report.repo.onOpen.length > 0) {
8231
+ if (opts.verbose && report2.repo.onOpen.length > 0) {
8171
8232
  lines.push(
8172
8233
  "Runs when you open or build this repository:",
8173
- ...report.repo.onOpen.map((h) => ` ${h.file} \u2014 ${h.what}`),
8234
+ ...report2.repo.onOpen.map((h) => ` ${h.file} \u2014 ${h.what}`),
8174
8235
  ""
8175
8236
  );
8176
8237
  }
8177
- const findings = sortFindings(report.findings);
8238
+ const findings = sortFindings(report2.findings);
8178
8239
  if (findings.length === 0) {
8179
8240
  lines.push("No findings. Everything Stroq can check on this machine is covered.");
8180
8241
  } else {
@@ -8185,13 +8246,13 @@ function formatExposure(report, opts = {}) {
8185
8246
  if (f.fix) lines.push(` fix: ${f.fix}`);
8186
8247
  }
8187
8248
  }
8188
- lines.push("", probeFooter(report));
8249
+ lines.push("", probeFooter(report2));
8189
8250
  return `${lines.join("\n")}
8190
8251
  `;
8191
8252
  }
8192
8253
 
8193
8254
  // src/exposure/surface.ts
8194
- import { homedir as homedir16 } from "os";
8255
+ import { homedir as homedir17 } from "os";
8195
8256
  var safe = (fn) => {
8196
8257
  try {
8197
8258
  return fn();
@@ -8227,7 +8288,7 @@ function isProtected(agent, cwd) {
8227
8288
  return false;
8228
8289
  }
8229
8290
  }
8230
- function agentSurface(cwd, home = homedir16()) {
8291
+ function agentSurface(cwd, home = homedir17()) {
8231
8292
  const detected = new Set(detectedAgents(cwd, home));
8232
8293
  return HOOK_AGENTS.map((agent) => ({
8233
8294
  agent,
@@ -8246,7 +8307,7 @@ function agentFindings(surfaces) {
8246
8307
 
8247
8308
  // src/commands/exposure.ts
8248
8309
  async function buildExposureReport(cwd, opts) {
8249
- const home = homedir17();
8310
+ const home = homedir18();
8250
8311
  const agents = agentSurface(cwd, home);
8251
8312
  const mcp = mcpSurface(cwd);
8252
8313
  const context = contextSurface(cwd, home);
@@ -8282,18 +8343,18 @@ async function buildExposureReport(cwd, opts) {
8282
8343
  };
8283
8344
  }
8284
8345
  async function runExposure(argv) {
8285
- const report = await buildExposureReport(process.cwd(), { probe: argv.includes("--probe") });
8346
+ const report2 = await buildExposureReport(process.cwd(), { probe: argv.includes("--probe") });
8286
8347
  const share = argv.includes("--share");
8287
8348
  if (argv.includes("--json")) {
8288
- const payload = share ? toShareable(report) : report;
8349
+ const payload = share ? toShareable(report2) : report2;
8289
8350
  process.stdout.write(`${JSON.stringify(payload, null, 2)}
8290
8351
  `);
8291
8352
  } else {
8292
8353
  process.stdout.write(
8293
- share ? formatShareable(toShareable(report)) : formatExposure(report, { verbose: argv.includes("--verbose") })
8354
+ share ? formatShareable(toShareable(report2)) : formatExposure(report2, { verbose: argv.includes("--verbose") })
8294
8355
  );
8295
8356
  }
8296
- return report.findings.length > 0 ? 1 : 0;
8357
+ return report2.findings.length > 0 ? 1 : 0;
8297
8358
  }
8298
8359
 
8299
8360
  // src/adapters/antigravity.ts
@@ -9090,8 +9151,8 @@ async function withDeadline(work, ms, onTimeout) {
9090
9151
  try {
9091
9152
  return await Promise.race([
9092
9153
  work,
9093
- new Promise((resolve11) => {
9094
- timer = setTimeout(() => resolve11(onTimeout()), ms);
9154
+ new Promise((resolve12) => {
9155
+ timer = setTimeout(() => resolve12(onTimeout()), ms);
9095
9156
  })
9096
9157
  ]);
9097
9158
  } finally {
@@ -9112,7 +9173,7 @@ async function runHookCommand(agent, arg = "", read3 = readStdin) {
9112
9173
 
9113
9174
  // src/commands/inspect.ts
9114
9175
  import { existsSync as existsSync13 } from "fs";
9115
- import { resolve as resolve5 } from "path";
9176
+ import { resolve as resolve6 } from "path";
9116
9177
  import { parseArgs as parseArgs6 } from "util";
9117
9178
  var GIT_HARDENING = [
9118
9179
  { key: "core.fsmonitor", value: "false" },
@@ -9186,7 +9247,7 @@ function runInspect(argv) {
9186
9247
  process.stdout.write(hardeningExports());
9187
9248
  return 0;
9188
9249
  }
9189
- const dir = resolve5(positionals[0] ?? process.cwd());
9250
+ const dir = resolve6(positionals[0] ?? process.cwd());
9190
9251
  if (!existsSync13(dir)) {
9191
9252
  process.stderr.write(`stroq inspect: no such directory: ${dir}
9192
9253
  `);
@@ -9228,14 +9289,14 @@ async function runLog(args) {
9228
9289
  }
9229
9290
 
9230
9291
  // src/commands/mcp.ts
9231
- import { resolve as resolve6 } from "path";
9292
+ import { resolve as resolve7 } from "path";
9232
9293
 
9233
9294
  // src/mcp/proxy.ts
9234
9295
  import { spawn as spawn3 } from "child_process";
9235
9296
 
9236
9297
  // src/mcp/cloak-factory.ts
9237
9298
  import { createHash as createHash6 } from "crypto";
9238
- import { homedir as homedir18 } from "os";
9299
+ import { homedir as homedir19 } from "os";
9239
9300
  import { join as join22 } from "path";
9240
9301
 
9241
9302
  // src/mcp/cloak.ts
@@ -9380,7 +9441,7 @@ ${server}`).digest("hex").slice(0, 16);
9380
9441
  function createMcpCloak(ctx) {
9381
9442
  return new McpCloak({
9382
9443
  detector: createCloakDetector({
9383
- secrets: new FileSecretIndex(secretsFile(), homedir18(), process.env),
9444
+ secrets: new FileSecretIndex(secretsFile(), homedir19(), process.env),
9384
9445
  cwd: ctx.cwd
9385
9446
  }),
9386
9447
  store: new FileCloakStore(join22(cloakDir(), `${cloakKey(ctx.sessionId, ctx.server)}.json`)),
@@ -9622,12 +9683,12 @@ async function writeBackpressured(sink, text, source) {
9622
9683
  return;
9623
9684
  }
9624
9685
  source.pause();
9625
- await new Promise((resolve11) => {
9686
+ await new Promise((resolve12) => {
9626
9687
  const done = () => {
9627
9688
  sink.off("drain", done);
9628
9689
  sink.off("error", done);
9629
9690
  sink.off("close", done);
9630
- resolve11();
9691
+ resolve12();
9631
9692
  };
9632
9693
  sink.once("drain", done);
9633
9694
  sink.once("error", done);
@@ -10178,14 +10239,14 @@ async function runMcpProxy(options) {
10178
10239
  const onSigterm = relay("SIGTERM");
10179
10240
  process.on("SIGINT", onSigint);
10180
10241
  process.on("SIGTERM", onSigterm);
10181
- const code = await new Promise((resolve11) => {
10242
+ const code = await new Promise((resolve12) => {
10182
10243
  child.on("error", (err) => {
10183
10244
  options.stderr.write(`stroq mcp: cannot start the MCP server: ${err.message}
10184
10245
  `);
10185
- resolve11(1);
10246
+ resolve12(1);
10186
10247
  });
10187
10248
  child.on("close", (exitCode, signal) => {
10188
- resolve11(signal === null ? exitCode ?? 1 : 1);
10249
+ resolve12(signal === null ? exitCode ?? 1 : 1);
10189
10250
  });
10190
10251
  });
10191
10252
  shuttingDown = true;
@@ -10247,7 +10308,7 @@ function parseMcpArgv(argv) {
10247
10308
  return { ok: true, invocation: { server, client, session, cwd, passEnv, cloak, command, args } };
10248
10309
  }
10249
10310
  function resolveMcpCwd(cwd) {
10250
- return resolve6(cwd ?? process.cwd());
10311
+ return resolve7(cwd ?? process.cwd());
10251
10312
  }
10252
10313
  var LEGACY_ENV_WARNING = 'stroq mcp: this wrapper predates environment filtering, so the server inherits every variable in this process (secrets included). Re-run "stroq init --agent mcp --client <your client>" to record the ones it actually needs.\n';
10253
10314
  async function runMcp(argv) {
@@ -10278,6 +10339,30 @@ ${MCP_USAGE}`);
10278
10339
  });
10279
10340
  }
10280
10341
 
10342
+ // src/terminal-safe.ts
10343
+ function safeWrite(original, stream) {
10344
+ return function write2(chunk, ...rest) {
10345
+ const text = typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? Buffer.from(chunk).toString("utf8") : String(chunk);
10346
+ return original.call(
10347
+ stream,
10348
+ neutralizeControls(text),
10349
+ ...rest
10350
+ );
10351
+ };
10352
+ }
10353
+ async function withSafeOutput(fn) {
10354
+ const out = process.stdout.write;
10355
+ const err = process.stderr.write;
10356
+ process.stdout.write = safeWrite(out, process.stdout);
10357
+ process.stderr.write = safeWrite(err, process.stderr);
10358
+ try {
10359
+ return await fn();
10360
+ } finally {
10361
+ process.stdout.write = out;
10362
+ process.stderr.write = err;
10363
+ }
10364
+ }
10365
+
10281
10366
  // src/commands/replay.ts
10282
10367
  import { mkdtemp as mkdtemp2, rm as rm5 } from "fs/promises";
10283
10368
  import { tmpdir as tmpdir2 } from "os";
@@ -10288,7 +10373,7 @@ import { parseArgs as parseArgs8 } from "util";
10288
10373
  import { createReadStream } from "fs";
10289
10374
  import { readdir, stat as stat3 } from "fs/promises";
10290
10375
  import { createInterface } from "readline";
10291
- import { homedir as homedir19 } from "os";
10376
+ import { homedir as homedir20 } from "os";
10292
10377
  import { join as join23 } from "path";
10293
10378
  var isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
10294
10379
  function resultText(content) {
@@ -10367,7 +10452,7 @@ async function feedLines(path, parser) {
10367
10452
  function projectSlug(cwd) {
10368
10453
  return cwd.replace(/[/.]/g, "-");
10369
10454
  }
10370
- var transcriptRoot = () => join23(homedir19(), ".claude", "projects");
10455
+ var transcriptRoot = () => join23(homedir20(), ".claude", "projects");
10371
10456
  async function findTranscripts(cwd) {
10372
10457
  const root = transcriptRoot();
10373
10458
  const scoped = join23(root, projectSlug(cwd));
@@ -10684,8 +10769,8 @@ replayed from ${path}
10684
10769
 
10685
10770
  // src/commands/run.ts
10686
10771
  import { mkdirSync as mkdirSync5, rmSync, writeFileSync as writeFileSync3 } from "fs";
10687
- import { homedir as homedir20, tmpdir as tmpdir3 } from "os";
10688
- import { join as join26, resolve as resolve8 } from "path";
10772
+ import { homedir as homedir21, tmpdir as tmpdir3 } from "os";
10773
+ import { join as join26, resolve as resolve9 } from "path";
10689
10774
 
10690
10775
  // src/run/agent-name.ts
10691
10776
  import { win32 } from "path";
@@ -10743,7 +10828,7 @@ async function launch(options) {
10743
10828
  const relays = RELAYED.map((signal) => ({ signal, listener: () => kill(child, signal) }));
10744
10829
  for (const { signal, listener } of relays) signals.on(signal, listener);
10745
10830
  try {
10746
- return await new Promise((resolve11) => {
10831
+ return await new Promise((resolve12) => {
10747
10832
  child.on("error", (err) => {
10748
10833
  const code = err.code;
10749
10834
  stderr.write(
@@ -10751,9 +10836,9 @@ async function launch(options) {
10751
10836
  ` : `stroq run: cannot start "${options.command}": ${err.message}
10752
10837
  `
10753
10838
  );
10754
- resolve11(code === "ENOENT" ? NOT_FOUND : 1);
10839
+ resolve12(code === "ENOENT" ? NOT_FOUND : 1);
10755
10840
  });
10756
- child.on("close", (code, signal) => resolve11(exitCodeFor(code, signal, plat)));
10841
+ child.on("close", (code, signal) => resolve12(exitCodeFor(code, signal, plat)));
10757
10842
  });
10758
10843
  } finally {
10759
10844
  for (const { signal, listener } of relays) signals.off(signal, listener);
@@ -10810,11 +10895,11 @@ function preflight(options) {
10810
10895
  }
10811
10896
 
10812
10897
  // src/run/sandbox.ts
10813
- import { resolve as resolve7 } from "path";
10898
+ import { resolve as resolve8 } from "path";
10814
10899
  var dedupe = (paths) => [
10815
- ...new Set(paths.filter((p) => p !== "").map((p) => resolve7(p)))
10900
+ ...new Set(paths.filter((p) => p !== "").map((p) => resolve8(p)))
10816
10901
  ];
10817
- var tooBroadToSandbox = (path, userHome) => path === resolve7("/") || path === resolve7(userHome);
10902
+ var tooBroadToSandbox = (path, userHome) => path === resolve8("/") || path === resolve8(userHome);
10818
10903
  function generateSandbox(inputs) {
10819
10904
  const wanted = dedupe([inputs.workspace, ...inputs.tmp, inputs.stroqHome, ...inputs.agentState]);
10820
10905
  const refused = wanted.filter((p) => tooBroadToSandbox(p, inputs.userHome));
@@ -11007,8 +11092,8 @@ ${RUN_USAGE}`);
11007
11092
  }
11008
11093
  const invocation = parsed.invocation;
11009
11094
  const env = deps.env ?? process.env;
11010
- const userHome = deps.userHome ?? homedir20();
11011
- const workspace = resolve8(cwd);
11095
+ const userHome = deps.userHome ?? homedir21();
11096
+ const workspace = resolve9(cwd);
11012
11097
  const agent = invocation.agent ?? agentIdFor(invocation.command);
11013
11098
  const checks = preflight({
11014
11099
  agent,
@@ -11069,7 +11154,7 @@ stroq run \u2014 ${invocation.command}${agent === null ? "" : ` (${agent})`}`);
11069
11154
  }
11070
11155
 
11071
11156
  // src/commands/sent.ts
11072
- import { homedir as homedir24 } from "os";
11157
+ import { homedir as homedir25 } from "os";
11073
11158
  import { parseArgs as parseArgs9 } from "util";
11074
11159
 
11075
11160
  // src/sent/format.ts
@@ -11084,15 +11169,15 @@ var FILE_EVIDENCE_LINE = {
11084
11169
  function timestamp(iso) {
11085
11170
  return Number.isNaN(Date.parse(iso)) ? "an unrecorded time" : iso.replace(".000Z", "Z");
11086
11171
  }
11087
- function duration2(report) {
11088
- if (!report.first || !report.last) return "";
11089
- return ` \xB7 ${ageLabel(report.first, new Date(report.last))} long`;
11172
+ function duration2(report2) {
11173
+ if (!report2.first || !report2.last) return "";
11174
+ return ` \xB7 ${ageLabel(report2.first, new Date(report2.last))} long`;
11090
11175
  }
11091
- function origin(report) {
11092
- if (report.origin === "audit-log") {
11176
+ function origin(report2) {
11177
+ if (report2.origin === "audit-log") {
11093
11178
  return " from Stroq's own audit log";
11094
11179
  }
11095
- return ` from ${report.path ?? "a transcript"} (${report.agent ?? "agent"} transcript)`;
11180
+ return ` from ${report2.path ?? "a transcript"} (${report2.agent ?? "agent"} transcript)`;
11096
11181
  }
11097
11182
  function occurrenceLine(occurrence, last) {
11098
11183
  const elbow = last ? " \u2514\u2500" : " \u251C\u2500";
@@ -11112,8 +11197,8 @@ function credentialBlock(credential) {
11112
11197
  ""
11113
11198
  ];
11114
11199
  }
11115
- function coverageLines(report) {
11116
- const c = report.coverage;
11200
+ function coverageLines(report2) {
11201
+ const c = report2.coverage;
11117
11202
  const sources = c.indexedSources.length > 0 ? c.indexedSources.join(", ") : "nothing on this machine";
11118
11203
  const lines = [
11119
11204
  "COVERAGE",
@@ -11147,28 +11232,28 @@ var MEANING = [
11147
11232
  " not have known it was.",
11148
11233
  " If one of them matters, rotate it. Rotating is cheap; being sure is not."
11149
11234
  ];
11150
- function formatSent(report) {
11235
+ function formatSent(report2) {
11151
11236
  const lines = [
11152
11237
  "stroq sent \u2014 which of your credentials already reached a model provider",
11153
11238
  "",
11154
- ` session ${report.sessionId} \xB7 ${report.coverage.calls} tool call(s)${duration2(report)}`,
11155
- origin(report),
11239
+ ` session ${report2.sessionId} \xB7 ${report2.coverage.calls} tool call(s)${duration2(report2)}`,
11240
+ origin(report2),
11156
11241
  ""
11157
11242
  ];
11158
- if (report.credentials.length > 0) {
11243
+ if (report2.credentials.length > 0) {
11159
11244
  lines.push(
11160
- `CREDENTIALS THAT WERE IN THIS SESSION'S TRAFFIC (${report.credentials.length})`,
11245
+ `CREDENTIALS THAT WERE IN THIS SESSION'S TRAFFIC (${report2.credentials.length})`,
11161
11246
  ""
11162
11247
  );
11163
- for (const credential of report.credentials) lines.push(...credentialBlock(credential));
11248
+ for (const credential of report2.credentials) lines.push(...credentialBlock(credential));
11164
11249
  }
11165
- if (report.files.length > 0) {
11166
- lines.push(`CREDENTIAL FILES THIS SESSION TOUCHED (${report.files.length})`, "");
11167
- for (const file of report.files) {
11250
+ if (report2.files.length > 0) {
11251
+ lines.push(`CREDENTIAL FILES THIS SESSION TOUCHED (${report2.files.length})`, "");
11252
+ for (const file of report2.files) {
11168
11253
  lines.push(` \u25A0 ${file.path} \u2014 ${file.tool} at ${timestamp(file.at)}`);
11169
11254
  lines.push(` ${FILE_EVIDENCE_LINE[file.evidence]}`);
11170
11255
  }
11171
- if (report.files.some((f) => f.evidence === "read")) {
11256
+ if (report2.files.some((f) => f.evidence === "read")) {
11172
11257
  lines.push(
11173
11258
  "",
11174
11259
  " A file whose contents came back was sent to the model whole, including any",
@@ -11177,7 +11262,7 @@ function formatSent(report) {
11177
11262
  }
11178
11263
  lines.push("");
11179
11264
  }
11180
- if (report.credentials.length === 0 && report.files.length === 0) {
11265
+ if (report2.credentials.length === 0 && report2.files.length === 0) {
11181
11266
  lines.push(
11182
11267
  "No indexed credential appeared in this session, and no credential file was",
11183
11268
  "read or named by it. Read the coverage below before treating that as clean.",
@@ -11186,18 +11271,18 @@ function formatSent(report) {
11186
11271
  } else {
11187
11272
  lines.push(...MEANING, "");
11188
11273
  }
11189
- lines.push(...coverageLines(report));
11274
+ lines.push(...coverageLines(report2));
11190
11275
  return `${lines.join("\n")}
11191
11276
  `;
11192
11277
  }
11193
11278
 
11194
11279
  // src/sent/readers.ts
11195
11280
  import { open as open3 } from "fs/promises";
11196
- import { homedir as homedir23 } from "os";
11281
+ import { homedir as homedir24 } from "os";
11197
11282
 
11198
11283
  // src/sent/codex.ts
11199
11284
  import { open as open2, readdir as readdir2, stat as stat4 } from "fs/promises";
11200
- import { homedir as homedir21 } from "os";
11285
+ import { homedir as homedir22 } from "os";
11201
11286
  import { join as join27 } from "path";
11202
11287
 
11203
11288
  // src/sent/codex-literal.ts
@@ -11511,7 +11596,7 @@ function createCodexParser() {
11511
11596
  }
11512
11597
  return { push, finish: () => ({ sessionId, cwd, events, skipped }) };
11513
11598
  }
11514
- var codexRoot = (home = homedir21()) => join27(home, ".codex", "sessions");
11599
+ var codexRoot = (home = homedir22()) => join27(home, ".codex", "sessions");
11515
11600
  var META_BYTES = 64 * 1024;
11516
11601
  async function rolloutCwd(path) {
11517
11602
  let head;
@@ -11565,7 +11650,7 @@ async function readCodexRollout(path) {
11565
11650
  }
11566
11651
 
11567
11652
  // src/sent/cursor.ts
11568
- import { homedir as homedir22 } from "os";
11653
+ import { homedir as homedir23 } from "os";
11569
11654
  import { join as join28 } from "path";
11570
11655
  var TOOL_NAMES = {
11571
11656
  run_terminal_command_v2: "Bash",
@@ -11689,7 +11774,7 @@ function parseCursorStore(rows) {
11689
11774
  }
11690
11775
  })).sort((a, b) => b.mtimeMs - a.mtimeMs);
11691
11776
  }
11692
- function cursorStateDb(plat = process.platform, env = process.env, home = homedir22()) {
11777
+ function cursorStateDb(plat = process.platform, env = process.env, home = homedir23()) {
11693
11778
  const base = plat === "darwin" ? join28(home, "Library", "Application Support", "Cursor") : plat === "win32" ? join28(env["APPDATA"] ?? join28(home, "AppData", "Roaming"), "Cursor") : join28(home, ".config", "Cursor");
11694
11779
  return join28(base, "User", "globalStorage", "state.vscdb");
11695
11780
  }
@@ -11815,7 +11900,7 @@ async function readerNotices() {
11815
11900
  return notices;
11816
11901
  }
11817
11902
  function tilde(path) {
11818
- const home = homedir23();
11903
+ const home = homedir24();
11819
11904
  return home !== "" && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
11820
11905
  }
11821
11906
  var isRecord4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -11860,15 +11945,16 @@ async function newestTranscript(cwd) {
11860
11945
  }
11861
11946
 
11862
11947
  // src/sent/scan.ts
11863
- import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve9 } from "path";
11948
+ import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve10 } from "path";
11864
11949
 
11865
11950
  // src/sent/heredoc.ts
11866
11951
  var HEREDOC = /(?<!<)<<(-?)\s*(['"]?)([A-Za-z_][\w.-]*)\2/g;
11867
- var EXECUTES_STDIN = /^(?:sudo\s+)?(?:(?:\S*\/)?env\s+(?:\S+=\S*\s+)*)?(?:\S*\/)?(?:sh|bash|zsh|dash|ksh|fish|python\d*(?:\.\d+)?|node|deno|bun|tsx|ruby|perl|php|osascript|ssh|eval|source|\.|xargs|npx|pnpm|uv)(?:\s|$)/;
11952
+ var EXECUTES_STDIN = /^(?:sudo\s+)?(?:(?:\S*\/)?env\s+(?:[A-Za-z_]\w*=\S*\s+)*)?(?:\S*\/)?(?:sh|bash|zsh|dash|ksh|fish|python\d*(?:\.\d+)?|node|deno|bun|tsx|ruby|perl|php|osascript|ssh|eval|source|\.|xargs|npx|pnpm|uv)(?:\s|$)/;
11953
+ var STAGE_PREFIX = 512;
11868
11954
  function bodyIsExecuted(line3, operatorStart, operatorEnd) {
11869
11955
  const before = line3.slice(0, operatorStart).split(/&&|\|\||;|\$\(|\|/);
11870
11956
  const stages = [before[before.length - 1] ?? "", ...line3.slice(operatorEnd).split("|").slice(1)];
11871
- return stages.some((stage) => EXECUTES_STDIN.test(stage.trim()));
11957
+ return stages.some((stage) => EXECUTES_STDIN.test(stage.trim().slice(0, STAGE_PREFIX)));
11872
11958
  }
11873
11959
  function withoutHeredocData(command) {
11874
11960
  if (!command.includes("<<")) return command;
@@ -11954,9 +12040,9 @@ function mentions(text, spelling) {
11954
12040
  }
11955
12041
  function spellingsOf(path, scope, sessionCwd) {
11956
12042
  const spellings2 = [path, displayPath(path, scope.home)];
11957
- const sameDirectory = sessionCwd !== null && resolve9(sessionCwd) === resolve9(scope.cwd);
11958
- const rel = relative3(scope.cwd, path);
11959
- if (sameDirectory && rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel)) {
12043
+ const sameDirectory = sessionCwd !== null && resolve10(sessionCwd) === resolve10(scope.cwd);
12044
+ const rel = relative4(scope.cwd, path);
12045
+ if (sameDirectory && rel !== "" && !rel.startsWith("..") && !isAbsolute4(rel)) {
11960
12046
  spellings2.push(rel, `./${rel}`);
11961
12047
  }
11962
12048
  return [...new Set(spellings2)];
@@ -12110,10 +12196,10 @@ function scanAuditLog(allEntries, sessionId, scope) {
12110
12196
  }
12111
12197
 
12112
12198
  // src/commands/sent.ts
12113
- function emit(report, out) {
12114
- process.stdout.write(out.json ? `${JSON.stringify(report, null, 2)}
12115
- ` : formatSent(report));
12116
- const found = report.credentials.length > 0 || report.files.length > 0;
12199
+ function emit(report2, out) {
12200
+ process.stdout.write(out.json ? `${JSON.stringify(report2, null, 2)}
12201
+ ` : formatSent(report2));
12202
+ const found = report2.credentials.length > 0 || report2.files.length > 0;
12117
12203
  return out.failOnFinding && found ? 1 : 0;
12118
12204
  }
12119
12205
  var USAGE = `stroq sent \u2014 which of your credentials already reached a model provider
@@ -12164,7 +12250,7 @@ ${USAGE}`);
12164
12250
  failOnFinding: values["fail-on-finding"] === true
12165
12251
  };
12166
12252
  const cwd = process.cwd();
12167
- const home = homedir24();
12253
+ const home = homedir25();
12168
12254
  const index = new FileSecretIndex(secretsFile(), home);
12169
12255
  const stats = await index.refresh(cwd);
12170
12256
  const scope = {
@@ -12195,13 +12281,13 @@ ${USAGE}`);
12195
12281
  `);
12196
12282
  return 1;
12197
12283
  }
12198
- const report = await scanTranscript(
12284
+ const report2 = await scanTranscript(
12199
12285
  transcript,
12200
12286
  { agent: found.reader.agent, path: found.path },
12201
12287
  index,
12202
12288
  scope
12203
12289
  );
12204
- return emit(report, out);
12290
+ return emit(report2, out);
12205
12291
  }
12206
12292
  const entries = await new AuditLog(auditFile()).readAll();
12207
12293
  const sessionId = positionals[0] ?? sessionsIn(entries)[0];
@@ -12216,7 +12302,7 @@ ${USAGE}`);
12216
12302
 
12217
12303
  // src/commands/trust.ts
12218
12304
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync4 } from "fs";
12219
- import { dirname as dirname8, resolve as resolve10 } from "path";
12305
+ import { dirname as dirname8, resolve as resolve11 } from "path";
12220
12306
  import { parseArgs as parseArgs10 } from "util";
12221
12307
  function read2(file) {
12222
12308
  try {
@@ -12279,7 +12365,7 @@ function runTrust(argv, now = () => /* @__PURE__ */ new Date()) {
12279
12365
  });
12280
12366
  const file = trustFile();
12281
12367
  if (values.remove !== void 0) {
12282
- const path = resolve10(values.remove);
12368
+ const path = resolve11(values.remove);
12283
12369
  const current = read2(file);
12284
12370
  const entries = current.entries.filter((e) => e.source !== path);
12285
12371
  if (entries.length === current.entries.length) {
@@ -12300,7 +12386,7 @@ function runTrust(argv, now = () => /* @__PURE__ */ new Date()) {
12300
12386
  );
12301
12387
  return 0;
12302
12388
  }
12303
- const { code, message } = add(file, resolve10(positionals[0]), now);
12389
+ const { code, message } = add(file, resolve11(positionals[0]), now);
12304
12390
  process.stdout.write(message);
12305
12391
  return code;
12306
12392
  }
@@ -12474,14 +12560,20 @@ async function main(argv) {
12474
12560
  if (out.timedOut) await exitNow(out.exitCode);
12475
12561
  return out.exitCode;
12476
12562
  }
12563
+ case "mcp":
12564
+ return runMcp(rest);
12565
+ default:
12566
+ return withSafeOutput(() => report(command, rest));
12567
+ }
12568
+ }
12569
+ async function report(command, rest) {
12570
+ switch (command) {
12477
12571
  case "inspect":
12478
12572
  return runInspect(rest);
12479
12573
  case "init":
12480
12574
  return runInit(rest);
12481
12575
  case "run":
12482
12576
  return runRun(rest);
12483
- case "mcp":
12484
- return runMcp(rest);
12485
12577
  case "doctor":
12486
12578
  return runDoctor(rest);
12487
12579
  case "log":
@@ -12522,8 +12614,8 @@ async function main(argv) {
12522
12614
  async function exitNow(code) {
12523
12615
  await Promise.all(
12524
12616
  [process.stdout, process.stderr].map(
12525
- (stream) => new Promise((resolve11) => {
12526
- stream.write("", () => resolve11());
12617
+ (stream) => new Promise((resolve12) => {
12618
+ stream.write("", () => resolve12());
12527
12619
  })
12528
12620
  )
12529
12621
  );
@@ -12534,7 +12626,7 @@ main(process.argv.slice(2)).then(
12534
12626
  process.exitCode = code;
12535
12627
  },
12536
12628
  (err) => {
12537
- process.stderr.write(`${String(err)}
12629
+ process.stderr.write(`${neutralizeControls(String(err))}
12538
12630
  `);
12539
12631
  process.exitCode = 1;
12540
12632
  }