處理 AI review findings 並改寫 Node.js entrypoint #1
@@ -9,7 +9,7 @@ ARG CODEX_DOC_MARKETPLACE_REF=f8da961328a85f267b4402566e127310370169da
|
||||
ARG CODEX_CODE_REVIEW_MARKETPLACE_REF=9e016edff016d58f4d64e0a5468220d35a0f657b
|
||||
|
||||
|
Ghost marked this conversation as resolved
|
||||
# 安裝必要的工具
|
||||
RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git jq util-linux
|
||||
RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git jq nodejs
|
||||
|
||||
# 安裝 Codex CLI 工具
|
||||
RUN install_script="$(mktemp)" \
|
||||
@@ -27,8 +27,9 @@ RUN codex plugin marketplace add "https://gitea.jsc.idv.tw/plugins/doc.git" --re
|
||||
&& codex plugin add "jsc@doc" \
|
||||
&& codex plugin add "jsc@code-review"
|
||||
|
||||
COPY app /app
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Mage
**問題**:在 Dockerfile 中直接使用 RUN 來下載並執行安裝腳本,沒有進行網路連接穩定性的驗證或完整的錯誤恢復機制。一旦網路不穩導致腳本不完整,後續的 sha256sum 檢查會失敗,但 Dockerfile 層疊技術可能會導致中間層殘留損壞的檔案。
**建議**:將下載、SHA256 驗證與安裝合併在同一個 RUN 指令中,並加入重試機制(如已有的 --retry),確保每一層的原子性。
|
||||
RUN chmod +x /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh /app/main.js
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const { spawn } = require("child_process");
|
||||
|
||||
const DEFAULT_PROMPT = "請自我介紹";
|
||||
const createdPaths = new Set();
|
||||
|
||||
function removeIfCreated(filePath) {
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Bard
**問題**:全域變數 `createdPaths` 在檔案層級被宣告,讓函式產生強依賴,缺乏封裝性,讀起來不夠優雅。
**建議**:將臨時檔案管理邏輯封裝成一個類別(如 `TempFileRegistry`),讓狀態更具備物件導向的封裝性。
|
||||
if (!filePath || !createdPaths.has(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
fs.rmSync(filePath, { force: true });
|
||||
} catch {
|
||||
// Best-effort cleanup only.
|
||||
}
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Leo
**問題**:在 `removeIfCreated` 函式中靜默捕捉錯誤 (`catch { ... }`),這會遮蔽潛在的權限或檔案系統問題,使除錯困難。
**建議**:建議至少加上 `console.error` 或在開發/除錯模式下將錯誤拋出,以便在清除失敗時能收到警示。
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
for (const filePath of Array.from(createdPaths).reverse()) {
|
||||
removeIfCreated(filePath);
|
||||
}
|
||||
}
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Rogue
**問題**:在 `makeTempFile` 中,使用了 `Math.random().toString(16).slice(2)` 來生成隨機檔名。對於高頻率呼叫的場景,這會產生不必要的計算開銷與效能損耗。
**建議**:建議使用 Node.js 內建的 `crypto.randomBytes` 或 `crypto.randomUUID`,雖然效能略有差異但更具安全性與標準化,且能減少字串轉換次數。
|
||||
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Bard
**問題**:隨機檔案名稱的生成邏輯過於冗長且複雜,破壞了程式碼的簡潔美感。
**建議**:建議使用 Node.js 原生的 `crypto` 模組,例如 `crypto.randomBytes(16).toString('hex')`,讓產生的字串更優雅、清晰。
|
||||
function makeTempFile(dir, prefix) {
|
||||
const random = `${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}`;
|
||||
const filePath = path.join(dir, `${prefix}.${random}`);
|
||||
const fd = fs.openSync(filePath, "wx", 0o600);
|
||||
fs.closeSync(fd);
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Mage
**問題**:在 `makeTempFile` 中使用了 `fs.openSync(filePath, "wx", 0o600)`。如果在 `fs.closeSync(fd)` 之前程式因例外或強制終止(SIGKILL),該檔案會留在硬碟上直到下次清理或手動刪除,且其檔案描述子會持續開啟直到 process 結束。
**建議**:建議使用 `fs.mkdtempSync` 建立獨立目錄,將所有臨時檔案放入該目錄,並在 `cleanup` 時直接移除整個目錄,以確保清理的原子性與完整性。
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Bard
**問題**:檔案權限(如 `0o600`, `0o700`)以數字字面量多次出現,散落在程式碼中,降低了可讀性與一致性。
**建議**:在檔案上方定義權限常數(例如 `const FILE_MODE_PRIVATE = 0o600;`),讓語義更清晰。
|
||||
createdPaths.add(filePath);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function appendGithubOutput(status, output) {
|
||||
const outputFile = process.env.GITHUB_OUTPUT;
|
||||
if (!outputFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
let delimiter;
|
||||
do {
|
||||
delimiter = `CODEX_OUTPUT_${Math.random().toString(36).slice(2)}${Date.now()}`;
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Rogue
**問題**:在 `TempFileRegistry` 的 `cleanup` 方法中,每次呼叫都使用 `Array.from` 將 Set 轉換為陣列,這在頻繁清理時會產生無謂的記憶體開銷。
**建議**:若無強烈反向迭代的需求,可考慮直接使用 `forEach` 遍歷 Set。若有嚴格順序需求,建議改用其他結構管理,避免每次 cleanup 都額外配置陣列。
|
||||
} while (output.includes(delimiter));
|
||||
|
||||
fs.appendFileSync(
|
||||
outputFile,
|
||||
`status=${status}\noutput<<${delimiter}\n${output}${output.endsWith("\n") ? "" : "\n"}${delimiter}\n`,
|
||||
{ encoding: "utf8", mode: 0o600 },
|
||||
);
|
||||
}
|
||||
|
||||
function fail(message, code = 1) {
|
||||
console.error(message);
|
||||
appendGithubOutput("failed", message);
|
||||
cleanup();
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Rogue
**問題**:在 `validateAuth` 中,為了驗證 base64 字串是否符合 base64 格式,進行了多次正規表達式替換與編解碼運算(如 `encodedAuth.replace`、`Buffer.from`、`decoded.toString('base64')` 等),這在每次執行都會發生的情況下,浪費了不必要的 CPU 週期。
**建議**:如果目的只是驗證結構,建議盡量簡化邏輯。可以直接將字串嘗試轉換為 Buffer 並檢查 `toString('base64')` 是否匹配,避免多重正規表達式替換。
|
||||
function validateAuth(encodedAuth, authFile) {
|
||||
const decoded = Buffer.from(encodedAuth, "base64");
|
||||
|
||||
if (decoded.length === 0 && encodedAuth.length > 0) {
|
||||
fail("OAUTH must be valid base64 encoded Codex auth.json.");
|
||||
}
|
||||
|
||||
const normalized = encodedAuth.replace(/\s+/g, "");
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Bard
**問題**:Base64 的驗證過程充滿了複雜的字串正規化與取代操作,讀起來像是在解迷宮,而非驗證身分。
**建議**:將驗證邏輯拆解或簡化,明確劃分「解碼」、「正規化」與「比較」三個步驟,提升程式碼的可讀性與可維護性。
|
||||
if (decoded.toString("base64").replace(/=+$/, "") !== normalized.replace(/=+$/, "")) {
|
||||
fail("OAUTH must be valid base64 encoded Codex auth.json.");
|
||||
}
|
||||
|
||||
fs.writeFileSync(authFile, decoded, { mode: 0o600 });
|
||||
|
||||
let parsed;
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Mage
**問題**:在 `validateAuth` 中,僅透過 `JSON.parse` 檢查 JSON 格式,但未針對 Codex 預期的 auth.json 結構(如必要的欄位)進行 Schema 驗證。如果傳入的 JSON 格式正確但內容無效,可能會導致 `codex exec` 在後續執行時失敗。
**建議**:建議加入對 JSON 內容的簡單結構驗證(例如確認是否有 `token` 或必要的連線設定欄位)。
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Mage
**問題**:在 `appendGithubOutput` 函式中,當 `output` 內容極大時,此處會將整個 `output` 字串在記憶體中進行檢查(`output.includes(delimiter)`)與多次複製。這可能導致在處理極端長度輸出時發生記憶體不足的問題。
**建議**:建議限制 `delimiter` 嘗試次數,或在檢查時避免讀取整個 `output` 字串,改用串流處理方式。
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:OutputCollector 的截斷機制未被測試。
**建議**:增加測試案例模擬輸出超過 DEFAULT_OUTPUT_LIMIT_BYTES,驗證截斷提示。
|
||||
try {
|
||||
parsed = JSON.parse(decoded.toString("utf8"));
|
||||
} catch {
|
||||
fail("Decoded OAUTH must be a JSON object.");
|
||||
}
|
||||
|
||||
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Rogue
**問題**:在 `runCodex` 中,使用 `new Promise` 封裝 `child_process.spawn` 並手動監聽 data 事件來拼接輸出。在高輸出量的場景下,不斷字串拼接(`output += chunk.toString()`)會導致大量記憶體配置與 garbage collection 壓力。
**建議**:如果預期輸出量大,建議將 stdout/stderr 直接寫入檔案流或使用 `Buffer` 陣列收集後最後合併,減少中間字串變更帶來的記憶體浪費。
|
||||
fail("Decoded OAUTH must be a JSON object.");
|
||||
}
|
||||
}
|
||||
|
||||
function runCodex(model, prompt) {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(
|
||||
"codex",
|
||||
[
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Bard
**問題**:makeTempDir 內部使用了硬編碼的 '.codex-action-' 前綴。
**建議**:將前綴提取為常數或設定檔參數。
|
||||
"exec",
|
||||
"--dangerously-bypass-approvals-and-sandbox",
|
||||
"--skip-git-repo-check",
|
||||
"--model",
|
||||
model,
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Leo
**問題**:在 `runCodex` 中使用了 `--dangerously-bypass-approvals-and-sandbox`,這類高風險標記若缺乏適當的說明,未來的維護者可能不清楚其安全意義而誤用或引發風險。
**建議**:建議在 `spawn` 呼叫前加上明確的註解,詳細說明為何在此環境中必須繞過沙盒,以及相關的安全考量。
|
||||
prompt,
|
||||
],
|
||||
{ stdio: ["ignore", "pipe", "pipe"] },
|
||||
);
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:在執行外部指令時沒有設定逾時(timeout),若 Codex CLI 發生無預期的掛起(hang),Action 將會永久卡住而不會自動終止。
**建議**:建議在 `spawn` 的選項中加入 `timeout` 機制,或是主動在啟動後設置一個計時器,當執行時間過長時強制終止子行程。
|
||||
|
||||
let output = "";
|
||||
|
||||
child.stdout.on("data", (chunk) => {
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔴 嚴重 **嚴重等級**:🔴 嚴重
**審查員**:Assassin
**問題**:在執行 `codex` 子行程時,使用了 `--dangerously-bypass-approvals-and-sandbox` 參數。這會完全繞過沙盒機制與審核流程,如果 `prompt` 內容受到攻擊者控制,該 CLI 工具將獲得在容器中執行任意代碼的權限。
**建議**:移除該標記。如果必須使用,請確保 `prompt` 來源完全可信,並將執行權限嚴格限制在最小範圍內。應考慮透過其他機制進行必要的操作,而非直接繞過安全保護。
|
||||
process.stdout.write(chunk);
|
||||
output += chunk.toString();
|
||||
});
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Rogue
**問題**:`runCodex` 函數中的 `spawn` 使用 `{ stdio: ["ignore", "pipe", "pipe"] }`,這會導致 node 程式在輸出流被填滿時阻塞等待,即便透過 `stdout.on('data')` 監聽,在高輸出的情境下仍可能因為緩衝區管理不當而浪費不必要的 CPU 週期。
**建議**:如果預期輸出量很大,建議改用 `child.stdout.pipe(process.stdout)` 直接導向,而非透過 node 的事件迴圈在兩者間搬運資料。
|
||||
|
||||
child.stderr.on("data", (chunk) => {
|
||||
process.stdout.write(chunk);
|
||||
output += chunk.toString();
|
||||
});
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Mage
**問題**:在 `runCodex` 中使用 `spawn` 時,沒有設定 `cwd`。如果 `codex` 工具依賴於當前工作目錄(例如需要編輯當前專案),這在 CI 環境中可能存在風險,雖然目前 CI 通常會設定好目錄,但這是一個隱含的契約。
**建議**:建議明確設定 `cwd` 為 `/github/workspace` 或 CI 定義的專案根目錄,確保 `codex` 運作在預期的上下文中。
|
||||
|
||||
child.on("error", (error) => {
|
||||
output += `${error.message}\n`;
|
||||
resolve({ status: 1, output });
|
||||
});
|
||||
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Bard
**問題**:validateAuth 函式中對於 Base64 的正規化與驗證邏輯混雜在一起,使用了大量的取代與判斷,讀起來節奏凌亂,缺乏優雅感。
**建議**:將驗證邏輯與基礎轉換邏輯抽離,建議提取一個輔助函式專門負責 Base64 格式檢查,使主要流程清晰明瞭。
|
||||
child.on("close", (code) => {
|
||||
resolve({ status: code ?? 1, output });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:直接將所有輸出串接在 `outputChunks` 中,若 CLI 輸出過大的日誌,可能會導致記憶體耗盡(OOM)。
**建議**:建議針對 output 大小設定上限,超過限制時截斷輸出,或是改用串流寫入暫存檔以避免將所有內容存於記憶體。
gitea-actions
commented
嚴重等級:🔴 嚴重 **嚴重等級**:🔴 嚴重
**審查員**:Mage
**問題**:這裡直接使用 `spawn` 執行 `codex` 命令,且參數 `prompt` 是直接從 `process.env.PROMPT` 讀取並傳入的。如果 CI 環境的 `PROMPT` 被惡意竄改,雖使用陣列傳遞參數避免了 shell injection,但 `codex exec` 的邏輯若沒有妥善限制(例如限制可執行指令類型),可能導致攻擊者在 CI Runner 環境執行任意指令。
**建議**:在 `runCodex` 函式中,除了已經加入的 `--dangerously-bypass-approvals-and-sandbox` 外,必須確保對 `prompt` 進行強力的白名單過濾,或改為使用非 `exec` 的子指令來限制權限。
|
||||
|
||||
async function main() {
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Rogue
**問題**:在處理輸出區塊時,使用 `chunks.reduce` 重複計算陣列大小,隨著資料量增加,這會造成不必要的 O(n²) 運算瓶頸,浪費 CPU 週期。
**建議**:應在 closure 中維護一個 `currentSize` 變數來追蹤當前總大小,避免每次有新資料時都重新遍歷整個區塊陣列。
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Rogue
**問題**:處理輸出區塊時使用 chunks.reduce 重複計算陣列大小,造成 O(n²) 運算瓶頸。
**建議**:在 closure 中維護 currentSize 變數追蹤總大小,避免遍歷。
|
||||
process.on("exit", cleanup);
|
||||
process.on("SIGINT", () => {
|
||||
cleanup();
|
||||
process.exit(130);
|
||||
});
|
||||
process.on("SIGTERM", () => {
|
||||
cleanup();
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Maya
**問題**:測試檔案 `tests/entrypoint_test.sh` 有測試 `missing_codex_command`,這很好。但實作中對於 `codex` 執行失敗的各種細節(如權限不足、找不到 binary 等)都統一處理為 `status: 1` 和簡單的訊息,測試僅驗證了 failure 狀態,未驗證具體錯誤來源。
**建議**:考慮在 `main.js` 中根據不同的錯誤類型回傳更細緻的 status code,並在測試中驗證這些 code,能更精確地協助 CI 使用者除錯。
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Maya
**問題**:對於 codex 執行失敗的各種細節(權限不足、找不到 binary 等)都統一處理為 status: 1,測試未驗證具體錯誤來源。
**建議**:根據錯誤類型回傳細緻 status code,並在測試中驗證這些 code。
|
||||
process.exit(143);
|
||||
});
|
||||
|
||||
const oauth = process.env.OAUTH || "";
|
||||
const model = process.env.MODEL || "";
|
||||
const codexHome = process.env.CODEX_HOME || "/root/.codex";
|
||||
const prompt = process.env.PROMPT || DEFAULT_PROMPT;
|
||||
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Rogue
**問題**:validateAuth 中重複執行 decoded.toString('base64') 並進行 normalizedBase64 處理,極度浪費資源。
**建議**:移除這些無謂的重新編碼比較,直接嘗試解碼。
|
||||
if (!oauth) {
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Assassin
**問題**:將 `OAUTH` 環境變數內容解碼並直接寫入 `auth.json`。雖然有檢查 base64 格式與 JSON 結構,但若解碼後的 JSON 內容包含惡意配置(如惡意插件路徑或偽造的 API 憑證),可能導致後續 `codex` CLI 在執行時被劫持或洩漏資料。
**建議**:除了驗證 JSON 結構外,應進一步驗證 `auth.json` 內的欄位是否符合預期格式,並限制其檔案權限為 `600`(已做),確保容器內其他行程無法讀取。
|
||||
fail("OAUTH is required: provide base64 encoded Codex auth.json.");
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Leo
**問題**:`main` 函式過於龐大且職責過多,它同時負責了訊號處理、路徑創建、檔案鎖定、認證驗證以及執行核心邏輯,這降低了程式碼的可讀性與單元測試的困難度。
**建議**:建議將 `main` 拆分為 `validateInput`、`setupAuth`、`runCodexAction` 與 `cleanup` 等子函式,讓職責分離。
|
||||
}
|
||||
|
||||
if (!model) {
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:在 `runCodex` 函數中雖然有處理 `child.on('error', ...)`,但若 `codex` 指令本身不存在(spawn ENOENT),這裡捕捉到的 error stack trace 可能會包含完整的系統路徑資訊,這在 CI 環境中屬於資訊洩漏風險。
**建議**:建議在錯誤處理中,針對 `error.code === 'ENOENT'` 做明確判斷,回傳簡潔的錯誤訊息(例如「找不到 codex 指令」),而非直接回傳完整的 `error.message`。
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:spawn ENOENT 錯誤處理可能洩漏系統路徑資訊。
**建議**:針對 error.code === 'ENOENT' 做明確判斷,回傳簡潔錯誤訊息而非完整 stack trace。
|
||||
fail("MODEL is required.");
|
||||
}
|
||||
|
||||
try {
|
||||
fs.mkdirSync(codexHome, { recursive: true, mode: 0o700 });
|
||||
} catch {
|
||||
fail("Unable to create CODEX_HOME.");
|
||||
}
|
||||
|
||||
const authFile = makeTempFile(codexHome, "auth");
|
||||
const authPath = path.join(codexHome, "auth.json");
|
||||
const lockPath = path.join(os.tmpdir(), `codex-auth-${Buffer.from(codexHome).toString("hex")}.lock`);
|
||||
|
||||
let lockHandle;
|
||||
try {
|
||||
lockHandle = fs.openSync(lockPath, "wx", 0o600);
|
||||
createdPaths.add(lockPath);
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:當 `child.on('close', ...)` 觸發時,若 `code` 為 null,預設回傳 1。雖然這處理了非預期終止,但缺少對 signal 終止(例如 SIGKILL)的具體紀錄,只知道失敗,無法區分是指令執行錯誤還是被系統殺掉。
**建議**:在 `close` 事件中,若 `code` 為 null,可以檢查 `signal` 參數(若有),並在 output 中加入被哪個 signal 終止的資訊,增加除錯便利性。
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:close 事件缺少對 signal 終止(如 SIGKILL)的具體紀錄。
**建議**:檢查 signal 參數,並在 output 中加入被哪個 signal 終止的資訊。
|
||||
} catch {
|
||||
fail("Unable to lock Codex auth.json.");
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Bard
**問題**:runCodex 函式過於臃腫,包含了執行、超時處理、輸出截斷與錯誤捕捉等多重責任,這段旋律太過冗長且複雜。
**建議**:建議將輸出處理 (Output truncation logic) 與超時設定分離為獨立函式,以提升函式的可讀性與維護性。
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Rogue
**問題**:在 `runCodex` 的輸出處理中,每收到一塊資料就進行 `Buffer.concat` 與 `toString`,若資料量大或封包碎,會產生大量不必要的記憶體配置與垃圾回收 (GC) 壓力。
**建議**:只在輸出完成、達到限制或必須輸出結果時才進行合併與轉型,不要在處理每一塊資料時都執行。
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Bard
**問題**:runCodex 函式過於臃腫,包含了過多職責。
**建議**:建議將輸出處理與超時設定分離為獨立函式。
|
||||
}
|
||||
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:parsePositiveInteger 的輸入回退機制未經測試。
**建議**:針對設定變數傳入無效數字或非法格式場景,驗證預設值套用。
|
||||
validateAuth(oauth, authFile);
|
||||
|
||||
if (fs.existsSync(authPath)) {
|
||||
fail("Refusing to overwrite existing Codex auth.json.");
|
||||
}
|
||||
|
||||
fs.copyFileSync(authFile, authPath);
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Mage
**問題**:在 `child.on('close', ...)` 事件中,使用 `Buffer.concat(outputChunks).toString()` 將所有輸出轉為單一字串。如果 `outputChunks` 總大小接近 `DEFAULT_OUTPUT_LIMIT_BYTES` (1MB),這會導致瞬間記憶體使用量增加,且對於極大輸出,字串轉換本身亦有潛在的負載。
**建議**:考慮使用 `Buffer` 處理後續輸出,或在達到 `outputLimitBytes` 時,僅保存 `Buffer` 片段即可,不必轉為大字串。
|
||||
fs.chmodSync(authPath, 0o600);
|
||||
createdPaths.add(authPath);
|
||||
removeIfCreated(authFile);
|
||||
|
||||
const result = await runCodex(model, prompt);
|
||||
appendGithubOutput(result.status === 0 ? "completed" : "failed", result.output);
|
||||
|
||||
if (lockHandle !== undefined) {
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Bard
**問題**:spawn 函式參數陣列過長且散亂,閱讀性較差。
**建議**:將參數拆分為數組變數並展開傳遞。
|
||||
fs.closeSync(lockHandle);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
process.exit(result.status);
|
||||
}
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔴 嚴重 **嚴重等級**:🔴 嚴重
**審查員**:Mage
**問題**:在 `setupAuth` 中,`lockPath` 使用 `os.tmpdir()`。在共享環境中,如果 `CODEX_HOME` 字串相同,會導致所有 process 競爭同一個鎖檔,且如果其他無關的 process 也剛好在 `os.tmpdir()` 中建立相同名稱的檔案,會導致誤判或鎖定失敗。
**建議**:應在 `CODEX_HOME` 內部建立鎖檔,而非使用全域的 `os.tmpdir()`,或者包含更具唯一性的識別碼(如 PID 或更長的路徑雜湊)以確保鎖的隔離性。
gitea-actions
commented
嚴重等級:🔴 嚴重 **嚴重等級**:🔴 嚴重
**審查員**:Maya
**問題**:Codex 超時處理路徑未經測試,無法確保 SIGTERM 能成功發送與訊息正確產出。
**建議**:增加測試案例模擬長期睡眠(如 sleep 10),驗證超時機制與輸出訊息。
|
||||
|
||||
main().catch((error) => {
|
||||
fail(error instanceof Error ? error.message : String(error));
|
||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Rogue
**問題**:在 setupAuth 中,`lockPath` 檔名產生使用了 `Buffer.from(codexHome).toString("hex")`。如果 `codexHome` 非常長,這個檔名可能會超過作業系統的檔案名稱長度限制(通常為 255 bytes),導致鎖定失敗,進而阻斷整個流程。
**建議**:改用 `crypto.createHash('sha256').update(codexHome).digest('hex')` 來產生固定長度的雜湊值作為檔名的一部分,既安全又保證長度可控。
|
||||
});
|
||||
@@ -1,85 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eo pipefail
|
||||
set -euo pipefail
|
||||
|
||||
die() {
|
||||
echo "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
cleanup() {
|
||||
rm -f "${auth_file:-}" "${auth_path:-}" "${auth_lock:-}" "${codex_output:-}"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
if [[ -z "${OAUTH:-}" ]]; then
|
||||
die "OAUTH is required: provide base64 encoded Codex auth.json."
|
||||
fi
|
||||
|
||||
if [[ -z "${MODEL:-}" ]]; then
|
||||
die "MODEL is required."
|
||||
fi
|
||||
|
||||
CODEX_HOME="${CODEX_HOME:-/root/.codex}"
|
||||
PROMPT="${PROMPT:-請自我介紹}"
|
||||
mkdir -p "$CODEX_HOME" || die "Unable to create CODEX_HOME."
|
||||
umask 077
|
||||
|
||||
auth_file="$(mktemp "$CODEX_HOME/auth.XXXXXX")"
|
||||
auth_path="$CODEX_HOME/auth.json"
|
||||
auth_lock="$(mktemp "$CODEX_HOME/auth.lock.XXXXXX")"
|
||||
|
||||
exec 9>"$auth_lock"
|
||||
flock -n 9 || die "Unable to lock Codex auth.json."
|
||||
|
||||
if ! printf '%s\n' "$OAUTH" | base64 -d > "$auth_file"; then
|
||||
die "OAUTH must be valid base64 encoded Codex auth.json."
|
||||
fi
|
||||
|
||||
if ! jq -e 'type == "object"' "$auth_file" >/dev/null; then
|
||||
die "Decoded OAUTH must be a JSON object."
|
||||
fi
|
||||
|
||||
if [[ -e "$auth_path" ]]; then
|
||||
die "Refusing to overwrite existing Codex auth.json."
|
||||
fi
|
||||
|
||||
install -m 600 "$auth_file" "$auth_path"
|
||||
rm -f "$auth_file"
|
||||
|
||||
codex_output="$(mktemp)"
|
||||
|
||||
if codex exec \
|
||||
--dangerously-bypass-approvals-and-sandbox \
|
||||
--skip-git-repo-check \
|
||||
--model "$MODEL" \
|
||||
"$PROMPT" 2>&1 | tee "$codex_output"; then
|
||||
codex_status=0
|
||||
else
|
||||
codex_status="${PIPESTATUS[0]}"
|
||||
fi
|
||||
|
||||
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
|
||||
while :; do
|
||||
output_delimiter="CODEX_OUTPUT_$(mktemp -u XXXXXXXXXXXXXXXX)"
|
||||
|
||||
if ! grep -qxF "$output_delimiter" "$codex_output"; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$codex_status" -eq 0 ]]; then
|
||||
echo "status=completed" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "status=failed" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "output<<$output_delimiter"
|
||||
cat "$codex_output"
|
||||
echo "$output_delimiter"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
exit "$codex_status"
|
||||
exec node "$SCRIPT_DIR/app/main.js"
|
||||
|
||||
嚴重等級:🟡 警告
審查員:Assassin
問題:Codex 外掛市集來源指向
gitea.jsc.idv.tw,未驗證來源的安全性。若該伺服器遭駭,將導致自動安裝惡意或篡改過的外掛,進而導致供應鏈攻擊。建議:若可能,請將外掛來源固定在受信任的內部儲存庫或使用經簽署的外掛版本。確保來源伺服器具有嚴格的存取控管與安全性掃描。
嚴重等級:🟡 警告
審查員:Leo
問題:環境變數使用以空格分隔的字串,若未來名稱中包含空格將導致 shell 展開錯誤,且難以維護。
建議:建議改用換行符號(
)分隔,並在安裝迴圈中使用
IFS=$' '處理,以提高 shell 指令的健壯性。