fix: reject unsafe AgentRun input disclosure combinations

This commit is contained in:
Codex
2026-07-12 06:23:08 +02:00
parent 2680ef6462
commit 26597700df
5 changed files with 63 additions and 11 deletions
+5 -1
View File
@@ -29,7 +29,11 @@ AipodSpec/Artificer、task manifest 和 queue 渐进披露见 [references/resour
- 新写入口不要用 `codex submit/steer/resume`;这些 legacy mutation 已冻结。
- 默认输出保持低噪声表格/摘要;机器消费显式 `-o json|yaml``--raw`
- 读取 task 的可重建输入使用 `agentrun describe task/<taskId> --input -o json`;该入口只投影 task create 字段、AipodSpec identity、资源引用和脱敏 provenance,不打印 Secret value。
- 读取 task 的可重建输入
- 使用 `agentrun describe task/<taskId> --input -o json`
- 该入口只投影 task create 字段、AipodSpec identity、资源引用和脱敏 provenance
- 不打印 Secret value
- `--input``--full``--raw` 互斥,非法组合必须在请求服务端前返回 `validation-failed`
- Session follow-up 使用 `agentrun send`,由服务端决定内部 steer vs turn。
- 终态失败重试只使用 `agentrun retry task/<taskId>`:保持同一 task,新增不可变 attempt;先用 `--dry-run` 完成服务端全校验,再以同一幂等键执行真实重试。
- retry 只接受 Queue 已处于 `failed``blocked``completed``cancelled` 和其他状态必须 fail closed。不得复制 task、重置 task、引入本地 scheduler/backoff/judge 或直接创建 runner Job 来模拟重试。
@@ -23,6 +23,7 @@ task 输入下钻必须遵守以下边界:
- 默认 `describe task/<taskId>``Next` 必须给出 `--input -o json` 语义化命令;
- `--input` 只读取现有 task durable fact,不创建、复制、retry 或 dispatch task
- 输出固定为 task create 字段、AipodSpec identity、bundle/SecretRef 和脱敏 provenancecredential 只保留 SecretRef 并声明 `valuesPrinted=false`
- `--input``--full``--raw` 互斥,非法组合必须在请求服务端前返回带修正路径的 `validation-failed`
- 完整服务端资源继续使用显式 `--full -o json``--raw`,禁止提高全局 stdout 阈值来掩盖大输出。
retry 必须遵守以下边界:
+24 -1
View File
@@ -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":');
}
});
+18
View File
@@ -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;
}
+15 -9
View File
@@ -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 ?? ""),