Files
moltbot/packages/memory-host-sdk/src/host/error-utils.ts
2026-04-28 06:02:17 +01:00

90 lines
3.0 KiB
TypeScript

const SECRET_PATTERNS: RegExp[] = [
/\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)\b\s*[=:]\s*(["']?)([^\s"'\\]+)\1/g,
/[?&](?:access[-_]?token|auth[-_]?token|hook[-_]?token|refresh[-_]?token|api[-_]?key|client[-_]?secret|token|key|secret|password|pass|passwd|auth|signature)=([^&\s"'<>]+)/gi,
/"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"/g,
/--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1/g,
/Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/g,
/\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/g,
/(^|[\s,;])(?:access_token|refresh_token|api[-_]?key|token|secret|password|passwd)=([^\s&#]+)/g,
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
/\b(sk-[A-Za-z0-9_-]{8,})\b/g,
/\b(ghp_[A-Za-z0-9]{20,})\b/g,
/\b(github_pat_[A-Za-z0-9_]{20,})\b/g,
/\b(xox[baprs]-[A-Za-z0-9-]{10,})\b/g,
/\b(xapp-[A-Za-z0-9-]{10,})\b/g,
/\b(gsk_[A-Za-z0-9_-]{10,})\b/g,
/\b(AIza[0-9A-Za-z\-_]{20,})\b/g,
/\b(pplx-[A-Za-z0-9_-]{10,})\b/g,
/\b(npm_[A-Za-z0-9]{10,})\b/g,
/\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g,
/\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g,
];
function maskToken(token: string): string {
if (token.length < 18) {
return "***";
}
return `${token.slice(0, 6)}...${token.slice(-4)}`;
}
function redactPemBlock(block: string): string {
const lines = block.split(/\r?\n/).filter(Boolean);
if (lines.length < 2) {
return "***";
}
return `${lines[0]}\n...redacted...\n${lines[lines.length - 1]}`;
}
function redactMatch(match: string, groups: string[]): string {
if (match.includes("PRIVATE KEY-----")) {
return redactPemBlock(match);
}
const token = groups.findLast((value) => typeof value === "string" && value.length > 0) ?? match;
const masked = maskToken(token);
return token === match ? masked : match.replace(token, masked);
}
function redactSensitiveText(text: string): string {
let next = text;
for (const pattern of SECRET_PATTERNS) {
next = next.replace(pattern, (...args: string[]) =>
redactMatch(args[0] ?? "", args.slice(1, -2)),
);
}
return next;
}
export function formatErrorMessage(err: unknown): string {
let formatted: string;
if (err instanceof Error) {
formatted = err.message || err.name || "Error";
let cause: unknown = err.cause;
const seen = new Set<unknown>([err]);
while (cause && !seen.has(cause)) {
seen.add(cause);
if (cause instanceof Error) {
if (cause.message) {
formatted += ` | ${cause.message}`;
}
cause = cause.cause;
} else if (typeof cause === "string") {
formatted += ` | ${cause}`;
break;
} else {
break;
}
}
} else if (typeof err === "string") {
formatted = err;
} else if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") {
formatted = String(err);
} else {
try {
formatted = JSON.stringify(err);
} catch {
formatted = Object.prototype.toString.call(err);
}
}
return redactSensitiveText(formatted);
}