fix: redact commander approval reasons

This commit is contained in:
Codex
2026-05-21 08:53:14 +00:00
parent f055e18523
commit cf40cd5f6c
3 changed files with 62 additions and 4 deletions
+12
View File
@@ -44,6 +44,18 @@ function displayCommandName(parts: string[]): string {
}
return shown.join(" ");
}
if (parts[0] === "commander" && parts[1] === "approval" && parts[2] === "request") {
const shown: string[] = [];
for (let index = 0; index < parts.length; index += 1) {
const part = parts[index] ?? "";
shown.push(part);
if (part === "--reason") {
shown.push("<reason:redacted>");
index += 1;
}
}
return shown.join(" ");
}
return parts.join(" ");
}
@@ -96,6 +96,32 @@ assertCondition(claudeqq.sendImplemented === false, "ClaudeQQ send must not be i
const invalidApproval = dataOf(runCli(["commander", "approval", "request", "--action", "read-token-file", "--dry-run"], 1));
assertCondition(invalidApproval.error === "validation-failed", "unsupported approval action must fail validation", invalidApproval);
const secretReasonResult = spawnSync("bun", [
"scripts/cli.ts",
"commander",
"approval",
"request",
"--action",
"code-queue-backend-restart",
"--reason",
"token=ghp_1234567890abcdef",
"--dry-run",
], {
cwd: process.cwd(),
encoding: "utf8",
maxBuffer: 4 * 1024 * 1024,
});
assertCondition(secretReasonResult.status === 0, "secret-like approval reason command should still return successfully", {
stdout: secretReasonResult.stdout,
stderr: secretReasonResult.stderr,
});
assertCondition(!secretReasonResult.stdout.includes("ghp_1234567890abcdef"), "secret-like approval reason must be redacted from stdout", {
stdout: secretReasonResult.stdout,
});
assertCondition(secretReasonResult.stdout.includes("<redacted>"), "redacted approval reason should disclose redaction marker", {
stdout: secretReasonResult.stdout,
});
const doc = readFileSync("docs/reference/host-codex-commander.md", "utf8");
for (const snippet of [
"不直接重启 Code Queue backend",
@@ -116,6 +142,7 @@ process.stdout.write(`${JSON.stringify({
"dry-run plan covers process discovery, SSH/PTY/stdio bridge, prompt guidance, trace summary, #20/#46 and ClaudeQQ approval",
"non-dry-run plan is rejected",
"approval request is dry-run only and rejects unsupported high-risk actions",
"secret-like approval reasons are redacted from stdout",
"reference doc states backend restart, task interrupt/cancel, and token-output prohibitions",
],
}, null, 2)}\n`);
+23 -4
View File
@@ -31,6 +31,23 @@ function isHighRiskAction(value: string): value is HighRiskAction {
return highRiskActions.some((action) => action === value);
}
function redactText(value: string): { text: string; redactionsApplied: number } {
let redactionsApplied = 0;
const patterns = [
/\b(?:sk|ghp|github_pat|xoxb|xoxp|AKIA)[A-Za-z0-9_=-]{8,}\b/g,
/\b(?:token|secret|password|passwd|authorization|cookie|api[_-]?key)\s*[:=]\s*[^,\s]+/gi,
/\bBearer\s+[A-Za-z0-9._~+/-]+=*\b/gi,
];
let text = value;
for (const pattern of patterns) {
text = text.replace(pattern, () => {
redactionsApplied += 1;
return "<redacted>";
});
}
return { text, redactionsApplied };
}
function commanderHelp(): Record<string, unknown> {
return {
command: "commander",
@@ -270,7 +287,8 @@ function commanderApprovalRequest(args: string[]): Record<string, unknown> {
highRiskActions,
};
}
const reason = optionValue(args, "--reason") ?? "operator-supplied reason required before live execution";
const rawReason = optionValue(args, "--reason") ?? "operator-supplied reason required before live execution";
const reason = redactText(rawReason);
const taskId = optionValue(args, "--task-id") ?? null;
return {
ok: true,
@@ -279,20 +297,21 @@ function commanderApprovalRequest(args: string[]): Record<string, unknown> {
mutation: false,
action,
taskId,
reason,
reason: reason.text,
redactionsApplied: reason.redactionsApplied,
requiresExplicitUserApproval: true,
claudeqq: {
mutation: false,
endpointShape: "POST /api/microservices/claudeqq/proxy/api/push/text",
target: "configured primary user private chat",
messageTemplate: `Approval required for ${action}. Reason: ${reason}. Reply with explicit approval id before execution.`,
messageTemplate: `Approval required for ${action}. Reason: ${reason.text}. Reply with explicit approval id before execution.`,
sendImplemented: false,
},
approvalRecordShape: {
id: "commander-approval-<stable-id>",
action,
taskId,
reason,
reason: reason.text,
status: "draft",
approvedBy: null,
approvedAt: null,