fix: redact gh auth output in code queue
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { appendOutput, configureTaskOutput, taskFullOutput } from "../src/components/microservices/code-queue/src/task-output";
|
||||
import { sanitizeTaskOutputText } from "../src/components/microservices/code-queue/src/output-redaction";
|
||||
import type { JsonValue, QueueTask } from "../src/components/microservices/code-queue/src/types";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
||||
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
||||
}
|
||||
|
||||
function fixtureTask(): QueueTask {
|
||||
const at = "2026-05-23T00:00:00.000Z";
|
||||
return {
|
||||
id: "codex_gh_auth_redaction_contract",
|
||||
queueId: "default",
|
||||
queueEnteredAt: at,
|
||||
prompt: "redaction fixture",
|
||||
basePrompt: "redaction fixture",
|
||||
referenceTaskIds: [],
|
||||
referenceInjection: null,
|
||||
providerId: "D601",
|
||||
cwd: "/workspace",
|
||||
model: "gpt-5.5",
|
||||
reasoningEffort: null,
|
||||
executionMode: "default",
|
||||
maxAttempts: 1,
|
||||
status: "running",
|
||||
createdAt: at,
|
||||
updatedAt: at,
|
||||
startedAt: at,
|
||||
finishedAt: null,
|
||||
readAt: null,
|
||||
currentAttempt: 1,
|
||||
currentMode: "initial",
|
||||
codexThreadId: null,
|
||||
activeTurnId: null,
|
||||
finalResponse: "",
|
||||
lastError: null,
|
||||
lastJudge: null,
|
||||
judgeFailCount: 0,
|
||||
promptHistory: [],
|
||||
output: [],
|
||||
events: [],
|
||||
attempts: [],
|
||||
cancelRequested: false,
|
||||
nextPrompt: null,
|
||||
nextMode: null,
|
||||
};
|
||||
}
|
||||
|
||||
function assertNoTokenFragments(value: string, label: string): void {
|
||||
assertCondition(!/\bgh[pousr]_[A-Za-z0-9_]{6,}\b/u.test(value), `${label} must redact gh token-like values`, value);
|
||||
assertCondition(!/\bgithub_pat_[A-Za-z0-9_]{6,}\b/u.test(value), `${label} must redact GitHub PAT-like values`, value);
|
||||
assertCondition(!/Token:\s*\S+/iu.test(value), `${label} must not expose raw gh auth token lines`, value);
|
||||
assertCondition(!/Token scopes?:\s*\S+/iu.test(value), `${label} must not expose raw gh auth scope lines`, value);
|
||||
}
|
||||
|
||||
export function runCodeQueueGhAuthRedactionContract(): JsonRecord {
|
||||
const tmp = mkdtempSync(join(tmpdir(), "code-queue-gh-auth-redaction-"));
|
||||
const task = fixtureTask();
|
||||
let seq = 0;
|
||||
try {
|
||||
configureTaskOutput({
|
||||
config: { maxInMemoryOutputRecords: 1000, outputArchiveDir: tmp },
|
||||
allocateSeq: () => {
|
||||
seq += 1;
|
||||
return seq;
|
||||
},
|
||||
errorToJson: (error: unknown): JsonValue => error instanceof Error ? { message: error.message } : String(error),
|
||||
logger: () => undefined,
|
||||
markTaskDirty: () => undefined,
|
||||
nowIso: () => "2026-05-23T00:00:01.000Z",
|
||||
schedulePersistState: () => undefined,
|
||||
});
|
||||
|
||||
const rawGhAuth = [
|
||||
"github.com",
|
||||
" \u2713 Logged in to github.com account example (keyring)",
|
||||
" - Active account: true",
|
||||
" - Git operations protocol: ssh",
|
||||
" - Token: ghp_abcdef1234567890abcdef1234567890",
|
||||
" - Token scopes: 'repo', 'read:org'",
|
||||
"generic token=github_pat_abcdef1234567890abcdef1234567890",
|
||||
].join("\n");
|
||||
const sanitized = sanitizeTaskOutputText(rawGhAuth);
|
||||
assertNoTokenFragments(sanitized, "direct sanitizer output");
|
||||
assertCondition(sanitized.includes("[redacted gh auth status line]"), "sanitizer must redact gh auth status lines", sanitized);
|
||||
assertCondition(sanitized.includes("bun scripts/cli.ts gh auth status"), "sanitizer must include UniDesk gh wrapper hint", sanitized);
|
||||
|
||||
appendOutput(task, "command", `${rawGhAuth}\n`, "item/commandExecution/outputDelta", "call-gh-auth", true);
|
||||
const retained = JSON.stringify(task.output);
|
||||
const archived = readFileSync(join(tmp, `${task.id}.jsonl`), "utf8");
|
||||
const full = JSON.stringify(taskFullOutput(task));
|
||||
assertNoTokenFragments(retained, "retained output");
|
||||
assertNoTokenFragments(archived, "archived output");
|
||||
assertNoTokenFragments(full, "full output replay");
|
||||
assertCondition(full.includes("bun scripts/cli.ts gh auth status"), "full output replay must keep wrapper hint", full);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
checks: [
|
||||
"raw gh auth status token and scope lines are redacted",
|
||||
"token-like GitHub values are redacted before retained output persistence",
|
||||
"output archive replay remains redacted",
|
||||
"redacted output nudges runner toward bun scripts/cli.ts gh auth status",
|
||||
],
|
||||
};
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
process.stdout.write(`${JSON.stringify(runCodeQueueGhAuthRedactionContract(), null, 2)}\n`);
|
||||
} catch (error) {
|
||||
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user