@wrongstack/core 0.309.0 → 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.d.ts +7 -0
- package/dist/coordination/explore-companion.d.ts +9 -6
- package/dist/coordination/index.js +331 -59
- package/dist/coordination/mutation-engine.d.ts +5 -3
- package/dist/core/index.js +39 -9
- package/dist/defaults/index.js +479 -101
- package/dist/execution/index.js +27 -9
- package/dist/hq/index.js +45 -5
- package/dist/index.js +640 -131
- package/dist/infrastructure/index.js +22 -3
- 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/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 +5 -1
- package/package.json +4 -4
|
@@ -2378,7 +2378,7 @@ function walk(node, vault, transform) {
|
|
|
2378
2378
|
}
|
|
2379
2379
|
return out;
|
|
2380
2380
|
}
|
|
2381
|
-
var SECRET_KEY_PATTERN = /(?:
|
|
2381
|
+
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;
|
|
2382
2382
|
var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
|
|
2383
2383
|
function isSecretField(name) {
|
|
2384
2384
|
const lc = name.toLowerCase();
|
|
@@ -2554,6 +2554,14 @@ function keyFileNeedsHardening(keyFile, opts) {
|
|
|
2554
2554
|
}
|
|
2555
2555
|
return false;
|
|
2556
2556
|
}
|
|
2557
|
+
function mkdirSecretDirSync(dir) {
|
|
2558
|
+
fs2.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2559
|
+
if (process.platform === "win32") return;
|
|
2560
|
+
try {
|
|
2561
|
+
fs2.chmodSync(dir, 448);
|
|
2562
|
+
} catch {
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2557
2565
|
function writeKeyFileAtomicSync(keyFile, content) {
|
|
2558
2566
|
const tmp = `${keyFile}.${randomBytes(4).toString("hex")}.tmp`;
|
|
2559
2567
|
const fd = fs2.openSync(tmp, "w", 384);
|
|
@@ -2701,7 +2709,7 @@ var DefaultSecretVault = class {
|
|
|
2701
2709
|
const oldVersion = this._keyVersion;
|
|
2702
2710
|
const newKey = randomBytes(KEY_BYTES);
|
|
2703
2711
|
const newVersion = oldVersion + 1;
|
|
2704
|
-
|
|
2712
|
+
mkdirSecretDirSync(path3.dirname(this.keyFile));
|
|
2705
2713
|
const passphrase = getVaultPassphrase();
|
|
2706
2714
|
if (passphrase) {
|
|
2707
2715
|
writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
|
|
@@ -2785,7 +2793,7 @@ var DefaultSecretVault = class {
|
|
|
2785
2793
|
} catch (err) {
|
|
2786
2794
|
if (err.code !== "ENOENT") throw err;
|
|
2787
2795
|
}
|
|
2788
|
-
|
|
2796
|
+
mkdirSecretDirSync(path3.dirname(this.keyFile));
|
|
2789
2797
|
const key = randomBytes(KEY_BYTES);
|
|
2790
2798
|
const passphrase = getVaultPassphrase();
|
|
2791
2799
|
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
@@ -3636,6 +3644,17 @@ var IN_PROJECT_DENIED_PATHS = [
|
|
|
3636
3644
|
// See discover-mailbox-bridge.ts:findWorkspaceCliEntry.
|
|
3637
3645
|
path: "features.mailboxBridge",
|
|
3638
3646
|
reason: "Enables the mailbox bridge, whose CLI-entry resolution walks up from the project root \u2014 a repo-supplied packages/cli/dist/index.js would be spawned on WebUI boot."
|
|
3647
|
+
},
|
|
3648
|
+
{
|
|
3649
|
+
// `plugins` is already denied above, so a repo cannot ADD a plugin. This
|
|
3650
|
+
// closes the other half: a repo could previously ship
|
|
3651
|
+
// `{"features":{"pluginsTrust":false}}` and switch off the integrity gate
|
|
3652
|
+
// for plugins the user had ALREADY installed globally — disarming the
|
|
3653
|
+
// trust-on-first-use pin that exists to catch a supply-chain update
|
|
3654
|
+
// rewriting a plugin's entry file. Same operator-owned class as the
|
|
3655
|
+
// switches above.
|
|
3656
|
+
path: "features.pluginsTrust",
|
|
3657
|
+
reason: "Disables the plugin trust-on-first-use integrity gate, re-trusting changed code in already-installed global plugins."
|
|
3639
3658
|
}
|
|
3640
3659
|
];
|
|
3641
3660
|
function deleteNestedPath(target, path13) {
|
|
@@ -388,7 +388,7 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
388
388
|
// redaction function (false positive = cosmetic noise; false negative = leak).
|
|
389
389
|
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
390
390
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
391
|
-
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
391
|
+
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD|PASSPHRASE)\s*[=:]\s*[^\s,]+/gi,
|
|
392
392
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
393
393
|
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
394
394
|
// every such flag in the command line is redacted, not just the first.
|
package/dist/plugin/index.js
CHANGED
|
@@ -1945,18 +1945,88 @@ var DefaultPluginAPI = class {
|
|
|
1945
1945
|
list: () => tr.list()
|
|
1946
1946
|
};
|
|
1947
1947
|
const pr = init.providerRegistry;
|
|
1948
|
+
const providerTypesIOwn = /* @__PURE__ */ new Set();
|
|
1949
|
+
const assertCanMutateProvider = (type, op) => {
|
|
1950
|
+
if (isOfficial) return;
|
|
1951
|
+
if (providerTypesIOwn.has(type)) return;
|
|
1952
|
+
if (!pr.has(type)) return;
|
|
1953
|
+
throw new Error(
|
|
1954
|
+
`Plugin "${owner}" may not ${op} provider "${type}" \u2014 it was not registered by this plugin. Replacing an existing provider would route prompts and credentials through plugin code.`
|
|
1955
|
+
);
|
|
1956
|
+
};
|
|
1948
1957
|
this.providers = {
|
|
1949
|
-
register: (f) =>
|
|
1950
|
-
|
|
1958
|
+
register: (f) => {
|
|
1959
|
+
assertCanMutateProvider(f.type, "replace");
|
|
1960
|
+
pr.register(f);
|
|
1961
|
+
providerTypesIOwn.add(f.type);
|
|
1962
|
+
},
|
|
1963
|
+
unregister: (type) => {
|
|
1964
|
+
assertCanMutateProvider(type, "unregister");
|
|
1965
|
+
providerTypesIOwn.delete(type);
|
|
1966
|
+
return pr.unregister(type);
|
|
1967
|
+
},
|
|
1951
1968
|
create: (cfg) => pr.create(cfg),
|
|
1952
1969
|
list: () => pr.list()
|
|
1953
1970
|
};
|
|
1954
|
-
|
|
1971
|
+
const mcpRegistry = init.mcpRegistry;
|
|
1972
|
+
if (!mcpRegistry) {
|
|
1973
|
+
this.mcp = noopMcp;
|
|
1974
|
+
} else {
|
|
1975
|
+
const mcpServersIStarted = /* @__PURE__ */ new Set();
|
|
1976
|
+
const assertOwnsMcpServer = (name, op) => {
|
|
1977
|
+
if (isOfficial) return;
|
|
1978
|
+
if (mcpServersIStarted.has(name)) return;
|
|
1979
|
+
if (!mcpRegistry.list().some((s) => s.name === name)) return;
|
|
1980
|
+
throw new Error(
|
|
1981
|
+
`Plugin "${owner}" may not ${op} MCP server "${name}" \u2014 it was not started by this plugin.`
|
|
1982
|
+
);
|
|
1983
|
+
};
|
|
1984
|
+
this.mcp = {
|
|
1985
|
+
start: async (cfg) => {
|
|
1986
|
+
const name = cfg?.name;
|
|
1987
|
+
if (typeof name === "string" && mcpRegistry.list().some((s) => s.name === name)) {
|
|
1988
|
+
assertOwnsMcpServer(name, "start");
|
|
1989
|
+
}
|
|
1990
|
+
await mcpRegistry.start(cfg);
|
|
1991
|
+
if (typeof name === "string") mcpServersIStarted.add(name);
|
|
1992
|
+
},
|
|
1993
|
+
stop: async (name) => {
|
|
1994
|
+
assertOwnsMcpServer(name, "stop");
|
|
1995
|
+
await mcpRegistry.stop(name);
|
|
1996
|
+
},
|
|
1997
|
+
restart: async (name) => {
|
|
1998
|
+
assertOwnsMcpServer(name, "restart");
|
|
1999
|
+
await mcpRegistry.restart(name);
|
|
2000
|
+
},
|
|
2001
|
+
list: () => mcpRegistry.list()
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
1955
2004
|
const scr = init.slashCommandRegistry;
|
|
1956
2005
|
const official = init.official === true;
|
|
2006
|
+
const commandsIOwn = /* @__PURE__ */ new Set();
|
|
1957
2007
|
this.slashCommands = scr ? {
|
|
1958
|
-
register: (cmd) =>
|
|
1959
|
-
|
|
2008
|
+
register: (cmd) => {
|
|
2009
|
+
scr.register(cmd, owner, { official });
|
|
2010
|
+
for (const key of [cmd.name, ...cmd.aliases ?? []]) {
|
|
2011
|
+
commandsIOwn.add(key);
|
|
2012
|
+
commandsIOwn.add(`${owner}:${key}`);
|
|
2013
|
+
}
|
|
2014
|
+
},
|
|
2015
|
+
unregister: (name) => {
|
|
2016
|
+
if (!official && !commandsIOwn.has(name) && scr.get(name) !== void 0) {
|
|
2017
|
+
throw new Error(
|
|
2018
|
+
`Plugin "${owner}" may not unregister slash command "${name}" \u2014 it was not registered by this plugin.`
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
for (const key of [
|
|
2022
|
+
name,
|
|
2023
|
+
`${owner}:${name}`,
|
|
2024
|
+
name.startsWith(`${owner}:`) ? name.slice(owner.length + 1) : name
|
|
2025
|
+
]) {
|
|
2026
|
+
commandsIOwn.delete(key);
|
|
2027
|
+
}
|
|
2028
|
+
return scr.unregister(name);
|
|
2029
|
+
},
|
|
1960
2030
|
get: (name) => scr.get(name),
|
|
1961
2031
|
list: () => scr.list()
|
|
1962
2032
|
} : noopSlashCommands;
|
|
@@ -5829,6 +5899,30 @@ function withSqliteExperimentalWarningSuppressed(run) {
|
|
|
5829
5899
|
}
|
|
5830
5900
|
}
|
|
5831
5901
|
|
|
5902
|
+
// src/utils/win32-cmd.ts
|
|
5903
|
+
var WIN32_CMD_META = /[&|<>"%\r\n\0]/;
|
|
5904
|
+
function buildWin32CmdShimInvocation(command, args = []) {
|
|
5905
|
+
assertSafeWin32CmdArgs([command, ...args]);
|
|
5906
|
+
const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
|
|
5907
|
+
return {
|
|
5908
|
+
command: process.env["COMSPEC"] ?? "cmd.exe",
|
|
5909
|
+
args: ["/d", "/c", line],
|
|
5910
|
+
windowsVerbatimArguments: true
|
|
5911
|
+
};
|
|
5912
|
+
}
|
|
5913
|
+
function assertSafeWin32CmdArgs(args) {
|
|
5914
|
+
for (const arg of args) {
|
|
5915
|
+
if (typeof arg === "string" && WIN32_CMD_META.test(arg)) {
|
|
5916
|
+
throw new Error(
|
|
5917
|
+
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
|
|
5918
|
+
);
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
function quoteWin32CmdArg(arg) {
|
|
5923
|
+
return `"${arg}"`;
|
|
5924
|
+
}
|
|
5925
|
+
|
|
5832
5926
|
// src/chronicle/sqlite-journal-schema.ts
|
|
5833
5927
|
var CHRONICLE_SQLITE_FILE = "chronicle.sqlite";
|
|
5834
5928
|
var LEGACY_JSONL_MIGRATION_KEY = "legacy-jsonl-v1";
|
|
@@ -11600,14 +11694,21 @@ async function openInEditor(filePath, env = process.env) {
|
|
|
11600
11694
|
context: { filePath }
|
|
11601
11695
|
});
|
|
11602
11696
|
}
|
|
11603
|
-
const
|
|
11697
|
+
const editorArgs = [...parts.slice(1), filePath];
|
|
11698
|
+
const child = shell ? (() => {
|
|
11699
|
+
const inv = buildWin32CmdShimInvocation(parts[0], editorArgs);
|
|
11700
|
+
return spawn5(inv.command, inv.args, {
|
|
11701
|
+
stdio: "ignore",
|
|
11702
|
+
detached: true,
|
|
11703
|
+
windowsVerbatimArguments: inv.windowsVerbatimArguments,
|
|
11704
|
+
// Suppresses the console flash the cmd.exe wrapper would otherwise
|
|
11705
|
+
// show before the editor appears. Repo convention — see
|
|
11706
|
+
// `core/tests/architecture/spawn-convention.test.ts`.
|
|
11707
|
+
windowsHide: true
|
|
11708
|
+
});
|
|
11709
|
+
})() : spawn5(parts[0], editorArgs, {
|
|
11604
11710
|
stdio: "ignore",
|
|
11605
11711
|
detached: true,
|
|
11606
|
-
shell,
|
|
11607
|
-
// `shell` routes through cmd.exe on win32, which flashes a console window
|
|
11608
|
-
// before the editor appears. A GUI editor is unaffected by the flag; the
|
|
11609
|
-
// shell wrapper is what it suppresses. Repo convention — see
|
|
11610
|
-
// `core/tests/architecture/spawn-convention.test.ts`.
|
|
11611
11712
|
windowsHide: true
|
|
11612
11713
|
});
|
|
11613
11714
|
child.unref();
|
|
@@ -13789,7 +13890,7 @@ function walk2(node, vault, transform) {
|
|
|
13789
13890
|
}
|
|
13790
13891
|
return out;
|
|
13791
13892
|
}
|
|
13792
|
-
var SECRET_KEY_PATTERN = /(?:
|
|
13893
|
+
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;
|
|
13793
13894
|
var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
|
|
13794
13895
|
function isSecretField(name) {
|
|
13795
13896
|
const lc = name.toLowerCase();
|
package/dist/prompts/index.js
CHANGED
|
@@ -326,6 +326,361 @@ var PromptManifestStore = class {
|
|
|
326
326
|
// src/prompts/prompt-journal.ts
|
|
327
327
|
import * as fs2 from "node:fs/promises";
|
|
328
328
|
import * as path2 from "node:path";
|
|
329
|
+
|
|
330
|
+
// src/security/secret-scrubber.ts
|
|
331
|
+
var JSON_CREDENTIAL_KEY_ANCHORS = [
|
|
332
|
+
'Key"',
|
|
333
|
+
'key"',
|
|
334
|
+
'KEY"',
|
|
335
|
+
'token"',
|
|
336
|
+
'Token"',
|
|
337
|
+
'TOKEN"',
|
|
338
|
+
'secret"',
|
|
339
|
+
'Secret"',
|
|
340
|
+
'SECRET"',
|
|
341
|
+
'password"',
|
|
342
|
+
'Password"',
|
|
343
|
+
'PASSWORD"',
|
|
344
|
+
'authorization"',
|
|
345
|
+
'Authorization"',
|
|
346
|
+
'bearer"',
|
|
347
|
+
'Bearer"'
|
|
348
|
+
];
|
|
349
|
+
var PATTERNS = [
|
|
350
|
+
// Anchored at the start where possible so partial matches inside larger
|
|
351
|
+
// strings don't trigger false positives.
|
|
352
|
+
{
|
|
353
|
+
type: "anthropic_key",
|
|
354
|
+
regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g,
|
|
355
|
+
anchor: "sk-ant-"
|
|
356
|
+
},
|
|
357
|
+
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
|
|
358
|
+
{
|
|
359
|
+
// `xai` is a first-class provider in this codebase, but its key shape was
|
|
360
|
+
// absent here — so the one credential format WrongStack itself hands users
|
|
361
|
+
// was the one the scrubber could not recognize (audit 2026-08-20).
|
|
362
|
+
type: "xai_key",
|
|
363
|
+
regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
|
|
364
|
+
anchor: "xai-"
|
|
365
|
+
},
|
|
366
|
+
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
|
|
367
|
+
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
|
|
368
|
+
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
|
|
369
|
+
{ type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g, anchor: "AIza" },
|
|
370
|
+
{ type: "slack_token", regex: /(?<![A-Za-z0-9-])xox[abpos]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g, anchor: "xox" },
|
|
371
|
+
{
|
|
372
|
+
type: "stripe_key",
|
|
373
|
+
regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g,
|
|
374
|
+
anchor: "sk_"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
type: "twilio_sid",
|
|
378
|
+
regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g,
|
|
379
|
+
anchor: "AC"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
type: "telegram_bot_token",
|
|
383
|
+
// Telegram tokens are of the form bot<digits>:<alphanum> in URL paths
|
|
384
|
+
regex: /\/bot\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
|
|
385
|
+
anchor: "/bot"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
type: "jwt",
|
|
389
|
+
// Anchored: look for literal "eyJ" which is unambiguous for JWT header
|
|
390
|
+
regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g,
|
|
391
|
+
anchor: "eyJ"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
type: "private_key",
|
|
395
|
+
// Anchored: start must be BEGIN, end must be END with no extra dashes after END
|
|
396
|
+
regex: /(?:^|\n)-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----[\s\S]*?-----END[^-]*-----(?:\n|$)/g,
|
|
397
|
+
anchor: "-----BEGIN"
|
|
398
|
+
},
|
|
399
|
+
{ type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s"'`]+/g, anchor: "mongodb" },
|
|
400
|
+
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g, anchor: "postgres" },
|
|
401
|
+
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g, anchor: "mysql://" },
|
|
402
|
+
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g, anchor: "redis://" },
|
|
403
|
+
// AI/ML provider keys — modern LLM services with well-known prefixes
|
|
404
|
+
{
|
|
405
|
+
type: "huggingface_token",
|
|
406
|
+
// HuggingFace tokens: hf_ followed by 34 alphanumeric chars
|
|
407
|
+
regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g,
|
|
408
|
+
anchor: "hf_"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
type: "replicate_token",
|
|
412
|
+
// Replicate tokens: r8_ followed by 40+ alphanumeric chars
|
|
413
|
+
regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
|
|
414
|
+
anchor: "r8_"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
type: "perplexity_key",
|
|
418
|
+
// Perplexity API keys: pplx- followed by 40+ alphanumeric chars
|
|
419
|
+
regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
|
|
420
|
+
anchor: "pplx-"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
type: "groq_key",
|
|
424
|
+
// Groq API keys: gsk_ followed by 40+ alphanumeric chars
|
|
425
|
+
regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
|
|
426
|
+
anchor: "gsk_"
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
type: "bearer_token",
|
|
430
|
+
// Anchored with alternation instead of negative lookahead — avoids V8
|
|
431
|
+
// backtracking risk on adversarial input. Bounded at 512 chars.
|
|
432
|
+
// Min 12 chars: some OAuth providers issue shorter-lived tokens (< 20
|
|
433
|
+
// chars). A 12-char base64 string has ~71 bits of entropy — above the
|
|
434
|
+
// threshold where random strings are unlikely to produce false matches.
|
|
435
|
+
// The trailing boundary is a NON-consuming lookahead: two adjacent bearer
|
|
436
|
+
// tokens sharing a single delimiter (`Bearer a… Bearer b…`) must both be
|
|
437
|
+
// redacted. A consuming trailing delimiter would eat the separator the
|
|
438
|
+
// next match needs for its leading anchor, leaking the second token.
|
|
439
|
+
regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?=$|[^A-Za-z0-9_.~+/-])/g,
|
|
440
|
+
anchor: "Bearer"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
type: "high_entropy_env",
|
|
444
|
+
// Anchored with alternation instead of lookbehind to avoid backtracking.
|
|
445
|
+
// Value bounded at 512 chars.
|
|
446
|
+
// The trailing boundary is a NON-consuming lookahead so two secrets
|
|
447
|
+
// separated by a single delimiter (one space OR one newline, e.g.
|
|
448
|
+
// `printenv` / `.env` dumps: `API_KEY=… \n SESSION_TOKEN=…`) are BOTH
|
|
449
|
+
// redacted. A consuming trailing `\s` would swallow the separator the
|
|
450
|
+
// next match needs for its leading anchor, so every other secret would
|
|
451
|
+
// leak in plaintext.
|
|
452
|
+
// The leading delimiter is CAPTURED (group 1) and re-emitted by the
|
|
453
|
+
// replacement so the separator between adjacent secrets is preserved
|
|
454
|
+
// rather than collapsed. Capture groups are therefore: 1=leading
|
|
455
|
+
// delimiter, 2=key name, 3=value.
|
|
456
|
+
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
457
|
+
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
type: "json_credential_key",
|
|
461
|
+
// The JSON counterpart to `high_entropy_env`, and the pattern that the
|
|
462
|
+
// now-deleted `JSON_KEY_ANCHORS` list was written for. Without it those
|
|
463
|
+
// anchors only widened the cheap pre-scan — `hasCredentialAnchors` said
|
|
464
|
+
// "this text may hold a secret", every pattern then declined to match, and
|
|
465
|
+
// the value went out verbatim. `high_entropy_env` cannot cover these: it
|
|
466
|
+
// requires an UPPERCASE unquoted key (`API_KEY=…`), so `{"apiKey":"…"}`
|
|
467
|
+
// never matched.
|
|
468
|
+
//
|
|
469
|
+
// Tool results are routinely serialised as JSON, and a credential with no
|
|
470
|
+
// recognisable prefix (Azure, self-hosted gateways, Anthropic/Codex OAuth)
|
|
471
|
+
// has no other pattern that can catch it — this is the only thing standing
|
|
472
|
+
// between such a value and the session JSONL, chronicle, HQ broadcast and
|
|
473
|
+
// the model's own context.
|
|
474
|
+
//
|
|
475
|
+
// The key may carry a prefix (`"anthropicApiKey"`), but the credential word
|
|
476
|
+
// must END the key: `"tokenCount"` and `"maxTokens"` do not match, because
|
|
477
|
+
// the closing quote has to follow the word immediately.
|
|
478
|
+
// Value floor of 8 chars keeps enum-ish values (`"authorization":"none"`)
|
|
479
|
+
// out. Capture groups: 1=key + punctuation, 2=value, 3=closing quote.
|
|
480
|
+
regex: /("[A-Za-z0-9_]*(?:apiKey|api_key|token|secret|password|authorization|bearer|private_key|access_token|refresh_token|client_secret)"\s*:\s*")([^"\\]{8,512})(")/gi,
|
|
481
|
+
anchor: JSON_CREDENTIAL_KEY_ANCHORS
|
|
482
|
+
},
|
|
483
|
+
// ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
|
|
484
|
+
// The plugin runtime carried 37 patterns while this scrubber — the one that
|
|
485
|
+
// guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
|
|
486
|
+
// audit — carried 22. The plugin side already had a parity test; it just did
|
|
487
|
+
// not cover core. Most consequential: WrongStack mints `gho_` tokens itself
|
|
488
|
+
// in the Copilot OAuth flow, and `gh[ousr]_` was absent here.
|
|
489
|
+
{
|
|
490
|
+
type: "github_oauth_token",
|
|
491
|
+
regex: /(?<![A-Za-z0-9])gh[ousr]_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g,
|
|
492
|
+
anchor: ["gho_", "ghu_", "ghs_", "ghr_"]
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
type: "gitlab_pat",
|
|
496
|
+
regex: /(?<![A-Za-z0-9])glpat-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
|
|
497
|
+
anchor: "glpat-"
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
type: "gitlab_runner_token",
|
|
501
|
+
regex: /(?<![A-Za-z0-9])glrt-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
|
|
502
|
+
anchor: "glrt-"
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
type: "npm_token",
|
|
506
|
+
regex: /(?<![A-Za-z0-9])npm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/g,
|
|
507
|
+
anchor: "npm_"
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
type: "slack_app_token",
|
|
511
|
+
regex: /(?<![A-Za-z0-9-])xapp-\d-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g,
|
|
512
|
+
anchor: "xapp-"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
type: "slack_webhook",
|
|
516
|
+
regex: /https:\/\/hooks\.slack\.com\/services\/T[A-Za-z0-9_-]+\/B[A-Za-z0-9_-]+\/[A-Za-z0-9]{16,}/g,
|
|
517
|
+
anchor: "hooks.slack.com"
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
type: "sendgrid_key",
|
|
521
|
+
regex: /(?<![A-Za-z0-9])SG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
|
|
522
|
+
anchor: "SG."
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
type: "digitalocean_token",
|
|
526
|
+
regex: /(?<![A-Za-z0-9])dop_v1_[a-f0-9]{64}(?![A-Za-z0-9])/g,
|
|
527
|
+
anchor: "dop_v1_"
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
type: "doppler_token",
|
|
531
|
+
regex: /(?<![A-Za-z0-9])dp\.(?:pt|st|sa|scim|audit)\.[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
|
|
532
|
+
anchor: "dp."
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
type: "shopify_token",
|
|
536
|
+
regex: /(?<![A-Za-z0-9])shp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}(?![A-Za-z0-9])/g,
|
|
537
|
+
anchor: "shp"
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
type: "docker_pat",
|
|
541
|
+
regex: /(?<![A-Za-z0-9])dckr_pat_[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
|
|
542
|
+
anchor: "dckr_pat_"
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
type: "linear_key",
|
|
546
|
+
regex: /(?<![A-Za-z0-9])lin_api_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g,
|
|
547
|
+
anchor: "lin_api_"
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
type: "atlassian_token",
|
|
551
|
+
regex: /(?<![A-Za-z0-9])ATATT3[A-Za-z0-9_\-=]{40,}(?![A-Za-z0-9_\-=])/g,
|
|
552
|
+
anchor: "ATATT3"
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
type: "square_token",
|
|
556
|
+
regex: /(?<![A-Za-z0-9])(?:sq0(?:atp|csp)-|EAAA)[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
|
|
557
|
+
anchor: ["sq0atp-", "sq0csp-", "EAAA"]
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
type: "google_oauth_client_secret",
|
|
561
|
+
regex: /(?<![A-Za-z0-9_-])GOCSPX-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
|
|
562
|
+
anchor: "GOCSPX-"
|
|
563
|
+
}
|
|
564
|
+
];
|
|
565
|
+
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
566
|
+
(p) => p.type !== "high_entropy_env" && p.type !== "json_credential_key"
|
|
567
|
+
);
|
|
568
|
+
var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
569
|
+
var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
|
|
570
|
+
var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key").regex;
|
|
571
|
+
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
572
|
+
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
573
|
+
var SCRUB_OVERLAP_BYTES = 1024;
|
|
574
|
+
var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
|
|
575
|
+
var PEM_END_MARKER = "-----END";
|
|
576
|
+
var MAX_PEM_BLOCK_BYTES = 64 * 1024;
|
|
577
|
+
var PEM_END_LINE_TOLERANCE = 64;
|
|
578
|
+
function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
|
|
579
|
+
const head = text.slice(chunkStart, proposedEnd);
|
|
580
|
+
const lastBegin = head.lastIndexOf("-----BEGIN ");
|
|
581
|
+
if (lastBegin === -1) return proposedEnd;
|
|
582
|
+
const fromBegin = text.slice(chunkStart + lastBegin);
|
|
583
|
+
const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
|
|
584
|
+
if (!marker || marker.index !== 0) return proposedEnd;
|
|
585
|
+
const bodyStart = marker[0].length;
|
|
586
|
+
const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
|
|
587
|
+
const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
|
|
588
|
+
if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
|
|
589
|
+
return proposedEnd;
|
|
590
|
+
}
|
|
591
|
+
const lineEnd = fromBegin.indexOf("\n", closeIdx);
|
|
592
|
+
const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
|
|
593
|
+
return Math.max(proposedEnd, end);
|
|
594
|
+
}
|
|
595
|
+
var PATTERN_ANCHORS = [
|
|
596
|
+
...new Set(
|
|
597
|
+
PATTERNS.flatMap(
|
|
598
|
+
(pattern) => typeof pattern.anchor === "string" ? [pattern.anchor] : [...pattern.anchor]
|
|
599
|
+
)
|
|
600
|
+
)
|
|
601
|
+
];
|
|
602
|
+
var ALL_ANCHORS = PATTERN_ANCHORS;
|
|
603
|
+
function hasCredentialAnchors(text) {
|
|
604
|
+
for (const anchor of ALL_ANCHORS) {
|
|
605
|
+
if (text.includes(anchor)) return true;
|
|
606
|
+
}
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
var DefaultSecretScrubber = class {
|
|
610
|
+
scrub(text) {
|
|
611
|
+
if (!text) return text;
|
|
612
|
+
if (!hasCredentialAnchors(text)) return text;
|
|
613
|
+
if (text.length <= SCRUB_CHUNK_BYTES) {
|
|
614
|
+
return this.scrubOne(text);
|
|
615
|
+
}
|
|
616
|
+
const out = [];
|
|
617
|
+
let i = 0;
|
|
618
|
+
while (i < text.length) {
|
|
619
|
+
let end = Math.min(i + SCRUB_CHUNK_BYTES, text.length);
|
|
620
|
+
if (end < text.length) {
|
|
621
|
+
const limit = Math.min(end + SCRUB_OVERLAP_BYTES, text.length);
|
|
622
|
+
let safe = -1;
|
|
623
|
+
for (let j = end; j < limit; j++) {
|
|
624
|
+
const ch = text.charCodeAt(j);
|
|
625
|
+
if (ch === 32 || ch === 9 || ch === 10 || ch === 13) {
|
|
626
|
+
safe = j;
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
end = safe === -1 ? end : safe + 1;
|
|
631
|
+
end = extendChunkBoundaryPastPem(text, i, end);
|
|
632
|
+
}
|
|
633
|
+
out.push(this.scrubOne(text.slice(i, end)));
|
|
634
|
+
i = end;
|
|
635
|
+
}
|
|
636
|
+
return out.join("");
|
|
637
|
+
}
|
|
638
|
+
scrubOne(text) {
|
|
639
|
+
if (!hasCredentialAnchors(text)) return text;
|
|
640
|
+
let out = text.replace(
|
|
641
|
+
COMBINED_REGEX,
|
|
642
|
+
(match, ...groups) => {
|
|
643
|
+
const idx = groups.findIndex((g) => g !== void 0);
|
|
644
|
+
if (idx < 0) return match;
|
|
645
|
+
const replacement = COMBINED_REPLACEMENTS[idx];
|
|
646
|
+
return replacement !== void 0 ? replacement : match;
|
|
647
|
+
}
|
|
648
|
+
);
|
|
649
|
+
out = out.replace(HIGH_ENTROPY_REGEX, (_match, lead, key, _value) => {
|
|
650
|
+
return `${lead}${key}=[REDACTED:high_entropy_env]`;
|
|
651
|
+
});
|
|
652
|
+
out = out.replace(JSON_CREDENTIAL_REGEX, (_match, keyPrefix, _value, closingQuote) => {
|
|
653
|
+
return `${keyPrefix}[REDACTED:json_credential_key]${closingQuote}`;
|
|
654
|
+
});
|
|
655
|
+
return out;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Recursively scrub every string value in an object/array graph. Secrets can
|
|
659
|
+
* appear under any key — a URL query param, an `authorization` header, an
|
|
660
|
+
* arbitrarily-named nested field — so we don't gate recursion on key names.
|
|
661
|
+
* The per-string `scrub()` fast-path (anchor pre-scan) keeps this cheap: any
|
|
662
|
+
* value without a credential anchor returns immediately without regex work.
|
|
663
|
+
*/
|
|
664
|
+
scrubObject(obj) {
|
|
665
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
666
|
+
const visit = (v) => {
|
|
667
|
+
if (typeof v === "string") return this.scrub(v);
|
|
668
|
+
if (v === null || typeof v !== "object") return v;
|
|
669
|
+
if (seen.has(v)) return v;
|
|
670
|
+
seen.add(v);
|
|
671
|
+
if (Array.isArray(v)) return v.map(visit);
|
|
672
|
+
const out = {};
|
|
673
|
+
for (const [k, val] of Object.entries(v)) {
|
|
674
|
+
out[k] = visit(val);
|
|
675
|
+
}
|
|
676
|
+
return out;
|
|
677
|
+
};
|
|
678
|
+
return visit(obj);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
// src/prompts/prompt-journal.ts
|
|
683
|
+
var defaultScrubber = new DefaultSecretScrubber();
|
|
329
684
|
var PROMPT_JOURNAL_RAW_MARKER = "promptJournal.raw";
|
|
330
685
|
async function ensureGitignore(projectRoot) {
|
|
331
686
|
const gitignorePath = path2.join(projectRoot, ".gitignore");
|
|
@@ -354,7 +709,9 @@ async function recordPromptJournalEntry(opts) {
|
|
|
354
709
|
const monthStr = dateStr.slice(0, 7);
|
|
355
710
|
const sessionId = opts.sessionId && opts.sessionId.trim() ? opts.sessionId.trim() : "general";
|
|
356
711
|
const id = `pmt_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
357
|
-
const content = opts.content ?? "";
|
|
712
|
+
const content = defaultScrubber.scrub(opts.content ?? "");
|
|
713
|
+
const rawContent = typeof opts.rawContent === "string" && opts.rawContent.length > 0 ? defaultScrubber.scrub(opts.rawContent) : opts.rawContent;
|
|
714
|
+
const decisionReason = typeof opts.decisionReason === "string" && opts.decisionReason.length > 0 ? defaultScrubber.scrub(opts.decisionReason) : opts.decisionReason;
|
|
358
715
|
const lines = content.split("\n");
|
|
359
716
|
const characterCount = content.length;
|
|
360
717
|
const lineCount = lines.length;
|
|
@@ -367,7 +724,7 @@ async function recordPromptJournalEntry(opts) {
|
|
|
367
724
|
role: opts.role ?? (opts.category === "system_prompt" ? "system" : "user"),
|
|
368
725
|
category: opts.category,
|
|
369
726
|
content,
|
|
370
|
-
rawContent
|
|
727
|
+
rawContent,
|
|
371
728
|
metadata: {
|
|
372
729
|
model: opts.model,
|
|
373
730
|
provider: opts.provider,
|
|
@@ -378,7 +735,7 @@ async function recordPromptJournalEntry(opts) {
|
|
|
378
735
|
activeTools: opts.activeTools,
|
|
379
736
|
contextFiles: opts.contextFiles,
|
|
380
737
|
durationMs: opts.durationMs,
|
|
381
|
-
decisionReason
|
|
738
|
+
decisionReason,
|
|
382
739
|
tags: opts.tags
|
|
383
740
|
}
|
|
384
741
|
};
|
|
@@ -26,12 +26,12 @@ export declare class AutoApprovePermissionPolicy implements PermissionPolicy {
|
|
|
26
26
|
private readonly allowedCapabilities;
|
|
27
27
|
constructor(allowedCapabilities?: readonly string[]);
|
|
28
28
|
private static isMcpTool;
|
|
29
|
-
evaluate(tool: Tool): Promise<PermissionDecision>;
|
|
29
|
+
evaluate(tool: Tool, input?: unknown): Promise<PermissionDecision>;
|
|
30
30
|
trust(): Promise<void>;
|
|
31
31
|
deny(): Promise<void>;
|
|
32
32
|
denyOnce(): void;
|
|
33
33
|
allowOnce(): void;
|
|
34
|
-
explain(tool: Tool): Promise<PermissionTrace>;
|
|
34
|
+
explain(tool: Tool, input?: unknown): Promise<PermissionTrace>;
|
|
35
35
|
reload(): Promise<void>;
|
|
36
36
|
}
|
|
37
37
|
//# sourceMappingURL=auto-approve-policy.d.ts.map
|