perf(security): bound regex input in filters and redaction

This commit is contained in:
Peter Steinberger
2026-03-02 16:37:23 +00:00
parent 31c7637e0f
commit b1592457fa
6 changed files with 103 additions and 5 deletions

View File

@@ -102,6 +102,15 @@ describe("redactSensitiveText", () => {
expect(output).toBe(input);
});
it("redacts large payloads with bounded regex passes", () => {
const input = `${"x".repeat(40_000)} OPENAI_API_KEY=sk-1234567890abcdef ${"y".repeat(40_000)}`;
const output = redactSensitiveText(input, {
mode: "tools",
patterns: defaults,
});
expect(output).toContain("OPENAI_API_KEY=sk-123…cdef");
});
it("skips redaction when mode is off", () => {
const input = "OPENAI_API_KEY=sk-1234567890abcdef";
const output = redactSensitiveText(input, {

View File

@@ -10,6 +10,8 @@ const DEFAULT_REDACT_MODE: RedactSensitiveMode = "tools";
const DEFAULT_REDACT_MIN_LENGTH = 18;
const DEFAULT_REDACT_KEEP_START = 6;
const DEFAULT_REDACT_KEEP_END = 4;
const REDACT_REGEX_CHUNK_THRESHOLD = 32_768;
const REDACT_REGEX_CHUNK_SIZE = 16_384;
const DEFAULT_REDACT_PATTERNS: string[] = [
// ENV-style assignments.
@@ -94,12 +96,26 @@ function redactMatch(match: string, groups: string[]): string {
return match.replace(token, masked);
}
function replacePatternWithBounds(text: string, pattern: RegExp): string {
const apply = (value: string) =>
value.replace(pattern, (...args: string[]) =>
redactMatch(args[0], args.slice(1, args.length - 2)),
);
if (text.length <= REDACT_REGEX_CHUNK_THRESHOLD) {
return apply(text);
}
let output = "";
for (let index = 0; index < text.length; index += REDACT_REGEX_CHUNK_SIZE) {
output += apply(text.slice(index, index + REDACT_REGEX_CHUNK_SIZE));
}
return output;
}
function redactText(text: string, patterns: RegExp[]): string {
let next = text;
for (const pattern of patterns) {
next = next.replace(pattern, (...args: string[]) =>
redactMatch(args[0], args.slice(1, args.length - 2)),
);
next = replacePatternWithBounds(next, pattern);
}
return next;
}