@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.
- package/dist/chronicle/index.js +113 -41
- package/dist/chronicle/metrics-ingest.d.ts +7 -0
- package/dist/chronicle/metrics-schema.d.ts +4 -1
- package/dist/chronicle/project-server.js +94 -38
- package/dist/chronicle/query.d.ts +1 -0
- package/dist/chronicle/sqlite-journal-schema.d.ts +2 -1
- package/dist/chronicle/types.d.ts +2 -0
- package/dist/{agent-status-helpers.d.ts → coordination/agent-status-helpers.d.ts} +1 -1
- package/dist/{agent-status-tracker.d.ts → coordination/agent-status-tracker.d.ts} +2 -2
- package/dist/{middleware → coordination}/collab-pause.d.ts +1 -1
- package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -1
- package/dist/coordination/director.d.ts +7 -0
- package/dist/coordination/explore-companion.d.ts +9 -6
- package/dist/coordination/fleet-status-tool.d.ts +1 -1
- package/dist/coordination/index.d.ts +5 -1
- package/dist/coordination/index.js +2392 -405
- package/dist/coordination/kanban-dispatch-port.d.ts +30 -0
- package/dist/coordination/kanban-ops-port.d.ts +21 -0
- package/dist/coordination/mailbox-hooks.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +14 -9
- package/dist/coordination/mutation-engine.d.ts +5 -3
- package/dist/core/context.d.ts +35 -44
- package/dist/core/conversation-state.d.ts +16 -52
- package/dist/core/index.js +92 -25
- package/dist/core/provider-runner.d.ts +2 -2
- package/dist/core/run-env.d.ts +7 -28
- package/dist/core/streaming-response-builder.d.ts +2 -2
- package/dist/execution/index.js +138 -162
- package/dist/goal/index.js +100 -22
- package/dist/hq/auth-store.d.ts +9 -0
- package/dist/hq/cost-bridge.d.ts +1 -1
- package/dist/hq/index.js +69 -7
- package/dist/index.d.ts +12 -5
- package/dist/index.js +21185 -26807
- package/dist/infrastructure/index.js +232 -135
- package/dist/infrastructure/provider-cache-ledger.d.ts +1 -1
- package/dist/infrastructure/token-counter.d.ts +5 -1
- package/dist/kernel/events/file-events.d.ts +6 -0
- package/dist/kernel/events/provider-events.d.ts +10 -7
- package/dist/kernel/events/session-events.d.ts +4 -4
- package/dist/kernel/events/tool-events.d.ts +12 -2
- package/dist/observability/index.js +1 -1
- package/dist/plugin/index.js +218 -47
- package/dist/prompts/index.js +360 -3
- package/dist/security/auto-approve-policy.d.ts +2 -2
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.js +224 -68
- package/dist/security/kanban-boundary.d.ts +1 -1
- package/dist/security/kanban-governance-port.d.ts +28 -0
- package/dist/security/permission-helpers.d.ts +11 -0
- package/dist/security/permission-policy.d.ts +10 -1
- package/dist/security/yolo-risk.d.ts +17 -0
- package/dist/session-catalog/index.js +34 -5
- package/dist/session-catalog/project-server.js +34 -5
- package/dist/session-catalog/protocol.d.ts +1 -1
- package/dist/session-catalog/registry.d.ts +1 -1
- package/dist/session-catalog/store-schema.d.ts +1 -1
- package/dist/session-catalog/store.d.ts +1 -1
- package/dist/skills/index.js +39 -6
- package/dist/storage/annotations-store.d.ts +1 -1
- package/dist/storage/board-store-port.d.ts +45 -0
- package/dist/storage/completed-work-checkpoint.d.ts +1 -1
- package/dist/storage/config-loader/types.d.ts +1 -1
- package/dist/storage/event-bus-port.d.ts +27 -0
- package/dist/storage/file-session-writer.d.ts +47 -5
- package/dist/storage/goal-coordination.d.ts +1 -1
- package/dist/storage/goal-store.d.ts +1 -1
- package/dist/storage/index.d.ts +3 -4
- package/dist/storage/index.js +1107 -1462
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/queue-store.d.ts +1 -1
- package/dist/storage/replay-log-store.d.ts +1 -1
- package/dist/storage/session-recovery.d.ts +10 -0
- package/dist/storage/session-resume-validation.d.ts +13 -1
- package/dist/storage/session-store/events.d.ts +1 -1
- package/dist/storage/session-store/load-session-data.d.ts +1 -1
- package/dist/storage/session-store/rename-session.d.ts +1 -1
- package/dist/storage/session-store/resume-session.d.ts +3 -1
- package/dist/storage/session-store/session-store-index.d.ts +2 -1
- package/dist/storage/session-store/types.d.ts +1 -1
- package/dist/storage/session-store.d.ts +70 -0
- package/dist/storage/session-summary-tracker.d.ts +8 -0
- package/dist/storage/session-write-buffer.d.ts +31 -2
- package/dist/storage/task-store.d.ts +1 -1
- package/dist/storage/todos-checkpoint.d.ts +1 -1
- package/dist/storage/tool-audit-log.d.ts +1 -1
- package/dist/tasking/index.js +100 -22
- package/dist/tasking/task-tracker.d.ts +24 -1
- package/dist/types/compactor.d.ts +2 -2
- package/dist/types/context.d.ts +212 -0
- package/dist/types/conversation-state.d.ts +109 -0
- package/dist/types/error-handler.d.ts +2 -2
- package/dist/types/file-event-record.d.ts +6 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +17 -1
- package/dist/types/permission.d.ts +3 -3
- package/dist/types/plugin.d.ts +3 -3
- package/dist/types/provider-runner.d.ts +2 -2
- package/dist/types/provider.d.ts +10 -0
- package/dist/types/run-env.d.ts +32 -0
- package/dist/types/session.d.ts +3 -0
- package/dist/types/slash-command.d.ts +2 -2
- package/dist/types/token-counter.d.ts +30 -1
- package/dist/types/tool-executor.d.ts +2 -2
- package/dist/types/tool.d.ts +20 -5
- package/dist/utils/context-breakdown.d.ts +2 -2
- package/dist/utils/context-evidence.d.ts +12 -12
- package/dist/utils/crash-shield.d.ts +9 -0
- package/dist/utils/heap-watchdog.js +11 -3
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +126 -160
- package/dist/utils/regex-guard.d.ts +7 -30
- package/dist/utils/terminal-sanitize.d.ts +41 -0
- package/dist/utils/todos-format.d.ts +1 -1
- package/dist/utils/tool-subject.d.ts +1 -1
- package/dist/utils/tree-kill.d.ts +2 -0
- package/dist/utils/tree-kill.js +1 -0
- package/instructions/agents/chaos-monkey.md +5 -1
- package/instructions/coordination/subagent-baseline.md +10 -0
- package/instructions/system-lite.md +2 -0
- package/instructions/system-pro.md +40 -0
- package/instructions/system.md +15 -0
- package/package.json +7 -10
- package/dist/defaults/index.d.ts +0 -69
- package/dist/defaults/index.js +0 -37755
- /package/dist/{fleet-notifier.d.ts → coordination/fleet-notifier.d.ts} +0 -0
- /package/dist/{session-registry-atomic-file.d.ts → session-catalog/session-registry-atomic-file.d.ts} +0 -0
- /package/dist/{session-registry-types.d.ts → session-catalog/session-registry-types.d.ts} +0 -0
- /package/dist/{session-registry.d.ts → session-catalog/session-registry.d.ts} +0 -0
package/dist/security/index.js
CHANGED
|
@@ -175,7 +175,7 @@ function walk(node, vault, transform) {
|
|
|
175
175
|
}
|
|
176
176
|
return out;
|
|
177
177
|
}
|
|
178
|
-
var SECRET_KEY_PATTERN = /(?:
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 === ".\\*")
|
|
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(
|
|
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(
|
|
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 =
|
|
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]
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|