@wrongstack/plugins 0.308.0 → 0.308.2
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/audit/index.d.ts +6 -6
- package/dist/audit.js +7 -7
- package/dist/factories/index.d.ts +1 -1
- package/dist/factories.js +1 -1
- package/dist/import-organizer.js +4 -0
- package/dist/index.js +285 -50
- package/dist/llm-cache.js +6 -0
- package/dist/manifest.js +1 -1
- package/dist/path-guard.js +68 -15
- package/dist/plugin-audit-catalog.js +7 -7
- package/dist/prompt-firewall/index.d.ts +43 -41
- package/dist/prompt-firewall.js +217 -34
- package/dist/spec-linker.js +1 -1
- package/package.json +7 -7
package/dist/audit/index.d.ts
CHANGED
|
@@ -208,6 +208,12 @@ export declare const OFFICIAL_PLUGIN_AUDIT_ENTRIES: readonly [{
|
|
|
208
208
|
readonly summary: 'Scans tool output (fetched pages, files) for prompt-injection patterns and warns the model that content is data, not instructions';
|
|
209
209
|
readonly defaultState: 'active';
|
|
210
210
|
readonly canDisable: true;
|
|
211
|
+
}, {
|
|
212
|
+
readonly name: 'prompt-firewall';
|
|
213
|
+
readonly risk: 'high';
|
|
214
|
+
readonly summary: 'Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.';
|
|
215
|
+
readonly defaultState: 'inactive';
|
|
216
|
+
readonly canDisable: true;
|
|
211
217
|
}, {
|
|
212
218
|
readonly name: 'llm-cache';
|
|
213
219
|
readonly risk: 'medium';
|
|
@@ -226,12 +232,6 @@ export declare const OFFICIAL_PLUGIN_AUDIT_ENTRIES: readonly [{
|
|
|
226
232
|
readonly summary: 'Collects session work (commits, edited files, diff) and drafts a pull-request description';
|
|
227
233
|
readonly defaultState: 'inactive';
|
|
228
234
|
readonly canDisable: true;
|
|
229
|
-
}, {
|
|
230
|
-
readonly name: 'prompt-firewall';
|
|
231
|
-
readonly risk: 'high';
|
|
232
|
-
readonly summary: 'Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.';
|
|
233
|
-
readonly defaultState: 'inactive';
|
|
234
|
-
readonly canDisable: true;
|
|
235
235
|
}, {
|
|
236
236
|
readonly name: 'auto-escalate';
|
|
237
237
|
readonly risk: 'medium';
|
package/dist/audit.js
CHANGED
|
@@ -245,6 +245,13 @@ var OFFICIAL_PLUGIN_AUDIT_ENTRIES = [
|
|
|
245
245
|
defaultState: "active",
|
|
246
246
|
canDisable: true
|
|
247
247
|
},
|
|
248
|
+
{
|
|
249
|
+
name: "prompt-firewall",
|
|
250
|
+
risk: "high",
|
|
251
|
+
summary: "Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.",
|
|
252
|
+
defaultState: "inactive",
|
|
253
|
+
canDisable: true
|
|
254
|
+
},
|
|
248
255
|
{
|
|
249
256
|
name: "llm-cache",
|
|
250
257
|
risk: "medium",
|
|
@@ -266,13 +273,6 @@ var OFFICIAL_PLUGIN_AUDIT_ENTRIES = [
|
|
|
266
273
|
defaultState: "inactive",
|
|
267
274
|
canDisable: true
|
|
268
275
|
},
|
|
269
|
-
{
|
|
270
|
-
name: "prompt-firewall",
|
|
271
|
-
risk: "high",
|
|
272
|
-
summary: "Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.",
|
|
273
|
-
defaultState: "inactive",
|
|
274
|
-
canDisable: true
|
|
275
|
-
},
|
|
276
276
|
{
|
|
277
277
|
name: "auto-escalate",
|
|
278
278
|
risk: "medium",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Plugin } from '@wrongstack/core/types';
|
|
2
|
-
export declare const OFFICIAL_PLUGIN_SPECIFIERS: readonly ['@wrongstack/plugins/agent-handoff', '@wrongstack/plugins/cost-tracker', '@wrongstack/plugins/file-watcher', '@wrongstack/plugins/git-autocommit', '@wrongstack/plugins/auto-doc', '@wrongstack/plugins/shell-check', '@wrongstack/plugins/cron', '@wrongstack/plugins/template-engine', '@wrongstack/plugins/semver-bump', '@wrongstack/plugins/secret-scanner', '@wrongstack/plugins/todo-tracker', '@wrongstack/plugins/token-budget', '@wrongstack/plugins/lint-gate', '@wrongstack/plugins/branch-guard', '@wrongstack/plugins/diff-summary', '@wrongstack/plugins/commit-validator', '@wrongstack/plugins/format-on-save', '@wrongstack/plugins/test-runner-gate', '@wrongstack/plugins/import-organizer', '@wrongstack/plugins/knowledge-graph', '@wrongstack/plugins/todo-listener', '@wrongstack/plugins/session-recap', '@wrongstack/plugins/spec-linker', '@wrongstack/plugins/loop-breaker', '@wrongstack/plugins/gitignore-guard', '@wrongstack/plugins/path-guard', '@wrongstack/plugins/process-guard', '@wrongstack/plugins/context-pins', '@wrongstack/plugins/checkpoint', '@wrongstack/plugins/error-lens', '@wrongstack/plugins/dep-guard', '@wrongstack/plugins/config-validator', '@wrongstack/plugins/notify-hub', '@wrongstack/plugins/changelog-writer', '@wrongstack/plugins/injection-shield', '@wrongstack/plugins/
|
|
2
|
+
export declare const OFFICIAL_PLUGIN_SPECIFIERS: readonly ['@wrongstack/plugins/agent-handoff', '@wrongstack/plugins/cost-tracker', '@wrongstack/plugins/file-watcher', '@wrongstack/plugins/git-autocommit', '@wrongstack/plugins/auto-doc', '@wrongstack/plugins/shell-check', '@wrongstack/plugins/cron', '@wrongstack/plugins/template-engine', '@wrongstack/plugins/semver-bump', '@wrongstack/plugins/secret-scanner', '@wrongstack/plugins/todo-tracker', '@wrongstack/plugins/token-budget', '@wrongstack/plugins/lint-gate', '@wrongstack/plugins/branch-guard', '@wrongstack/plugins/diff-summary', '@wrongstack/plugins/commit-validator', '@wrongstack/plugins/format-on-save', '@wrongstack/plugins/test-runner-gate', '@wrongstack/plugins/import-organizer', '@wrongstack/plugins/knowledge-graph', '@wrongstack/plugins/todo-listener', '@wrongstack/plugins/session-recap', '@wrongstack/plugins/spec-linker', '@wrongstack/plugins/loop-breaker', '@wrongstack/plugins/gitignore-guard', '@wrongstack/plugins/path-guard', '@wrongstack/plugins/process-guard', '@wrongstack/plugins/context-pins', '@wrongstack/plugins/checkpoint', '@wrongstack/plugins/error-lens', '@wrongstack/plugins/dep-guard', '@wrongstack/plugins/config-validator', '@wrongstack/plugins/notify-hub', '@wrongstack/plugins/changelog-writer', '@wrongstack/plugins/injection-shield', '@wrongstack/plugins/prompt-firewall', '@wrongstack/plugins/llm-cache', '@wrongstack/plugins/model-router', '@wrongstack/plugins/pr-drafter', '@wrongstack/plugins/auto-escalate', '@wrongstack/plugins/test-coverage-gate', '@wrongstack/plugins/type-gate', '@wrongstack/plugins/token-throttle', '@wrongstack/plugins/plugin-stack-observer', '@wrongstack/plugins/dependency-vulnerability-gate', '@wrongstack/plugins/migration-planner', '@wrongstack/plugins/semantic-search-indexer', '@wrongstack/plugins/auto-i18n-extractor', '@wrongstack/plugins/doc-sync-guard', '@wrongstack/plugins/api-compatibility-gate', '@wrongstack/plugins/performance-regression-gate', '@wrongstack/plugins/test-flake-detector', '@wrongstack/plugins/schema-evolution-guard', '@wrongstack/plugins/license-audit-gate', '@wrongstack/plugins/accessibility-auditor', '@wrongstack/plugins/security-hotspot-scanner', '@wrongstack/plugins/dead-code-detector', '@wrongstack/plugins/duplicate-code-detector', '@wrongstack/plugins/code-metrics', '@wrongstack/plugins/refactor-suggester', '@wrongstack/plugins/test-generator', '@wrongstack/plugins/release-notes-generator', '@wrongstack/plugins/smart-rename', '@wrongstack/plugins/feature-flag-tracker', '@wrongstack/plugins/interface-contract-guard'];
|
|
3
3
|
export declare const OFFICIAL_PLUGIN_FACTORIES: readonly (() => Promise<Plugin>)[];
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/factories.js
CHANGED
|
@@ -35,10 +35,10 @@ var OFFICIAL_PLUGIN_SPECIFIERS = [
|
|
|
35
35
|
"@wrongstack/plugins/notify-hub",
|
|
36
36
|
"@wrongstack/plugins/changelog-writer",
|
|
37
37
|
"@wrongstack/plugins/injection-shield",
|
|
38
|
+
"@wrongstack/plugins/prompt-firewall",
|
|
38
39
|
"@wrongstack/plugins/llm-cache",
|
|
39
40
|
"@wrongstack/plugins/model-router",
|
|
40
41
|
"@wrongstack/plugins/pr-drafter",
|
|
41
|
-
"@wrongstack/plugins/prompt-firewall",
|
|
42
42
|
"@wrongstack/plugins/auto-escalate",
|
|
43
43
|
"@wrongstack/plugins/test-coverage-gate",
|
|
44
44
|
"@wrongstack/plugins/type-gate",
|
package/dist/import-organizer.js
CHANGED
|
@@ -37,6 +37,9 @@ function cachePut(key, value) {
|
|
|
37
37
|
binCache.set(key, { value, cachedAt: Date.now() });
|
|
38
38
|
return value;
|
|
39
39
|
}
|
|
40
|
+
function clearLocalBinCache() {
|
|
41
|
+
binCache.clear();
|
|
42
|
+
}
|
|
40
43
|
function resolveNodeBin(packageName, binName, cwd, extraArgs = []) {
|
|
41
44
|
const key = `${packageName}|${binName}|${cwd}`;
|
|
42
45
|
const cached = binCache.get(key);
|
|
@@ -450,6 +453,7 @@ ${result.stderr.trim()}`
|
|
|
450
453
|
});
|
|
451
454
|
},
|
|
452
455
|
teardown(api) {
|
|
456
|
+
clearLocalBinCache();
|
|
453
457
|
if (state.hookUnregister) {
|
|
454
458
|
try {
|
|
455
459
|
state.hookUnregister();
|
package/dist/index.js
CHANGED
|
@@ -10298,6 +10298,7 @@ ${result.stderr.trim()}`
|
|
|
10298
10298
|
});
|
|
10299
10299
|
},
|
|
10300
10300
|
teardown(api) {
|
|
10301
|
+
clearLocalBinCache();
|
|
10301
10302
|
if (state25.hookUnregister) {
|
|
10302
10303
|
try {
|
|
10303
10304
|
state25.hookUnregister();
|
|
@@ -11951,6 +11952,12 @@ var plugin34 = {
|
|
|
11951
11952
|
description: "Caches identical provider requests and short-circuits the provider call on a hit (wrapProviderRunner). Opt-in; deterministic-only by default.",
|
|
11952
11953
|
apiVersion: "^0.1.10",
|
|
11953
11954
|
capabilities: { tools: true },
|
|
11955
|
+
// Wrap-stack contract (issue #362): ExtensionRegistry composes wrappers
|
|
11956
|
+
// first-registered = outermost, and both dependsOn and optionalDeps load
|
|
11957
|
+
// dependencies first. Declaring prompt-firewall here guarantees the
|
|
11958
|
+
// firewall wraps OUTSIDE this cache even if the manifest order changes —
|
|
11959
|
+
// otherwise a cache hit short-circuits `inner` and bypasses redaction.
|
|
11960
|
+
optionalDeps: ["prompt-firewall"],
|
|
11954
11961
|
defaultConfig: { ...DEFAULTS28 },
|
|
11955
11962
|
configSchema: {
|
|
11956
11963
|
type: "object",
|
|
@@ -14263,14 +14270,19 @@ function stripLauncherAtBoundary(command, launcher, valueTaking) {
|
|
|
14263
14270
|
}
|
|
14264
14271
|
return `${command.slice(0, match.index)}${boundary}${after.slice(consumed).replace(/^\s+/, "")}`;
|
|
14265
14272
|
}
|
|
14273
|
+
var XARGS_OPTIONS = String.raw`(?:\s+(?:(?:-[InLsPjeE]|--(?:arg-file|replace|max-args|max-lines|max-chars|max-procs))\s+[^\s]+|-[^\s]+))*`;
|
|
14274
|
+
var COMMAND_BOUNDARY = String.raw`(?:^|[;&|\r\n]\s*|\{\s*|(?<![$(])\(\s*|\bxargs${XARGS_OPTIONS}\s+)`;
|
|
14266
14275
|
function commandRecursivelyDeletes(command) {
|
|
14267
14276
|
const stripped = stripTransparentLaunchers(maskNonExecutingHeredocBodies(command));
|
|
14268
|
-
if (/(?:^|[;&|\r\n]\s*|\$\(\s*|\(\s*|`\s*)find\b[^;&|)`]*(?:-delete\b|-exec(?:dir)?\s+(?:[^\s;&|]+[\\/])?(?:rm|rmdir)\b)/i.test(
|
|
14277
|
+
if (/(?:^|[;&|\r\n]\s*|\{\s*|\$\(\s*|\(\s*|`\s*)find\b[^;&|)`]*(?:-delete\b|-exec(?:dir)?\s+(?:[^\s;&|]+[\\/])?(?:rm|rmdir)\b)/i.test(
|
|
14269
14278
|
stripped
|
|
14270
14279
|
)) {
|
|
14271
14280
|
return true;
|
|
14272
14281
|
}
|
|
14273
|
-
const destructive =
|
|
14282
|
+
const destructive = new RegExp(
|
|
14283
|
+
String.raw`${COMMAND_BOUNDARY}(rm|rmdir|del|rd|Remove-Item)\s+((?:"[^"]*"|'[^']*'|\\.|\{[^}]*\}|\([^()]*\)|[^;&|\r\n}])+)`,
|
|
14284
|
+
"gi"
|
|
14285
|
+
);
|
|
14274
14286
|
let match = destructive.exec(stripped);
|
|
14275
14287
|
while (match !== null) {
|
|
14276
14288
|
const tool = match[1]?.toLowerCase();
|
|
@@ -14487,6 +14499,27 @@ ${remainingCommand}`;
|
|
|
14487
14499
|
if (heredoc !== null) maskBody(heredoc.bodyStart, lines.length, heredoc.quoted);
|
|
14488
14500
|
return lines.join("");
|
|
14489
14501
|
}
|
|
14502
|
+
function stripSubshellClosers(raw) {
|
|
14503
|
+
let result = raw.trimEnd();
|
|
14504
|
+
while (result.endsWith(")")) {
|
|
14505
|
+
if (quoteIsEscaped(result, result.length - 1)) break;
|
|
14506
|
+
let quote = null;
|
|
14507
|
+
let depth = 0;
|
|
14508
|
+
for (let index = 0; index < result.length - 1; index += 1) {
|
|
14509
|
+
const char = result[index];
|
|
14510
|
+
if (isQuoteBoundary(result, index, quote)) {
|
|
14511
|
+
quote = quote === char ? null : char === "'" ? "'" : '"';
|
|
14512
|
+
continue;
|
|
14513
|
+
}
|
|
14514
|
+
if (quote !== null) continue;
|
|
14515
|
+
if (char === "(" && !quoteIsEscaped(result, index)) depth += 1;
|
|
14516
|
+
else if (char === ")" && !quoteIsEscaped(result, index)) depth -= 1;
|
|
14517
|
+
}
|
|
14518
|
+
if (depth > 0) break;
|
|
14519
|
+
result = result.slice(0, -1).trimEnd();
|
|
14520
|
+
}
|
|
14521
|
+
return result;
|
|
14522
|
+
}
|
|
14490
14523
|
function destructiveTargets(command) {
|
|
14491
14524
|
return destructiveTargetsAtDepth(command, 0);
|
|
14492
14525
|
}
|
|
@@ -14533,20 +14566,26 @@ function destructiveTargetsAtDepth(command, depth) {
|
|
|
14533
14566
|
}
|
|
14534
14567
|
targets.push(...destructiveTargetsAtDepth(executableBody, depth + 1));
|
|
14535
14568
|
}
|
|
14536
|
-
const destructive =
|
|
14569
|
+
const destructive = new RegExp(
|
|
14570
|
+
String.raw`${COMMAND_BOUNDARY}(?:sudo\s+)?(rm|rmdir|del|rd|Remove-Item|unlink|truncate|shred|mv)\s+((?:"[^"]*"|'[^']*'|\\.|\{[^}]*\}|\([^()]*\)|[^;&|\r\n}])+)`,
|
|
14571
|
+
"gi"
|
|
14572
|
+
);
|
|
14537
14573
|
let m = destructive.exec(normalizedCommand);
|
|
14538
14574
|
while (m !== null) {
|
|
14539
14575
|
if (!tokenIsQuoted(m, m[1] ?? "")) targets.push(...shellArgs(m[2] ?? ""));
|
|
14540
14576
|
m = destructive.exec(normalizedCommand);
|
|
14541
14577
|
}
|
|
14542
|
-
const copy =
|
|
14578
|
+
const copy = new RegExp(
|
|
14579
|
+
String.raw`${COMMAND_BOUNDARY}(?:sudo\s+)?(cp|install)\s+([^;&|\r\n]+)`,
|
|
14580
|
+
"gi"
|
|
14581
|
+
);
|
|
14543
14582
|
let c = copy.exec(normalizedCommand);
|
|
14544
14583
|
while (c !== null) {
|
|
14545
14584
|
if (tokenIsQuoted(c, c[1] ?? "")) {
|
|
14546
14585
|
c = copy.exec(normalizedCommand);
|
|
14547
14586
|
continue;
|
|
14548
14587
|
}
|
|
14549
|
-
const tokens = shellTokens(c[2] ?? "");
|
|
14588
|
+
const tokens = shellTokens(stripSubshellClosers(c[2] ?? ""));
|
|
14550
14589
|
let destination;
|
|
14551
14590
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
14552
14591
|
const token = tokens[index];
|
|
@@ -14563,23 +14602,32 @@ function destructiveTargetsAtDepth(command, depth) {
|
|
|
14563
14602
|
if (destination) targets.push(destination);
|
|
14564
14603
|
c = copy.exec(normalizedCommand);
|
|
14565
14604
|
}
|
|
14566
|
-
const tee =
|
|
14605
|
+
const tee = new RegExp(
|
|
14606
|
+
String.raw`${COMMAND_BOUNDARY}(?:sudo\s+)?(tee)\s+([^;&|\r\n]+)`,
|
|
14607
|
+
"gi"
|
|
14608
|
+
);
|
|
14567
14609
|
let t = tee.exec(normalizedCommand);
|
|
14568
14610
|
while (t !== null) {
|
|
14569
14611
|
if (!tokenIsQuoted(t, t[1] ?? "")) targets.push(...shellArgs(t[2] ?? ""));
|
|
14570
14612
|
t = tee.exec(normalizedCommand);
|
|
14571
14613
|
}
|
|
14572
|
-
const dd =
|
|
14614
|
+
const dd = new RegExp(
|
|
14615
|
+
String.raw`${COMMAND_BOUNDARY}(?:sudo\s+)?(dd)\s+([^;&|\r\n]+)`,
|
|
14616
|
+
"gi"
|
|
14617
|
+
);
|
|
14573
14618
|
let d = dd.exec(normalizedCommand);
|
|
14574
14619
|
while (d !== null) {
|
|
14575
14620
|
if (!tokenIsQuoted(d, d[1] ?? "")) {
|
|
14576
14621
|
const outputMatch = /(?:^|\s)of=("[^"]*"|'[^']*'|[^\s]+)/i.exec(d[2] ?? "");
|
|
14577
|
-
const output = outputMatch?.[1]
|
|
14622
|
+
const output = outputMatch?.[1] ? stripSubshellClosers(outputMatch[1]).replace(/^['"]|['"]$/g, "") : void 0;
|
|
14578
14623
|
if (output) targets.push(output);
|
|
14579
14624
|
}
|
|
14580
14625
|
d = dd.exec(normalizedCommand);
|
|
14581
14626
|
}
|
|
14582
|
-
const overwrite =
|
|
14627
|
+
const overwrite = new RegExp(
|
|
14628
|
+
String.raw`${COMMAND_BOUNDARY}(?:sudo\s+)?(sed|ln)\s+([^;&|\r\n]+)`,
|
|
14629
|
+
"gi"
|
|
14630
|
+
);
|
|
14583
14631
|
let o = overwrite.exec(normalizedCommand);
|
|
14584
14632
|
while (o !== null) {
|
|
14585
14633
|
const rawArgs = o[2] ?? "";
|
|
@@ -14597,7 +14645,10 @@ function destructiveTargetsAtDepth(command, depth) {
|
|
|
14597
14645
|
}
|
|
14598
14646
|
o = overwrite.exec(normalizedCommand);
|
|
14599
14647
|
}
|
|
14600
|
-
const xargsPipeline =
|
|
14648
|
+
const xargsPipeline = new RegExp(
|
|
14649
|
+
String.raw`\b(?:echo|printf)\s+([^|]+)\|\s*xargs${XARGS_OPTIONS}\s+(?:sudo\s+)?(?:rm|rmdir|del|unlink|truncate|shred)\b`,
|
|
14650
|
+
"gi"
|
|
14651
|
+
);
|
|
14601
14652
|
let x = xargsPipeline.exec(normalizedCommand);
|
|
14602
14653
|
while (x !== null) {
|
|
14603
14654
|
if (!tokenIsQuoted(x, "xargs")) targets.push(...shellArgs(x[1] ?? ""));
|
|
@@ -14714,7 +14765,7 @@ function destructiveTargetsAtDepth(command, depth) {
|
|
|
14714
14765
|
targets.push(gitTarget("."));
|
|
14715
14766
|
}
|
|
14716
14767
|
}
|
|
14717
|
-
const findDelete = /(?:^|[;&|\r\n]\s*|\$\(\s*|\(\s*|`\s*)find\b([^;&|)`]*(?:\s-delete(
|
|
14768
|
+
const findDelete = /(?:^|[;&|\r\n]\s*|\{\s*|\$\(\s*|\(\s*|`\s*)find\b([^;&|)`]*(?:\s-delete(?:[\s;})]|$)|\s-exec(?:dir)?\s+(?:[^\s;&|]+[\\/])?(?:rm|rmdir)\b)[^;&|)`]*)/gi;
|
|
14718
14769
|
let f = findDelete.exec(normalizedCommand);
|
|
14719
14770
|
while (f !== null) {
|
|
14720
14771
|
const tokens = shellTokens(f[1] ?? "");
|
|
@@ -15147,10 +15198,19 @@ var plugin39 = {
|
|
|
15147
15198
|
const ti = input.toolInput ?? {};
|
|
15148
15199
|
const command = typeof ti["command"] === "string" ? ti["command"] : "";
|
|
15149
15200
|
const commandArgs = Array.isArray(ti["args"]) ? ti["args"].filter((arg) => typeof arg === "string") : [];
|
|
15150
|
-
const
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15201
|
+
const serializeArgForInspection = (arg) => {
|
|
15202
|
+
if (/^[\w./:@%+=,-]+$/.test(arg)) return arg;
|
|
15203
|
+
const assignment = /^([A-Za-z_]\w*)=(.+)$/.exec(arg);
|
|
15204
|
+
const key = assignment?.[1];
|
|
15205
|
+
const value = assignment?.[2];
|
|
15206
|
+
if (key !== void 0 && value !== void 0) {
|
|
15207
|
+
return `${key}="${value.replaceAll('"', "")}"`;
|
|
15208
|
+
}
|
|
15209
|
+
return JSON.stringify(arg);
|
|
15210
|
+
};
|
|
15211
|
+
const commandForInspection = [command, ...commandArgs.map(serializeArgForInspection)].join(
|
|
15212
|
+
" "
|
|
15213
|
+
);
|
|
15154
15214
|
if (command && executesShell({
|
|
15155
15215
|
toolName: input.toolName,
|
|
15156
15216
|
toolInput: ti,
|
|
@@ -16226,6 +16286,9 @@ var plugin42 = {
|
|
|
16226
16286
|
};
|
|
16227
16287
|
var process_guard_default = plugin42;
|
|
16228
16288
|
|
|
16289
|
+
// src/prompt-firewall/index.ts
|
|
16290
|
+
import { performance as performance2 } from "node:perf_hooks";
|
|
16291
|
+
|
|
16229
16292
|
// src/runtime/credential-patterns.ts
|
|
16230
16293
|
var CREDENTIAL_PATTERNS = [
|
|
16231
16294
|
// LLM provider keys
|
|
@@ -16397,32 +16460,156 @@ var PATTERNS2 = [
|
|
|
16397
16460
|
})),
|
|
16398
16461
|
...EXTRA_PATTERNS
|
|
16399
16462
|
];
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16404
|
-
|
|
16463
|
+
var PATTERN_BUDGET_MS = 250;
|
|
16464
|
+
var GUARD_PROBE_LENGTH = 1e5;
|
|
16465
|
+
var GUARD_PROBE_OVERLAP = 4096;
|
|
16466
|
+
var SCAN_PASS_BUDGET_MS = 250;
|
|
16467
|
+
function createScanDeadline() {
|
|
16468
|
+
return { deadline: performance2.now() + SCAN_PASS_BUDGET_MS, tripped: /* @__PURE__ */ new Set() };
|
|
16469
|
+
}
|
|
16470
|
+
var SCAN_WINDOW_STRIDE = GUARD_PROBE_LENGTH - GUARD_PROBE_OVERLAP;
|
|
16471
|
+
var SCAN_WINDOW_TAIL = 65536;
|
|
16472
|
+
var SCAN_WINDOW_LIMIT = GUARD_PROBE_LENGTH + SCAN_WINDOW_TAIL;
|
|
16473
|
+
var SCAN_MAX_GROWTH = 16;
|
|
16474
|
+
function growMatch(re, p, text, absStart, deadline) {
|
|
16475
|
+
let size = GUARD_PROBE_LENGTH;
|
|
16476
|
+
for (let grown = 0; grown < SCAN_MAX_GROWTH; grown++) {
|
|
16477
|
+
if (deadline && performance2.now() > deadline.deadline) {
|
|
16478
|
+
deadline.tripped.add(p.kind);
|
|
16479
|
+
return null;
|
|
16480
|
+
}
|
|
16481
|
+
const extEnd = Math.min(text.length, absStart + size);
|
|
16482
|
+
const ext = text.slice(absStart, extEnd);
|
|
16483
|
+
re.lastIndex = 0;
|
|
16484
|
+
const em = re.exec(ext);
|
|
16485
|
+
if (!em || em.index !== 0 || em[0].length === 0) return null;
|
|
16486
|
+
const end = absStart + em[0].length;
|
|
16487
|
+
if (end < extEnd || extEnd === text.length) {
|
|
16488
|
+
return { start: absStart, end, matched: em[0] };
|
|
16489
|
+
}
|
|
16490
|
+
size *= 2;
|
|
16491
|
+
}
|
|
16492
|
+
return null;
|
|
16493
|
+
}
|
|
16494
|
+
function* execWindowed(p, text, deadline) {
|
|
16495
|
+
const re = new RegExp(p.re.source, p.re.flags);
|
|
16496
|
+
let acceptLo = 0;
|
|
16497
|
+
let highWater = 0;
|
|
16498
|
+
for (let window = 0; acceptLo < text.length; window++) {
|
|
16499
|
+
const sliceStart = window === 0 ? 0 : acceptLo - GUARD_PROBE_OVERLAP;
|
|
16500
|
+
const sliceEnd = Math.min(text.length, sliceStart + SCAN_WINDOW_LIMIT);
|
|
16501
|
+
const slice = text.slice(sliceStart, sliceEnd);
|
|
16502
|
+
const acceptHi = Math.min(acceptLo + SCAN_WINDOW_STRIDE, text.length);
|
|
16503
|
+
re.lastIndex = 0;
|
|
16504
|
+
let m = re.exec(slice);
|
|
16405
16505
|
while (m !== null) {
|
|
16506
|
+
if (deadline && performance2.now() > deadline.deadline) {
|
|
16507
|
+
deadline.tripped.add(p.kind);
|
|
16508
|
+
return;
|
|
16509
|
+
}
|
|
16406
16510
|
const matched = m[0];
|
|
16407
|
-
if (
|
|
16408
|
-
|
|
16511
|
+
if (matched.length === 0) {
|
|
16512
|
+
re.lastIndex += 1;
|
|
16513
|
+
m = re.exec(slice);
|
|
16514
|
+
continue;
|
|
16409
16515
|
}
|
|
16410
|
-
|
|
16516
|
+
const absStart = sliceStart + m.index;
|
|
16517
|
+
const absEnd = absStart + matched.length;
|
|
16518
|
+
if (absStart >= acceptLo && absStart < acceptHi) {
|
|
16519
|
+
let final = { start: absStart, end: absEnd, matched };
|
|
16520
|
+
if (absEnd === sliceEnd && sliceEnd < text.length) {
|
|
16521
|
+
final = growMatch(re, p, text, absStart, deadline) ?? final;
|
|
16522
|
+
re.lastIndex = m.index + final.matched.length;
|
|
16523
|
+
}
|
|
16524
|
+
if (final.start >= highWater) {
|
|
16525
|
+
highWater = Math.max(highWater, final.end);
|
|
16526
|
+
yield final;
|
|
16527
|
+
}
|
|
16528
|
+
}
|
|
16529
|
+
m = re.exec(slice);
|
|
16411
16530
|
}
|
|
16531
|
+
acceptLo += SCAN_WINDOW_STRIDE;
|
|
16412
16532
|
}
|
|
16413
|
-
return [...counts.entries()].map(([kind, count]) => ({ kind, count }));
|
|
16414
16533
|
}
|
|
16415
|
-
function
|
|
16534
|
+
function countMatches(p, text, allow, counts, deadline) {
|
|
16535
|
+
if (deadline && performance2.now() > deadline.deadline) {
|
|
16536
|
+
deadline.tripped.add(p.kind);
|
|
16537
|
+
return;
|
|
16538
|
+
}
|
|
16539
|
+
for (const m of execWindowed(p, text, deadline)) {
|
|
16540
|
+
if (!allow.some((a) => a.test(m.matched))) {
|
|
16541
|
+
counts.set(p.kind, (counts.get(p.kind) ?? 0) + 1);
|
|
16542
|
+
}
|
|
16543
|
+
}
|
|
16544
|
+
}
|
|
16545
|
+
function replacePattern(p, text, allow, redactions, deadline) {
|
|
16546
|
+
if (deadline && performance2.now() > deadline.deadline) {
|
|
16547
|
+
deadline.tripped.add(p.kind);
|
|
16548
|
+
return text;
|
|
16549
|
+
}
|
|
16550
|
+
let out = "";
|
|
16551
|
+
let copied = 0;
|
|
16552
|
+
for (const m of execWindowed(p, text, deadline)) {
|
|
16553
|
+
out += text.slice(copied, m.start);
|
|
16554
|
+
if (allow.some((a) => a.test(m.matched))) {
|
|
16555
|
+
out += m.matched;
|
|
16556
|
+
} else {
|
|
16557
|
+
redactions.n += 1;
|
|
16558
|
+
out += `[REDACTED:${p.kind}]`;
|
|
16559
|
+
}
|
|
16560
|
+
copied = m.end;
|
|
16561
|
+
}
|
|
16562
|
+
return out + text.slice(copied);
|
|
16563
|
+
}
|
|
16564
|
+
function redactSecrets(text, allow, deadline) {
|
|
16565
|
+
const redactions = { n: 0 };
|
|
16416
16566
|
let out = text;
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16567
|
+
for (const p of PATTERNS2) out = replacePattern(p, out, allow, redactions, deadline);
|
|
16568
|
+
return { text: out, redactions: redactions.n };
|
|
16569
|
+
}
|
|
16570
|
+
var DISTINCT_PATTERN_KINDS = new Set(PATTERNS2.map((p) => p.kind)).size;
|
|
16571
|
+
async function probeTimedOutPatterns(text) {
|
|
16572
|
+
const timedOut = /* @__PURE__ */ new Set();
|
|
16573
|
+
if (text.length === 0) return timedOut;
|
|
16574
|
+
const probeWindow = (offset) => {
|
|
16575
|
+
const window = text.slice(offset, offset + GUARD_PROBE_LENGTH);
|
|
16576
|
+
const combined = new RegExp(PATTERNS2.map((p) => `(${p.re.source})`).join("|"), "gi");
|
|
16577
|
+
return withReDoSGuard(combined, window, PATTERN_BUDGET_MS).then(async (combinedResult) => {
|
|
16578
|
+
if (!combinedResult.timedOut) return;
|
|
16579
|
+
for (const p of PATTERNS2) {
|
|
16580
|
+
if (timedOut.has(p.kind)) continue;
|
|
16581
|
+
const result = await withReDoSGuard(p.re, window, PATTERN_BUDGET_MS);
|
|
16582
|
+
if (result.timedOut) timedOut.add(p.kind);
|
|
16583
|
+
}
|
|
16423
16584
|
});
|
|
16585
|
+
};
|
|
16586
|
+
const stride = GUARD_PROBE_LENGTH - GUARD_PROBE_OVERLAP;
|
|
16587
|
+
for (let offset = 0; offset < text.length; offset += stride) {
|
|
16588
|
+
if (timedOut.size >= DISTINCT_PATTERN_KINDS) break;
|
|
16589
|
+
await probeWindow(offset);
|
|
16590
|
+
}
|
|
16591
|
+
return timedOut;
|
|
16592
|
+
}
|
|
16593
|
+
async function detectSecretsGuarded(text, allow) {
|
|
16594
|
+
const timedOut = await probeTimedOutPatterns(text);
|
|
16595
|
+
const deadline = createScanDeadline();
|
|
16596
|
+
const counts = /* @__PURE__ */ new Map();
|
|
16597
|
+
for (const p of PATTERNS2) {
|
|
16598
|
+
if (!timedOut.has(p.kind)) countMatches(p, text, allow, counts, deadline);
|
|
16424
16599
|
}
|
|
16425
|
-
|
|
16600
|
+
const allSkipped = /* @__PURE__ */ new Set([...timedOut, ...deadline.tripped]);
|
|
16601
|
+
return {
|
|
16602
|
+
detections: [...counts.entries()].map(([kind, count]) => ({ kind, count })),
|
|
16603
|
+
skipped: [...allSkipped].sort().map((kind) => ({ kind, reason: "redos-timeout" }))
|
|
16604
|
+
};
|
|
16605
|
+
}
|
|
16606
|
+
function redactSecretsGuarded(text, allow, skip, deadline) {
|
|
16607
|
+
const redactions = { n: 0 };
|
|
16608
|
+
let out = text;
|
|
16609
|
+
for (const p of PATTERNS2) {
|
|
16610
|
+
if (!skip.has(p.kind)) out = replacePattern(p, out, allow, redactions, deadline);
|
|
16611
|
+
}
|
|
16612
|
+
return { text: out, redactions: redactions.n };
|
|
16426
16613
|
}
|
|
16427
16614
|
function collectText(request) {
|
|
16428
16615
|
const parts = [];
|
|
@@ -16436,17 +16623,27 @@ function collectText(request) {
|
|
|
16436
16623
|
walk(request["messages"]);
|
|
16437
16624
|
return parts.join("\n");
|
|
16438
16625
|
}
|
|
16439
|
-
|
|
16626
|
+
var RESPONSE_SCAN_BUDGET = 1e6;
|
|
16627
|
+
function redactDeep(value, allow, counter, skip, budget, deadline) {
|
|
16440
16628
|
if (typeof value === "string") {
|
|
16441
|
-
|
|
16629
|
+
if (budget) {
|
|
16630
|
+
if (value.length > budget.remaining) {
|
|
16631
|
+
budget.remaining = 0;
|
|
16632
|
+
budget.truncated = true;
|
|
16633
|
+
return value;
|
|
16634
|
+
}
|
|
16635
|
+
budget.remaining -= value.length;
|
|
16636
|
+
}
|
|
16637
|
+
const { text, redactions } = skip ? redactSecretsGuarded(value, allow, skip, deadline) : redactSecrets(value, allow, deadline);
|
|
16442
16638
|
counter.n += redactions;
|
|
16443
16639
|
return text;
|
|
16444
16640
|
}
|
|
16445
|
-
if (Array.isArray(value))
|
|
16641
|
+
if (Array.isArray(value))
|
|
16642
|
+
return value.map((v) => redactDeep(v, allow, counter, skip, budget, deadline));
|
|
16446
16643
|
if (value && typeof value === "object") {
|
|
16447
16644
|
const out = {};
|
|
16448
16645
|
for (const [k, v] of Object.entries(value)) {
|
|
16449
|
-
out[k] = redactDeep(v, allow, counter);
|
|
16646
|
+
out[k] = redactDeep(v, allow, counter, skip, budget, deadline);
|
|
16450
16647
|
}
|
|
16451
16648
|
return out;
|
|
16452
16649
|
}
|
|
@@ -16482,16 +16679,33 @@ var state40 = {
|
|
|
16482
16679
|
responseRedactions: 0,
|
|
16483
16680
|
blocked: 0,
|
|
16484
16681
|
timeoutCount: 0,
|
|
16682
|
+
skippedPatterns: [],
|
|
16683
|
+
responseTruncated: false,
|
|
16485
16684
|
byKind: /* @__PURE__ */ new Map(),
|
|
16486
16685
|
lastDetection: null,
|
|
16487
16686
|
extensionUnregister: null
|
|
16488
16687
|
};
|
|
16688
|
+
function surfaceScanTrips(api, tripped) {
|
|
16689
|
+
for (const kind of tripped) {
|
|
16690
|
+
if (!state40.skippedPatterns.includes(kind)) state40.skippedPatterns.push(kind);
|
|
16691
|
+
}
|
|
16692
|
+
state40.timeoutCount += 1;
|
|
16693
|
+
api.metrics.counter("redos_skips", 1);
|
|
16694
|
+
api.log.warn("prompt-firewall: scan-pass budget exceeded \u2014 patterns skipped mid-pass (issue #370)", {
|
|
16695
|
+
skipped: [...tripped]
|
|
16696
|
+
});
|
|
16697
|
+
}
|
|
16489
16698
|
var plugin43 = {
|
|
16490
16699
|
name: "prompt-firewall",
|
|
16491
16700
|
version: "0.1.0",
|
|
16492
16701
|
description: "Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.",
|
|
16493
16702
|
apiVersion: "^0.1.10",
|
|
16494
16703
|
capabilities: { tools: true },
|
|
16704
|
+
// Wrap-stack contract (issue #362): ExtensionRegistry composes wrappers
|
|
16705
|
+
// first-registered = outermost. The manifest lists this plugin before
|
|
16706
|
+
// llm-cache, and llm-cache declares this plugin in optionalDeps, so the
|
|
16707
|
+
// firewall is the outer wrap: every request is scanned/redacted before
|
|
16708
|
+
// llm-cache can fingerprint or cache it.
|
|
16495
16709
|
defaultConfig: { enabled: false, mode: "redact", scanResponse: true, allow: [] },
|
|
16496
16710
|
configSchema: {
|
|
16497
16711
|
type: "object",
|
|
@@ -16527,6 +16741,8 @@ var plugin43 = {
|
|
|
16527
16741
|
state40.responseRedactions = 0;
|
|
16528
16742
|
state40.blocked = 0;
|
|
16529
16743
|
state40.timeoutCount = 0;
|
|
16744
|
+
state40.skippedPatterns = [];
|
|
16745
|
+
state40.responseTruncated = false;
|
|
16530
16746
|
state40.byKind.clear();
|
|
16531
16747
|
state40.lastDetection = null;
|
|
16532
16748
|
if (state40.extensionUnregister) {
|
|
@@ -16550,15 +16766,16 @@ var plugin43 = {
|
|
|
16550
16766
|
const req = request ?? {};
|
|
16551
16767
|
state40.invocations += 1;
|
|
16552
16768
|
const requestText = collectText(req);
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16769
|
+
const { detections, skipped } = await detectSecretsGuarded(requestText, cfg.allow);
|
|
16770
|
+
state40.skippedPatterns = skipped.map((s) => s.kind);
|
|
16771
|
+
if (skipped.length > 0) {
|
|
16772
|
+
state40.timeoutCount += 1;
|
|
16773
|
+
api.log.warn("prompt-firewall: ReDoS budget exceeded, patterns skipped", {
|
|
16774
|
+
skipped: state40.skippedPatterns
|
|
16558
16775
|
});
|
|
16559
|
-
|
|
16776
|
+
api.metrics.counter("redos_skips", 1);
|
|
16560
16777
|
}
|
|
16561
|
-
const
|
|
16778
|
+
const skipSet = new Set(state40.skippedPatterns);
|
|
16562
16779
|
if (detections.length > 0) {
|
|
16563
16780
|
state40.requestsWithSecrets += 1;
|
|
16564
16781
|
for (const d of detections) {
|
|
@@ -16577,27 +16794,39 @@ var plugin43 = {
|
|
|
16577
16794
|
}
|
|
16578
16795
|
if (cfg.mode === "redact") {
|
|
16579
16796
|
const counter = { n: 0 };
|
|
16580
|
-
const
|
|
16797
|
+
const deadline = createScanDeadline();
|
|
16798
|
+
const redactedReq = redactDeep(req, cfg.allow, counter, skipSet, void 0, deadline);
|
|
16581
16799
|
state40.requestRedactions += counter.n;
|
|
16582
16800
|
api.metrics.counter("request_redactions", counter.n);
|
|
16801
|
+
if (deadline.tripped.size > 0) surfaceScanTrips(api, deadline.tripped);
|
|
16583
16802
|
const response2 = await inner(_ctx, redactedReq);
|
|
16584
|
-
return cfg.scanResponse ? redactResponse(response2, cfg.allow) : response2;
|
|
16803
|
+
return cfg.scanResponse ? redactResponse(response2, cfg.allow, skipSet) : response2;
|
|
16585
16804
|
}
|
|
16586
16805
|
}
|
|
16587
16806
|
const response = await inner(_ctx, request);
|
|
16588
16807
|
if (cfg.mode === "redact" && cfg.scanResponse) {
|
|
16589
|
-
return redactResponse(response, cfg.allow);
|
|
16808
|
+
return redactResponse(response, cfg.allow, skipSet);
|
|
16590
16809
|
}
|
|
16591
16810
|
return response;
|
|
16592
16811
|
}
|
|
16593
16812
|
});
|
|
16594
16813
|
}
|
|
16595
|
-
function redactResponse(response, allow) {
|
|
16814
|
+
function redactResponse(response, allow, skip) {
|
|
16596
16815
|
if (!response || typeof response !== "object") return response;
|
|
16597
16816
|
const counter = { n: 0 };
|
|
16598
16817
|
const content = response.content;
|
|
16599
16818
|
if (content === void 0) return response;
|
|
16600
|
-
const
|
|
16819
|
+
const budget = { remaining: RESPONSE_SCAN_BUDGET, truncated: false };
|
|
16820
|
+
const deadline = createScanDeadline();
|
|
16821
|
+
const redacted = redactDeep(content, allow, counter, skip, budget, deadline);
|
|
16822
|
+
if (deadline.tripped.size > 0) surfaceScanTrips(api, deadline.tripped);
|
|
16823
|
+
if (budget.truncated) {
|
|
16824
|
+
state40.responseTruncated = true;
|
|
16825
|
+
api.log.warn(
|
|
16826
|
+
"prompt-firewall: response scan budget exhausted \u2014 part of the response was returned unredacted"
|
|
16827
|
+
);
|
|
16828
|
+
api.metrics.counter("response_scan_truncated", 1);
|
|
16829
|
+
}
|
|
16601
16830
|
if (counter.n > 0) {
|
|
16602
16831
|
state40.responseRedactions += counter.n;
|
|
16603
16832
|
api.metrics.counter("response_redactions", counter.n);
|
|
@@ -16623,6 +16852,8 @@ var plugin43 = {
|
|
|
16623
16852
|
mode: cfg.mode,
|
|
16624
16853
|
scanResponse: cfg.scanResponse,
|
|
16625
16854
|
patterns: PATTERNS2.map((p) => p.kind),
|
|
16855
|
+
skippedPatterns: state40.skippedPatterns,
|
|
16856
|
+
responseTruncated: state40.responseTruncated,
|
|
16626
16857
|
counters: {
|
|
16627
16858
|
invocations: state40.invocations,
|
|
16628
16859
|
requestsWithSecrets: state40.requestsWithSecrets,
|
|
@@ -16656,13 +16887,17 @@ var plugin43 = {
|
|
|
16656
16887
|
requestsWithSecrets: state40.requestsWithSecrets,
|
|
16657
16888
|
requestRedactions: state40.requestRedactions,
|
|
16658
16889
|
responseRedactions: state40.responseRedactions,
|
|
16659
|
-
blocked: state40.blocked
|
|
16890
|
+
blocked: state40.blocked,
|
|
16891
|
+
timeoutCount: state40.timeoutCount
|
|
16660
16892
|
};
|
|
16661
16893
|
state40.invocations = 0;
|
|
16662
16894
|
state40.requestsWithSecrets = 0;
|
|
16663
16895
|
state40.requestRedactions = 0;
|
|
16664
16896
|
state40.responseRedactions = 0;
|
|
16665
16897
|
state40.blocked = 0;
|
|
16898
|
+
state40.timeoutCount = 0;
|
|
16899
|
+
state40.skippedPatterns = [];
|
|
16900
|
+
state40.responseTruncated = false;
|
|
16666
16901
|
state40.byKind.clear();
|
|
16667
16902
|
state40.lastDetection = null;
|
|
16668
16903
|
api.log.info("prompt-firewall: teardown complete", { final });
|
|
@@ -20629,10 +20864,10 @@ var OFFICIAL_PLUGIN_NAMES = [
|
|
|
20629
20864
|
"notify-hub",
|
|
20630
20865
|
"changelog-writer",
|
|
20631
20866
|
"injection-shield",
|
|
20867
|
+
"prompt-firewall",
|
|
20632
20868
|
"llm-cache",
|
|
20633
20869
|
"model-router",
|
|
20634
20870
|
"pr-drafter",
|
|
20635
|
-
"prompt-firewall",
|
|
20636
20871
|
"auto-escalate",
|
|
20637
20872
|
"test-coverage-gate",
|
|
20638
20873
|
"type-gate",
|
package/dist/llm-cache.js
CHANGED
|
@@ -82,6 +82,12 @@ var plugin = {
|
|
|
82
82
|
description: "Caches identical provider requests and short-circuits the provider call on a hit (wrapProviderRunner). Opt-in; deterministic-only by default.",
|
|
83
83
|
apiVersion: "^0.1.10",
|
|
84
84
|
capabilities: { tools: true },
|
|
85
|
+
// Wrap-stack contract (issue #362): ExtensionRegistry composes wrappers
|
|
86
|
+
// first-registered = outermost, and both dependsOn and optionalDeps load
|
|
87
|
+
// dependencies first. Declaring prompt-firewall here guarantees the
|
|
88
|
+
// firewall wraps OUTSIDE this cache even if the manifest order changes —
|
|
89
|
+
// otherwise a cache hit short-circuits `inner` and bypasses redaction.
|
|
90
|
+
optionalDeps: ["prompt-firewall"],
|
|
85
91
|
defaultConfig: { ...DEFAULTS },
|
|
86
92
|
configSchema: {
|
|
87
93
|
type: "object",
|