refactor: reuse support redaction classifiers

This commit is contained in:
Gustavo Madeira Santana
2026-04-22 17:29:39 -04:00
parent 9c819d94c8
commit 10f128413d
4 changed files with 22 additions and 8 deletions

View File

@@ -384,6 +384,14 @@ describe("diagnostic support export", () => {
"connect wss://support-user:support-password@gateway.example/ws?token=short-token&ok=1",
"connect wss://<redacted>:<redacted>@gateway.example/ws?token=<redacted>",
],
[
"connect https://gateway.example/ws?access-token=short-token",
"connect https://gateway.example/ws?access-token=<redacted>",
],
[
"connect https://gateway.example/ws?hook-token=hook-secret",
"connect https://gateway.example/ws?hook-token=<redacted>",
],
["connect https://token@gateway.example/ws", "connect https://<redacted>@gateway.example/ws"],
["auth Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", "auth Basic <redacted>"],
["Cookie: sid=secret; theme=light", "Cookie: <redacted>"],

View File

@@ -1,4 +1,6 @@
import path from "node:path";
import { isSecretRefShape } from "../config/redact-snapshot.secret-ref.js";
import { isSensitiveUrlQueryParamName } from "../shared/net/redact-sensitive-url.js";
import { redactSensitiveText } from "./redact.js";
const SECRET_SUPPORT_FIELD_RE =
@@ -17,8 +19,7 @@ const COOKIE_HEADER_RE = /\b(Cookie|Set-Cookie)\s*:\s*[^\r\n]+/giu;
const AWS_ACCESS_KEY_ID_RE = /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu;
const JWT_RE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/gu;
const URL_USERINFO_RE = /\b([a-z][a-z0-9+.-]*:\/\/)([^/@\s:?#]+)(?::([^/@\s?#]+))?@/giu;
const SENSITIVE_URL_PARAM_RE =
/([?&](?:api[-_]?key|access[-_]?token|auth[-_]?token|hook[-_]?token|password|passwd|refresh[-_]?token|secret|token)=)[^&#\s]+/giu;
const URL_PARAM_RE = /([?&])([^=&\s]+)=([^&#\s]+)/giu;
const EMAIL_RE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu;
const MATRIX_USER_ID_RE = /@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+/gu;
const MATRIX_ROOM_ID_RE = /![A-Za-z0-9._=-]+:[A-Za-z0-9.-]+/gu;
@@ -58,10 +59,6 @@ function isPrivateConfigField(key: string): boolean {
return isPrivateSupportField(key) || CONFIG_PRIVATE_FIELD_RE.test(key);
}
function isSecretRefShape(value: Record<string, unknown>): boolean {
return typeof value.source === "string" && typeof value.id === "string";
}
function sanitizeSecretRefForSupport(value: Record<string, unknown>): Record<string, unknown> {
const sanitized: Record<string, unknown> = {};
if (typeof value.source === "string") {
@@ -143,7 +140,9 @@ function redactUrlSecretsForSupport(value: string): string {
.replace(URL_USERINFO_RE, (_match, scheme: string, _username: string, password?: string) =>
password ? `${scheme}<redacted>:<redacted>@` : `${scheme}<redacted>@`,
)
.replace(SENSITIVE_URL_PARAM_RE, "$1<redacted>");
.replace(URL_PARAM_RE, (match, prefix: string, key: string) =>
isSensitiveUrlQueryParamName(key) ? `${prefix}${key}=<redacted>` : match,
);
}
function redactContactIdentifiersForSupport(value: string): string {

View File

@@ -48,6 +48,9 @@ describe("isSensitiveUrlQueryParamName", () => {
it("matches the auth-oriented query params used by MCP SSE config redaction", () => {
expect(isSensitiveUrlQueryParamName("token")).toBe(true);
expect(isSensitiveUrlQueryParamName("refresh_token")).toBe(true);
expect(isSensitiveUrlQueryParamName("access-token")).toBe(true);
expect(isSensitiveUrlQueryParamName("hook-token")).toBe(true);
expect(isSensitiveUrlQueryParamName("passwd")).toBe(true);
expect(isSensitiveUrlQueryParamName("signature")).toBe(true);
expect(isSensitiveUrlQueryParamName("safe")).toBe(false);
});

View File

@@ -10,16 +10,20 @@ const SENSITIVE_URL_QUERY_PARAM_NAMES = new Set([
"apikey",
"secret",
"access_token",
"auth_token",
"password",
"pass",
"passwd",
"auth",
"client_secret",
"hook_token",
"refresh_token",
"signature",
]);
export function isSensitiveUrlQueryParamName(name: string): boolean {
return SENSITIVE_URL_QUERY_PARAM_NAMES.has(normalizeLowercaseStringOrEmpty(name));
const normalized = normalizeLowercaseStringOrEmpty(name).replaceAll("-", "_");
return SENSITIVE_URL_QUERY_PARAM_NAMES.has(normalized);
}
export function isSensitiveUrlConfigPath(path: string): boolean {