fix: reject unsafe AgentRun input disclosure combinations
This commit is contained in:
@@ -301,6 +301,25 @@ describe("AgentRun default transport contract", () => {
|
||||
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
|
||||
await Bun.write(tempConfigPath, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
|
||||
|
||||
const invalidCombinations = [
|
||||
{ args: ["--input", "--full"], contentType: "text/plain" },
|
||||
{ args: ["--full", "--input", "-o", "json"], contentType: "application/json" },
|
||||
{ args: ["--input", "--full", "-o", "yaml"], contentType: "application/yaml" },
|
||||
{ args: ["--input", "--raw"], contentType: "application/json" },
|
||||
];
|
||||
for (const invalid of invalidCombinations) {
|
||||
const rejected = await runAgentRunCommand(null, ["describe", `task/${taskId}`, ...invalid.args]);
|
||||
const rejectedText = renderedTextOf(rejected);
|
||||
expect(rejected.ok).toBe(false);
|
||||
expect(renderedContentTypeOf(rejected)).toBe(invalid.contentType);
|
||||
expect(rejectedText).toContain("validation-failed");
|
||||
expect(rejectedText).toContain("Use --input without --full or --raw");
|
||||
expect(rejectedText).not.toContain(hiddenCredential);
|
||||
expect(rejectedText).not.toContain("supervisor");
|
||||
expect(Buffer.byteLength(rejectedText, "utf8")).toBeLessThan(2400);
|
||||
}
|
||||
expect(requests).toBe(0);
|
||||
|
||||
const summaryText = renderedTextOf(await runAgentRunCommand(null, ["describe", `task/${taskId}`]));
|
||||
expect(summaryText).toContain(`agentrun describe task/${taskId} --input -o json`);
|
||||
expect(summaryText).not.toContain(hiddenCredential);
|
||||
@@ -485,7 +504,7 @@ describe("AgentRun default transport contract", () => {
|
||||
expect(rootHelpText.split("\n").length).toBeLessThan(40);
|
||||
});
|
||||
|
||||
test("local retry and attempt validation preserves bounded human and machine errors", async () => {
|
||||
test("local resource validation preserves bounded human and machine errors", async () => {
|
||||
const cases = [
|
||||
{
|
||||
args: ["retry", "task/qt_failed_1", "--reason", "dependency repaired"],
|
||||
@@ -532,6 +551,8 @@ describe("AgentRun default transport contract", () => {
|
||||
for (const cliArgs of [
|
||||
["retry", "run/run_invalid", "--idempotency-key", "retry-key-1", "--reason", "dependency repaired", "-o", "json"],
|
||||
["get", "attempts", "--raw"],
|
||||
["describe", "task/qt_sensitive", "--input", "--full", "-o", "json"],
|
||||
["describe", "task/qt_sensitive", "--input", "--raw"],
|
||||
]) {
|
||||
const cli = Bun.spawnSync(["bun", "scripts/cli.ts", "agentrun", ...cliArgs], {
|
||||
cwd: new URL("../..", import.meta.url).pathname,
|
||||
@@ -543,6 +564,8 @@ describe("AgentRun default transport contract", () => {
|
||||
expect(payload.ok).toBe(false);
|
||||
expect(payload.failureKind).toBe("validation-failed");
|
||||
expect(cli.stdout.toString().length).toBeLessThan(2400);
|
||||
expect(cli.stdout.toString()).not.toContain('"resource":');
|
||||
expect(cli.stdout.toString()).not.toContain('"data":');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -179,6 +179,24 @@ export function parseResourceOptions(args: string[]): AgentRunResourceOptions {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (options.taskInput && (options.full || options.raw)) {
|
||||
const conflictingOptions = ["--input", options.full ? "--full" : null, options.raw ? "--raw" : null]
|
||||
.filter((value): value is string => value !== null);
|
||||
throw new AgentRunRestError(
|
||||
"validation-failed",
|
||||
`${conflictingOptions.join(" with ")} is not allowed: --input is the bounded SecretRef-only TaskInput projection and cannot be combined with complete resource disclosure.`,
|
||||
{
|
||||
details: {
|
||||
conflictingOptions,
|
||||
recoveryActions: [
|
||||
"Use --input without --full or --raw for the bounded SecretRef-only TaskInput projection.",
|
||||
"Remove --input when explicitly requesting the complete resource with --full or --raw.",
|
||||
],
|
||||
valuesPrinted: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@@ -1068,6 +1068,10 @@ export function renderAgentRunRestError(command: string, error: AgentRunRestErro
|
||||
const nextCommands = Array.isArray(laneHint.nextCommands)
|
||||
? laneHint.nextCommands.filter((value): value is string => typeof value === "string" && value.length > 0).slice(0, 4)
|
||||
: [];
|
||||
const validationDetails = record(payload.agentrun);
|
||||
const recoveryActions = Array.isArray(validationDetails.recoveryActions)
|
||||
? validationDetails.recoveryActions.filter((value): value is string => typeof value === "string" && value.length > 0).slice(0, 4)
|
||||
: [];
|
||||
const hintLines = Object.keys(laneHint).length === 0
|
||||
? []
|
||||
: [
|
||||
@@ -1080,15 +1084,17 @@ export function renderAgentRunRestError(command: string, error: AgentRunRestErro
|
||||
const validationHelp = `bun scripts/cli.ts agentrun ${command.split(/\s+/u)[1] ?? "--help"} --help`;
|
||||
const nextLines = nextCommands.length > 0
|
||||
? nextCommands
|
||||
: error.failureKind === "validation-failed"
|
||||
? [
|
||||
"Review the AgentRun validation message and correct the request; do not bypass the formal resource API.",
|
||||
validationHelp,
|
||||
]
|
||||
: [
|
||||
"Check config/agentrun.yaml manager.baseUrl and the AgentRun API route.",
|
||||
"Verify HWLAB_API_KEY is present in env or in the configured auth.file without printing the key.",
|
||||
];
|
||||
: recoveryActions.length > 0
|
||||
? recoveryActions
|
||||
: error.failureKind === "validation-failed"
|
||||
? [
|
||||
"Review the AgentRun validation message and correct the request; do not bypass the formal resource API.",
|
||||
validationHelp,
|
||||
]
|
||||
: [
|
||||
"Check config/agentrun.yaml manager.baseUrl and the AgentRun API route.",
|
||||
"Verify HWLAB_API_KEY is present in env or in the configured auth.file without printing the key.",
|
||||
];
|
||||
return renderedCliResult(false, command, [
|
||||
`Error: ${payload.failureKind}`,
|
||||
String(payload.message ?? ""),
|
||||
|
||||
Reference in New Issue
Block a user