@wrongstack/core 0.309.0 → 0.310.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 (129) hide show
  1. package/dist/chronicle/index.js +113 -41
  2. package/dist/chronicle/metrics-ingest.d.ts +7 -0
  3. package/dist/chronicle/metrics-schema.d.ts +4 -1
  4. package/dist/chronicle/project-server.js +94 -38
  5. package/dist/chronicle/query.d.ts +1 -0
  6. package/dist/chronicle/sqlite-journal-schema.d.ts +2 -1
  7. package/dist/chronicle/types.d.ts +2 -0
  8. package/dist/{agent-status-helpers.d.ts → coordination/agent-status-helpers.d.ts} +1 -1
  9. package/dist/{agent-status-tracker.d.ts → coordination/agent-status-tracker.d.ts} +2 -2
  10. package/dist/{middleware → coordination}/collab-pause.d.ts +1 -1
  11. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -1
  12. package/dist/coordination/director.d.ts +7 -0
  13. package/dist/coordination/explore-companion.d.ts +9 -6
  14. package/dist/coordination/fleet-status-tool.d.ts +1 -1
  15. package/dist/coordination/index.d.ts +5 -1
  16. package/dist/coordination/index.js +2392 -405
  17. package/dist/coordination/kanban-dispatch-port.d.ts +30 -0
  18. package/dist/coordination/kanban-ops-port.d.ts +21 -0
  19. package/dist/coordination/mailbox-hooks.d.ts +1 -1
  20. package/dist/coordination/mailbox-project-server.js +14 -9
  21. package/dist/coordination/mutation-engine.d.ts +5 -3
  22. package/dist/core/context.d.ts +35 -44
  23. package/dist/core/conversation-state.d.ts +16 -52
  24. package/dist/core/index.js +92 -25
  25. package/dist/core/provider-runner.d.ts +2 -2
  26. package/dist/core/run-env.d.ts +7 -28
  27. package/dist/core/streaming-response-builder.d.ts +2 -2
  28. package/dist/execution/index.js +138 -162
  29. package/dist/goal/index.js +100 -22
  30. package/dist/hq/auth-store.d.ts +9 -0
  31. package/dist/hq/cost-bridge.d.ts +1 -1
  32. package/dist/hq/index.js +69 -7
  33. package/dist/index.d.ts +12 -5
  34. package/dist/index.js +21185 -26807
  35. package/dist/infrastructure/index.js +232 -135
  36. package/dist/infrastructure/provider-cache-ledger.d.ts +1 -1
  37. package/dist/infrastructure/token-counter.d.ts +5 -1
  38. package/dist/kernel/events/file-events.d.ts +6 -0
  39. package/dist/kernel/events/provider-events.d.ts +10 -7
  40. package/dist/kernel/events/session-events.d.ts +4 -4
  41. package/dist/kernel/events/tool-events.d.ts +12 -2
  42. package/dist/observability/index.js +1 -1
  43. package/dist/plugin/index.js +218 -47
  44. package/dist/prompts/index.js +360 -3
  45. package/dist/security/auto-approve-policy.d.ts +2 -2
  46. package/dist/security/index.d.ts +1 -0
  47. package/dist/security/index.js +224 -68
  48. package/dist/security/kanban-boundary.d.ts +1 -1
  49. package/dist/security/kanban-governance-port.d.ts +28 -0
  50. package/dist/security/permission-helpers.d.ts +11 -0
  51. package/dist/security/permission-policy.d.ts +10 -1
  52. package/dist/security/yolo-risk.d.ts +17 -0
  53. package/dist/session-catalog/index.js +34 -5
  54. package/dist/session-catalog/project-server.js +34 -5
  55. package/dist/session-catalog/protocol.d.ts +1 -1
  56. package/dist/session-catalog/registry.d.ts +1 -1
  57. package/dist/session-catalog/store-schema.d.ts +1 -1
  58. package/dist/session-catalog/store.d.ts +1 -1
  59. package/dist/skills/index.js +39 -6
  60. package/dist/storage/annotations-store.d.ts +1 -1
  61. package/dist/storage/board-store-port.d.ts +45 -0
  62. package/dist/storage/completed-work-checkpoint.d.ts +1 -1
  63. package/dist/storage/config-loader/types.d.ts +1 -1
  64. package/dist/storage/event-bus-port.d.ts +27 -0
  65. package/dist/storage/file-session-writer.d.ts +47 -5
  66. package/dist/storage/goal-coordination.d.ts +1 -1
  67. package/dist/storage/goal-store.d.ts +1 -1
  68. package/dist/storage/index.d.ts +3 -4
  69. package/dist/storage/index.js +1107 -1462
  70. package/dist/storage/plan-store.d.ts +1 -1
  71. package/dist/storage/queue-store.d.ts +1 -1
  72. package/dist/storage/replay-log-store.d.ts +1 -1
  73. package/dist/storage/session-recovery.d.ts +10 -0
  74. package/dist/storage/session-resume-validation.d.ts +13 -1
  75. package/dist/storage/session-store/events.d.ts +1 -1
  76. package/dist/storage/session-store/load-session-data.d.ts +1 -1
  77. package/dist/storage/session-store/rename-session.d.ts +1 -1
  78. package/dist/storage/session-store/resume-session.d.ts +3 -1
  79. package/dist/storage/session-store/session-store-index.d.ts +2 -1
  80. package/dist/storage/session-store/types.d.ts +1 -1
  81. package/dist/storage/session-store.d.ts +70 -0
  82. package/dist/storage/session-summary-tracker.d.ts +8 -0
  83. package/dist/storage/session-write-buffer.d.ts +31 -2
  84. package/dist/storage/task-store.d.ts +1 -1
  85. package/dist/storage/todos-checkpoint.d.ts +1 -1
  86. package/dist/storage/tool-audit-log.d.ts +1 -1
  87. package/dist/tasking/index.js +100 -22
  88. package/dist/tasking/task-tracker.d.ts +24 -1
  89. package/dist/types/compactor.d.ts +2 -2
  90. package/dist/types/context.d.ts +212 -0
  91. package/dist/types/conversation-state.d.ts +109 -0
  92. package/dist/types/error-handler.d.ts +2 -2
  93. package/dist/types/file-event-record.d.ts +6 -0
  94. package/dist/types/index.d.ts +3 -3
  95. package/dist/types/index.js +17 -1
  96. package/dist/types/permission.d.ts +3 -3
  97. package/dist/types/plugin.d.ts +3 -3
  98. package/dist/types/provider-runner.d.ts +2 -2
  99. package/dist/types/provider.d.ts +10 -0
  100. package/dist/types/run-env.d.ts +32 -0
  101. package/dist/types/session.d.ts +3 -0
  102. package/dist/types/slash-command.d.ts +2 -2
  103. package/dist/types/token-counter.d.ts +30 -1
  104. package/dist/types/tool-executor.d.ts +2 -2
  105. package/dist/types/tool.d.ts +20 -5
  106. package/dist/utils/context-breakdown.d.ts +2 -2
  107. package/dist/utils/context-evidence.d.ts +12 -12
  108. package/dist/utils/crash-shield.d.ts +9 -0
  109. package/dist/utils/heap-watchdog.js +11 -3
  110. package/dist/utils/index.d.ts +2 -1
  111. package/dist/utils/index.js +126 -160
  112. package/dist/utils/regex-guard.d.ts +7 -30
  113. package/dist/utils/terminal-sanitize.d.ts +41 -0
  114. package/dist/utils/todos-format.d.ts +1 -1
  115. package/dist/utils/tool-subject.d.ts +1 -1
  116. package/dist/utils/tree-kill.d.ts +2 -0
  117. package/dist/utils/tree-kill.js +1 -0
  118. package/instructions/agents/chaos-monkey.md +5 -1
  119. package/instructions/coordination/subagent-baseline.md +10 -0
  120. package/instructions/system-lite.md +2 -0
  121. package/instructions/system-pro.md +40 -0
  122. package/instructions/system.md +15 -0
  123. package/package.json +7 -10
  124. package/dist/defaults/index.d.ts +0 -69
  125. package/dist/defaults/index.js +0 -37755
  126. /package/dist/{fleet-notifier.d.ts → coordination/fleet-notifier.d.ts} +0 -0
  127. /package/dist/{session-registry-atomic-file.d.ts → session-catalog/session-registry-atomic-file.d.ts} +0 -0
  128. /package/dist/{session-registry-types.d.ts → session-catalog/session-registry-types.d.ts} +0 -0
  129. /package/dist/{session-registry.d.ts → session-catalog/session-registry.d.ts} +0 -0
@@ -175,7 +175,7 @@ function walk(node, vault, transform) {
175
175
  }
176
176
  return out;
177
177
  }
178
- var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key|token\b)/i;
178
+ var SECRET_KEY_PATTERN = /(?:api[-_]?key|auth[-_]?token|authorization|proxy-authorization|cookie|bearer|secret|password|passwd|pwd|refresh[-_]?token|session[-_]?key|access[_-]?token|private[_-]?key|token\b)/i;
179
179
  var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
180
180
  function isSecretField(name) {
181
181
  const lc = name.toLowerCase();
@@ -183,6 +183,20 @@ function isSecretField(name) {
183
183
  return SECRET_KEY_PATTERN.test(lc);
184
184
  }
185
185
 
186
+ // src/security/kanban-governance-port.ts
187
+ var notWired = () => {
188
+ throw new Error(
189
+ "KanbanGovernancePort is not wired \u2014 register the implementation at the CLI composition root (see setKanbanGovernance)."
190
+ );
191
+ };
192
+ var port = void 0;
193
+ function setKanbanGovernance(impl) {
194
+ port = impl;
195
+ }
196
+ function kanbanGovernance() {
197
+ return port ?? notWired();
198
+ }
199
+
186
200
  // src/security/directory-permission-policy.ts
187
201
  import * as path from "node:path";
188
202
 
@@ -921,6 +935,14 @@ var PATTERNS = [
921
935
  anchor: "sk-ant-"
922
936
  },
923
937
  { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
938
+ {
939
+ // `xai` is a first-class provider in this codebase, but its key shape was
940
+ // absent here — so the one credential format WrongStack itself hands users
941
+ // was the one the scrubber could not recognize (audit 2026-08-20).
942
+ type: "xai_key",
943
+ regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
944
+ anchor: "xai-"
945
+ },
924
946
  { type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
925
947
  { type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
926
948
  { type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
@@ -1011,8 +1033,8 @@ var PATTERNS = [
1011
1033
  // replacement so the separator between adjacent secrets is preserved
1012
1034
  // rather than collapsed. Capture groups are therefore: 1=leading
1013
1035
  // delimiter, 2=key name, 3=value.
1014
- regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
1015
- anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
1036
+ regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
1037
+ anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
1016
1038
  },
1017
1039
  {
1018
1040
  type: "json_credential_key",
@@ -1129,6 +1151,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
1129
1151
  var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
1130
1152
  var SCRUB_CHUNK_BYTES = 64 * 1024;
1131
1153
  var SCRUB_OVERLAP_BYTES = 1024;
1154
+ var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
1155
+ var PEM_END_MARKER = "-----END";
1156
+ var MAX_PEM_BLOCK_BYTES = 64 * 1024;
1157
+ var PEM_END_LINE_TOLERANCE = 64;
1158
+ function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
1159
+ const head = text.slice(chunkStart, proposedEnd);
1160
+ const lastBegin = head.lastIndexOf("-----BEGIN ");
1161
+ if (lastBegin === -1) return proposedEnd;
1162
+ const fromBegin = text.slice(chunkStart + lastBegin);
1163
+ const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
1164
+ if (!marker || marker.index !== 0) return proposedEnd;
1165
+ const bodyStart = marker[0].length;
1166
+ const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
1167
+ const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
1168
+ if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
1169
+ return proposedEnd;
1170
+ }
1171
+ const lineEnd = fromBegin.indexOf("\n", closeIdx);
1172
+ const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
1173
+ return Math.max(proposedEnd, end);
1174
+ }
1132
1175
  var PATTERN_ANCHORS = [
1133
1176
  ...new Set(
1134
1177
  PATTERNS.flatMap(
@@ -1165,6 +1208,7 @@ var DefaultSecretScrubber = class {
1165
1208
  }
1166
1209
  }
1167
1210
  end = safe === -1 ? end : safe + 1;
1211
+ end = extendChunkBoundaryPastPem(text, i, end);
1168
1212
  }
1169
1213
  out.push(this.scrubOne(text.slice(i, end)));
1170
1214
  i = end;
@@ -1288,14 +1332,7 @@ import {
1288
1332
  // src/security/kanban-boundary.ts
1289
1333
  import { realpath } from "node:fs/promises";
1290
1334
  import * as path2 from "node:path";
1291
- import {
1292
- evaluateContractGraphReadiness,
1293
- evaluateKanbanBoundaryOpaque,
1294
- evaluateKanbanBoundaryPath,
1295
- readBoard,
1296
- resolveKanbanBoundaryLayers
1297
- } from "@wrongstack/kanban";
1298
- var GOVERNANCE_CONTROL_TOOLS = /* @__PURE__ */ new Set(["kanban", "plan", "task", "todo"]);
1335
+ var GOVERNANCE_CONTROL_TOOLS = /* @__PURE__ */ new Set(["kanban", "plan", "task", "todo", "nextsteps"]);
1299
1336
  var PATH_KEYS2 = /* @__PURE__ */ new Set([
1300
1337
  "path",
1301
1338
  "paths",
@@ -1330,7 +1367,10 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
1330
1367
  reason: "Kanban governance is mandatory before product mutation. Create a managed card, add its required details and executable acceptance criteria, then call kanban start_task to bind it to this run."
1331
1368
  } : { decision: "allow" };
1332
1369
  }
1333
- const board = await readBoard(identity.policyRoot ?? ctx.projectRoot, identity.boardId);
1370
+ const board = await kanbanGovernance().readBoard(
1371
+ identity.policyRoot ?? ctx.projectRoot,
1372
+ identity.boardId
1373
+ );
1334
1374
  if (!board) {
1335
1375
  return governanceRequired ? {
1336
1376
  decision: "block",
@@ -1356,22 +1396,39 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
1356
1396
  taskId: identity.taskId
1357
1397
  };
1358
1398
  }
1359
- const readiness = evaluateContractGraphReadiness(board, task.id);
1399
+ const readiness = kanbanGovernance().evaluateContractGraphReadiness(board, task.id);
1360
1400
  if (!readiness.ready) {
1401
+ const issueLines = readiness.issues.map((issue, index) => ` ${index + 1}. ${issue.message}`).join("\n");
1361
1402
  return {
1362
1403
  decision: "block",
1363
- reason: "Active card is not implementation-ready: " + readiness.issues.map((issue) => issue.message).join(" | "),
1404
+ reason: `Active card failed contract-readiness check (${readiness.issues.length} issue(s)):
1405
+ ` + issueLines + `
1406
+ Resolve the readiness issues, then call kanban start_task to retry.`,
1364
1407
  boardId: board.id,
1365
1408
  taskId: task.id,
1366
1409
  readinessIssues: readiness.issues
1367
1410
  };
1368
1411
  }
1369
- if (task.lifecycle?.currentStage !== "running" || task.assignment?.status !== "running") {
1370
- const lifecycleStage = task.lifecycle?.currentStage ?? "missing";
1371
- const assignmentStatus = task.assignment?.status ?? "missing";
1412
+ const lifecycleStage = task.lifecycle?.currentStage;
1413
+ const assignmentStatus = task.assignment?.status;
1414
+ const lifecycleOk = lifecycleStage === "running";
1415
+ const assignmentOk = assignmentStatus === "running";
1416
+ if (!lifecycleOk || !assignmentOk) {
1417
+ const failed = [];
1418
+ let reassessmentNote = "";
1419
+ if (!lifecycleOk) {
1420
+ failed.push(`lifecycle.currentStage (got: ${lifecycleStage ?? "missing"}; want: 'running')`);
1421
+ }
1422
+ if (!assignmentOk) {
1423
+ failed.push(`assignment.status (got: ${assignmentStatus ?? "missing"}; want: 'running')`);
1424
+ if (lifecycleOk && (assignmentStatus === "queued" || assignmentStatus === void 0 || assignmentStatus === null)) {
1425
+ reassessmentNote = '\nNote: lifecycle is still "running" but the assignment was cleared \u2014 the automated kanban reassessment agent likely reset the binding. Re-issue kanban start_task to re-bind the leader to this card.';
1426
+ }
1427
+ }
1372
1428
  return {
1373
1429
  decision: "block",
1374
- reason: `Active card must be in Running with a live assignment before product mutation (lifecycle: ${lifecycleStage}; assignment: ${assignmentStatus}). Call kanban start_task after completing the required card details.`,
1430
+ reason: `Active card failed ${failed.length} governance check(s) before product mutation:
1431
+ ` + failed.map((line, index) => ` ${index + 1}. ${line}`).join("\n") + reassessmentNote,
1375
1432
  boardId: board.id,
1376
1433
  taskId: task.id
1377
1434
  };
@@ -1389,7 +1446,7 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
1389
1446
  };
1390
1447
  }
1391
1448
  }
1392
- const layers = resolveKanbanBoundaryLayers(board, task);
1449
+ const layers = kanbanGovernance().resolveKanbanBoundaryLayers(board, task);
1393
1450
  if (layers.length === 0) return { decision: "allow" };
1394
1451
  const caps = tool.capabilities ?? [];
1395
1452
  const access = caps.includes("fs.write") || caps.includes("fs.write.outside-project") ? "write" : caps.includes("fs.read") ? "read" : void 0;
@@ -1397,10 +1454,10 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
1397
1454
  const candidates = access ? await extractCandidatePaths(tool.name, input, ctx) : [];
1398
1455
  let evaluation;
1399
1456
  if (shellLike || access && candidates.length === 0) {
1400
- evaluation = evaluateKanbanBoundaryOpaque(layers, tool.name);
1457
+ evaluation = kanbanGovernance().evaluateKanbanBoundaryOpaque(layers, tool.name);
1401
1458
  } else if (access) {
1402
1459
  const results = candidates.map(
1403
- (candidate) => evaluateKanbanBoundaryPath(layers, candidate, access)
1460
+ (candidate) => kanbanGovernance().evaluateKanbanBoundaryPath(layers, candidate, access)
1404
1461
  );
1405
1462
  evaluation = results.find((result) => result.decision === "block") ?? results.find((result) => result.decision === "confirm") ?? { decision: "allow" };
1406
1463
  } else {
@@ -1737,9 +1794,21 @@ function renderCommandLine(command, args) {
1737
1794
  });
1738
1795
  return [command, ...rendered].join(" ");
1739
1796
  }
1740
- function subjectForToolInput(toolName, input, subjectKey) {
1797
+ function renderSubjectFields(obj, fields) {
1798
+ const parts = [];
1799
+ for (const field of fields) {
1800
+ const value = obj[field];
1801
+ if (value === void 0 || value === null || value === "" || value === false) continue;
1802
+ const str = String(value);
1803
+ parts.push(`${field}=${/\s/.test(str) ? `"${str.replace(/"/g, '\\"')}"` : str}`);
1804
+ }
1805
+ return parts.join(" ");
1806
+ }
1807
+ function subjectForToolInput(toolName, input, subjectKey, subjectFields) {
1741
1808
  if (!input || typeof input !== "object") return void 0;
1742
1809
  const obj = input;
1810
+ const extra = subjectFields && subjectFields.length > 0 ? renderSubjectFields(obj, subjectFields) : "";
1811
+ const withExtra = (base) => extra ? `${base} ${extra}` : base;
1743
1812
  if (subjectKey) {
1744
1813
  const value = obj[subjectKey];
1745
1814
  if (Array.isArray(value)) {
@@ -1755,9 +1824,9 @@ function subjectForToolInput(toolName, input, subjectKey) {
1755
1824
  if (subjectKey === "command") {
1756
1825
  const rendered = renderCommandLine(value, obj["args"]);
1757
1826
  if (value === "commit" && obj["dry_run"] === true) {
1758
- return `${escapeGlobSubject(rendered)}:dry-run`;
1827
+ return `${escapeGlobSubject(withExtra(rendered))}:dry-run`;
1759
1828
  }
1760
- return escapeGlobSubject(rendered);
1829
+ return escapeGlobSubject(withExtra(rendered));
1761
1830
  }
1762
1831
  if (subjectKey === "directory" && obj["dry_run"] === true) {
1763
1832
  return `${escapeGlobSubject(value)}:dry-run`;
@@ -1918,7 +1987,9 @@ function hasRecursiveForceDelete(command, projectRoot) {
1918
1987
  if (token === "rd" || token === "rmdir") {
1919
1988
  const args = commandSegment(tokens, i + 1).map((arg) => arg.toLowerCase());
1920
1989
  if (args.includes("/s")) {
1921
- const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
1990
+ const targets = args.filter(
1991
+ (arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
1992
+ );
1922
1993
  if (targets.length === 0) return true;
1923
1994
  if (targets.some(isCatastrophicDeleteTarget)) return true;
1924
1995
  if (targets.some((target) => !pathLooksInsideProject(target, projectRoot))) return true;
@@ -1987,7 +2058,8 @@ function hasFindExec(command) {
1987
2058
  function isCatastrophicDeleteTarget(rawTarget) {
1988
2059
  const t = rawTarget.replace(/^['"]|['"]$/g, "").trim();
1989
2060
  if (!t) return false;
1990
- if (t === "*" || t === "." || t === "./" || t === ".\\" || t === "./*" || t === ".\\*") return true;
2061
+ if (t === "*" || t === "." || t === "./" || t === ".\\" || t === "./*" || t === ".\\*")
2062
+ return true;
1991
2063
  const s = t.replace(/[\\/]\*+$/, "").replace(/[\\/]+$/, "");
1992
2064
  if (s === "") return true;
1993
2065
  if (s === "~" || /^\$HOME$/i.test(s) || /^%USERPROFILE%$/i.test(s)) return true;
@@ -2027,12 +2099,16 @@ function hasCatastrophicDelete(command) {
2027
2099
  const args = tokens.slice(i + 1);
2028
2100
  const recursive = args.some((arg) => arg.toLowerCase() === "/s");
2029
2101
  if (!recursive) continue;
2030
- const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
2102
+ const targets = args.filter(
2103
+ (arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
2104
+ );
2031
2105
  if (targets.some(isCatastrophicDeleteTarget)) return true;
2032
2106
  }
2033
2107
  if (token === "del" || token === "erase") {
2034
2108
  const args = tokens.slice(i + 1);
2035
- const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
2109
+ const targets = args.filter(
2110
+ (arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
2111
+ );
2036
2112
  if (targets.some(isCatastrophicDeleteTarget)) return true;
2037
2113
  }
2038
2114
  }
@@ -2093,6 +2169,47 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
2093
2169
  if (HIGH_IMPACT_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
2094
2170
  return false;
2095
2171
  }
2172
+ var WELL_KNOWN_CREDENTIAL_ENV_VARS = /* @__PURE__ */ new Set([
2173
+ "ANTHROPIC_API_KEY",
2174
+ "ANTHROPIC_AUTH_TOKEN",
2175
+ "OPENAI_API_KEY",
2176
+ "AZURE_OPENAI_API_KEY",
2177
+ "GEMINI_API_KEY",
2178
+ "GOOGLE_API_KEY",
2179
+ "GOOGLE_APPLICATION_CREDENTIALS",
2180
+ "GOOGLE_GENERATIVE_AI_API_KEY",
2181
+ "GROQ_API_KEY",
2182
+ "MISTRAL_API_KEY",
2183
+ "COHERE_API_KEY",
2184
+ "DEEPSEEK_API_KEY",
2185
+ "XAI_API_KEY",
2186
+ "OPENROUTER_API_KEY",
2187
+ "PERPLEXITY_API_KEY",
2188
+ "TOGETHER_API_KEY",
2189
+ "FIREWORKS_API_KEY",
2190
+ "HUGGINGFACE_API_KEY",
2191
+ "HF_TOKEN",
2192
+ "GITHUB_TOKEN",
2193
+ "GH_TOKEN",
2194
+ "NPM_TOKEN",
2195
+ "AWS_ACCESS_KEY_ID",
2196
+ "AWS_SECRET_ACCESS_KEY",
2197
+ "AWS_SESSION_TOKEN",
2198
+ "AZURE_CLIENT_SECRET",
2199
+ "GITLAB_TOKEN",
2200
+ "SLACK_TOKEN",
2201
+ "STRIPE_SECRET_KEY",
2202
+ "TELEGRAM_BOT_TOKEN",
2203
+ "WRONGSTACK_VAULT_PASSPHRASE"
2204
+ ]);
2205
+ function attachesWellKnownCredential(input) {
2206
+ if (!input || typeof input !== "object") return false;
2207
+ const envVars = input["envVars"];
2208
+ if (!Array.isArray(envVars)) return false;
2209
+ return envVars.some(
2210
+ (name) => typeof name === "string" && WELL_KNOWN_CREDENTIAL_ENV_VARS.has(name.toUpperCase())
2211
+ );
2212
+ }
2096
2213
 
2097
2214
  // src/security/permission-helpers.ts
2098
2215
  function matchesTrust(patterns, subject) {
@@ -2109,7 +2226,7 @@ function hasShellSubject(tool) {
2109
2226
  ]);
2110
2227
  }
2111
2228
  function alwaysAllowUnavailableReason(tool, input) {
2112
- const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
2229
+ const subject = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
2113
2230
  if (subject !== void 0) return void 0;
2114
2231
  return `"always allow" needs a subject to remember, and ${tool.name} calls do not carry one (no subjectKey, and no path/url/name input). Recording it would store a rule that can never match. Approve this call, or set a trust rule for ${tool.name} explicitly.`;
2115
2232
  }
@@ -2169,8 +2286,18 @@ var AGENT_STATE_SENSITIVE_BASENAMES = /^(?:config\.json|config\.local\.json|trus
2169
2286
  function unescapeGlobSubject(value) {
2170
2287
  return value.replace(/\\([*?[\]])/g, "$1");
2171
2288
  }
2289
+ function stripAdsSuffix(forwardSlashPath) {
2290
+ const cut = forwardSlashPath.lastIndexOf("/");
2291
+ const dir = cut === -1 ? "" : forwardSlashPath.slice(0, cut + 1);
2292
+ const base = cut === -1 ? forwardSlashPath : forwardSlashPath.slice(cut + 1);
2293
+ const colon = base.indexOf(":");
2294
+ if (colon === -1 || cut === -1 && colon === 1 && base.length <= 2) return forwardSlashPath;
2295
+ return dir + base.slice(0, colon);
2296
+ }
2172
2297
  function normalizeForCompare(value) {
2173
- const forward = unescapeGlobSubject(value).replace(/\\/g, "/").replace(/\/+$/, "");
2298
+ const forward = stripAdsSuffix(
2299
+ unescapeGlobSubject(value).replace(/\\/g, "/").replace(/\/+$/, "")
2300
+ );
2174
2301
  return process.platform === "win32" ? forward.toLowerCase() : forward;
2175
2302
  }
2176
2303
  function realpathOfNearestExisting(p) {
@@ -2207,7 +2334,7 @@ function isProtectedAgentStatePath(absPath) {
2207
2334
  return AGENT_STATE_SENSITIVE_BASENAMES.test(path5.basename(normalizeForCompare(absPath)));
2208
2335
  }
2209
2336
  function pathLooksSensitive(rawPath) {
2210
- const normalized = stripShellQuotes(rawPath).replace(/\\/g, "/");
2337
+ const normalized = stripAdsSuffix(stripShellQuotes(rawPath).replace(/\\/g, "/"));
2211
2338
  if (SENSITIVE_READ_PATHS.some((pattern) => pattern.test(normalized))) return true;
2212
2339
  return isProtectedAgentStatePath(normalized);
2213
2340
  }
@@ -2231,10 +2358,24 @@ function shellCommandReadsSensitivePath(command) {
2231
2358
  }
2232
2359
  return false;
2233
2360
  }
2361
+ function isSensitiveReadCall(tool, input) {
2362
+ const isReadTool = hasCapability(tool, ToolCapabilities.FS_READ) || tool.name === "read" || tool.name === "grep" || tool.name === "glob" || tool.name === "tree";
2363
+ if (isReadTool && inputPathLooksSensitive(input)) return true;
2364
+ const hasShellCap = hasCapability(tool, [
2365
+ ToolCapabilities.SHELL_ARBITRARY,
2366
+ ToolCapabilities.SHELL_RESTRICTED,
2367
+ ToolCapabilities.SHELL_EXEC
2368
+ ]);
2369
+ if (!hasShellCap && tool.name !== "bash" && tool.name !== "shell" && tool.name !== "exec") {
2370
+ return false;
2371
+ }
2372
+ const command = shellCommandLineFromInput(input);
2373
+ return command ? shellCommandReadsSensitivePath(command) : false;
2374
+ }
2234
2375
 
2235
2376
  // src/security/permission-explain.ts
2236
2377
  function explainPermissionTrace(state, tool, input, ctx) {
2237
- const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
2378
+ const subject = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
2238
2379
  const steps = [];
2239
2380
  let winnerIndex = -1;
2240
2381
  const add = (rule, matched, decision, source, detail) => {
@@ -2455,13 +2596,7 @@ function explainPermissionTrace(state, tool, input, ctx) {
2455
2596
  }
2456
2597
  };
2457
2598
  }
2458
- add(
2459
- "yolo",
2460
- true,
2461
- "auto",
2462
- "yolo",
2463
- "YOLO mode is active \u2014 auto-approving every non-denied call"
2464
- );
2599
+ add("yolo", true, "auto", "yolo", "YOLO mode is active \u2014 auto-approving every non-denied call");
2465
2600
  winnerIndex = steps.length - 1;
2466
2601
  return {
2467
2602
  toolName: tool.name,
@@ -2733,7 +2868,14 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
2733
2868
  static isMcpTool(name) {
2734
2869
  return name.startsWith("mcp__");
2735
2870
  }
2736
- async evaluate(tool) {
2871
+ async evaluate(tool, input) {
2872
+ if (input !== void 0 && isSensitiveReadCall(tool, input)) {
2873
+ return {
2874
+ permission: "deny",
2875
+ source: "subagent_guard",
2876
+ reason: "subagents may not read credential-bearing paths \u2014 the leader must perform this read so the user can approve it"
2877
+ };
2878
+ }
2737
2879
  const caps = tool.capabilities ?? [];
2738
2880
  const hasAllowedCap = caps.some((c) => this.allowedCapabilities.includes(c));
2739
2881
  const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
@@ -2760,8 +2902,8 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
2760
2902
  }
2761
2903
  allowOnce() {
2762
2904
  }
2763
- async explain(tool) {
2764
- const decision = await this.evaluate(tool);
2905
+ async explain(tool, input) {
2906
+ const decision = await this.evaluate(tool, input);
2765
2907
  return {
2766
2908
  toolName: tool.name,
2767
2909
  subject: null,
@@ -2811,6 +2953,17 @@ function fsWriteTargetPaths(input) {
2811
2953
  }
2812
2954
  return out;
2813
2955
  }
2956
+ function mergeTrustEntries(exact, wildcard) {
2957
+ if (!exact) return wildcard;
2958
+ if (!wildcard) return exact;
2959
+ const deny2 = [...wildcard.deny ?? [], ...exact.deny ?? []];
2960
+ const merged = {
2961
+ ...wildcard,
2962
+ ...exact
2963
+ };
2964
+ if (deny2.length > 0) merged.deny = [...new Set(deny2)];
2965
+ return merged;
2966
+ }
2814
2967
  var DefaultPermissionPolicy = class {
2815
2968
  policy = {};
2816
2969
  loaded = false;
@@ -2849,6 +3002,7 @@ var DefaultPermissionPolicy = class {
2849
3002
  yoloBlockedAsDestructive(tool, input, ctx) {
2850
3003
  if (!this.yolo || this.yoloDestructive) return false;
2851
3004
  if (this.hasAgentStateWriteTarget(tool, input, ctx)) return true;
3005
+ if (attachesWellKnownCredential(input)) return true;
2852
3006
  const isShellSurface = tool.name === "bash" || tool.name === "exec" || (tool.capabilities ?? []).includes("shell.arbitrary");
2853
3007
  if (!isShellSurface) return false;
2854
3008
  const command = getInputString(input, "command") ?? shellCommandLineFromInput(input);
@@ -2942,8 +3096,8 @@ var DefaultPermissionPolicy = class {
2942
3096
  };
2943
3097
  }
2944
3098
  const namespaceEntry = this.findNamespaceEntry(tool.name);
2945
- const entry = this.policy[tool.name] ?? namespaceEntry;
2946
- const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
3099
+ const entry = mergeTrustEntries(this.policy[tool.name], namespaceEntry);
3100
+ const subject = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
2947
3101
  const cacheKey = `${tool.name}::${subject ?? tool.name}`;
2948
3102
  const evalKey = `${cacheKey}::${permissionFingerprint(tool)}`;
2949
3103
  if (tool.name !== "write" && !this.hasAgentStateWriteTarget(tool, input, ctx)) {
@@ -2960,15 +3114,6 @@ var DefaultPermissionPolicy = class {
2960
3114
  this._evalCache.set(evalKey, decision);
2961
3115
  return decision;
2962
3116
  }
2963
- if (this.sessionAllowed.has(cacheKey)) {
2964
- this.sessionAllowed.delete(cacheKey);
2965
- const decision = {
2966
- permission: "auto",
2967
- source: "trust",
2968
- reason: "session one-shot allow (user pressed yes)"
2969
- };
2970
- return decision;
2971
- }
2972
3117
  if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
2973
3118
  this._logDeny(tool.name, subject, "matched deny pattern");
2974
3119
  const decision = {
@@ -2979,6 +3124,15 @@ var DefaultPermissionPolicy = class {
2979
3124
  this._evalCache.set(evalKey, decision);
2980
3125
  return decision;
2981
3126
  }
3127
+ if (this.sessionAllowed.has(cacheKey)) {
3128
+ this.sessionAllowed.delete(cacheKey);
3129
+ const decision = {
3130
+ permission: "auto",
3131
+ source: "trust",
3132
+ reason: "session one-shot allow (user pressed yes)"
3133
+ };
3134
+ return decision;
3135
+ }
2982
3136
  if (tool.permission === "deny") {
2983
3137
  this._logDeny(tool.name, subject, "tool default deny");
2984
3138
  const decision = {
@@ -2989,6 +3143,7 @@ var DefaultPermissionPolicy = class {
2989
3143
  this._evalCache.set(evalKey, decision);
2990
3144
  return decision;
2991
3145
  }
3146
+ const denyUnevaluated = Boolean(entry?.deny?.length) && subject === void 0;
2992
3147
  const allowMatches = hasShellSubject(tool) ? matchesCommandTrust : matchesTrust;
2993
3148
  if (entry?.allow && subject && allowMatches(entry.allow, subject)) {
2994
3149
  const decision = {
@@ -2999,7 +3154,7 @@ var DefaultPermissionPolicy = class {
2999
3154
  this._evalCache.set(evalKey, decision);
3000
3155
  return decision;
3001
3156
  }
3002
- if (entry?.auto) {
3157
+ if (entry?.auto && !denyUnevaluated) {
3003
3158
  const decision = { permission: "auto", source: "trust" };
3004
3159
  this._evalCache.set(evalKey, decision);
3005
3160
  return decision;
@@ -3099,19 +3254,10 @@ var DefaultPermissionPolicy = class {
3099
3254
  }
3100
3255
  return { permission: "confirm", source: "default" };
3101
3256
  }
3257
+ // Delegates to the shared helper so the subagent policy applies the exact
3258
+ // same rule — see `isSensitiveReadCall` in ./permission-helpers.ts.
3102
3259
  isSensitiveReadCall(tool, input) {
3103
- const isReadTool = hasCapability(tool, ToolCapabilities.FS_READ) || tool.name === "read" || tool.name === "grep" || tool.name === "glob" || tool.name === "tree";
3104
- if (isReadTool && inputPathLooksSensitive(input)) return true;
3105
- const hasShellCap = hasCapability(tool, [
3106
- ToolCapabilities.SHELL_ARBITRARY,
3107
- ToolCapabilities.SHELL_RESTRICTED,
3108
- ToolCapabilities.SHELL_EXEC
3109
- ]);
3110
- if (!hasShellCap && tool.name !== "bash" && tool.name !== "shell" && tool.name !== "exec") {
3111
- return false;
3112
- }
3113
- const command = shellCommandLineFromInput(input);
3114
- return command ? shellCommandReadsSensitivePath(command) : false;
3260
+ return isSensitiveReadCall(tool, input);
3115
3261
  }
3116
3262
  async trust(rule) {
3117
3263
  if (!this.loaded) await this.reload();
@@ -3592,6 +3738,14 @@ function keyFileNeedsHardening(keyFile, opts) {
3592
3738
  }
3593
3739
  return false;
3594
3740
  }
3741
+ function mkdirSecretDirSync(dir) {
3742
+ fs2.mkdirSync(dir, { recursive: true, mode: 448 });
3743
+ if (process.platform === "win32") return;
3744
+ try {
3745
+ fs2.chmodSync(dir, 448);
3746
+ } catch {
3747
+ }
3748
+ }
3595
3749
  function writeKeyFileAtomicSync(keyFile, content) {
3596
3750
  const tmp = `${keyFile}.${randomBytes2(4).toString("hex")}.tmp`;
3597
3751
  const fd = fs2.openSync(tmp, "w", 384);
@@ -3739,7 +3893,7 @@ var DefaultSecretVault = class {
3739
3893
  const oldVersion = this._keyVersion;
3740
3894
  const newKey = randomBytes2(KEY_BYTES);
3741
3895
  const newVersion = oldVersion + 1;
3742
- fs2.mkdirSync(path8.dirname(this.keyFile), { recursive: true });
3896
+ mkdirSecretDirSync(path8.dirname(this.keyFile));
3743
3897
  const passphrase = getVaultPassphrase();
3744
3898
  if (passphrase) {
3745
3899
  writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
@@ -3823,7 +3977,7 @@ var DefaultSecretVault = class {
3823
3977
  } catch (err) {
3824
3978
  if (err.code !== "ENOENT") throw err;
3825
3979
  }
3826
- fs2.mkdirSync(path8.dirname(this.keyFile), { recursive: true });
3980
+ mkdirSecretDirSync(path8.dirname(this.keyFile));
3827
3981
  const key = randomBytes2(KEY_BYTES);
3828
3982
  const passphrase = getVaultPassphrase();
3829
3983
  const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
@@ -4260,6 +4414,7 @@ export {
4260
4414
  hashRecoveryCode,
4261
4415
  isSecretField,
4262
4416
  isTrustDecisionAllowed,
4417
+ kanbanGovernance,
4263
4418
  matchRule,
4264
4419
  migratePlaintextSecrets,
4265
4420
  noOpVault,
@@ -4271,6 +4426,7 @@ export {
4271
4426
  sanitizeApiError,
4272
4427
  scrubErrorDetail,
4273
4428
  scrubErrorText,
4429
+ setKanbanGovernance,
4274
4430
  toolMutates,
4275
4431
  validateDirectoryPolicy,
4276
4432
  validateTrustPolicy,
@@ -1,4 +1,4 @@
1
- import { type KanbanBoundaryEvaluation, type KanbanContractReadinessIssue } from '@wrongstack/kanban';
1
+ import type { KanbanBoundaryEvaluation, KanbanContractReadinessIssue } from '@wrongstack/kanban';
2
2
  import type { Context } from '../core/context.js';
3
3
  import type { Tool } from '../types/tool.js';
4
4
  export interface ToolKanbanBoundaryEvaluation extends KanbanBoundaryEvaluation {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Kanban governance-evaluation port for the security area.
3
+ *
4
+ * `kanban-boundary.ts` previously imported the boundary evaluation and
5
+ * board-read functions directly from `@wrongstack/kanban`, keeping a
6
+ * core → kanban runtime edge (roadmap #11 / report §2.1). This port is
7
+ * the leaf contract; the CLI composition root registers the real
8
+ * implementation at boot alongside the storage and coordination ports.
9
+ *
10
+ * Type-only imports of kanban's data shapes remain permitted — they erase
11
+ * at compile time and do not create a runtime edge.
12
+ */
13
+ import type { KanbanBoundaryAccess, KanbanBoundaryEvaluation, KanbanBoundaryLayer, KanbanBoard, KanbanContractReadinessIssue, KanbanTask } from '@wrongstack/kanban';
14
+ export interface KanbanGovernancePort {
15
+ readBoard(projectRoot: string, boardId: string): Promise<KanbanBoard | null>;
16
+ resolveKanbanBoundaryLayers(board: KanbanBoard, task: Pick<KanbanTask, 'boundary'> | null | undefined): KanbanBoundaryLayer[];
17
+ evaluateKanbanBoundaryOpaque(layers: KanbanBoundaryLayer[], toolName: string): KanbanBoundaryEvaluation;
18
+ evaluateKanbanBoundaryPath(layers: KanbanBoundaryLayer[], candidate: unknown, access: KanbanBoundaryAccess): KanbanBoundaryEvaluation;
19
+ evaluateContractGraphReadiness(board: KanbanBoard, taskId: string): {
20
+ ready: boolean;
21
+ issues: KanbanContractReadinessIssue[];
22
+ };
23
+ }
24
+ /** Composition-root hook: register the real kanban-backed implementation. */
25
+ export declare function setKanbanGovernance(impl: KanbanGovernancePort): void;
26
+ /** Resolve the governance port (throws when not wired, mirroring BoardStorePort). */
27
+ export declare function kanbanGovernance(): KanbanGovernancePort;
28
+ //# sourceMappingURL=kanban-governance-port.d.ts.map
@@ -107,4 +107,15 @@ export declare function isInsideAgentStateRoot(absPath: string): boolean;
107
107
  export declare function isProtectedAgentStatePath(absPath: string): boolean;
108
108
  export declare function inputPathLooksSensitive(input: unknown): boolean;
109
109
  export declare function shellCommandReadsSensitivePath(command: string): boolean;
110
+ /**
111
+ * True when `tool` + `input` amount to reading a credential-bearing path.
112
+ *
113
+ * Shared by both permission policies. It lived as a private method on
114
+ * `DefaultPermissionPolicy`, so `AutoApprovePermissionPolicy` — the policy
115
+ * subagents run under — had no way to apply it, and a delegated read of
116
+ * `~/.aws/credentials` was auto-approved where the leader would have prompted.
117
+ * Keeping one implementation here means widening the sensitive-path list
118
+ * protects the leader and its subagents in the same edit.
119
+ */
120
+ export declare function isSensitiveReadCall(tool: Tool, input: unknown): boolean;
110
121
  //# sourceMappingURL=permission-helpers.d.ts.map
@@ -1,10 +1,19 @@
1
1
  import type { Context } from '../core/context.js';
2
2
  import type { InputReader } from '../types/input-reader.js';
3
- import type { PermissionDecision, PermissionPolicy, PermissionTrace } from '../types/permission.js';
3
+ import type { PermissionDecision, PermissionPolicy, PermissionTrace, TrustPolicy } from '../types/permission.js';
4
4
  import type { Tool } from '../types/tool.js';
5
5
  import { type TrustPolicyDiagnostic } from './permission-policy-schema.js';
6
6
  export { AutoApprovePermissionPolicy } from './auto-approve-policy.js';
7
7
  export { alwaysAllowUnavailableReason, matchesTrust, matchesCommandTrust, hasShellSubject, permissionFingerprint, shellCommandLineFromInput, inputPathLooksSensitive, shellCommandReadsSensitivePath, } from './permission-helpers.js';
8
+ /**
9
+ * Combine an exact-name trust entry with the wildcard entry that also matched.
10
+ *
11
+ * Deny is the union of both levels — a narrow "always allow" must never be able
12
+ * to drop a broad guardrail. Everything permissive (allow / auto / trustWorkdir
13
+ * / denyPrivate) comes from the more specific entry when it says anything, so
14
+ * exact-name rules still win where they are meant to.
15
+ */
16
+ export declare function mergeTrustEntries(exact: TrustPolicy[string] | undefined, wildcard: TrustPolicy[string] | undefined): TrustPolicy[string] | undefined;
8
17
  export interface PermissionPolicyOptions {
9
18
  trustFile: string;
10
19
  yolo?: boolean | undefined;
@@ -9,4 +9,21 @@ export declare function pathLooksInsideProject(rawPath: string, projectRoot: str
9
9
  * state-root detection either (those paths are resolved absolutely).
10
10
  */
11
11
  export declare function isClearlyDestructiveBashCommand(command: string, projectRoot: string | undefined): boolean;
12
+ /**
13
+ * True when a tool call would bind a well-known third-party credential to a
14
+ * provider endpoint.
15
+ *
16
+ * `provider_manage` lets the model create a provider, choose its `baseUrl`
17
+ * (validated for scheme only — no host allowlist) and name the environment
18
+ * variables its key is read from. Nothing claims `ANTHROPIC_API_KEY` on a stock
19
+ * install, so `rejectBorrowedEnvVars` — which only rejects names another
20
+ * provider already lists — let it through. Combined with the sibling
21
+ * `fallback_chain_manage` / `leader_model_set` tools in the same bundle, that is
22
+ * a complete "send my real key to a host I chose" primitive, reachable by
23
+ * prompt injection.
24
+ *
25
+ * The tool stays usable: this only forces the decision back to the human rather
26
+ * than letting YOLO auto-approve it.
27
+ */
28
+ export declare function attachesWellKnownCredential(input: unknown): boolean;
12
29
  //# sourceMappingURL=yolo-risk.d.ts.map