@wrongstack/core 0.308.7 → 0.309.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/coordination/director/director-toolset.d.ts +2 -2
- package/dist/coordination/director-mutation-test-tool.d.ts +29 -0
- package/dist/coordination/director-tools.d.ts +2 -0
- package/dist/coordination/director.d.ts +16 -0
- package/dist/coordination/explore-companion.d.ts +9 -6
- package/dist/coordination/fleet.d.ts +12 -0
- package/dist/coordination/index.d.ts +1 -1
- package/dist/coordination/index.js +990 -61
- package/dist/coordination/mail-tools.d.ts +10 -6
- package/dist/coordination/mailbox-codecs.d.ts +31 -0
- package/dist/coordination/multi-agent-coordinator.d.ts +14 -0
- package/dist/coordination/multi-agent-timeout.d.ts +11 -1
- package/dist/coordination/mutation-engine.d.ts +76 -0
- package/dist/coordination/subagent-budget.d.ts +54 -0
- package/dist/coordination/subagent-finish.d.ts +78 -0
- package/dist/core/index.js +58 -13
- package/dist/defaults/index.js +1132 -106
- package/dist/execution/index.js +260 -20
- package/dist/hq/index.js +45 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1456 -263
- package/dist/infrastructure/index.js +22 -3
- package/dist/kernel/events/agent-events.d.ts +31 -2
- package/dist/models/index.js +11 -1
- package/dist/observability/index.js +1 -1
- package/dist/plugin/index.js +113 -12
- package/dist/prompts/index.js +360 -3
- package/dist/security/auto-approve-policy.d.ts +2 -2
- package/dist/security/index.js +177 -50
- 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 +32 -2
- package/dist/session-catalog/project-server.js +32 -2
- package/dist/skills/index.js +39 -6
- package/dist/storage/index.js +44 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +14 -0
- package/dist/types/multi-agent.d.ts +15 -0
- package/dist/types/provider.d.ts +29 -1
- package/dist/types/tool.d.ts +15 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +54 -4
- package/dist/utils/terminal-sanitize.d.ts +41 -0
- package/dist/utils/tool-subject.d.ts +1 -1
- package/instructions/agents/chaos-monkey.md +61 -0
- package/package.json +4 -4
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();
|
|
@@ -921,6 +921,14 @@ var PATTERNS = [
|
|
|
921
921
|
anchor: "sk-ant-"
|
|
922
922
|
},
|
|
923
923
|
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
|
|
924
|
+
{
|
|
925
|
+
// `xai` is a first-class provider in this codebase, but its key shape was
|
|
926
|
+
// absent here — so the one credential format WrongStack itself hands users
|
|
927
|
+
// was the one the scrubber could not recognize (audit 2026-08-20).
|
|
928
|
+
type: "xai_key",
|
|
929
|
+
regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
|
|
930
|
+
anchor: "xai-"
|
|
931
|
+
},
|
|
924
932
|
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
|
|
925
933
|
{ 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
934
|
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
|
|
@@ -1011,8 +1019,8 @@ var PATTERNS = [
|
|
|
1011
1019
|
// replacement so the separator between adjacent secrets is preserved
|
|
1012
1020
|
// rather than collapsed. Capture groups are therefore: 1=leading
|
|
1013
1021
|
// 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"]
|
|
1022
|
+
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
1023
|
+
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
|
|
1016
1024
|
},
|
|
1017
1025
|
{
|
|
1018
1026
|
type: "json_credential_key",
|
|
@@ -1129,6 +1137,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
|
|
|
1129
1137
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
1130
1138
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
1131
1139
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
1140
|
+
var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
|
|
1141
|
+
var PEM_END_MARKER = "-----END";
|
|
1142
|
+
var MAX_PEM_BLOCK_BYTES = 64 * 1024;
|
|
1143
|
+
var PEM_END_LINE_TOLERANCE = 64;
|
|
1144
|
+
function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
|
|
1145
|
+
const head = text.slice(chunkStart, proposedEnd);
|
|
1146
|
+
const lastBegin = head.lastIndexOf("-----BEGIN ");
|
|
1147
|
+
if (lastBegin === -1) return proposedEnd;
|
|
1148
|
+
const fromBegin = text.slice(chunkStart + lastBegin);
|
|
1149
|
+
const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
|
|
1150
|
+
if (!marker || marker.index !== 0) return proposedEnd;
|
|
1151
|
+
const bodyStart = marker[0].length;
|
|
1152
|
+
const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
|
|
1153
|
+
const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
|
|
1154
|
+
if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
|
|
1155
|
+
return proposedEnd;
|
|
1156
|
+
}
|
|
1157
|
+
const lineEnd = fromBegin.indexOf("\n", closeIdx);
|
|
1158
|
+
const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
|
|
1159
|
+
return Math.max(proposedEnd, end);
|
|
1160
|
+
}
|
|
1132
1161
|
var PATTERN_ANCHORS = [
|
|
1133
1162
|
...new Set(
|
|
1134
1163
|
PATTERNS.flatMap(
|
|
@@ -1165,6 +1194,7 @@ var DefaultSecretScrubber = class {
|
|
|
1165
1194
|
}
|
|
1166
1195
|
}
|
|
1167
1196
|
end = safe === -1 ? end : safe + 1;
|
|
1197
|
+
end = extendChunkBoundaryPastPem(text, i, end);
|
|
1168
1198
|
}
|
|
1169
1199
|
out.push(this.scrubOne(text.slice(i, end)));
|
|
1170
1200
|
i = end;
|
|
@@ -1737,9 +1767,21 @@ function renderCommandLine(command, args) {
|
|
|
1737
1767
|
});
|
|
1738
1768
|
return [command, ...rendered].join(" ");
|
|
1739
1769
|
}
|
|
1740
|
-
function
|
|
1770
|
+
function renderSubjectFields(obj, fields) {
|
|
1771
|
+
const parts = [];
|
|
1772
|
+
for (const field of fields) {
|
|
1773
|
+
const value = obj[field];
|
|
1774
|
+
if (value === void 0 || value === null || value === "" || value === false) continue;
|
|
1775
|
+
const str = String(value);
|
|
1776
|
+
parts.push(`${field}=${/\s/.test(str) ? `"${str.replace(/"/g, '\\"')}"` : str}`);
|
|
1777
|
+
}
|
|
1778
|
+
return parts.join(" ");
|
|
1779
|
+
}
|
|
1780
|
+
function subjectForToolInput(toolName, input, subjectKey, subjectFields) {
|
|
1741
1781
|
if (!input || typeof input !== "object") return void 0;
|
|
1742
1782
|
const obj = input;
|
|
1783
|
+
const extra = subjectFields && subjectFields.length > 0 ? renderSubjectFields(obj, subjectFields) : "";
|
|
1784
|
+
const withExtra = (base) => extra ? `${base} ${extra}` : base;
|
|
1743
1785
|
if (subjectKey) {
|
|
1744
1786
|
const value = obj[subjectKey];
|
|
1745
1787
|
if (Array.isArray(value)) {
|
|
@@ -1755,9 +1797,9 @@ function subjectForToolInput(toolName, input, subjectKey) {
|
|
|
1755
1797
|
if (subjectKey === "command") {
|
|
1756
1798
|
const rendered = renderCommandLine(value, obj["args"]);
|
|
1757
1799
|
if (value === "commit" && obj["dry_run"] === true) {
|
|
1758
|
-
return `${escapeGlobSubject(rendered)}:dry-run`;
|
|
1800
|
+
return `${escapeGlobSubject(withExtra(rendered))}:dry-run`;
|
|
1759
1801
|
}
|
|
1760
|
-
return escapeGlobSubject(rendered);
|
|
1802
|
+
return escapeGlobSubject(withExtra(rendered));
|
|
1761
1803
|
}
|
|
1762
1804
|
if (subjectKey === "directory" && obj["dry_run"] === true) {
|
|
1763
1805
|
return `${escapeGlobSubject(value)}:dry-run`;
|
|
@@ -1918,7 +1960,9 @@ function hasRecursiveForceDelete(command, projectRoot) {
|
|
|
1918
1960
|
if (token === "rd" || token === "rmdir") {
|
|
1919
1961
|
const args = commandSegment(tokens, i + 1).map((arg) => arg.toLowerCase());
|
|
1920
1962
|
if (args.includes("/s")) {
|
|
1921
|
-
const targets = args.filter(
|
|
1963
|
+
const targets = args.filter(
|
|
1964
|
+
(arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
|
|
1965
|
+
);
|
|
1922
1966
|
if (targets.length === 0) return true;
|
|
1923
1967
|
if (targets.some(isCatastrophicDeleteTarget)) return true;
|
|
1924
1968
|
if (targets.some((target) => !pathLooksInsideProject(target, projectRoot))) return true;
|
|
@@ -1987,7 +2031,8 @@ function hasFindExec(command) {
|
|
|
1987
2031
|
function isCatastrophicDeleteTarget(rawTarget) {
|
|
1988
2032
|
const t = rawTarget.replace(/^['"]|['"]$/g, "").trim();
|
|
1989
2033
|
if (!t) return false;
|
|
1990
|
-
if (t === "*" || t === "." || t === "./" || t === ".\\" || t === "./*" || t === ".\\*")
|
|
2034
|
+
if (t === "*" || t === "." || t === "./" || t === ".\\" || t === "./*" || t === ".\\*")
|
|
2035
|
+
return true;
|
|
1991
2036
|
const s = t.replace(/[\\/]\*+$/, "").replace(/[\\/]+$/, "");
|
|
1992
2037
|
if (s === "") return true;
|
|
1993
2038
|
if (s === "~" || /^\$HOME$/i.test(s) || /^%USERPROFILE%$/i.test(s)) return true;
|
|
@@ -2027,12 +2072,16 @@ function hasCatastrophicDelete(command) {
|
|
|
2027
2072
|
const args = tokens.slice(i + 1);
|
|
2028
2073
|
const recursive = args.some((arg) => arg.toLowerCase() === "/s");
|
|
2029
2074
|
if (!recursive) continue;
|
|
2030
|
-
const targets = args.filter(
|
|
2075
|
+
const targets = args.filter(
|
|
2076
|
+
(arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
|
|
2077
|
+
);
|
|
2031
2078
|
if (targets.some(isCatastrophicDeleteTarget)) return true;
|
|
2032
2079
|
}
|
|
2033
2080
|
if (token === "del" || token === "erase") {
|
|
2034
2081
|
const args = tokens.slice(i + 1);
|
|
2035
|
-
const targets = args.filter(
|
|
2082
|
+
const targets = args.filter(
|
|
2083
|
+
(arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
|
|
2084
|
+
);
|
|
2036
2085
|
if (targets.some(isCatastrophicDeleteTarget)) return true;
|
|
2037
2086
|
}
|
|
2038
2087
|
}
|
|
@@ -2093,6 +2142,47 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
|
|
|
2093
2142
|
if (HIGH_IMPACT_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
|
|
2094
2143
|
return false;
|
|
2095
2144
|
}
|
|
2145
|
+
var WELL_KNOWN_CREDENTIAL_ENV_VARS = /* @__PURE__ */ new Set([
|
|
2146
|
+
"ANTHROPIC_API_KEY",
|
|
2147
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
2148
|
+
"OPENAI_API_KEY",
|
|
2149
|
+
"AZURE_OPENAI_API_KEY",
|
|
2150
|
+
"GEMINI_API_KEY",
|
|
2151
|
+
"GOOGLE_API_KEY",
|
|
2152
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
2153
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
2154
|
+
"GROQ_API_KEY",
|
|
2155
|
+
"MISTRAL_API_KEY",
|
|
2156
|
+
"COHERE_API_KEY",
|
|
2157
|
+
"DEEPSEEK_API_KEY",
|
|
2158
|
+
"XAI_API_KEY",
|
|
2159
|
+
"OPENROUTER_API_KEY",
|
|
2160
|
+
"PERPLEXITY_API_KEY",
|
|
2161
|
+
"TOGETHER_API_KEY",
|
|
2162
|
+
"FIREWORKS_API_KEY",
|
|
2163
|
+
"HUGGINGFACE_API_KEY",
|
|
2164
|
+
"HF_TOKEN",
|
|
2165
|
+
"GITHUB_TOKEN",
|
|
2166
|
+
"GH_TOKEN",
|
|
2167
|
+
"NPM_TOKEN",
|
|
2168
|
+
"AWS_ACCESS_KEY_ID",
|
|
2169
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
2170
|
+
"AWS_SESSION_TOKEN",
|
|
2171
|
+
"AZURE_CLIENT_SECRET",
|
|
2172
|
+
"GITLAB_TOKEN",
|
|
2173
|
+
"SLACK_TOKEN",
|
|
2174
|
+
"STRIPE_SECRET_KEY",
|
|
2175
|
+
"TELEGRAM_BOT_TOKEN",
|
|
2176
|
+
"WRONGSTACK_VAULT_PASSPHRASE"
|
|
2177
|
+
]);
|
|
2178
|
+
function attachesWellKnownCredential(input) {
|
|
2179
|
+
if (!input || typeof input !== "object") return false;
|
|
2180
|
+
const envVars = input["envVars"];
|
|
2181
|
+
if (!Array.isArray(envVars)) return false;
|
|
2182
|
+
return envVars.some(
|
|
2183
|
+
(name) => typeof name === "string" && WELL_KNOWN_CREDENTIAL_ENV_VARS.has(name.toUpperCase())
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2096
2186
|
|
|
2097
2187
|
// src/security/permission-helpers.ts
|
|
2098
2188
|
function matchesTrust(patterns, subject) {
|
|
@@ -2109,7 +2199,7 @@ function hasShellSubject(tool) {
|
|
|
2109
2199
|
]);
|
|
2110
2200
|
}
|
|
2111
2201
|
function alwaysAllowUnavailableReason(tool, input) {
|
|
2112
|
-
const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
2202
|
+
const subject = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
|
|
2113
2203
|
if (subject !== void 0) return void 0;
|
|
2114
2204
|
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
2205
|
}
|
|
@@ -2169,8 +2259,18 @@ var AGENT_STATE_SENSITIVE_BASENAMES = /^(?:config\.json|config\.local\.json|trus
|
|
|
2169
2259
|
function unescapeGlobSubject(value) {
|
|
2170
2260
|
return value.replace(/\\([*?[\]])/g, "$1");
|
|
2171
2261
|
}
|
|
2262
|
+
function stripAdsSuffix(forwardSlashPath) {
|
|
2263
|
+
const cut = forwardSlashPath.lastIndexOf("/");
|
|
2264
|
+
const dir = cut === -1 ? "" : forwardSlashPath.slice(0, cut + 1);
|
|
2265
|
+
const base = cut === -1 ? forwardSlashPath : forwardSlashPath.slice(cut + 1);
|
|
2266
|
+
const colon = base.indexOf(":");
|
|
2267
|
+
if (colon === -1 || cut === -1 && colon === 1 && base.length <= 2) return forwardSlashPath;
|
|
2268
|
+
return dir + base.slice(0, colon);
|
|
2269
|
+
}
|
|
2172
2270
|
function normalizeForCompare(value) {
|
|
2173
|
-
const forward =
|
|
2271
|
+
const forward = stripAdsSuffix(
|
|
2272
|
+
unescapeGlobSubject(value).replace(/\\/g, "/").replace(/\/+$/, "")
|
|
2273
|
+
);
|
|
2174
2274
|
return process.platform === "win32" ? forward.toLowerCase() : forward;
|
|
2175
2275
|
}
|
|
2176
2276
|
function realpathOfNearestExisting(p) {
|
|
@@ -2207,7 +2307,7 @@ function isProtectedAgentStatePath(absPath) {
|
|
|
2207
2307
|
return AGENT_STATE_SENSITIVE_BASENAMES.test(path5.basename(normalizeForCompare(absPath)));
|
|
2208
2308
|
}
|
|
2209
2309
|
function pathLooksSensitive(rawPath) {
|
|
2210
|
-
const normalized = stripShellQuotes(rawPath).replace(/\\/g, "/");
|
|
2310
|
+
const normalized = stripAdsSuffix(stripShellQuotes(rawPath).replace(/\\/g, "/"));
|
|
2211
2311
|
if (SENSITIVE_READ_PATHS.some((pattern) => pattern.test(normalized))) return true;
|
|
2212
2312
|
return isProtectedAgentStatePath(normalized);
|
|
2213
2313
|
}
|
|
@@ -2231,10 +2331,24 @@ function shellCommandReadsSensitivePath(command) {
|
|
|
2231
2331
|
}
|
|
2232
2332
|
return false;
|
|
2233
2333
|
}
|
|
2334
|
+
function isSensitiveReadCall(tool, input) {
|
|
2335
|
+
const isReadTool = hasCapability(tool, ToolCapabilities.FS_READ) || tool.name === "read" || tool.name === "grep" || tool.name === "glob" || tool.name === "tree";
|
|
2336
|
+
if (isReadTool && inputPathLooksSensitive(input)) return true;
|
|
2337
|
+
const hasShellCap = hasCapability(tool, [
|
|
2338
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
2339
|
+
ToolCapabilities.SHELL_RESTRICTED,
|
|
2340
|
+
ToolCapabilities.SHELL_EXEC
|
|
2341
|
+
]);
|
|
2342
|
+
if (!hasShellCap && tool.name !== "bash" && tool.name !== "shell" && tool.name !== "exec") {
|
|
2343
|
+
return false;
|
|
2344
|
+
}
|
|
2345
|
+
const command = shellCommandLineFromInput(input);
|
|
2346
|
+
return command ? shellCommandReadsSensitivePath(command) : false;
|
|
2347
|
+
}
|
|
2234
2348
|
|
|
2235
2349
|
// src/security/permission-explain.ts
|
|
2236
2350
|
function explainPermissionTrace(state, tool, input, ctx) {
|
|
2237
|
-
const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
2351
|
+
const subject = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
|
|
2238
2352
|
const steps = [];
|
|
2239
2353
|
let winnerIndex = -1;
|
|
2240
2354
|
const add = (rule, matched, decision, source, detail) => {
|
|
@@ -2455,13 +2569,7 @@ function explainPermissionTrace(state, tool, input, ctx) {
|
|
|
2455
2569
|
}
|
|
2456
2570
|
};
|
|
2457
2571
|
}
|
|
2458
|
-
add(
|
|
2459
|
-
"yolo",
|
|
2460
|
-
true,
|
|
2461
|
-
"auto",
|
|
2462
|
-
"yolo",
|
|
2463
|
-
"YOLO mode is active \u2014 auto-approving every non-denied call"
|
|
2464
|
-
);
|
|
2572
|
+
add("yolo", true, "auto", "yolo", "YOLO mode is active \u2014 auto-approving every non-denied call");
|
|
2465
2573
|
winnerIndex = steps.length - 1;
|
|
2466
2574
|
return {
|
|
2467
2575
|
toolName: tool.name,
|
|
@@ -2733,7 +2841,14 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
2733
2841
|
static isMcpTool(name) {
|
|
2734
2842
|
return name.startsWith("mcp__");
|
|
2735
2843
|
}
|
|
2736
|
-
async evaluate(tool) {
|
|
2844
|
+
async evaluate(tool, input) {
|
|
2845
|
+
if (input !== void 0 && isSensitiveReadCall(tool, input)) {
|
|
2846
|
+
return {
|
|
2847
|
+
permission: "deny",
|
|
2848
|
+
source: "subagent_guard",
|
|
2849
|
+
reason: "subagents may not read credential-bearing paths \u2014 the leader must perform this read so the user can approve it"
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2737
2852
|
const caps = tool.capabilities ?? [];
|
|
2738
2853
|
const hasAllowedCap = caps.some((c) => this.allowedCapabilities.includes(c));
|
|
2739
2854
|
const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
|
|
@@ -2760,8 +2875,8 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
2760
2875
|
}
|
|
2761
2876
|
allowOnce() {
|
|
2762
2877
|
}
|
|
2763
|
-
async explain(tool) {
|
|
2764
|
-
const decision = await this.evaluate(tool);
|
|
2878
|
+
async explain(tool, input) {
|
|
2879
|
+
const decision = await this.evaluate(tool, input);
|
|
2765
2880
|
return {
|
|
2766
2881
|
toolName: tool.name,
|
|
2767
2882
|
subject: null,
|
|
@@ -2811,6 +2926,17 @@ function fsWriteTargetPaths(input) {
|
|
|
2811
2926
|
}
|
|
2812
2927
|
return out;
|
|
2813
2928
|
}
|
|
2929
|
+
function mergeTrustEntries(exact, wildcard) {
|
|
2930
|
+
if (!exact) return wildcard;
|
|
2931
|
+
if (!wildcard) return exact;
|
|
2932
|
+
const deny2 = [...wildcard.deny ?? [], ...exact.deny ?? []];
|
|
2933
|
+
const merged = {
|
|
2934
|
+
...wildcard,
|
|
2935
|
+
...exact
|
|
2936
|
+
};
|
|
2937
|
+
if (deny2.length > 0) merged.deny = [...new Set(deny2)];
|
|
2938
|
+
return merged;
|
|
2939
|
+
}
|
|
2814
2940
|
var DefaultPermissionPolicy = class {
|
|
2815
2941
|
policy = {};
|
|
2816
2942
|
loaded = false;
|
|
@@ -2849,6 +2975,7 @@ var DefaultPermissionPolicy = class {
|
|
|
2849
2975
|
yoloBlockedAsDestructive(tool, input, ctx) {
|
|
2850
2976
|
if (!this.yolo || this.yoloDestructive) return false;
|
|
2851
2977
|
if (this.hasAgentStateWriteTarget(tool, input, ctx)) return true;
|
|
2978
|
+
if (attachesWellKnownCredential(input)) return true;
|
|
2852
2979
|
const isShellSurface = tool.name === "bash" || tool.name === "exec" || (tool.capabilities ?? []).includes("shell.arbitrary");
|
|
2853
2980
|
if (!isShellSurface) return false;
|
|
2854
2981
|
const command = getInputString(input, "command") ?? shellCommandLineFromInput(input);
|
|
@@ -2942,8 +3069,8 @@ var DefaultPermissionPolicy = class {
|
|
|
2942
3069
|
};
|
|
2943
3070
|
}
|
|
2944
3071
|
const namespaceEntry = this.findNamespaceEntry(tool.name);
|
|
2945
|
-
const entry = this.policy[tool.name]
|
|
2946
|
-
const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
3072
|
+
const entry = mergeTrustEntries(this.policy[tool.name], namespaceEntry);
|
|
3073
|
+
const subject = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
|
|
2947
3074
|
const cacheKey = `${tool.name}::${subject ?? tool.name}`;
|
|
2948
3075
|
const evalKey = `${cacheKey}::${permissionFingerprint(tool)}`;
|
|
2949
3076
|
if (tool.name !== "write" && !this.hasAgentStateWriteTarget(tool, input, ctx)) {
|
|
@@ -2960,15 +3087,6 @@ var DefaultPermissionPolicy = class {
|
|
|
2960
3087
|
this._evalCache.set(evalKey, decision);
|
|
2961
3088
|
return decision;
|
|
2962
3089
|
}
|
|
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
3090
|
if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
|
|
2973
3091
|
this._logDeny(tool.name, subject, "matched deny pattern");
|
|
2974
3092
|
const decision = {
|
|
@@ -2979,6 +3097,15 @@ var DefaultPermissionPolicy = class {
|
|
|
2979
3097
|
this._evalCache.set(evalKey, decision);
|
|
2980
3098
|
return decision;
|
|
2981
3099
|
}
|
|
3100
|
+
if (this.sessionAllowed.has(cacheKey)) {
|
|
3101
|
+
this.sessionAllowed.delete(cacheKey);
|
|
3102
|
+
const decision = {
|
|
3103
|
+
permission: "auto",
|
|
3104
|
+
source: "trust",
|
|
3105
|
+
reason: "session one-shot allow (user pressed yes)"
|
|
3106
|
+
};
|
|
3107
|
+
return decision;
|
|
3108
|
+
}
|
|
2982
3109
|
if (tool.permission === "deny") {
|
|
2983
3110
|
this._logDeny(tool.name, subject, "tool default deny");
|
|
2984
3111
|
const decision = {
|
|
@@ -2989,6 +3116,7 @@ var DefaultPermissionPolicy = class {
|
|
|
2989
3116
|
this._evalCache.set(evalKey, decision);
|
|
2990
3117
|
return decision;
|
|
2991
3118
|
}
|
|
3119
|
+
const denyUnevaluated = Boolean(entry?.deny?.length) && subject === void 0;
|
|
2992
3120
|
const allowMatches = hasShellSubject(tool) ? matchesCommandTrust : matchesTrust;
|
|
2993
3121
|
if (entry?.allow && subject && allowMatches(entry.allow, subject)) {
|
|
2994
3122
|
const decision = {
|
|
@@ -2999,7 +3127,7 @@ var DefaultPermissionPolicy = class {
|
|
|
2999
3127
|
this._evalCache.set(evalKey, decision);
|
|
3000
3128
|
return decision;
|
|
3001
3129
|
}
|
|
3002
|
-
if (entry?.auto) {
|
|
3130
|
+
if (entry?.auto && !denyUnevaluated) {
|
|
3003
3131
|
const decision = { permission: "auto", source: "trust" };
|
|
3004
3132
|
this._evalCache.set(evalKey, decision);
|
|
3005
3133
|
return decision;
|
|
@@ -3099,19 +3227,10 @@ var DefaultPermissionPolicy = class {
|
|
|
3099
3227
|
}
|
|
3100
3228
|
return { permission: "confirm", source: "default" };
|
|
3101
3229
|
}
|
|
3230
|
+
// Delegates to the shared helper so the subagent policy applies the exact
|
|
3231
|
+
// same rule — see `isSensitiveReadCall` in ./permission-helpers.ts.
|
|
3102
3232
|
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;
|
|
3233
|
+
return isSensitiveReadCall(tool, input);
|
|
3115
3234
|
}
|
|
3116
3235
|
async trust(rule) {
|
|
3117
3236
|
if (!this.loaded) await this.reload();
|
|
@@ -3592,6 +3711,14 @@ function keyFileNeedsHardening(keyFile, opts) {
|
|
|
3592
3711
|
}
|
|
3593
3712
|
return false;
|
|
3594
3713
|
}
|
|
3714
|
+
function mkdirSecretDirSync(dir) {
|
|
3715
|
+
fs2.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
3716
|
+
if (process.platform === "win32") return;
|
|
3717
|
+
try {
|
|
3718
|
+
fs2.chmodSync(dir, 448);
|
|
3719
|
+
} catch {
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3595
3722
|
function writeKeyFileAtomicSync(keyFile, content) {
|
|
3596
3723
|
const tmp = `${keyFile}.${randomBytes2(4).toString("hex")}.tmp`;
|
|
3597
3724
|
const fd = fs2.openSync(tmp, "w", 384);
|
|
@@ -3739,7 +3866,7 @@ var DefaultSecretVault = class {
|
|
|
3739
3866
|
const oldVersion = this._keyVersion;
|
|
3740
3867
|
const newKey = randomBytes2(KEY_BYTES);
|
|
3741
3868
|
const newVersion = oldVersion + 1;
|
|
3742
|
-
|
|
3869
|
+
mkdirSecretDirSync(path8.dirname(this.keyFile));
|
|
3743
3870
|
const passphrase = getVaultPassphrase();
|
|
3744
3871
|
if (passphrase) {
|
|
3745
3872
|
writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
|
|
@@ -3823,7 +3950,7 @@ var DefaultSecretVault = class {
|
|
|
3823
3950
|
} catch (err) {
|
|
3824
3951
|
if (err.code !== "ENOENT") throw err;
|
|
3825
3952
|
}
|
|
3826
|
-
|
|
3953
|
+
mkdirSecretDirSync(path8.dirname(this.keyFile));
|
|
3827
3954
|
const key = randomBytes2(KEY_BYTES);
|
|
3828
3955
|
const passphrase = getVaultPassphrase();
|
|
3829
3956
|
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
@@ -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
|
|
@@ -804,6 +804,14 @@ var PATTERNS = [
|
|
|
804
804
|
anchor: "sk-ant-"
|
|
805
805
|
},
|
|
806
806
|
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
|
|
807
|
+
{
|
|
808
|
+
// `xai` is a first-class provider in this codebase, but its key shape was
|
|
809
|
+
// absent here — so the one credential format WrongStack itself hands users
|
|
810
|
+
// was the one the scrubber could not recognize (audit 2026-08-20).
|
|
811
|
+
type: "xai_key",
|
|
812
|
+
regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
|
|
813
|
+
anchor: "xai-"
|
|
814
|
+
},
|
|
807
815
|
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
|
|
808
816
|
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
|
|
809
817
|
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
|
|
@@ -894,8 +902,8 @@ var PATTERNS = [
|
|
|
894
902
|
// replacement so the separator between adjacent secrets is preserved
|
|
895
903
|
// rather than collapsed. Capture groups are therefore: 1=leading
|
|
896
904
|
// delimiter, 2=key name, 3=value.
|
|
897
|
-
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
898
|
-
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
905
|
+
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
906
|
+
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
|
|
899
907
|
},
|
|
900
908
|
{
|
|
901
909
|
type: "json_credential_key",
|
|
@@ -1012,6 +1020,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
|
|
|
1012
1020
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
1013
1021
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
1014
1022
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
1023
|
+
var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
|
|
1024
|
+
var PEM_END_MARKER = "-----END";
|
|
1025
|
+
var MAX_PEM_BLOCK_BYTES = 64 * 1024;
|
|
1026
|
+
var PEM_END_LINE_TOLERANCE = 64;
|
|
1027
|
+
function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
|
|
1028
|
+
const head = text.slice(chunkStart, proposedEnd);
|
|
1029
|
+
const lastBegin = head.lastIndexOf("-----BEGIN ");
|
|
1030
|
+
if (lastBegin === -1) return proposedEnd;
|
|
1031
|
+
const fromBegin = text.slice(chunkStart + lastBegin);
|
|
1032
|
+
const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
|
|
1033
|
+
if (!marker || marker.index !== 0) return proposedEnd;
|
|
1034
|
+
const bodyStart = marker[0].length;
|
|
1035
|
+
const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
|
|
1036
|
+
const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
|
|
1037
|
+
if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
|
|
1038
|
+
return proposedEnd;
|
|
1039
|
+
}
|
|
1040
|
+
const lineEnd = fromBegin.indexOf("\n", closeIdx);
|
|
1041
|
+
const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
|
|
1042
|
+
return Math.max(proposedEnd, end);
|
|
1043
|
+
}
|
|
1015
1044
|
var PATTERN_ANCHORS = [
|
|
1016
1045
|
...new Set(
|
|
1017
1046
|
PATTERNS.flatMap(
|
|
@@ -1048,6 +1077,7 @@ var DefaultSecretScrubber = class {
|
|
|
1048
1077
|
}
|
|
1049
1078
|
}
|
|
1050
1079
|
end = safe === -1 ? end : safe + 1;
|
|
1080
|
+
end = extendChunkBoundaryPastPem(text, i, end);
|
|
1051
1081
|
}
|
|
1052
1082
|
out.push(this.scrubOne(text.slice(i, end)));
|
|
1053
1083
|
i = end;
|
|
@@ -248,6 +248,14 @@ var PATTERNS = [
|
|
|
248
248
|
anchor: "sk-ant-"
|
|
249
249
|
},
|
|
250
250
|
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
|
|
251
|
+
{
|
|
252
|
+
// `xai` is a first-class provider in this codebase, but its key shape was
|
|
253
|
+
// absent here — so the one credential format WrongStack itself hands users
|
|
254
|
+
// was the one the scrubber could not recognize (audit 2026-08-20).
|
|
255
|
+
type: "xai_key",
|
|
256
|
+
regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
|
|
257
|
+
anchor: "xai-"
|
|
258
|
+
},
|
|
251
259
|
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
|
|
252
260
|
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
|
|
253
261
|
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
|
|
@@ -338,8 +346,8 @@ var PATTERNS = [
|
|
|
338
346
|
// replacement so the separator between adjacent secrets is preserved
|
|
339
347
|
// rather than collapsed. Capture groups are therefore: 1=leading
|
|
340
348
|
// delimiter, 2=key name, 3=value.
|
|
341
|
-
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
342
|
-
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
349
|
+
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
350
|
+
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
|
|
343
351
|
},
|
|
344
352
|
{
|
|
345
353
|
type: "json_credential_key",
|
|
@@ -456,6 +464,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
|
|
|
456
464
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
457
465
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
458
466
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
467
|
+
var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
|
|
468
|
+
var PEM_END_MARKER = "-----END";
|
|
469
|
+
var MAX_PEM_BLOCK_BYTES = 64 * 1024;
|
|
470
|
+
var PEM_END_LINE_TOLERANCE = 64;
|
|
471
|
+
function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
|
|
472
|
+
const head = text.slice(chunkStart, proposedEnd);
|
|
473
|
+
const lastBegin = head.lastIndexOf("-----BEGIN ");
|
|
474
|
+
if (lastBegin === -1) return proposedEnd;
|
|
475
|
+
const fromBegin = text.slice(chunkStart + lastBegin);
|
|
476
|
+
const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
|
|
477
|
+
if (!marker || marker.index !== 0) return proposedEnd;
|
|
478
|
+
const bodyStart = marker[0].length;
|
|
479
|
+
const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
|
|
480
|
+
const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
|
|
481
|
+
if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
|
|
482
|
+
return proposedEnd;
|
|
483
|
+
}
|
|
484
|
+
const lineEnd = fromBegin.indexOf("\n", closeIdx);
|
|
485
|
+
const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
|
|
486
|
+
return Math.max(proposedEnd, end);
|
|
487
|
+
}
|
|
459
488
|
var PATTERN_ANCHORS = [
|
|
460
489
|
...new Set(
|
|
461
490
|
PATTERNS.flatMap(
|
|
@@ -492,6 +521,7 @@ var DefaultSecretScrubber = class {
|
|
|
492
521
|
}
|
|
493
522
|
}
|
|
494
523
|
end = safe === -1 ? end : safe + 1;
|
|
524
|
+
end = extendChunkBoundaryPastPem(text, i, end);
|
|
495
525
|
}
|
|
496
526
|
out.push(this.scrubOne(text.slice(i, end)));
|
|
497
527
|
i = end;
|