1/** Last few KiB of ACP child stderr kept for startup / exit diagnostics. */
2export const ACP_STDERR_TAIL_MAX_CHARS = 4_096;
3
4const PAIRING_URL_PATTERN = /https?:\/\/[^\s]*\/pair#[^\s]*/gi;
5const BEARER_TOKEN_PATTERN = /\bBearer\s+[A-Za-z0-9._\-+=/]+/gi;
6const BASIC_AUTH_PATTERN = /\bAuthorization:\s*Basic\s+\S+/gi;
7const API_KEY_HEADER_PATTERN = /\bx-api-key:\s*\S+/gi;
8const 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
11export 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. */
17export 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}