openreplay/networkProxy/src/sanitizers.ts
2025-04-16 11:39:17 +02:00

140 lines
3.2 KiB
TypeScript

export const sensitiveParams = new Set([
"password",
"pass",
"pwd",
"mdp",
"token",
"bearer",
"jwt",
"api_key",
"api-key",
"apiKey",
"secret",
"ssn",
"zip",
"zipcode",
"x-api-key",
"www-authenticate",
"x-csrf-token",
"x-requested-with",
"x-forwarded-for",
"x-real-ip",
"cookie",
"authorization",
"auth",
"proxy-authorization",
"set-cookie",
"account_key",
]);
function numDigits(x) {
return (Math.log10((x ^ (x >> 31)) - (x >> 31)) | 0) + 1;
}
function obscure(value: string | number) {
if (typeof value === "number") {
const digits = numDigits(value)
return "9".repeat(digits)
}
return value.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*')
}
export function filterHeaders(headers: Record<string, string> | { name: string; value: string }[]) {
const filteredHeaders: Record<string, string> = {};
if (Array.isArray(headers)) {
headers.forEach(({ name, value }) => {
if (sensitiveParams.has(name.toLowerCase())) {
filteredHeaders[name] = obscure(value);
} else {
filteredHeaders[name] = value;
}
});
} else {
for (const [key, value] of Object.entries(headers)) {
if (sensitiveParams.has(key.toLowerCase())) {
filteredHeaders[key] = obscure(value);
} else {
filteredHeaders[key] = value;
}
}
}
return filteredHeaders;
}
export function filterBody(body: any): string {
if (!body) {
return body;
}
let parsedBody;
let isJSON = false;
try {
parsedBody = JSON.parse(body);
isJSON = true;
} catch (e) {
// not json
}
if (isJSON) {
obscureSensitiveData(parsedBody);
return JSON.stringify(parsedBody);
} else {
const isUrlSearch = typeof body === "string" && body.includes("?") && body.includes("=");
if (isUrlSearch) {
try {
const params = new URLSearchParams(body);
for (const key of params.keys()) {
if (sensitiveParams.has(key.toLowerCase())) {
const value = obscure(params.get(key))
params.set(key, value);
}
}
return params.toString();
} catch (e) {
// not url query ?
return body;
}
} else {
// not json or url query
return body;
}
}
}
export function sanitizeObject(obj: Record<string, any>) {
obscureSensitiveData(obj)
return obj
}
function obscureSensitiveData(obj: Record<string, any> | any[]) {
if (Array.isArray(obj)) {
obj.forEach(obscureSensitiveData);
} else if (obj && typeof obj === "object") {
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
if (sensitiveParams.has(key.toLowerCase())) {
obj[key] = obscure(obj[key]);
} else if (obj[key] !== null && typeof obj[key] === "object") {
obscureSensitiveData(obj[key]);
}
}
}
}
}
export function tryFilterUrl(url: string) {
if (!url) return "";
try {
const urlObj = new URL(url);
if (urlObj.searchParams) {
for (const key of urlObj.searchParams.keys()) {
if (sensitiveParams.has(key.toLowerCase())) {
urlObj.searchParams.set(key, "******");
}
}
}
return urlObj.toString();
} catch (e) {
return url;
}
}