Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. 🫖 Status page with uptime monitoring & API monitoring as code 🫖 openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
3.3 kB · 104 lines
TypeScript
at incident-api
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105const REDACTED = "[redacted]";
const SENSITIVE_HEADER_NAMES = new Set([ "authorization", "cookie", "set-cookie", "proxy-authorization", "x-api-key", "x-auth-token",]);
// Matched against whole words of a name split on `-`, `_` and camelCase, so// `api_key`, `accessToken` and `x-auth-token` match but `monkey` does not.const SENSITIVE_WORD = /^(?:auth(?:orization|entication|n|z)?|cookies?|credentials?|keys?|secrets?|sessions?|tokens?|passwords?|passwd)$/;
// Common sensitive names written as a single lowercase word.const SENSITIVE_COMPOUND_WORDS = new Set([ "apikey", "accesskey", "secretkey", "privatekey", "accesstoken", "refreshtoken", "idtoken", "authtoken", "clientsecret", "sessionid",]);
function isSensitiveName(name: string) { if (SENSITIVE_HEADER_NAMES.has(name.toLowerCase())) return true; return name .replace(/([a-z0-9])([A-Z])/g, "$1 $2") .toLowerCase() .split(/[^a-z0-9]+/) .some( (word) => SENSITIVE_WORD.test(word) || SENSITIVE_COMPOUND_WORDS.has(word), );}
export function redactSensitiveHeaders( headers: Record<string, string> | null,): Record<string, string> { if (!headers) return {}; return Object.fromEntries( Object.entries(headers).map(([name, value]) => [ name, isSensitiveName(name) ? REDACTED : value, ]), );}
// A bare `Bearer …` value or a JWT can sit anywhere in a non-JSON body.const BEARER_PATTERN = /\bBearer\s+[\w.~+/=-]+/gi;const JWT_PATTERN = /\beyJ[\w-]+\.[\w-]+\.[\w-]+/g;// `token=…`, `"api_key": "…"`, `secret: …` in form-encoded or plain text bodies.// The pattern only pre-filters candidate keys; `isSensitiveName` decides.// Quoted values are consumed whole so secrets containing spaces don't leak.const KEY_VALUE_PATTERN = /\b([\w-]*(?:auth|cookie|credential|key|secret|session|token|passw)[\w-]*)(["']?\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s"'&,;}]+)/gi;
function redactJsonValue(value: unknown): unknown { if (Array.isArray(value)) return value.map(redactJsonValue); if (value && typeof value === "object") { return Object.fromEntries( Object.entries(value).map(([key, inner]) => [ key, isSensitiveName(key) ? REDACTED : redactJsonValue(inner), ]), ); } if (typeof value === "string") return redactText(value); return value;}
function redactText(text: string): string { return text .replace(BEARER_PATTERN, `Bearer ${REDACTED}`) .replace(JWT_PATTERN, REDACTED) .replace( KEY_VALUE_PATTERN, (match, key: string, sep: string, value: string) => { if (!isSensitiveName(key)) return match; const quote = value[0] === '"' || value[0] === "'" ? value[0] : ""; return `${key}${sep}${quote}${REDACTED}${quote}`; }, );}
/** * Best-effort secret scrubbing for a checked endpoint's response body: JSON * values under sensitive keys, plus bearer tokens, JWTs and `key=value` pairs * in anything else. Bodies are third-party content, so callers that hand them * to an LLM should still treat the result as untrusted data. */export function redactSensitiveBody(body: string | null): string | null { if (!body) return body; try { return JSON.stringify(redactJsonValue(JSON.parse(body))); } catch { return redactText(body); }}