Files
rea/tests/boundary/cli/explicitSensitiveArgumentsCli.test.ts
T
Kaoru0822 c083fedaf4 fix(cli): exclude declared sensitive values from parser diagnostics (#6258)
* fix(cli): exclude declared sensitive values from parser diagnostics

Incur rejects malformed command tokens and output-flag values before the
web capture service applies --sensitive-value declarations, so its
diagnostics repeated declared literals.

Read declarations from the tokens Incur leaves for command parsing,
independently of parse success. Commands accepting the option get an
outermost middleware that excludes declarations from parse, validation
and thrown failures while keeping type, code and exit status. Rejected
--format/--token-limit/--token-offset values are replaced by a
literal-free value Incur still rejects. The option definition and the
Incur builtin-flag rules now have one owner each.

Fixes #6252

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): redact whole rejected output-flag diagnostics

Incur builds its rejected --format/--token-limit/--token-offset message
from fixed text and the value, outside any middleware. Substituting only
the value left declarations that match the fixed text, such as "token"
in "--token-limit", in the diagnostic.

Report that first rejection at the serve boundary instead, redacting the
whole message as parser failures are, while keeping the UNKNOWN code and
Incur's toon or TTY rendering. Incur's message is now mirrored beside its
acceptance rule, and the value-substitution path is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:22:16 -07:00

272 lines
7.7 KiB
TypeScript

import { describe, expect } from "vitest";
import { cliTest, type CliResult } from "../../support/cli/cliFixture.js";
const LITERAL = "SYNTHETIC_CAPTURE_LITERAL_83A4";
const CAPTURE = ["inspect-web-network-capture", "/nonexistent/capture.har"];
const DECLARED = [...CAPTURE, "har", "--sensitive-value", LITERAL];
const expectExcluded = (
result: CliResult,
literals: readonly string[] = [LITERAL],
): void => {
expect(result.exitCode).toBe(1);
for (const literal of literals) {
expect(result.stdout).not.toContain(literal);
expect(result.stderr).not.toContain(literal);
}
};
describe("CLI --sensitive-value parser diagnostics", () => {
cliTest.for([
{
name: "negated unknown flag",
arguments: [...DECLARED, `--no-unknown-option=${LITERAL}`],
message: "Unknown flag: --no-unknown-option=[REDACTED]",
},
{
name: "unknown flag name",
arguments: [...DECLARED, `--${LITERAL}`],
message: "Unknown flag: --[REDACTED]",
},
{
name: "declaration after the rejected token",
arguments: [
...CAPTURE,
"har",
`--no-${LITERAL}`,
`--sensitive-value=${LITERAL}`,
],
message: "Unknown flag: --no-[REDACTED]",
},
{
name: "camel-case declaration",
arguments: [
...CAPTURE,
"har",
`--sensitiveValue=${LITERAL}`,
`--no-${LITERAL}`,
],
message: "Unknown flag: --no-[REDACTED]",
},
{
// Incur removes builtin output flags before reading the declared value.
name: "builtin flag before the declared value",
arguments: [
...CAPTURE,
"har",
"--sensitive-value",
"--json",
LITERAL,
`--no-${LITERAL}`,
],
message: "Unknown flag: --no-[REDACTED]",
},
])(
"excludes declarations from a JSON $name rejection",
async ({ arguments: arguments_, message }, { cli }) => {
const result = await cli.run({ arguments: [...arguments_, "--json"] });
expectExcluded(result);
expect(JSON.parse(result.stdout)).toEqual({ code: "UNKNOWN", message });
},
);
cliTest(
"redacts fixed parser text but keeps JSON keys and codes",
async ({ cli }) => {
const result = await cli.run({
arguments: [
...CAPTURE,
"har",
...["flag", "code", "UNKNOWN"].flatMap((literal) => [
"--sensitive-value",
literal,
]),
"--no-x",
"--json",
],
});
expect(result.exitCode).toBe(1);
expect(JSON.parse(result.stdout)).toEqual({
code: "UNKNOWN",
message: "Unknown [REDACTED]: --no-x",
});
},
);
cliTest(
"excludes declarations from validation text while keeping field errors",
async ({ cli }) => {
const result = await cli.run({
arguments: [
...CAPTURE,
"bogus",
"--sensitive-value",
"mitmproxy",
"--json",
],
});
expectExcluded(result, ["mitmproxy"]);
expect(JSON.parse(result.stdout)).toMatchObject({
code: "VALIDATION_ERROR",
message: expect.stringContaining('"har"|"[REDACTED]"|"saz"'),
fieldErrors: [
expect.objectContaining({ code: "invalid_value", path: "format" }),
],
});
},
);
cliTest(
"retains undeclared tokens and declared values that the parser accepts",
async ({ cli }) => {
const undeclared = await cli.run({
arguments: [...CAPTURE, "har", `--no-${LITERAL}`, "--json"],
});
expect(undeclared.exitCode).toBe(1);
expect(JSON.parse(undeclared.stdout)).toEqual({
code: "UNKNOWN",
message: `Unknown flag: --no-${LITERAL}`,
});
// An accepted output value is honored, so the service failure is JSON.
const accepted = await cli.run({
arguments: [
...CAPTURE,
"har",
"--sensitive-value",
"json",
"--format",
"json",
],
});
expect(accepted.exitCode).toBe(1);
expect(JSON.parse(accepted.stdout)).toMatchObject({
code: "invalid_request",
});
},
);
});
describe("CLI --sensitive-value output-value diagnostics", () => {
cliTest.for([
{
name: "a spaced --format value",
arguments: [...DECLARED, "--format", LITERAL],
message: 'Invalid format: \\"[REDACTED]\\"',
},
{
name: "an equals-form --format value",
arguments: [...DECLARED, `--format=${LITERAL}`],
message: 'Invalid format: \\"[REDACTED]\\"',
},
{
name: "a --token-limit value",
arguments: [...DECLARED, "--token-limit", LITERAL],
message: 'Invalid value for --token-limit: \\"[REDACTED]\\"',
},
{
name: "a partially declared --token-offset value",
arguments: [...DECLARED, "--token-offset", `1${LITERAL}`],
message: 'Invalid value for --token-offset: \\"1[REDACTED]\\"',
},
])(
"keeps rejecting $name without the declaration",
async ({ arguments: arguments_, message }, { cli }) => {
const result = await cli.run({ arguments: arguments_ });
expectExcluded(result);
expect(result.stdout).toContain(`code: UNKNOWN\nmessage: "${message}`);
},
);
cliTest(
"rejects an output value that would be accepted once declarations are removed",
async ({ cli }) => {
const declared = ["X", "[REDACTED]", "…"];
const result = await cli.run({
arguments: [
...CAPTURE,
"har",
...declared.flatMap((literal) => ["--sensitive-value", literal]),
"--token-limit",
"12X",
],
});
expectExcluded(result, declared);
expect(result.stdout).toContain("Invalid value for --token-limit:");
},
);
cliTest.for([
{
name: "flag-name and value",
declared: ["token"],
arguments: ["--token-limit", "token"],
message: 'Invalid value for --[REDACTED]-limit: \\"[REDACTED]\\"',
},
{
name: "overlapping",
declared: ["tok", "token"],
arguments: ["--token-offset", "token1"],
message: 'Invalid value for --[REDACTED]-offset: \\"[REDACTED]1\\"',
},
{
name: "fixed-text",
declared: ["Invalid", "Expected"],
arguments: ["--format", "bogus"],
message:
'[REDACTED] format: \\"bogus\\". [REDACTED] one of: toon, json, yaml, md, jsonl',
},
{
// Codes and keys identify the contract, not caller text.
name: "code and key",
declared: ["UNKNOWN", "code", "message"],
arguments: ["--format", "bogus"],
message:
'Invalid format: \\"bogus\\". Expected one of: toon, json, yaml, md, jsonl',
},
])(
"redacts the whole rejected output message for $name declarations",
async ({ declared, arguments: arguments_, message }, { cli }) => {
const result = await cli.run({
arguments: [
...CAPTURE,
"har",
...declared.flatMap((literal) => ["--sensitive-value", literal]),
...arguments_,
],
});
expect(result.exitCode).toBe(1);
expect(result.stdout).toBe(`code: UNKNOWN\nmessage: "${message}"\n`);
},
);
cliTest.for([
["--format", "bogus"],
["--token-limit=x"],
["--token-offset", " "],
])(
"renders %s rejections exactly as Incur does without declarations",
async (arguments_, { cli }) => {
const command = [...CAPTURE, "har", ...arguments_];
const incur = await cli.run({ arguments: command });
const declared = await cli.run({
arguments: [...command, "--sensitive-value", "unused"],
});
expect(incur.exitCode).toBe(1);
expect(declared).toMatchObject({
exitCode: incur.exitCode,
stdout: incur.stdout,
stderr: incur.stderr,
});
},
);
});