import { describe, expect, it } from
"vitest";
import {
resolveExecApprovalCommandDisplay,
sanitizeExecApprovalDisplayText,
} from
"./exec-approval-command-display.js";
describe(
"sanitizeExecApprovalDisplayText", () => {
it.each([
[
"echo hi\u200Bthere",
"echo hi\\u{200B}there"],
[
"date\u3164\uFFA0\u115F\u1160가",
"date\\u{3164}\\u{FFA0}\\u{115F}\\u{1160}가"],
[
"echo safe\n\rcurl https://example.test", "echo safe\\u{A}\\u{D}curl https://example.test"],
[
"echo ok\u2028curl https://example.test", "echo ok\\u{2028}curl https://example.test"],
[
"echo ok\u2029curl https://example.test", "echo ok\\u{2029}curl https://example.test"],
])(
"sanitizes exec approval display text for %j", (input, expected) => {
expect(sanitizeExecApprovalDisplayText(input)).toBe(expected);
});
it(
"redacts bearer tokens embedded in commands", () => {
const cmd =
'curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.longtoken.sig" https://api.example.com';
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.longtoken.sig");
expect(result).toContain(
"curl");
expect(result).toContain(
"https://api.example.com");
});
it(
"redacts API keys in environment variable assignments", () => {
const cmd =
'API_SECRET="sk-abc123456789012345678" python script.py';
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"sk-abc123456789012345678");
expect(result).toContain(
"python script.py");
});
it(
"redacts GitHub personal access tokens", () => {
const cmd =
"git clone https://ghp_1234567890abcdefghij1234567890abcdef@github.com/user/repo";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"ghp_1234567890abcdefghij1234567890abcdef");
expect(result).toContain(
"git clone");
});
it(
"masks the full token when a zero-width character is spliced into the middle", () => {
const cmd =
"echo sk-abc123\u200B456789012345678 remainder";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"sk-abc123");
expect(result).not.toContain(
"456789012345678");
expect(result).toContain(
"echo ");
expect(result).toContain(
"remainder");
});
it(
"masks the full token when NBSP (Zs) is spliced into the middle", () => {
const cmd =
"echo sk-abc123\u00A0456789012345678 remainder";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"sk-abc123");
expect(result).not.toContain(
"456789012345678");
expect(result).toContain(
"echo ");
expect(result).toContain(
"remainder");
});
it(
"masks the full token when narrow no-break space is spliced into the middle", () => {
const cmd =
"echo sk-abc123\u202F456789012345678 remainder";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"sk-abc123");
expect(result).not.toContain(
"456789012345678");
expect(result).toContain(
"remainder");
});
it(
"keeps newline boundaries visible as escape markers even when bypass is detected", () => {
// Stripping invisibles lets the stripped-view greedy-match across the original newline
// boundaries, so the trailing `line3` gets absorbed into the union mask alongside the
// secret. The important guarantees are: (1) the secret is not visible, and (2) the
// newlines that existed in the original are still visible as `\u{A}` escapes so the
// operator is not misled about multi-line structure.
const cmd =
"line1\necho sk-abc123\u00A0456789012345678\nline3";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"sk-abc123");
expect(result).not.toContain(
"456789012345678");
expect(result).toContain(
"line1");
expect(result).toContain(
"\\u{A}");
});
it(
"detects bypass even when raw and stripped redactions happen to produce the same normalized length", () => {
// Raw masks the 16-char prefix `sk-abc1234567890` as the fixed literal `***` while the
// trailing 8 chars past the zero-width stay visible. The stripped view masks the full
// 24-char token as `sk-abc…5678`. Both normalized outputs are the same length (11 chars),
// so a length-based bypass check would falsely return the raw view and leak the tail.
const cmd =
"sk-abc1234567890\u200B12345678";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"12345678");
expect(result).not.toContain(
"1234567890");
});
it(
"does not leak bearer tokens when bypass is triggered by a separate spliced secret", () => {
// Bearer+NBSP is caught by the raw view (NBSP matches \s in non-u JS regex) but stripping
// removes NBSP, turning `Bearer<jwt>` into a pattern the bearer regex no longer matches.
// A separate spliced-invisible token triggers bypass detection, and the union-mask output
// must cover both the bearer span (from raw) and the spliced sk- span (from stripped).
const cmd =
'curl -H "Authorization: Bearer\u00A0eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.longtoken.sig" https://api.example.com; echo sk-abc123\u200B456789012345678';
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.longtoken.sig");
expect(result).not.toContain(
"456789012345678");
expect(result).toContain(
"https://api.example.com");
});
it(
"keeps PEM private-key context visible when raw redaction already covers the key (not a bypass)", () => {
const cmd =
"echo -----BEGIN RSA PRIVATE KEY-----\nABCDEF0123456789abcdef\n-----END RSA PRIVATE KEY----- > key.pem";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain(
"ABCDEF0123456789abcdef");
expect(result).toContain(
"BEGIN RSA PRIVATE KEY");
expect(result).toContain(
"END RSA PRIVATE KEY");
expect(result).toContain(
"> key.pem");
});
it(
"truncates the redacted output (not the raw input) so large commands are bounded", () => {
const padding =
"x".repeat(
20 *
1024);
const result = sanitizeExecApprovalDisplayText(padding);
expect(result.length).toBeLessThan(padding.length);
expect(result).toContain(
"[truncated]");
});
it(
"refuses to display commands above the hard input cap", () => {
const huge =
"x".repeat(
300 *
1024);
const result = sanitizeExecApprovalDisplayText(huge);
expect(result).toContain(
"exceeds display size limit");
expect(result.length).toBeLessThan(
1024);
});
it(
"redacts tokens at the tail of long inputs instead of truncating them below pattern length", () => {
// Pad with non-token content, then append a secret at the end. Truncating BEFORE redaction
// would split the token below the pattern's minimum length and leak the prefix. With
// redaction first, the full token is masked before any size-based truncation runs.
const padding = "a ".repeat(10 * 1024);
const cmd = padding + "ghp_1234567890abcdefghij1234567890abcdef";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain("ghp_1234567890abcdefghij1234567890abcdef");
expect(result).not.toContain("ghp_1234567890");
});
it("escapes astral-plane invisible characters (e.g. U+E0061 tag characters)", () => {
const cmd = "echo hi\u{E0061}there";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).toContain("\\u{E0061}");
expect(result).not.toMatch(/hi[\uDB40\uDC61]there/u);
});
it("masks a secret spliced with an astral-plane invisible character", () => {
// U+E0061 is a Cf (format) code point in the supplementary plane. Iterating the input by
// UTF-16 code unit would see two surrogate halves, neither of which matches \p{Cf}, so
// the splice would survive stripping and the stripped-view redaction would miss the
// full token. Code-point iteration strips it correctly and bypass detection fires.
const cmd = "echo sk-abc123\u{E0061}456789012345678 remainder";
const result = sanitizeExecApprovalDisplayText(cmd);
expect(result).not.toContain("sk-abc123");
expect(result).not.toContain("456789012345678");
expect(result).toContain("remainder");
});
});
describe("resolveExecApprovalCommandDisplay", () => {
it.each([
{
name: "prefers explicit command fields and drops identical previews after trimming",
input: {
command: "echo hi",
commandPreview: " echo hi ",
host: "gateway" as const,
},
expected: {
commandText: "echo hi",
commandPreview: null,
},
},
{
name: "falls back to node systemRunPlan values and sanitizes preview text",
input: {
command: "",
host: "node" as const,
systemRunPlan: {
argv: ["python3", "-c", "print(1)"],
cwd: null,
commandText: 'python3 -c "print(1)"',
commandPreview: "print\u200B(1)",
agentId: null,
sessionKey: null,
},
},
expected: {
commandText: 'python3 -c "print(1)"',
commandPreview: "print\\u{200B}(1)",
},
},
{
name: "ignores systemRunPlan fallback for non-node hosts",
input: {
command: "",
host: "sandbox" as const,
systemRunPlan: {
argv: ["echo", "hi"],
cwd: null,
commandText: "echo hi",
commandPreview: "echo hi",
agentId: null,
sessionKey: null,
},
},
expected: {
commandText: "",
commandPreview: null,
},
},
])("$name", ({ input, expected }) => {
expect(resolveExecApprovalCommandDisplay(input)).toEqual(expected);
});
});