From f40de2a8fb7866f889fcdccd0bf6271181011f1c Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:05:45 +0000 Subject: [PATCH] =?UTF-8?q?refactor(app):=20=E6=8B=86=E5=88=86=20Codex=20?= =?UTF-8?q?=E5=9F=B7=E8=A1=8C=E8=A8=AD=E5=AE=9A=E8=88=87=20auth=20?= =?UTF-8?q?=E9=8E=96=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.js | 92 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/app/main.js b/app/main.js index 543a620..de5feed 100644 --- a/app/main.js +++ b/app/main.js @@ -10,6 +10,8 @@ const FILE_MODE_PRIVATE = 0o600; const DIR_MODE_PRIVATE = 0o700; const DEFAULT_CODEX_TIMEOUT_MS = 30 * 60 * 1000; const DEFAULT_OUTPUT_LIMIT_BYTES = 1024 * 1024; +const TEMP_DIR_PREFIX = ".codex-action-"; +const OUTPUT_DELIMITER_PREFIX = "CODEX_OUTPUT_"; class TempFileRegistry { constructor() { @@ -92,7 +94,7 @@ function cleanup() { } function makeTempDir(dir) { - const tempDir = fs.mkdtempSync(path.join(dir, ".codex-action-")); + const tempDir = fs.mkdtempSync(path.join(dir, TEMP_DIR_PREFIX)); fs.chmodSync(tempDir, DIR_MODE_PRIVATE); tempFiles.trackDir(tempDir); return tempDir; @@ -115,7 +117,7 @@ function appendGithubOutput(status, output) { let delimiter; do { - delimiter = `CODEX_OUTPUT_${crypto.randomBytes(12).toString("hex")}`; + delimiter = `${OUTPUT_DELIMITER_PREFIX}${crypto.randomBytes(12).toString("hex")}`; } while (output.includes(delimiter)); fs.appendFileSync( @@ -132,22 +134,27 @@ function fail(message, code = 1) { process.exit(code); } -function normalizeBase64(value) { - return value.replace(/\s+/g, "").replace(/=+$/, ""); +function compactBase64(value) { + return value.replace(/\s+/g, ""); +} + +function isBase64(value) { + const normalized = compactBase64(value); + const paddingIndex = normalized.indexOf("="); + + if (!normalized || normalized.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(normalized)) { + return false; + } + + return paddingIndex === -1 || /^=+$/.test(normalized.slice(paddingIndex)); } function validateAuth(encodedAuth, authFile) { - const decoded = Buffer.from(encodedAuth, "base64"); - const normalizedDecoded = normalizeBase64(decoded.toString("base64")); - const normalizedInput = normalizeBase64(encodedAuth); - - if (decoded.length === 0 && normalizedInput.length > 0) { + if (!isBase64(encodedAuth)) { fail("OAUTH must be valid base64 encoded Codex auth.json."); } - if (normalizedDecoded !== normalizedInput) { - fail("OAUTH must be valid base64 encoded Codex auth.json."); - } + const decoded = Buffer.from(compactBase64(encodedAuth), "base64"); fs.writeFileSync(authFile, decoded, { mode: FILE_MODE_PRIVATE }); @@ -168,26 +175,33 @@ function parsePositiveInteger(value, fallback) { return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; } +function readExecutionConfig() { + return { + timeoutMs: parsePositiveInteger(process.env.CODEX_TIMEOUT_MS, DEFAULT_CODEX_TIMEOUT_MS), + outputLimitBytes: parsePositiveInteger(process.env.CODEX_OUTPUT_LIMIT_BYTES, DEFAULT_OUTPUT_LIMIT_BYTES), + workspace: process.env.GITHUB_WORKSPACE || process.cwd(), + }; +} + +function codexExecArgs(model, prompt) { + return [ + "exec", + "--dangerously-bypass-approvals-and-sandbox", + "--skip-git-repo-check", + "--model", + model, + prompt, + ]; +} + function runCodex(model, prompt) { return new Promise((resolve) => { - const timeoutMs = parsePositiveInteger(process.env.CODEX_TIMEOUT_MS, DEFAULT_CODEX_TIMEOUT_MS); - const outputLimitBytes = parsePositiveInteger(process.env.CODEX_OUTPUT_LIMIT_BYTES, DEFAULT_OUTPUT_LIMIT_BYTES); - const workspace = process.env.GITHUB_WORKSPACE || process.cwd(); + const { timeoutMs, outputLimitBytes, workspace } = readExecutionConfig(); + const args = codexExecArgs(model, prompt); // This Docker Action runs inside an ephemeral CI container where Codex must be // able to edit the checked-out workspace without interactive approvals. - const child = spawn( - "codex", - [ - "exec", - "--dangerously-bypass-approvals-and-sandbox", - "--skip-git-repo-check", - "--model", - model, - prompt, - ], - { cwd: workspace, stdio: ["ignore", "pipe", "pipe"] }, - ); + const child = spawn("codex", args, { cwd: workspace, stdio: ["ignore", "pipe", "pipe"] }); const output = new OutputCollector(outputLimitBytes); @@ -255,6 +269,19 @@ function readConfig() { return { oauth, model, codexHome, prompt }; } +function createAuthLock(codexHome) { + const lockName = crypto.createHash("sha256").update(codexHome).digest("hex"); + const lockPath = path.join(codexHome, `.codex-auth-${lockName}.lock`); + + try { + const lockHandle = fs.openSync(lockPath, "wx", FILE_MODE_PRIVATE); + tempFiles.trackFile(lockPath); + return lockHandle; + } catch { + fail("Unable to lock Codex auth.json."); + } +} + function setupAuth(oauth, codexHome) { try { fs.mkdirSync(codexHome, { recursive: true, mode: DIR_MODE_PRIVATE }); @@ -267,16 +294,7 @@ function setupAuth(oauth, codexHome) { const tempDir = makeTempDir(codexHome); const authFile = makeTempFile(tempDir, "auth"); const authPath = path.join(codexHome, "auth.json"); - const lockName = crypto.createHash("sha256").update(codexHome).digest("hex"); - const lockPath = path.join(codexHome, `.codex-auth-${lockName}.lock`); - - let lockHandle; - try { - lockHandle = fs.openSync(lockPath, "wx", FILE_MODE_PRIVATE); - tempFiles.trackFile(lockPath); - } catch { - fail("Unable to lock Codex auth.json."); - } + const lockHandle = createAuthLock(codexHome); validateAuth(oauth, authFile);