| 1 | /** Last few KiB of ACP child stderr kept for startup / exit diagnostics. */ |
| 2 | export const ACP_STDERR_TAIL_MAX_CHARS = 4_096; |
| 3 | |
| 4 | const PAIRING_URL_PATTERN = /https?:\/\/[^\s]*\/pair#[^\s]*/gi; |
| 5 | const BEARER_TOKEN_PATTERN = /\bBearer\s+[A-Za-z0-9._\-+=/]+/gi; |
| 6 | const BASIC_AUTH_PATTERN = /\bAuthorization:\s*Basic\s+\S+/gi; |
| 7 | const API_KEY_HEADER_PATTERN = /\bx-api-key:\s*\S+/gi; |
| 8 | const SECRET_TOKEN_PATTERN = |
| 9 | /\b(?:sk-[A-Za-z0-9][A-Za-z0-9-]{7,}|ghp_[A-Za-z0-9]+|xox[a-zA-Z]-[A-Za-z0-9-]+)\b/g; |
| 10 | |
| 11 | export function appendAcpStderrTail(current: string, chunk: string): string { |
| 12 | const next = `${current}${chunk}`; |
| 13 | return next.length <= ACP_STDERR_TAIL_MAX_CHARS ? next : next.slice(-ACP_STDERR_TAIL_MAX_CHARS); |
| 14 | } |
| 15 | |
| 16 | /** Bounded, redacted excerpt safe to put on user-facing adapter errors. */ |
| 17 | export function sanitizeAcpStderrExcerpt( |
| 18 | text: string, |
| 19 | homeDirectory: string, |
| 20 | environment: NodeJS.ProcessEnv = process.env, |
| 21 | ): string { |
| 22 | let result = text.replaceAll("\0", ""); |
| 23 | const homes = [environment.HOME, environment.USERPROFILE, homeDirectory].filter( |
| 24 | (value): value is string => typeof value === "string" && value.length > 1, |
| 25 | ); |
| 26 | for (const home of new Set(homes)) { |
| 27 | result = result.split(home).join("~"); |
| 28 | } |
| 29 | result = result |
| 30 | .replace(PAIRING_URL_PATTERN, "[pairing-url]") |
| 31 | .replace(BEARER_TOKEN_PATTERN, "Bearer [redacted]") |
| 32 | .replace(BASIC_AUTH_PATTERN, "Authorization: Basic [redacted]") |
| 33 | .replace(API_KEY_HEADER_PATTERN, "x-api-key: [redacted]") |
| 34 | .replace(SECRET_TOKEN_PATTERN, "[redacted]"); |
| 35 | return result.trim(); |
| 36 | } |