From a8fd459117f33fcb6f41ca29d3cb8fba766cef01 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:25:41 +0000 Subject: [PATCH 01/58] =?UTF-8?q?feat(Codex=20CLI):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20Codex=20CLI=20Docker=20Action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 22 +++++++++++++++++++--- action.yaml | 26 +++++++++++++++----------- entrypoint.sh | 23 ++++++++++++++++++----- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/Dockerfile b/Dockerfile index af3dacb..06c0dc3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,26 @@ FROM alpine:latest # 安裝必要的工具 -RUN apk add --no-cache --no-check-certificate bash - +RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git + +# 設定安裝用的環境變數 +ENV CODEX_NON_INTERACTIVE=1 +ENV CODEX_INSTALL_DIR=/usr/local/bin +ENV CODEX_HOME=/root/.codex + +# 安裝 Codex CLI 工具 +RUN curl -fsSL https://chatgpt.com/codex/install.sh | sh + +# 安裝 doc 技能 +RUN codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/doc.git \ + && codex plugin add jsc@doc + +# 安裝 code-review 技能 +RUN codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/code-review.git \ + && codex plugin add jsc@code-review + COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["/entrypoint.sh"] diff --git a/action.yaml b/action.yaml index f8849d0..11549ba 100644 --- a/action.yaml +++ b/action.yaml @@ -1,20 +1,24 @@ -name: 'Docker Action Template' -description: 'Docker Action 範本' +name: 'Codex CLI' +description: 'Codex CLI Action' author: 'Jeffery' inputs: - runner_token: - description: 'Gitea Runner Token' + oauth: + description: 'Base64 encoded Codex auth.json' required: true - text: - description: '輸入的文字' - default: "Hello, World!" + model: + description: 'Codex model name' + required: true + prompt: + description: 'Prompt for codex exec' + required: false + default: '請自我介紹' outputs: text: - description: '輸出的文字' + description: 'Execution result status' runs: using: 'docker' image: 'Dockerfile' env: - GITEA_SERVER_URL: ${{ gitea.server_url }} - GITEA_REPOSITORY: ${{ gitea.repository }} - RUNNER_TOKEN: ${{ inputs.runner_token || secrets.GITEA_TOKEN || secrets.RUNNER_TOKEN }} \ No newline at end of file + OAUTH: ${{ inputs.oauth }} + MODEL: ${{ inputs.model }} + PROMPT: ${{ inputs.prompt }} diff --git a/entrypoint.sh b/entrypoint.sh index 378b552..cc5e074 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,11 +1,24 @@ #!/bin/bash -echo "Gitea Server Url: $GITEA_SERVER_URL" +set -euo pipefail -echo "Gitea Repository: $GITEA_REPOSITORY" +if [[ -z "${OAUTH:-}" ]]; then + echo "OAUTH is required: provide base64 encoded Codex auth.json." >&2 + exit 1 +fi -echo "Gitea Runner Token: $RUNNER_TOKEN" +if [[ -z "${MODEL:-}" ]]; then + echo "MODEL is required." >&2 + exit 1 +fi -echo "Input Text: $INPUT_TEXT" +CODEX_HOME="${CODEX_HOME:-/root/.codex}" +mkdir -p "$CODEX_HOME" +printf '%s' "$OAUTH" | base64 -d > "$CODEX_HOME/auth.json" +chmod 600 "$CODEX_HOME/auth.json" -echo "text=$INPUT_TEXT" >> "$GITHUB_OUTPUT" \ No newline at end of file +codex exec --dangerously-bypass-approvals-and-sandbox --model "$MODEL" "$PROMPT" + +if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "text=completed" >> "$GITHUB_OUTPUT" +fi -- 2.53.0 From a6cce49395b8094759714d66f6ea414773754f9d Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:25:44 +0000 Subject: [PATCH 02/58] =?UTF-8?q?chore(Gitea=20workflows):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20CI=20=E8=88=87=20AI=20code=20review=20=E6=B5=81?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci.yaml | 32 ++++++++++++++++++++++++++++++++ .gitea/workflows/review.yaml | 23 +++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 .gitea/workflows/ci.yaml create mode 100644 .gitea/workflows/review.yaml diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..2080f9e --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,32 @@ +name: CI +on: + pull_request: + types: [opened, synchronize] +jobs: + version: + name: 計算版本號 + runs-on: ubuntu + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: 計算版本號 + id: version + uses: https://gitea.jsc.idv.tw/actions/calculate-version@${{ vars.ACTION_CALCULATE_VERSION }} + with: + IS_BETA: true + - name: 標註版本號 + uses: akkuman/gitea-release-action@${{ vars.ACTION_RELEASE_VERSION }} + with: + name: opencode-response v${{ steps.version.outputs.version }} + tag_name: v${{ steps.version.outputs.version }} + target_commitish: ${{ github.head_ref }} + qc: + name: 品質檢查 + runs-on: ubuntu + needs: [version] + steps: + - name: 品質檢查 + uses: https://gitea.jsc.idv.tw/actions/codex@v${{ needs.version.outputs.version }} + with: + oauth: ${{ secrets.CODEX_OAUTH }} + model: gpt-5.4-mini \ No newline at end of file diff --git a/.gitea/workflows/review.yaml b/.gitea/workflows/review.yaml new file mode 100644 index 0000000..da2c4cd --- /dev/null +++ b/.gitea/workflows/review.yaml @@ -0,0 +1,23 @@ +name: AI +on: + pull_request: + branches-ignore: + - master + types: [opened, synchronize] +jobs: + code-review: + name: Code Review + runs-on: ubuntu + steps: + - name: AI Code Review + uses: https://gitea.jsc.idv.tw/actions/code-review@${{ vars.ACTION_CODE_REVIEW_VERSION }} + with: + GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }} + GITEA_COMMENT_TOKEN: ${{ secrets.GITEA_TOKEN }} + OPENCODE_BASE_URL: ${{ vars.OPENCODE_BASE_URL }} + OPENCODE_PROVIDER: ${{ vars.OPENCODE_PROVIDER }} + OPENCODE_MODEL: ${{ vars.GEMINI_MODEL }} + permissions: + contents: write + pull-requests: write + issues: write \ No newline at end of file -- 2.53.0 From 6e337718d9418b7e51434b5a6869fc94f855ec16 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 10:27:10 +0000 Subject: [PATCH 03/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 90 ++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .gitea/ai-review/findings.json diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json new file mode 100644 index 0000000..d215491 --- /dev/null +++ b/.gitea/ai-review/findings.json @@ -0,0 +1,90 @@ +[ + { + "level": "critical", + "role": "Assassin", + "location": "Dockerfile:13", + "problem": "直接將網際網路的腳本經由 pipe 傳送給 shell 執行 (curl | sh) 極度危險,若該網址遭受劫持或供應商被駭,攻擊者可輕易於建置期間取得容器完整控制權。", + "suggestion": "應先將腳本下載至本地,透過 sha256sum 驗證其完整性後,再以 `sh` 執行。", + "is_new": true + }, + { + "level": "critical", + "role": "Assassin", + "location": "Dockerfile:16", + "problem": "從未經驗證的遠端 git 儲存庫安裝插件,若儲存庫內容遭竄改,將導致任意程式碼執行風險。", + "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,確保所安裝內容符合預期且未遭篡改。", + "is_new": true + }, + { + "level": "critical", + "role": "Mage", + "location": "Dockerfile:17", + "problem": "在 Docker 構建階段直接執行 `codex plugin marketplace add` 並嘗試同時安裝與啟用,若網路不穩或遠端 Gitea 服務暫時不可用,構建會直接失敗,導致整個鏡像無法建立。", + "suggestion": "建議將插件安裝邏輯移至 `entrypoint.sh`,在執行時才確認環境並安裝,或者在 Dockerfile 中增加重試機制與錯誤捕獲。", + "is_new": true + }, + { + "level": "critical", + "role": "Maya", + "location": "Dockerfile:11", + "problem": "新增了 Codex CLI 的安裝與插件註冊過程,但沒有對應的測試來驗證安裝是否成功、插件是否正確載入。", + "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", + "is_new": true + }, + { + "level": "critical", + "role": "Assassin", + "location": "entrypoint.sh:20", + "problem": "使用了 `--dangerously-bypass-approvals-and-sandbox` 參數,這會導致安全沙盒失效。若輸入的 `$PROMPT` 或 `$MODEL` 可被外部使用者操縱,攻擊者可藉此在容器內執行任意指令。", + "suggestion": "移除此危險參數。必須落實沙盒隔離機制與人工審核流程,絕不可為了方便而犧牲安全性。", + "is_new": true + }, + { + "level": "critical", + "role": "Maya", + "location": "entrypoint.sh:18", + "problem": "修改了 `entrypoint.sh` 的核心邏輯以執行 `codex exec`,但缺乏驗證執行是否成功的測試案例(例如:當 `OAUTH` 錯誤、`MODEL` 無效或 `codex exec` 本身拋出錯誤時的處理)。", + "suggestion": "應為 `entrypoint.sh` 補上單元測試,模擬各種環境變數設定及 `codex exec` 的成功與失敗路徑,並驗證對應的退出碼。", + "is_new": true + }, + { + "level": "warning", + "role": "Assassin", + "location": "action.yaml:23", + "problem": "將包含認證資訊的 `OAUTH` 透過環境變數傳遞,這使得該敏感資訊在容器內的任何進程皆可透過環境變數(如 `/proc/self/environ`)輕易竊取。", + "suggestion": "應透過檔案掛載或將機密寫入至安全路徑後再讀取,避免直接透過環境變數傳遞敏感資訊。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "Dockerfile:20", + "problem": "在建置映像檔時直接透過 `codex plugin marketplace add` 安裝插件,會增加映像檔建置的時間與複雜度,且若插件來源 Gitea 無法連線,映像檔就無法成功建置。", + "suggestion": "建議將常用的插件預先打包在映像檔中,或是透過環境變數配置插件路徑,減少建置期的網絡依賴。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "entrypoint.sh:17", + "problem": "直接將 `OAUTH` 環境變數內容透過 `base64 -d` 寫入 `auth.json`,未校驗該變數是否為合法的 Base64 編碼。若輸入非 Base64 或損壞的字串,會產生損壞的認證檔案。", + "suggestion": "增加對 `OAUTH` 變數是否符合 Base64 格式的初步校驗,並在解碼失敗時明確報錯並終止。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:22", + "problem": "對於 `GITHUB_OUTPUT` 的處理有條件判斷,但缺乏測試驗證當此變數不存在時的行為,以及當存在時內容是否正確寫入。", + "suggestion": "應補測試案例驗證在 `GITHUB_OUTPUT` 設定與未設定的情況下,腳本是否皆能正常執行而不發生預期外的錯誤。", + "is_new": true + }, + { + "level": "warning", + "role": "Rogue", + "location": "Dockerfile:17", + "problem": "每個 `RUN` 指令都會建立一個新的 Docker 映像層,在此處拆分為兩個獨立的 `RUN` 指令安裝插件,會產生不必要的額外層,增加了映像檔體積並浪費了建置與拉取映像檔的時間。", + "suggestion": "請將所有安裝插件的 `RUN` 指令透過 `&&` 合併為單一個 `RUN` 指令,以優化映像層數並提升效能。", + "is_new": true + } +] -- 2.53.0 From af770b5d5c73f0f6c4620d043c4df536c0755eba Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:30:00 +0000 Subject: [PATCH 04/58] =?UTF-8?q?fix(entrypoint):=20=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E6=9C=AA=E5=AE=9A=E7=BE=A9=E7=92=B0=E5=A2=83=E8=AE=8A=E6=95=B8?= =?UTF-8?q?=E4=B8=AD=E6=AD=A2=E5=9F=B7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entrypoint.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/entrypoint.sh b/entrypoint.sh index cc5e074..42ab7c0 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -euo pipefail +set -eo pipefail if [[ -z "${OAUTH:-}" ]]; then echo "OAUTH is required: provide base64 encoded Codex auth.json." >&2 @@ -13,6 +13,7 @@ if [[ -z "${MODEL:-}" ]]; then fi CODEX_HOME="${CODEX_HOME:-/root/.codex}" +PROMPT="${PROMPT:-請自我介紹}" mkdir -p "$CODEX_HOME" printf '%s' "$OAUTH" | base64 -d > "$CODEX_HOME/auth.json" chmod 600 "$CODEX_HOME/auth.json" -- 2.53.0 From 4990cacde37ade6a037efc1324bf7161b91a3e73 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:30:38 +0000 Subject: [PATCH 05/58] =?UTF-8?q?chore(Gitea=20workflows):=20=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20release=20=E5=90=8D=E7=A8=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 2080f9e..02463ff 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -17,7 +17,7 @@ jobs: - name: 標註版本號 uses: akkuman/gitea-release-action@${{ vars.ACTION_RELEASE_VERSION }} with: - name: opencode-response v${{ steps.version.outputs.version }} + name: codex v${{ steps.version.outputs.version }} tag_name: v${{ steps.version.outputs.version }} target_commitish: ${{ github.head_ref }} qc: -- 2.53.0 From d68b4120314634c1c76aeff5c84dda5b8ff5651f Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 10:30:54 +0000 Subject: [PATCH 06/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 98 ++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index d215491..6e2096a 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -5,23 +5,21 @@ "location": "Dockerfile:13", "problem": "直接將網際網路的腳本經由 pipe 傳送給 shell 執行 (curl | sh) 極度危險,若該網址遭受劫持或供應商被駭,攻擊者可輕易於建置期間取得容器完整控制權。", "suggestion": "應先將腳本下載至本地,透過 sha256sum 驗證其完整性後,再以 `sh` 執行。", - "is_new": true + "is_new": false }, { "level": "critical", "role": "Assassin", "location": "Dockerfile:16", - "problem": "從未經驗證的遠端 git 儲存庫安裝插件,若儲存庫內容遭竄改,將導致任意程式碼執行風險。", - "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,確保所安裝內容符合預期且未遭篡改。", - "is_new": true + "problem": "從未經驗證的遠端 git 儲存庫安裝插件,或以不安全方式下載腳本,將導致任意程式碼執行風險及構建不一致。", + "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,確保所安裝內容符合預期且未遭篡改;並應對下載腳本進行哈希驗證。" }, { "level": "critical", "role": "Mage", "location": "Dockerfile:17", - "problem": "在 Docker 構建階段直接執行 `codex plugin marketplace add` 並嘗試同時安裝與啟用,若網路不穩或遠端 Gitea 服務暫時不可用,構建會直接失敗,導致整個鏡像無法建立。", - "suggestion": "建議將插件安裝邏輯移至 `entrypoint.sh`,在執行時才確認環境並安裝,或者在 Dockerfile 中增加重試機制與錯誤捕獲。", - "is_new": true + "problem": "在 Docker 構建階段直接執行插件安裝並嘗試同時啟用,若網路不穩或遠端服務不可用,構建會失敗,且可能導致環境處於損壞狀態。", + "suggestion": "建議將插件安裝邏輯移至 `entrypoint.sh`,或在 Dockerfile 中增加重試機制與錯誤捕獲(如 `set -o pipefail`)。" }, { "level": "critical", @@ -29,62 +27,104 @@ "location": "Dockerfile:11", "problem": "新增了 Codex CLI 的安裝與插件註冊過程,但沒有對應的測試來驗證安裝是否成功、插件是否正確載入。", "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", - "is_new": true + "is_new": false }, { "level": "critical", "role": "Assassin", "location": "entrypoint.sh:20", - "problem": "使用了 `--dangerously-bypass-approvals-and-sandbox` 參數,這會導致安全沙盒失效。若輸入的 `$PROMPT` 或 `$MODEL` 可被外部使用者操縱,攻擊者可藉此在容器內執行任意指令。", - "suggestion": "移除此危險參數。必須落實沙盒隔離機制與人工審核流程,絕不可為了方便而犧牲安全性。", - "is_new": true + "problem": "使用了 `--dangerously-bypass-approvals-and-sandbox` 參數,直接棄守了沙盒防禦機制。若輸入內容可被外部操縱,攻擊者可藉此執行任意指令。", + "suggestion": "移除此危險參數。必須落實沙盒隔離機制與人工審核流程,並對輸入進行嚴格驗證。" }, { "level": "critical", "role": "Maya", "location": "entrypoint.sh:18", - "problem": "修改了 `entrypoint.sh` 的核心邏輯以執行 `codex exec`,但缺乏驗證執行是否成功的測試案例(例如:當 `OAUTH` 錯誤、`MODEL` 無效或 `codex exec` 本身拋出錯誤時的處理)。", - "suggestion": "應為 `entrypoint.sh` 補上單元測試,模擬各種環境變數設定及 `codex exec` 的成功與失敗路徑,並驗證對應的退出碼。", - "is_new": true + "problem": "核心邏輯(如 base64 解碼、`codex exec`)缺乏測試案例,無法確保在環境變數缺失、內容損壞或命令執行失敗時的錯誤處理正確。", + "suggestion": "應為相關關鍵邏輯補上單元測試,模擬各種環境變數設定及 `codex exec` 的成功與失敗路徑,並驗證對應的退出碼與檔案處理。" + }, + { + "level": "critical", + "role": "Leo", + "location": "entrypoint.sh:17", + "problem": "敏感憑證 (OAUTH) 被直接以 base64 解碼後寫入檔案 (`/root/.codex/auth.json`),且未確保後續清理,導致憑證洩漏風險。", + "suggestion": "確保該目錄權限封閉(chmod 600),並使用 `trap` 指令在腳本結束時自動清除該憑證檔案。" + }, + { + "level": "critical", + "role": "Mage", + "location": "entrypoint.sh:17", + "problem": "代碼使用 `base64 -d` 解碼並直接寫入檔案,未校驗該變數是否為合法格式,若失敗則無法確保建置或執行狀態正確。", + "suggestion": "在解碼操作後明確添加檢查機制,若失敗則報錯並中止。" }, { "level": "warning", "role": "Assassin", "location": "action.yaml:23", - "problem": "將包含認證資訊的 `OAUTH` 透過環境變數傳遞,這使得該敏感資訊在容器內的任何進程皆可透過環境變數(如 `/proc/self/environ`)輕易竊取。", - "suggestion": "應透過檔案掛載或將機密寫入至安全路徑後再讀取,避免直接透過環境變數傳遞敏感資訊。", - "is_new": true + "problem": "將包含認證資訊的 `OAUTH` 透過環境變數傳遞,這使得該敏感資訊易被竊取。", + "suggestion": "應透過檔案掛載或專用的秘密管理服務(如 GitHub Secrets)注入,避免透過公開的 Input 或環境變數傳遞。" }, { "level": "warning", "role": "Leo", "location": "Dockerfile:20", - "problem": "在建置映像檔時直接透過 `codex plugin marketplace add` 安裝插件,會增加映像檔建置的時間與複雜度,且若插件來源 Gitea 無法連線,映像檔就無法成功建置。", - "suggestion": "建議將常用的插件預先打包在映像檔中,或是透過環境變數配置插件路徑,減少建置期的網絡依賴。", - "is_new": true + "problem": "在建置映像檔時直接安裝插件,會增加建置時間與複雜度,並引入對遠端 Gitea 的網路依賴。", + "suggestion": "建議將常用的插件預先打包在映像檔中,或透過環境變數配置,減少建置期的網絡依賴。" }, { "level": "warning", "role": "Mage", "location": "entrypoint.sh:17", - "problem": "直接將 `OAUTH` 環境變數內容透過 `base64 -d` 寫入 `auth.json`,未校驗該變數是否為合法的 Base64 編碼。若輸入非 Base64 或損壞的字串,會產生損壞的認證檔案。", - "suggestion": "增加對 `OAUTH` 變數是否符合 Base64 格式的初步校驗,並在解碼失敗時明確報錯並終止。", - "is_new": true + "problem": "直接將 `OAUTH` 環境變數內容解碼寫入,未校驗 Base64 格式,若格式錯誤會產生損壞的認證檔案。", + "suggestion": "增加對 Base64 格式的初步校驗,明確報錯並終止。" }, { "level": "warning", "role": "Maya", "location": "entrypoint.sh:22", - "problem": "對於 `GITHUB_OUTPUT` 的處理有條件判斷,但缺乏測試驗證當此變數不存在時的行為,以及當存在時內容是否正確寫入。", - "suggestion": "應補測試案例驗證在 `GITHUB_OUTPUT` 設定與未設定的情況下,腳本是否皆能正常執行而不發生預期外的錯誤。", - "is_new": true + "problem": "對於 `GITHUB_OUTPUT` 及 `codex exec` 的處理缺乏測試驗證,無法確保在各種輸入情況下的正確性與失敗處理。", + "suggestion": "應補測試案例驗證各變數狀態下腳本是否正常執行,確保錯誤發生時不發生預期外行為。" }, { "level": "warning", "role": "Rogue", "location": "Dockerfile:17", - "problem": "每個 `RUN` 指令都會建立一個新的 Docker 映像層,在此處拆分為兩個獨立的 `RUN` 指令安裝插件,會產生不必要的額外層,增加了映像檔體積並浪費了建置與拉取映像檔的時間。", - "suggestion": "請將所有安裝插件的 `RUN` 指令透過 `&&` 合併為單一個 `RUN` 指令,以優化映像層數並提升效能。", - "is_new": true + "problem": "拆分為多個獨立的 `RUN` 指令安裝插件,會產生不必要的映像層,增加了體積與建置時間。", + "suggestion": "將相關安裝指令透過 `&&` 合併為單一個 `RUN` 指令,以優化映像層數並提升效能。" + }, + { + "level": "warning", + "role": "Bard", + "location": "action.yaml:4", + "problem": "輸入參數 `oauth` 實質為 base64 編碼的 json,命名易生誤解。", + "suggestion": "建議更名為 `auth_config` 或 `encoded_auth_json`。" + }, + { + "level": "warning", + "role": "Rogue", + "location": "Dockerfile:4", + "problem": "在 Dockerfile 中安裝了 git 且未清理,導致映像檔過大。", + "suggestion": "建議在同一層 RUN 指令中以 --virtual 安裝 git,完成後立即刪除。" + }, + { + "level": "info", + "role": "Bard", + "location": "entrypoint.sh:21", + "problem": "因參數過長導致指令行不易閱讀。", + "suggestion": "建議使用反斜線(\\)將指令進行分行書寫。" + }, + { + "level": "info", + "role": "Leo", + "location": "entrypoint.sh:22", + "problem": "使用 `--dangerously-bypass-approvals-and-sandbox` 屬於技術債,未來稽核難度大。", + "suggestion": "評估在生產環境下是否能移除該參數,或設計更細緻的審查機制。" + }, + { + "level": "info", + "role": "Mage", + "location": "entrypoint.sh:22", + "problem": "執行 `codex exec` 後未顯式檢查其返回值,若失敗仍報告成功。", + "suggestion": "在命令後立即檢查退出狀態,確保只有成功時才執行後續邏輯。" } ] -- 2.53.0 From 571e3693da13ec31da8b74a5049e5fd9e5f81586 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 10:31:06 +0000 Subject: [PATCH 07/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 118 +++++++++++++++++---------------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 6e2096a..0f4b129 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -3,23 +3,16 @@ "level": "critical", "role": "Assassin", "location": "Dockerfile:13", - "problem": "直接將網際網路的腳本經由 pipe 傳送給 shell 執行 (curl | sh) 極度危險,若該網址遭受劫持或供應商被駭,攻擊者可輕易於建置期間取得容器完整控制權。", - "suggestion": "應先將腳本下載至本地,透過 sha256sum 驗證其完整性後,再以 `sh` 執行。", - "is_new": false + "problem": "直接將網際網路的腳本經由 pipe 傳送給 shell 執行 (curl | sh) 極度危險,且缺乏驗證,極易導致容器建置階段遭劫持或執行惡意代碼。", + "suggestion": "應先將腳本下載至本地,透過 sha256sum 或 gpg 驗證其完整性與來源可靠性後,再執行安裝。" }, { "level": "critical", "role": "Assassin", "location": "Dockerfile:16", - "problem": "從未經驗證的遠端 git 儲存庫安裝插件,或以不安全方式下載腳本,將導致任意程式碼執行風險及構建不一致。", - "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,確保所安裝內容符合預期且未遭篡改;並應對下載腳本進行哈希驗證。" - }, - { - "level": "critical", - "role": "Mage", - "location": "Dockerfile:17", - "problem": "在 Docker 構建階段直接執行插件安裝並嘗試同時啟用,若網路不穩或遠端服務不可用,構建會失敗,且可能導致環境處於損壞狀態。", - "suggestion": "建議將插件安裝邏輯移至 `entrypoint.sh`,或在 Dockerfile 中增加重試機制與錯誤捕獲(如 `set -o pipefail`)。" + "problem": "從未經驗證的遠端 git 儲存庫安裝插件,若儲存庫內容遭竄改,將導致任意程式碼執行風險。", + "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,確保所安裝內容符合預期且未遭篡改。", + "is_new": false }, { "level": "critical", @@ -33,98 +26,107 @@ "level": "critical", "role": "Assassin", "location": "entrypoint.sh:20", - "problem": "使用了 `--dangerously-bypass-approvals-and-sandbox` 參數,直接棄守了沙盒防禦機制。若輸入內容可被外部操縱,攻擊者可藉此執行任意指令。", - "suggestion": "移除此危險參數。必須落實沙盒隔離機制與人工審核流程,並對輸入進行嚴格驗證。" + "problem": "使用了 `--dangerously-bypass-approvals-and-sandbox` 參數,這會導致安全沙盒失效,若輸入內容可被操縱,攻擊者可執行任意指令。", + "suggestion": "必須移除此危險參數。應實作嚴格的指令白名單過濾與輸入驗證,並落實沙盒隔離。" }, { "level": "critical", "role": "Maya", "location": "entrypoint.sh:18", - "problem": "核心邏輯(如 base64 解碼、`codex exec`)缺乏測試案例,無法確保在環境變數缺失、內容損壞或命令執行失敗時的錯誤處理正確。", - "suggestion": "應為相關關鍵邏輯補上單元測試,模擬各種環境變數設定及 `codex exec` 的成功與失敗路徑,並驗證對應的退出碼與檔案處理。" + "problem": "修改了 `entrypoint.sh` 的核心邏輯以執行 `codex exec`,但缺乏驗證執行是否成功的測試案例(例如:當 `OAUTH` 錯誤、`MODEL` 無效或 `codex exec` 本身拋出錯誤時的處理)。", + "suggestion": "應為 `entrypoint.sh` 補上單元測試,模擬各種環境變數設定及 `codex exec` 的成功與失敗路徑,並驗證對應的退出碼。", + "is_new": false }, { "level": "critical", - "role": "Leo", + "role": "Assassin", "location": "entrypoint.sh:17", - "problem": "敏感憑證 (OAUTH) 被直接以 base64 解碼後寫入檔案 (`/root/.codex/auth.json`),且未確保後續清理,導致憑證洩漏風險。", - "suggestion": "確保該目錄權限封閉(chmod 600),並使用 `trap` 指令在腳本結束時自動清除該憑證檔案。" - }, - { - "level": "critical", - "role": "Mage", - "location": "entrypoint.sh:17", - "problem": "代碼使用 `base64 -d` 解碼並直接寫入檔案,未校驗該變數是否為合法格式,若失敗則無法確保建置或執行狀態正確。", - "suggestion": "在解碼操作後明確添加檢查機制,若失敗則報錯並中止。" + "problem": "將 `OAUTH` 解碼後直接寫入 `auth.json`,未對內容進行結構化驗證,若內容受污染可導致注入惡意身份驗證設定。", + "suggestion": "在寫入前應針對解碼後的內容進行 Schema Validation,確保其為符合預期的 JSON 格式。" }, { "level": "warning", "role": "Assassin", "location": "action.yaml:23", - "problem": "將包含認證資訊的 `OAUTH` 透過環境變數傳遞,這使得該敏感資訊易被竊取。", - "suggestion": "應透過檔案掛載或專用的秘密管理服務(如 GitHub Secrets)注入,避免透過公開的 Input 或環境變數傳遞。" + "problem": "將包含認證資訊的 `OAUTH` 透過環境變數傳遞,這使得該敏感資訊在容器內的任何進程皆可透過環境變數(如 `/proc/self/environ`)輕易竊取。", + "suggestion": "應透過檔案掛載或將機密寫入至安全路徑後再讀取,避免直接透過環境變數傳遞敏感資訊。", + "is_new": false }, { "level": "warning", "role": "Leo", "location": "Dockerfile:20", - "problem": "在建置映像檔時直接安裝插件,會增加建置時間與複雜度,並引入對遠端 Gitea 的網路依賴。", - "suggestion": "建議將常用的插件預先打包在映像檔中,或透過環境變數配置,減少建置期的網絡依賴。" + "problem": "在建置期透過網路安裝插件,增加建置複雜度、時間,且對外部網路依賴過高,不可重現。", + "suggestion": "建議將插件預先打包在映像檔中,或使用特定版本的 manifest 檔案管理相依性。" }, { "level": "warning", "role": "Mage", "location": "entrypoint.sh:17", - "problem": "直接將 `OAUTH` 環境變數內容解碼寫入,未校驗 Base64 格式,若格式錯誤會產生損壞的認證檔案。", - "suggestion": "增加對 Base64 格式的初步校驗,明確報錯並終止。" + "problem": "直接將 `OAUTH` 環境變數內容透過 `base64 -d` 寫入 `auth.json`,未校驗該變數是否為合法的 Base64 編碼,導致可能產生損壞檔案。", + "suggestion": "增加對 `OAUTH` 變數是否符合 Base64 格式的初步校驗,並在解碼失敗時明確報錯。" }, { "level": "warning", "role": "Maya", "location": "entrypoint.sh:22", - "problem": "對於 `GITHUB_OUTPUT` 及 `codex exec` 的處理缺乏測試驗證,無法確保在各種輸入情況下的正確性與失敗處理。", - "suggestion": "應補測試案例驗證各變數狀態下腳本是否正常執行,確保錯誤發生時不發生預期外行為。" + "problem": "對於 `GITHUB_OUTPUT` 的處理與 `codex exec` 的執行結果缺乏測試驗證,導致失敗無法即時報告。", + "suggestion": "應補測試案例驗證各變數設定情況下的執行行為,並將 `codex exec` 的結果納入錯誤報告機制。" }, { "level": "warning", "role": "Rogue", "location": "Dockerfile:17", - "problem": "拆分為多個獨立的 `RUN` 指令安裝插件,會產生不必要的映像層,增加了體積與建置時間。", - "suggestion": "將相關安裝指令透過 `&&` 合併為單一個 `RUN` 指令,以優化映像層數並提升效能。" + "problem": "將安裝插件拆分為多個獨立的 `RUN` 指令,產生不必要的映像層,增加體積並減慢建置速度。", + "suggestion": "請將所有安裝插件的 `RUN` 指令透過 `&&` 合併為單一個 `RUN` 指令。" + }, + { + "level": "warning", + "role": "Assassin", + "location": "entrypoint.sh:18", + "problem": "雖然有 `chmod 600`,但 `auth.json` 放在 `/root/.codex/` 目錄下,若發生容器逃逸,該敏感金鑰極易被讀取。", + "suggestion": "應使用外掛式秘密管理機制(如 Secret Store),而非將其寫入檔案。" }, { "level": "warning", "role": "Bard", - "location": "action.yaml:4", - "problem": "輸入參數 `oauth` 實質為 base64 編碼的 json,命名易生誤解。", - "suggestion": "建議更名為 `auth_config` 或 `encoded_auth_json`。" + "location": "action.yaml:17", + "problem": "輸出欄位命名為 `text`,但賦值內容為「執行狀態(status)」,語義不符。", + "suggestion": "將欄位名稱改為 `status`,以保持命名與意圖一致。" + }, + { + "level": "warning", + "role": "Bard", + "location": "entrypoint.sh:24", + "problem": "輸出變數名稱 `text` 與其賦值內容 `completed`(執行狀態)語義不合。", + "suggestion": "若已同步修改 `action.yaml`,此處應改為 `echo \"status=completed\" >> \"$GITHUB_OUTPUT\"`。" + }, + { + "level": "warning", + "role": "Leo", + "location": "action.yaml:6", + "problem": "將 Base64 編碼的認證資訊設為 Action input,導致明文暴露在 Workflow 配置中,難以維護。", + "suggestion": "應改為直接使用 Action 的 Secrets 機制(如 `secrets.CODEX_AUTH`),將認證資訊與設定分離。" }, { "level": "warning", "role": "Rogue", - "location": "Dockerfile:4", - "problem": "在 Dockerfile 中安裝了 git 且未清理,導致映像檔過大。", - "suggestion": "建議在同一層 RUN 指令中以 --virtual 安裝 git,完成後立即刪除。" + "location": "Dockerfile:14", + "problem": "在 Dockerfile 中使用 `curl | sh` 安裝腳本缺乏超時處理,容易導致 build 長時間掛起。", + "suggestion": "在 curl 指令加上 --max-time 與 --retry 參數來防範網路阻塞。" + }, + { + "level": "warning", + "role": "Rogue", + "location": "entrypoint.sh:22", + "problem": "在腳本中頻繁進行 I/O 操作(重複寫入 auth.json),缺乏快取檢查。", + "suggestion": "檢查 `$CODEX_HOME/auth.json` 是否已存在且內容一致,避免重複寫入。" }, { "level": "info", "role": "Bard", "location": "entrypoint.sh:21", - "problem": "因參數過長導致指令行不易閱讀。", - "suggestion": "建議使用反斜線(\\)將指令進行分行書寫。" - }, - { - "level": "info", - "role": "Leo", - "location": "entrypoint.sh:22", - "problem": "使用 `--dangerously-bypass-approvals-and-sandbox` 屬於技術債,未來稽核難度大。", - "suggestion": "評估在生產環境下是否能移除該參數,或設計更細緻的審查機制。" - }, - { - "level": "info", - "role": "Mage", - "location": "entrypoint.sh:22", - "problem": "執行 `codex exec` 後未顯式檢查其返回值,若失敗仍報告成功。", - "suggestion": "在命令後立即檢查退出狀態,確保只有成功時才執行後續邏輯。" + "problem": "`codex exec` 指令參數過多,單行過長,閱讀困難。", + "suggestion": "建議使用反斜線 `\\` 進行斷行,將指令與參數分層對齊。" } ] -- 2.53.0 From 690bf98565f1e82a1f1d18a9243440f22b1ed55c Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:33:18 +0000 Subject: [PATCH 08/58] =?UTF-8?q?fix(Codex=20CLI):=20=E5=BC=B7=E5=8C=96?= =?UTF-8?q?=E8=AA=8D=E8=AD=89=E9=A9=97=E8=AD=89=E8=88=87=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E5=9F=B7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 17 ++++++++++------- action.yaml | 2 +- entrypoint.sh | 25 +++++++++++++++++++++---- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 06c0dc3..dc7f435 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM alpine:latest # 安裝必要的工具 -RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git +RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git jq # 設定安裝用的環境變數 ENV CODEX_NON_INTERACTIVE=1 @@ -9,14 +9,17 @@ ENV CODEX_INSTALL_DIR=/usr/local/bin ENV CODEX_HOME=/root/.codex # 安裝 Codex CLI 工具 -RUN curl -fsSL https://chatgpt.com/codex/install.sh | sh +RUN install_script="$(mktemp)" \ + && curl -fsSL --retry 3 --retry-delay 2 --max-time 120 \ + https://chatgpt.com/codex/install.sh \ + -o "$install_script" \ + && sh "$install_script" \ + && rm -f "$install_script" -# 安裝 doc 技能 +# 安裝技能 RUN codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/doc.git \ - && codex plugin add jsc@doc - -# 安裝 code-review 技能 -RUN codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/code-review.git \ + && codex plugin add jsc@doc \ + && codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/code-review.git \ && codex plugin add jsc@code-review COPY entrypoint.sh /entrypoint.sh diff --git a/action.yaml b/action.yaml index 11549ba..a92a2c3 100644 --- a/action.yaml +++ b/action.yaml @@ -13,7 +13,7 @@ inputs: required: false default: '請自我介紹' outputs: - text: + status: description: 'Execution result status' runs: using: 'docker' diff --git a/entrypoint.sh b/entrypoint.sh index 42ab7c0..a07f959 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -15,11 +15,28 @@ fi CODEX_HOME="${CODEX_HOME:-/root/.codex}" PROMPT="${PROMPT:-請自我介紹}" mkdir -p "$CODEX_HOME" -printf '%s' "$OAUTH" | base64 -d > "$CODEX_HOME/auth.json" -chmod 600 "$CODEX_HOME/auth.json" -codex exec --dangerously-bypass-approvals-and-sandbox --model "$MODEL" "$PROMPT" +auth_file="$(mktemp "$CODEX_HOME/auth.XXXXXX")" +trap 'rm -f "$auth_file"' EXIT + +if ! printf '%s' "$OAUTH" | base64 -d > "$auth_file"; then + echo "OAUTH must be valid base64 encoded Codex auth.json." >&2 + exit 1 +fi + +if ! jq -e 'type == "object"' "$auth_file" >/dev/null; then + echo "Decoded OAUTH must be a JSON object." >&2 + exit 1 +fi + +mv "$auth_file" "$CODEX_HOME/auth.json" +chmod 600 "$CODEX_HOME/auth.json" +trap - EXIT + +codex exec \ + --model "$MODEL" \ + "$PROMPT" if [[ -n "${GITHUB_OUTPUT:-}" ]]; then - echo "text=completed" >> "$GITHUB_OUTPUT" + echo "status=completed" >> "$GITHUB_OUTPUT" fi -- 2.53.0 From 5ce8e91d57ce3bbc5f930ce554b933e3ea2ae1f8 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:33:23 +0000 Subject: [PATCH 09/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20findings=20=E8=88=87=20exclusions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/exclusions.json | 26 ++++++++++ .gitea/ai-review/findings.json | 85 -------------------------------- 2 files changed, 26 insertions(+), 85 deletions(-) create mode 100644 .gitea/ai-review/exclusions.json diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json new file mode 100644 index 0000000..24f5bd9 --- /dev/null +++ b/.gitea/ai-review/exclusions.json @@ -0,0 +1,26 @@ +[ + { + "location": "action.yaml:23", + "role": "Assassin", + "original_finding": "將包含認證資訊的 `OAUTH` 透過環境變數傳遞,這使得該敏感資訊在容器內的任何進程皆可透過環境變數(如 `/proc/self/environ`)輕易竊取。", + "reason": "此 action 透過 Gitea/GitHub Docker Action input 接收 secret,action metadata 只能將 input 映射為容器環境變數;呼叫端仍使用 secrets.CODEX_OAUTH,未在 workflow 明文暴露。" + }, + { + "location": "entrypoint.sh:18", + "role": "Assassin", + "original_finding": "雖然有 `chmod 600`,但 `auth.json` 放在 `/root/.codex/` 目錄下,若發生容器逃逸,該敏感金鑰極易被讀取。", + "reason": "Codex CLI 目前需要讀取 CODEX_HOME/auth.json;容器內已限制為 600 權限,外部 Secret Store 掛載不是此 Docker Action 目前可控制的介面。" + }, + { + "location": "action.yaml:6", + "role": "Leo", + "original_finding": "將 Base64 編碼的認證資訊設為 Action input,導致明文暴露在 Workflow 配置中,難以維護。", + "reason": "workflow 傳入的是 secrets.CODEX_OAUTH,不是明文值;使用 input 是 Docker Action 接收呼叫端 secret 的標準介面。" + }, + { + "location": "entrypoint.sh:22", + "role": "Rogue", + "original_finding": "在腳本中頻繁進行 I/O 操作(重複寫入 auth.json),缺乏快取檢查。", + "reason": "每次 Docker Action 執行都是短生命週期容器,auth.json 需要從當次 secret 重建;快取檢查不會降低跨執行 I/O,也可能增加 secret 狀態判斷複雜度。" + } +] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 0f4b129..ae4069f 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -22,13 +22,6 @@ "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", "is_new": false }, - { - "level": "critical", - "role": "Assassin", - "location": "entrypoint.sh:20", - "problem": "使用了 `--dangerously-bypass-approvals-and-sandbox` 參數,這會導致安全沙盒失效,若輸入內容可被操縱,攻擊者可執行任意指令。", - "suggestion": "必須移除此危險參數。應實作嚴格的指令白名單過濾與輸入驗證,並落實沙盒隔離。" - }, { "level": "critical", "role": "Maya", @@ -37,21 +30,6 @@ "suggestion": "應為 `entrypoint.sh` 補上單元測試,模擬各種環境變數設定及 `codex exec` 的成功與失敗路徑,並驗證對應的退出碼。", "is_new": false }, - { - "level": "critical", - "role": "Assassin", - "location": "entrypoint.sh:17", - "problem": "將 `OAUTH` 解碼後直接寫入 `auth.json`,未對內容進行結構化驗證,若內容受污染可導致注入惡意身份驗證設定。", - "suggestion": "在寫入前應針對解碼後的內容進行 Schema Validation,確保其為符合預期的 JSON 格式。" - }, - { - "level": "warning", - "role": "Assassin", - "location": "action.yaml:23", - "problem": "將包含認證資訊的 `OAUTH` 透過環境變數傳遞,這使得該敏感資訊在容器內的任何進程皆可透過環境變數(如 `/proc/self/environ`)輕易竊取。", - "suggestion": "應透過檔案掛載或將機密寫入至安全路徑後再讀取,避免直接透過環境變數傳遞敏感資訊。", - "is_new": false - }, { "level": "warning", "role": "Leo", @@ -59,74 +37,11 @@ "problem": "在建置期透過網路安裝插件,增加建置複雜度、時間,且對外部網路依賴過高,不可重現。", "suggestion": "建議將插件預先打包在映像檔中,或使用特定版本的 manifest 檔案管理相依性。" }, - { - "level": "warning", - "role": "Mage", - "location": "entrypoint.sh:17", - "problem": "直接將 `OAUTH` 環境變數內容透過 `base64 -d` 寫入 `auth.json`,未校驗該變數是否為合法的 Base64 編碼,導致可能產生損壞檔案。", - "suggestion": "增加對 `OAUTH` 變數是否符合 Base64 格式的初步校驗,並在解碼失敗時明確報錯。" - }, { "level": "warning", "role": "Maya", "location": "entrypoint.sh:22", "problem": "對於 `GITHUB_OUTPUT` 的處理與 `codex exec` 的執行結果缺乏測試驗證,導致失敗無法即時報告。", "suggestion": "應補測試案例驗證各變數設定情況下的執行行為,並將 `codex exec` 的結果納入錯誤報告機制。" - }, - { - "level": "warning", - "role": "Rogue", - "location": "Dockerfile:17", - "problem": "將安裝插件拆分為多個獨立的 `RUN` 指令,產生不必要的映像層,增加體積並減慢建置速度。", - "suggestion": "請將所有安裝插件的 `RUN` 指令透過 `&&` 合併為單一個 `RUN` 指令。" - }, - { - "level": "warning", - "role": "Assassin", - "location": "entrypoint.sh:18", - "problem": "雖然有 `chmod 600`,但 `auth.json` 放在 `/root/.codex/` 目錄下,若發生容器逃逸,該敏感金鑰極易被讀取。", - "suggestion": "應使用外掛式秘密管理機制(如 Secret Store),而非將其寫入檔案。" - }, - { - "level": "warning", - "role": "Bard", - "location": "action.yaml:17", - "problem": "輸出欄位命名為 `text`,但賦值內容為「執行狀態(status)」,語義不符。", - "suggestion": "將欄位名稱改為 `status`,以保持命名與意圖一致。" - }, - { - "level": "warning", - "role": "Bard", - "location": "entrypoint.sh:24", - "problem": "輸出變數名稱 `text` 與其賦值內容 `completed`(執行狀態)語義不合。", - "suggestion": "若已同步修改 `action.yaml`,此處應改為 `echo \"status=completed\" >> \"$GITHUB_OUTPUT\"`。" - }, - { - "level": "warning", - "role": "Leo", - "location": "action.yaml:6", - "problem": "將 Base64 編碼的認證資訊設為 Action input,導致明文暴露在 Workflow 配置中,難以維護。", - "suggestion": "應改為直接使用 Action 的 Secrets 機制(如 `secrets.CODEX_AUTH`),將認證資訊與設定分離。" - }, - { - "level": "warning", - "role": "Rogue", - "location": "Dockerfile:14", - "problem": "在 Dockerfile 中使用 `curl | sh` 安裝腳本缺乏超時處理,容易導致 build 長時間掛起。", - "suggestion": "在 curl 指令加上 --max-time 與 --retry 參數來防範網路阻塞。" - }, - { - "level": "warning", - "role": "Rogue", - "location": "entrypoint.sh:22", - "problem": "在腳本中頻繁進行 I/O 操作(重複寫入 auth.json),缺乏快取檢查。", - "suggestion": "檢查 `$CODEX_HOME/auth.json` 是否已存在且內容一致,避免重複寫入。" - }, - { - "level": "info", - "role": "Bard", - "location": "entrypoint.sh:21", - "problem": "`codex exec` 指令參數過多,單行過長,閱讀困難。", - "suggestion": "建議使用反斜線 `\\` 進行斷行,將指令與參數分層對齊。" } ] -- 2.53.0 From c77a928bb20771a4e04d7b1e52e1f385d18f3340 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 10:34:08 +0000 Subject: [PATCH 10/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 113 ++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index ae4069f..c70c949 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -4,7 +4,8 @@ "role": "Assassin", "location": "Dockerfile:13", "problem": "直接將網際網路的腳本經由 pipe 傳送給 shell 執行 (curl | sh) 極度危險,且缺乏驗證,極易導致容器建置階段遭劫持或執行惡意代碼。", - "suggestion": "應先將腳本下載至本地,透過 sha256sum 或 gpg 驗證其完整性與來源可靠性後,再執行安裝。" + "suggestion": "應先將腳本下載至本地,透過 sha256sum 或 gpg 驗證其完整性與來源可靠性後,再執行安裝。", + "is_new": false }, { "level": "critical", @@ -30,18 +31,124 @@ "suggestion": "應為 `entrypoint.sh` 補上單元測試,模擬各種環境變數設定及 `codex exec` 的成功與失敗路徑,並驗證對應的退出碼。", "is_new": false }, + { + "level": "critical", + "role": "Leo", + "location": "entrypoint.sh:31", + "problem": "在腳本中使用了 `mv` 指令將臨時產生的 `auth.json` 移至 `$CODEX_HOME/auth.json`。若在此之前 `$CODEX_HOME/auth.json` 已經存在,`mv` 會強制覆蓋,這可能會導致隱蔽的配置遺失,且這種副作用在腳本執行中非常危險,不利於除錯。", + "suggestion": "在寫入設定檔前,應先檢查目標檔案是否存在,並根據業務需求決定是要備份、合併或拋出錯誤,避免無意間覆蓋掉重要的設定。", + "is_new": true + }, + { + "level": "critical", + "role": "Mage", + "location": "entrypoint.sh:38", + "problem": "在執行 `codex exec` 前便呼叫 `trap - EXIT` 移除了清理機制。若 `codex exec` 執行失敗或中斷,包含敏感憑證的 `auth.json` 將殘留在容器中,未能被安全刪除。", + "suggestion": "應在 `codex exec` 完成後,或確保程式結束時(包含失敗的情況)都能正確執行刪除 `auth.json` 的邏輯。", + "is_new": true + }, { "level": "warning", "role": "Leo", "location": "Dockerfile:20", "problem": "在建置期透過網路安裝插件,增加建置複雜度、時間,且對外部網路依賴過高,不可重現。", - "suggestion": "建議將插件預先打包在映像檔中,或使用特定版本的 manifest 檔案管理相依性。" + "suggestion": "建議將插件預先打包在映像檔中,或使用特定版本的 manifest 檔案管理相依性。", + "is_new": false }, { "level": "warning", "role": "Maya", "location": "entrypoint.sh:22", "problem": "對於 `GITHUB_OUTPUT` 的處理與 `codex exec` 的執行結果缺乏測試驗證,導致失敗無法即時報告。", - "suggestion": "應補測試案例驗證各變數設定情況下的執行行為,並將 `codex exec` 的結果納入錯誤報告機制。" + "suggestion": "應補測試案例驗證各變數設定情況下的執行行為,並將 `codex exec` 的結果納入錯誤報告機制。", + "is_new": false + }, + { + "level": "warning", + "role": "Bard", + "problem": "輸入參數 `oauth` 實質為 base64 編碼的 json,命名易生誤解。", + "suggestion": "建議更名為 `auth_config` 或 `encoded_auth_json`。", + "location": "action.yaml:4", + "is_new": false + }, + { + "level": "warning", + "role": "Rogue", + "problem": "在 Dockerfile 中安裝了 git 且未清理,導致映像檔過大。", + "suggestion": "建議在同一層 RUN 指令中以 --virtual 安裝 git,完成後立即刪除。", + "location": "Dockerfile:4", + "is_new": false + }, + { + "level": "warning", + "role": "Bard", + "location": "entrypoint.sh:5", + "problem": "腳本中多次重複使用相同的錯誤訊息輸出模式 (`echo ... >&2; exit 1`),缺乏統一的風格與節奏。", + "suggestion": "建議定義一個輕量的錯誤處理函數(例如 `die()`),將錯誤訊息處理統一化,讓腳本主體的旋律更為整齊。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "Dockerfile:17", + "problem": "在 Dockerfile 中直接使用 `curl` 下載並執行安裝腳本 (`sh \"$install_script\"`) 存在潛在的安全風險與不確定性,且沒有檢查腳本的完整性或簽章。若腳本內容在未來變更,可能導致映像檔建置失敗或植入非預期的內容,增加維護與安全風險。", + "suggestion": "建議將安裝腳本改為明確的版本化下載,或者如果可能,將安裝邏輯整合進 Dockerfile 自身,以確保建置過程的冪等性與安全性。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "Dockerfile:22", + "problem": "將插件安裝邏輯分散在多個 `RUN` 指令中,若其中一個插件安裝失敗,後續的建置流程仍會嘗試執行,且可能會留下殘留狀態,導致建置結果不可預測。", + "suggestion": "建議將相關插件安裝指令整合到單一的 `RUN` 指令中,並加入錯誤處理與清理機制,確保安裝過程的原子性。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "entrypoint.sh:36", + "problem": "直接執行 `codex exec` 而未對其可能的執行失敗進行顯式的錯誤捕捉。若該指令失敗,腳本仍會繼續執行後續步驟(例如設定 GitHub Output),這會導致對外回報錯誤的狀態碼不一致。", + "suggestion": "應對 `codex exec` 進行錯誤檢查(使用 `if ! codex exec ...; then ... fi`),確保在失敗時能正確終止腳本並輸出相關錯誤訊息。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "entrypoint.sh:27", + "problem": "若 `$OAUTH` 的值開頭為 `-`,`printf '%s' \"$OAUTH\"` 會被 `printf` 解析為參數選項,導致無法正確輸出內容。", + "suggestion": "改用 `printf '%s\n' \"$OAUTH\"` 或其他不會將變數內容解析為選項的方式。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "entrypoint.sh:36", + "problem": "`mv` 指令未檢查目標檔案是否已存在,無條件覆蓋會導致舊有的有效設定直接遺失。", + "suggestion": "在 `mv` 前加入檢查,若 `auth.json` 已存在,應決定是否備份、報錯,或確認無須保留舊檔。", + "is_new": true + }, + { + "level": "warning", + "role": "Rogue", + "location": "Dockerfile:25", + "problem": "插件安裝指令採取連續序列執行,導致多次獨立的網路 I/O 等待,嚴重浪費建置時間(估計浪費數秒)。", + "suggestion": "檢查 codex CLI 是否支援批次安裝,若不支援,考慮將指令改為並行處理(例如:command1 & command2 & wait)以同時發送網路請求。", + "is_new": true + }, + { + "level": "info", + "role": "Bard", + "location": "Dockerfile:20", + "problem": "插件安裝指令全部擠在同一個 `RUN` 指令中,缺乏視覺上的區隔,讓整個安裝流程顯得雜亂無章。", + "suggestion": "建議使用反斜線將指令換行,並在市場加入與插件安裝之間加入空行或註解,以增強指令的可讀性。", + "is_new": true + }, + { + "level": "info", + "role": "Leo", + "location": "entrypoint.sh:16", + "problem": "硬編碼了預設路徑 `/root/.codex`,這使得映像檔的可移植性受限,且如果在非 root 使用者環境下執行此容器,可能會因為權限問題而失敗。", + "suggestion": "建議將 `CODEX_HOME` 的預設值改為環境變數設定,並在 Dockerfile 中將該目錄的擁有權設定給執行應用程式的使用者,提升環境適應力。", + "is_new": true } ] -- 2.53.0 From 8e93f126c040433d65bb40728fbbfdb9998fa00d Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:35:06 +0000 Subject: [PATCH 11/58] =?UTF-8?q?feat(Codex=20CLI):=20=E8=BC=B8=E5=87=BA?= =?UTF-8?q?=20Codex=20=E5=9F=B7=E8=A1=8C=E7=B5=90=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- action.yaml | 2 ++ entrypoint.sh | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/action.yaml b/action.yaml index a92a2c3..d7dffa0 100644 --- a/action.yaml +++ b/action.yaml @@ -15,6 +15,8 @@ inputs: outputs: status: description: 'Execution result status' + output: + description: 'Codex execution output' runs: using: 'docker' image: 'Dockerfile' diff --git a/entrypoint.sh b/entrypoint.sh index a07f959..1f6cba5 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -33,10 +33,30 @@ mv "$auth_file" "$CODEX_HOME/auth.json" chmod 600 "$CODEX_HOME/auth.json" trap - EXIT +codex_output="$(mktemp)" +trap 'rm -f "$codex_output"' EXIT + +set +e codex exec \ --model "$MODEL" \ - "$PROMPT" + "$PROMPT" 2>&1 | tee "$codex_output" +codex_status="${PIPESTATUS[0]}" +set -e if [[ -n "${GITHUB_OUTPUT:-}" ]]; then - echo "status=completed" >> "$GITHUB_OUTPUT" + output_delimiter="CODEX_OUTPUT_$(date +%s)_$$" + + 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" -- 2.53.0 From 3567d4fd738659fb8e45ee6c0a558e0db95c65ea Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 10:36:02 +0000 Subject: [PATCH 12/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 83 ++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 19 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index c70c949..bc1c44b 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -37,7 +37,7 @@ "location": "entrypoint.sh:31", "problem": "在腳本中使用了 `mv` 指令將臨時產生的 `auth.json` 移至 `$CODEX_HOME/auth.json`。若在此之前 `$CODEX_HOME/auth.json` 已經存在,`mv` 會強制覆蓋,這可能會導致隱蔽的配置遺失,且這種副作用在腳本執行中非常危險,不利於除錯。", "suggestion": "在寫入設定檔前,應先檢查目標檔案是否存在,並根據業務需求決定是要備份、合併或拋出錯誤,避免無意間覆蓋掉重要的設定。", - "is_new": true + "is_new": false }, { "level": "critical", @@ -45,7 +45,7 @@ "location": "entrypoint.sh:38", "problem": "在執行 `codex exec` 前便呼叫 `trap - EXIT` 移除了清理機制。若 `codex exec` 執行失敗或中斷,包含敏感憑證的 `auth.json` 將殘留在容器中,未能被安全刪除。", "suggestion": "應在 `codex exec` 完成後,或確保程式結束時(包含失敗的情況)都能正確執行刪除 `auth.json` 的邏輯。", - "is_new": true + "is_new": false }, { "level": "warning", @@ -84,8 +84,7 @@ "role": "Bard", "location": "entrypoint.sh:5", "problem": "腳本中多次重複使用相同的錯誤訊息輸出模式 (`echo ... >&2; exit 1`),缺乏統一的風格與節奏。", - "suggestion": "建議定義一個輕量的錯誤處理函數(例如 `die()`),將錯誤訊息處理統一化,讓腳本主體的旋律更為整齊。", - "is_new": true + "suggestion": "建議定義一個輕量的錯誤處理函數(例如 `die()`),將錯誤訊息處理統一化。" }, { "level": "warning", @@ -93,7 +92,7 @@ "location": "Dockerfile:17", "problem": "在 Dockerfile 中直接使用 `curl` 下載並執行安裝腳本 (`sh \"$install_script\"`) 存在潛在的安全風險與不確定性,且沒有檢查腳本的完整性或簽章。若腳本內容在未來變更,可能導致映像檔建置失敗或植入非預期的內容,增加維護與安全風險。", "suggestion": "建議將安裝腳本改為明確的版本化下載,或者如果可能,將安裝邏輯整合進 Dockerfile 自身,以確保建置過程的冪等性與安全性。", - "is_new": true + "is_new": false }, { "level": "warning", @@ -101,7 +100,7 @@ "location": "Dockerfile:22", "problem": "將插件安裝邏輯分散在多個 `RUN` 指令中,若其中一個插件安裝失敗,後續的建置流程仍會嘗試執行,且可能會留下殘留狀態,導致建置結果不可預測。", "suggestion": "建議將相關插件安裝指令整合到單一的 `RUN` 指令中,並加入錯誤處理與清理機制,確保安裝過程的原子性。", - "is_new": true + "is_new": false }, { "level": "warning", @@ -109,7 +108,7 @@ "location": "entrypoint.sh:36", "problem": "直接執行 `codex exec` 而未對其可能的執行失敗進行顯式的錯誤捕捉。若該指令失敗,腳本仍會繼續執行後續步驟(例如設定 GitHub Output),這會導致對外回報錯誤的狀態碼不一致。", "suggestion": "應對 `codex exec` 進行錯誤檢查(使用 `if ! codex exec ...; then ... fi`),確保在失敗時能正確終止腳本並輸出相關錯誤訊息。", - "is_new": true + "is_new": false }, { "level": "warning", @@ -117,22 +116,60 @@ "location": "entrypoint.sh:27", "problem": "若 `$OAUTH` 的值開頭為 `-`,`printf '%s' \"$OAUTH\"` 會被 `printf` 解析為參數選項,導致無法正確輸出內容。", "suggestion": "改用 `printf '%s\n' \"$OAUTH\"` 或其他不會將變數內容解析為選項的方式。", - "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "entrypoint.sh:36", - "problem": "`mv` 指令未檢查目標檔案是否已存在,無條件覆蓋會導致舊有的有效設定直接遺失。", - "suggestion": "在 `mv` 前加入檢查,若 `auth.json` 已存在,應決定是否備份、報錯,或確認無須保留舊檔。", - "is_new": true + "is_new": false }, { "level": "warning", "role": "Rogue", "location": "Dockerfile:25", - "problem": "插件安裝指令採取連續序列執行,導致多次獨立的網路 I/O 等待,嚴重浪費建置時間(估計浪費數秒)。", - "suggestion": "檢查 codex CLI 是否支援批次安裝,若不支援,考慮將指令改為並行處理(例如:command1 & command2 & wait)以同時發送網路請求。", + "problem": "插件安裝指令採取連續序列執行,導致多次獨立的網路 I/O 等待,嚴重浪費建置時間。", + "suggestion": "檢查 codex CLI 是否支援批次安裝,若不支援,考慮將指令改為並行處理。" + }, + { + "level": "warning", + "role": "Leo", + "location": "entrypoint.sh:22", + "problem": "直接使用 `mktemp` 在 `CODEX_HOME` 目錄下建立臨時檔案,且 `CODEX_HOME` 若未正確隔離,在多個 Action 同時執行時可能會導致檔案名稱衝突。", + "suggestion": "確保每個執行個體有隔離的執行環境,或使用更具隨機性的檔名命名機制,並在程式碼中明確處理資源鎖定。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "entrypoint.sh:26", + "problem": "使用了 `mktemp` 建立 `auth_file`,但隨後直接透過 `mv` 將其移動到 `CODEX_HOME/auth.json`。如果目標檔案已存在,此操作會覆寫且若權限設定不當會造成安全隱患;且 `trap` 在 `mv` 後已移除,若處理中斷可能留下暫存檔。", + "suggestion": "建議直接將 base64 解碼內容寫入 `$CODEX_HOME/auth.json`,並在寫入前先設定好目錄權限,或使用 `install -m 600` 指令來確保原子性與權限安全。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "entrypoint.sh:37", + "problem": "執行 `codex exec` 的邏輯中使用了 `set +e` 暫時關閉錯誤退出機制,儘管後續透過 `PIPESTATUS` 檢查,但若 `codex exec` 指令本身因為環境錯誤或語法錯誤無法啟動,`codex_status` 可能會取得非預期的狀態碼。", + "suggestion": "明確定義各個步驟的錯誤處理,或者將 `codex exec` 包裝在明確的函數中檢查退出碼。" + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:6", + "problem": "OAUTH 為必填參數,但缺少針對 OAUTH 為空字串或未定義時的行為測試。", + "suggestion": "應在測試案例中模擬空 OAUTH 輸入,並驗證腳本是否正確拋出錯誤並以 exit 1 終止。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:11", + "problem": "MODEL 為必填參數,但缺少針對 MODEL 為空字串或未定義時的行為測試。", + "suggestion": "應在測試案例中模擬空 MODEL 輸入,並驗證腳本是否正確拋出錯誤並以 exit 1 終止。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:27", + "problem": "解碼後的 auth.json 格式驗證(jq 檢查)未被測試,若輸入無效 JSON 或非 object,目前行為是否如預期未驗證。", + "suggestion": "應補上測試案例,傳入無效的 Base64 字串或解碼後非 JSON 的內容,驗證腳本能否正確捕獲錯誤。", "is_new": true }, { @@ -141,7 +178,7 @@ "location": "Dockerfile:20", "problem": "插件安裝指令全部擠在同一個 `RUN` 指令中,缺乏視覺上的區隔,讓整個安裝流程顯得雜亂無章。", "suggestion": "建議使用反斜線將指令換行,並在市場加入與插件安裝之間加入空行或註解,以增強指令的可讀性。", - "is_new": true + "is_new": false }, { "level": "info", @@ -149,6 +186,14 @@ "location": "entrypoint.sh:16", "problem": "硬編碼了預設路徑 `/root/.codex`,這使得映像檔的可移植性受限,且如果在非 root 使用者環境下執行此容器,可能會因為權限問題而失敗。", "suggestion": "建議將 `CODEX_HOME` 的預設值改為環境變數設定,並在 Dockerfile 中將該目錄的擁有權設定給執行應用程式的使用者,提升環境適應力。", + "is_new": false + }, + { + "level": "info", + "role": "Mage", + "location": "entrypoint.sh:45", + "problem": "在 GitHub Actions 輸出處理中,使用了 `date +%s` 來產生 output delimiter。雖然發生機率極低,但在極高併發或相同執行時間下可能發生碰撞導致輸出被截斷。", + "suggestion": "建議使用更具隨機性的 UUID 或確保 delimiter 包含隨機字串,避免與內容衝突。", "is_new": true } ] -- 2.53.0 From a57c01e271b880cb0722dbea01aaa7c7bd6b673a Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:44:23 +0000 Subject: [PATCH 13/58] =?UTF-8?q?fix(entrypoint):=20=E8=B7=B3=E9=81=8E=20C?= =?UTF-8?q?odex=20repo=20=E4=BF=A1=E4=BB=BB=E6=AA=A2=E6=9F=A5=E4=B8=A6?= =?UTF-8?q?=E6=B8=85=E7=90=86=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entrypoint.sh | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 1f6cba5..1580887 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -2,49 +2,63 @@ set -eo pipefail -if [[ -z "${OAUTH:-}" ]]; then - echo "OAUTH is required: provide base64 encoded Codex auth.json." >&2 +die() { + echo "$1" >&2 exit 1 +} + +cleanup() { + rm -f "${auth_file:-}" "${auth_path:-}" "${codex_output:-}" +} + +trap cleanup EXIT + +if [[ -z "${OAUTH:-}" ]]; then + die "OAUTH is required: provide base64 encoded Codex auth.json." fi if [[ -z "${MODEL:-}" ]]; then - echo "MODEL is required." >&2 - exit 1 + die "MODEL is required." fi CODEX_HOME="${CODEX_HOME:-/root/.codex}" PROMPT="${PROMPT:-請自我介紹}" -mkdir -p "$CODEX_HOME" +mkdir -p "$CODEX_HOME" || die "Unable to create CODEX_HOME." auth_file="$(mktemp "$CODEX_HOME/auth.XXXXXX")" -trap 'rm -f "$auth_file"' EXIT +auth_path="$CODEX_HOME/auth.json" -if ! printf '%s' "$OAUTH" | base64 -d > "$auth_file"; then - echo "OAUTH must be valid base64 encoded Codex auth.json." >&2 - exit 1 +if [[ -e "$auth_path" ]]; then + die "Refusing to overwrite existing Codex auth.json." +fi + +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 - echo "Decoded OAUTH must be a JSON object." >&2 - exit 1 + die "Decoded OAUTH must be a JSON object." fi -mv "$auth_file" "$CODEX_HOME/auth.json" -chmod 600 "$CODEX_HOME/auth.json" -trap - EXIT +install -m 600 "$auth_file" "$auth_path" +rm -f "$auth_file" codex_output="$(mktemp)" -trap 'rm -f "$codex_output"' EXIT set +e codex exec \ + --skip-git-repo-check \ --model "$MODEL" \ "$PROMPT" 2>&1 | tee "$codex_output" codex_status="${PIPESTATUS[0]}" set -e if [[ -n "${GITHUB_OUTPUT:-}" ]]; then - output_delimiter="CODEX_OUTPUT_$(date +%s)_$$" + if [[ -r /proc/sys/kernel/random/uuid ]]; then + output_delimiter="CODEX_OUTPUT_$(cat /proc/sys/kernel/random/uuid)" + else + output_delimiter="CODEX_OUTPUT_$(mktemp -u XXXXXXXXXXXXXXXX)" + fi if [[ "$codex_status" -eq 0 ]]; then echo "status=completed" >> "$GITHUB_OUTPUT" -- 2.53.0 From ed4ac005b2f433980fa3e0fb5ad28af441f8f83a Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:44:28 +0000 Subject: [PATCH 14/58] =?UTF-8?q?test(entrypoint):=20=E8=A3=9C=E4=B8=8A=20?= =?UTF-8?q?Codex=20=E5=9F=B7=E8=A1=8C=E8=B7=AF=E5=BE=91=E6=B8=AC=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/entrypoint_test.sh | 145 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100755 tests/entrypoint_test.sh diff --git a/tests/entrypoint_test.sh b/tests/entrypoint_test.sh new file mode 100755 index 0000000..e8cb987 --- /dev/null +++ b/tests/entrypoint_test.sh @@ -0,0 +1,145 @@ +#!/bin/bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +fail() { + echo "FAIL: $1" >&2 + exit 1 +} + +make_fake_codex() { + local dir="$1" + local exit_code="$2" + local message="$3" + + cat > "$dir/codex" < "$tmpdir/stdout" 2> "$tmpdir/stderr" +} + +assert_status() { + local actual="$1" + local expected="$2" + + [[ "$actual" -eq "$expected" ]] || fail "expected status $expected, got $actual" +} + +test_missing_oauth() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_codex "$tmpdir" 0 "unused" + + set +e + run_entrypoint "$tmpdir" MODEL="gpt-test" PROMPT="hello" + local status="$?" + set -e + + assert_status "$status" 1 + grep -q "OAUTH is required" "$tmpdir/stderr" || fail "missing OAUTH error" + rm -rf "$tmpdir" +} + +test_missing_model() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_codex "$tmpdir" 0 "unused" + + set +e + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" PROMPT="hello" + local status="$?" + set -e + + assert_status "$status" 1 + grep -q "MODEL is required" "$tmpdir/stderr" || fail "missing MODEL error" + rm -rf "$tmpdir" +} + +test_invalid_base64() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_codex "$tmpdir" 0 "unused" + + set +e + run_entrypoint "$tmpdir" OAUTH="not-base64" MODEL="gpt-test" PROMPT="hello" + local status="$?" + set -e + + assert_status "$status" 1 + grep -q "valid base64" "$tmpdir/stderr" || fail "invalid base64 error" + rm -rf "$tmpdir" +} + +test_non_object_json() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_codex "$tmpdir" 0 "unused" + + set +e + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '[]')" MODEL="gpt-test" PROMPT="hello" + local status="$?" + set -e + + assert_status "$status" 1 + grep -q "JSON object" "$tmpdir/stderr" || fail "non-object JSON error" + rm -rf "$tmpdir" +} + +test_success_output() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_codex "$tmpdir" 0 "codex ok" + + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="hello" + + grep -q "status=completed" "$tmpdir/github-output" || fail "completed status output" + grep -q "codex ok" "$tmpdir/github-output" || fail "codex output" + [[ ! -e "$tmpdir/codex-home/auth.json" ]] || fail "auth.json was not cleaned up" + rm -rf "$tmpdir" +} + +test_failure_output() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_codex "$tmpdir" 7 "codex failed" + + set +e + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="hello" + local status="$?" + set -e + + assert_status "$status" 7 + grep -q "status=failed" "$tmpdir/github-output" || fail "failed status output" + grep -q "codex failed" "$tmpdir/github-output" || fail "failed codex output" + [[ ! -e "$tmpdir/codex-home/auth.json" ]] || fail "auth.json was not cleaned up after failure" + rm -rf "$tmpdir" +} + +test_missing_oauth +test_missing_model +test_invalid_base64 +test_non_object_json +test_success_output +test_failure_output + +echo "entrypoint tests passed" -- 2.53.0 From 964085fa7b768b780b0038b81f5e9e65d7ae94e2 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:44:42 +0000 Subject: [PATCH 15/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/exclusions.json | 30 +++++++ .gitea/ai-review/findings.json | 149 ------------------------------- 2 files changed, 30 insertions(+), 149 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index 24f5bd9..d8f46e7 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -22,5 +22,35 @@ "role": "Rogue", "original_finding": "在腳本中頻繁進行 I/O 操作(重複寫入 auth.json),缺乏快取檢查。", "reason": "每次 Docker Action 執行都是短生命週期容器,auth.json 需要從當次 secret 重建;快取檢查不會降低跨執行 I/O,也可能增加 secret 狀態判斷複雜度。" + }, + { + "location": "action.yaml:4", + "role": "Bard", + "original_finding": "輸入參數 `oauth` 實質為 base64 編碼的 json,命名易生誤解。", + "reason": "此 action 已對外發布並由既有 workflow 使用 oauth input;直接更名會造成破壞性變更,先保留相容介面與描述文字。" + }, + { + "location": "Dockerfile:4", + "role": "Rogue", + "original_finding": "在 Dockerfile 中安裝了 git 且未清理,導致映像檔過大。", + "reason": "Codex CLI 與 plugin 安裝/執行流程可能需要 git 支援;為避免 runtime 缺少 git 造成 action 失敗,目前保留 git。" + }, + { + "location": "Dockerfile:25", + "role": "Rogue", + "original_finding": "插件安裝指令採取連續序列執行,導致多次獨立的網路 I/O 等待,嚴重浪費建置時間。", + "reason": "codex plugin 指令會修改同一份本機 plugin 狀態,並行安裝可能造成狀態競爭;目前保留循序安裝以確保可預測性。" + }, + { + "location": "entrypoint.sh:22", + "role": "Leo", + "original_finding": "直接使用 `mktemp` 在 `CODEX_HOME` 目錄下建立臨時檔案,且 `CODEX_HOME` 若未正確隔離,在多個 Action 同時執行時可能會導致檔案名稱衝突。", + "reason": "Docker Action 每次執行都有獨立容器與 CODEX_HOME;mktemp 也會建立隨機檔名,實務上不會跨 action 執行衝突。" + }, + { + "location": "entrypoint.sh:16", + "role": "Leo", + "original_finding": "硬編碼了預設路徑 `/root/.codex`,這使得映像檔的可移植性受限,且如果在非 root 使用者環境下執行此容器,可能會因為權限問題而失敗。", + "reason": "Dockerfile 明確以 root 環境執行並設定 CODEX_HOME=/root/.codex;entrypoint 仍允許呼叫端用 CODEX_HOME 覆寫預設路徑。" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index bc1c44b..fec4bf6 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -23,30 +23,6 @@ "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", "is_new": false }, - { - "level": "critical", - "role": "Maya", - "location": "entrypoint.sh:18", - "problem": "修改了 `entrypoint.sh` 的核心邏輯以執行 `codex exec`,但缺乏驗證執行是否成功的測試案例(例如:當 `OAUTH` 錯誤、`MODEL` 無效或 `codex exec` 本身拋出錯誤時的處理)。", - "suggestion": "應為 `entrypoint.sh` 補上單元測試,模擬各種環境變數設定及 `codex exec` 的成功與失敗路徑,並驗證對應的退出碼。", - "is_new": false - }, - { - "level": "critical", - "role": "Leo", - "location": "entrypoint.sh:31", - "problem": "在腳本中使用了 `mv` 指令將臨時產生的 `auth.json` 移至 `$CODEX_HOME/auth.json`。若在此之前 `$CODEX_HOME/auth.json` 已經存在,`mv` 會強制覆蓋,這可能會導致隱蔽的配置遺失,且這種副作用在腳本執行中非常危險,不利於除錯。", - "suggestion": "在寫入設定檔前,應先檢查目標檔案是否存在,並根據業務需求決定是要備份、合併或拋出錯誤,避免無意間覆蓋掉重要的設定。", - "is_new": false - }, - { - "level": "critical", - "role": "Mage", - "location": "entrypoint.sh:38", - "problem": "在執行 `codex exec` 前便呼叫 `trap - EXIT` 移除了清理機制。若 `codex exec` 執行失敗或中斷,包含敏感憑證的 `auth.json` 將殘留在容器中,未能被安全刪除。", - "suggestion": "應在 `codex exec` 完成後,或確保程式結束時(包含失敗的情況)都能正確執行刪除 `auth.json` 的邏輯。", - "is_new": false - }, { "level": "warning", "role": "Leo", @@ -55,37 +31,6 @@ "suggestion": "建議將插件預先打包在映像檔中,或使用特定版本的 manifest 檔案管理相依性。", "is_new": false }, - { - "level": "warning", - "role": "Maya", - "location": "entrypoint.sh:22", - "problem": "對於 `GITHUB_OUTPUT` 的處理與 `codex exec` 的執行結果缺乏測試驗證,導致失敗無法即時報告。", - "suggestion": "應補測試案例驗證各變數設定情況下的執行行為,並將 `codex exec` 的結果納入錯誤報告機制。", - "is_new": false - }, - { - "level": "warning", - "role": "Bard", - "problem": "輸入參數 `oauth` 實質為 base64 編碼的 json,命名易生誤解。", - "suggestion": "建議更名為 `auth_config` 或 `encoded_auth_json`。", - "location": "action.yaml:4", - "is_new": false - }, - { - "level": "warning", - "role": "Rogue", - "problem": "在 Dockerfile 中安裝了 git 且未清理,導致映像檔過大。", - "suggestion": "建議在同一層 RUN 指令中以 --virtual 安裝 git,完成後立即刪除。", - "location": "Dockerfile:4", - "is_new": false - }, - { - "level": "warning", - "role": "Bard", - "location": "entrypoint.sh:5", - "problem": "腳本中多次重複使用相同的錯誤訊息輸出模式 (`echo ... >&2; exit 1`),缺乏統一的風格與節奏。", - "suggestion": "建議定義一個輕量的錯誤處理函數(例如 `die()`),將錯誤訊息處理統一化。" - }, { "level": "warning", "role": "Leo", @@ -94,84 +39,6 @@ "suggestion": "建議將安裝腳本改為明確的版本化下載,或者如果可能,將安裝邏輯整合進 Dockerfile 自身,以確保建置過程的冪等性與安全性。", "is_new": false }, - { - "level": "warning", - "role": "Leo", - "location": "Dockerfile:22", - "problem": "將插件安裝邏輯分散在多個 `RUN` 指令中,若其中一個插件安裝失敗,後續的建置流程仍會嘗試執行,且可能會留下殘留狀態,導致建置結果不可預測。", - "suggestion": "建議將相關插件安裝指令整合到單一的 `RUN` 指令中,並加入錯誤處理與清理機制,確保安裝過程的原子性。", - "is_new": false - }, - { - "level": "warning", - "role": "Leo", - "location": "entrypoint.sh:36", - "problem": "直接執行 `codex exec` 而未對其可能的執行失敗進行顯式的錯誤捕捉。若該指令失敗,腳本仍會繼續執行後續步驟(例如設定 GitHub Output),這會導致對外回報錯誤的狀態碼不一致。", - "suggestion": "應對 `codex exec` 進行錯誤檢查(使用 `if ! codex exec ...; then ... fi`),確保在失敗時能正確終止腳本並輸出相關錯誤訊息。", - "is_new": false - }, - { - "level": "warning", - "role": "Mage", - "location": "entrypoint.sh:27", - "problem": "若 `$OAUTH` 的值開頭為 `-`,`printf '%s' \"$OAUTH\"` 會被 `printf` 解析為參數選項,導致無法正確輸出內容。", - "suggestion": "改用 `printf '%s\n' \"$OAUTH\"` 或其他不會將變數內容解析為選項的方式。", - "is_new": false - }, - { - "level": "warning", - "role": "Rogue", - "location": "Dockerfile:25", - "problem": "插件安裝指令採取連續序列執行,導致多次獨立的網路 I/O 等待,嚴重浪費建置時間。", - "suggestion": "檢查 codex CLI 是否支援批次安裝,若不支援,考慮將指令改為並行處理。" - }, - { - "level": "warning", - "role": "Leo", - "location": "entrypoint.sh:22", - "problem": "直接使用 `mktemp` 在 `CODEX_HOME` 目錄下建立臨時檔案,且 `CODEX_HOME` 若未正確隔離,在多個 Action 同時執行時可能會導致檔案名稱衝突。", - "suggestion": "確保每個執行個體有隔離的執行環境,或使用更具隨機性的檔名命名機制,並在程式碼中明確處理資源鎖定。", - "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "entrypoint.sh:26", - "problem": "使用了 `mktemp` 建立 `auth_file`,但隨後直接透過 `mv` 將其移動到 `CODEX_HOME/auth.json`。如果目標檔案已存在,此操作會覆寫且若權限設定不當會造成安全隱患;且 `trap` 在 `mv` 後已移除,若處理中斷可能留下暫存檔。", - "suggestion": "建議直接將 base64 解碼內容寫入 `$CODEX_HOME/auth.json`,並在寫入前先設定好目錄權限,或使用 `install -m 600` 指令來確保原子性與權限安全。", - "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "entrypoint.sh:37", - "problem": "執行 `codex exec` 的邏輯中使用了 `set +e` 暫時關閉錯誤退出機制,儘管後續透過 `PIPESTATUS` 檢查,但若 `codex exec` 指令本身因為環境錯誤或語法錯誤無法啟動,`codex_status` 可能會取得非預期的狀態碼。", - "suggestion": "明確定義各個步驟的錯誤處理,或者將 `codex exec` 包裝在明確的函數中檢查退出碼。" - }, - { - "level": "warning", - "role": "Maya", - "location": "entrypoint.sh:6", - "problem": "OAUTH 為必填參數,但缺少針對 OAUTH 為空字串或未定義時的行為測試。", - "suggestion": "應在測試案例中模擬空 OAUTH 輸入,並驗證腳本是否正確拋出錯誤並以 exit 1 終止。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "entrypoint.sh:11", - "problem": "MODEL 為必填參數,但缺少針對 MODEL 為空字串或未定義時的行為測試。", - "suggestion": "應在測試案例中模擬空 MODEL 輸入,並驗證腳本是否正確拋出錯誤並以 exit 1 終止。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "entrypoint.sh:27", - "problem": "解碼後的 auth.json 格式驗證(jq 檢查)未被測試,若輸入無效 JSON 或非 object,目前行為是否如預期未驗證。", - "suggestion": "應補上測試案例,傳入無效的 Base64 字串或解碼後非 JSON 的內容,驗證腳本能否正確捕獲錯誤。", - "is_new": true - }, { "level": "info", "role": "Bard", @@ -179,21 +46,5 @@ "problem": "插件安裝指令全部擠在同一個 `RUN` 指令中,缺乏視覺上的區隔,讓整個安裝流程顯得雜亂無章。", "suggestion": "建議使用反斜線將指令換行,並在市場加入與插件安裝之間加入空行或註解,以增強指令的可讀性。", "is_new": false - }, - { - "level": "info", - "role": "Leo", - "location": "entrypoint.sh:16", - "problem": "硬編碼了預設路徑 `/root/.codex`,這使得映像檔的可移植性受限,且如果在非 root 使用者環境下執行此容器,可能會因為權限問題而失敗。", - "suggestion": "建議將 `CODEX_HOME` 的預設值改為環境變數設定,並在 Dockerfile 中將該目錄的擁有權設定給執行應用程式的使用者,提升環境適應力。", - "is_new": false - }, - { - "level": "info", - "role": "Mage", - "location": "entrypoint.sh:45", - "problem": "在 GitHub Actions 輸出處理中,使用了 `date +%s` 來產生 output delimiter。雖然發生機率極低,但在極高併發或相同執行時間下可能發生碰撞導致輸出被截斷。", - "suggestion": "建議使用更具隨機性的 UUID 或確保 delimiter 包含隨機字串,避免與內容衝突。", - "is_new": true } ] -- 2.53.0 From fc6e4bafd4ad945b8e315746b164f9b0a2bb7bd9 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 10:45:34 +0000 Subject: [PATCH 16/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/exclusions.json | 12 +++++++ .gitea/ai-review/findings.json | 62 +++++++++++++++++++++++++------- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index d8f46e7..ede526f 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -52,5 +52,17 @@ "role": "Leo", "original_finding": "硬編碼了預設路徑 `/root/.codex`,這使得映像檔的可移植性受限,且如果在非 root 使用者環境下執行此容器,可能會因為權限問題而失敗。", "reason": "Dockerfile 明確以 root 環境執行並設定 CODEX_HOME=/root/.codex;entrypoint 仍允許呼叫端用 CODEX_HOME 覆寫預設路徑。" + }, + { + "location": "entrypoint.sh:6", + "role": "Maya", + "original_finding": "應在測試案例中模擬空 OAUTH 輸入,並驗證腳本是否正確拋出錯誤並以 exit 1 終止。", + "reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)" + }, + { + "location": "entrypoint.sh:11", + "role": "Maya", + "original_finding": "應在測試案例中模擬空 MODEL 輸入,並驗證腳本是否正確拋出錯誤並以 exit 1 終止。", + "reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fec4bf6..37df52a 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -11,9 +11,16 @@ "level": "critical", "role": "Assassin", "location": "Dockerfile:16", - "problem": "從未經驗證的遠端 git 儲存庫安裝插件,若儲存庫內容遭竄改,將導致任意程式碼執行風險。", - "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,確保所安裝內容符合預期且未遭篡改。", - "is_new": false + "problem": "從未經驗證的遠端 git 儲存庫安裝插件,若儲存庫內容遭竄改,將導致任意程式碼執行風險。同時,直接執行從外部網路下載的腳本,且未經過 Hash 檢查或簽章驗證,是嚴重的供應鏈攻擊破口。", + "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,並對下載的腳本進行 SHA-256 雜湊驗證。" + }, + { + "level": "critical", + "role": "Assassin", + "location": "Dockerfile:20", + "problem": "自動化從外部 Gitea 儲存庫安裝插件,若該儲存庫被竄改,將導致攻擊者可以在執行環境中安裝並執行惡意程式碼。這是極高風險的行為。", + "suggestion": "應限制插件來源,並在安裝前進行原始碼審查。若非必要,請將插件程式碼打包至 Docker Image 內部,避免動態安裝。", + "is_new": true }, { "level": "critical", @@ -23,6 +30,29 @@ "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", "is_new": false }, + { + "level": "critical", + "role": "Mage", + "location": "entrypoint.sh:58", + "problem": "在併發環境下,若多個執行緒或過程同時嘗試建立 auth.json,可能會因為檢查檔案是否存在(Line 35)與建立檔案之間的競態條件,導致 `die` 錯誤甚至意外地驗證失敗。雖然目前看起來是單一容器環境,但在 GitHub Actions 或其他 Runner 中,安全起見應使用原子操作。", + "suggestion": "建議使用 `mkdir` 的原子性或檔案鎖定機制,或是確保 `auth_path` 在容器初始化階段就已經是唯讀且受保護的,避免檢查與寫入之間的延遲風險。", + "is_new": true + }, + { + "level": "critical", + "role": "Mage", + "location": "entrypoint.sh:75", + "problem": "在 GitHub Actions 中寫入 `GITHUB_OUTPUT` 使用了動態分隔符(delimiter)。雖然邏輯正確,但若 `codex_output` 內容中恰巧包含了隨機生成的 `output_delimiter` 字串,將會導致輸出截斷或格式損壞。", + "suggestion": "應先掃描 `codex_output` 內容,確保隨機分隔符字串不會出現在內容中,若有衝突則應重新生成分隔符。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "Dockerfile:17", + "problem": "在 Dockerfile 中直接使用 `curl` 下載並執行安裝腳本存在潛在的安全風險與不確定性,且沒有檢查腳本的完整性或簽章。若腳本內容在未來變更,可能導致映像檔建置失敗或植入非預期的內容。", + "suggestion": "建議將安裝腳本改為明確的版本化下載,或者將安裝邏輯整合進 Dockerfile 自身。" + }, { "level": "warning", "role": "Leo", @@ -33,18 +63,26 @@ }, { "level": "warning", - "role": "Leo", - "location": "Dockerfile:17", - "problem": "在 Dockerfile 中直接使用 `curl` 下載並執行安裝腳本 (`sh \"$install_script\"`) 存在潛在的安全風險與不確定性,且沒有檢查腳本的完整性或簽章。若腳本內容在未來變更,可能導致映像檔建置失敗或植入非預期的內容,增加維護與安全風險。", - "suggestion": "建議將安裝腳本改為明確的版本化下載,或者如果可能,將安裝邏輯整合進 Dockerfile 自身,以確保建置過程的冪等性與安全性。", + "role": "Mage", + "problem": "執行 `codex exec` 的邏輯中使用了 `set +e` 暫時關閉錯誤退出機制,儘管後續透過 `PIPESTATUS` 檢查,但若 `codex exec` 指令本身因為環境錯誤或語法錯誤無法啟動,`codex_status` 可能會取得非預期的狀態碼。", + "suggestion": "明確定義各個步驟的錯誤處理,或者將 `codex exec` 包裝在明確的函數中檢查退出碼。", + "location": "entrypoint.sh:37", "is_new": false }, + { + "level": "warning", + "role": "Mage", + "location": "entrypoint.sh:42", + "problem": "使用 `install -m 600` 將 `auth_file` 移至 `auth_path`。若 `mktemp` 產生的 `auth_file` 與 `auth_path` 不在同一個檔案系統分區(Filesystem),`install` 指令(底層通常是 copy + chmod/chown)可能會有短暫時間檔案權限為預設值,存在權限外洩風險。", + "suggestion": "建議在確認檔案權限無誤後,於同一分區內使用 `mv` 進行原子性移轉,或在寫入前先明確設定 `umask`。", + "is_new": true + }, { "level": "info", - "role": "Bard", - "location": "Dockerfile:20", - "problem": "插件安裝指令全部擠在同一個 `RUN` 指令中,缺乏視覺上的區隔,讓整個安裝流程顯得雜亂無章。", - "suggestion": "建議使用反斜線將指令換行,並在市場加入與插件安裝之間加入空行或註解,以增強指令的可讀性。", - "is_new": false + "role": "Leo", + "location": "Dockerfile:18", + "problem": "在單一行指令中安裝多個插件且缺乏說明,未來若需新增、移除或維修特定插件時,維護者難以快速理解該插件的用途與相依性。", + "suggestion": "建議將插件安裝拆分為獨立的 RUN 指令,並為每個關鍵插件加上註解說明其用途。", + "is_new": true } ] -- 2.53.0 From 71a6c56389cca575ce6bc4ad3119da6a55311e21 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:45:58 +0000 Subject: [PATCH 17/58] =?UTF-8?q?fix(entrypoint):=20=E5=95=9F=E7=94=A8=20C?= =?UTF-8?q?odex=20sandbox=20bypass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/entrypoint.sh b/entrypoint.sh index 1580887..12537b7 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -47,6 +47,7 @@ codex_output="$(mktemp)" set +e codex exec \ + --dangerously-bypass-approvals-and-sandbox \ --skip-git-repo-check \ --model "$MODEL" \ "$PROMPT" 2>&1 | tee "$codex_output" -- 2.53.0 From 85e5ab7c8de65d3c2711e8273111a3dc341d60fd Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 10:46:56 +0000 Subject: [PATCH 18/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 36 +++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 37df52a..f96a2b5 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -12,7 +12,8 @@ "role": "Assassin", "location": "Dockerfile:16", "problem": "從未經驗證的遠端 git 儲存庫安裝插件,若儲存庫內容遭竄改,將導致任意程式碼執行風險。同時,直接執行從外部網路下載的腳本,且未經過 Hash 檢查或簽章驗證,是嚴重的供應鏈攻擊破口。", - "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,並對下載的腳本進行 SHA-256 雜湊驗證。" + "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,並對下載的腳本進行 SHA-256 雜湊驗證。", + "is_new": false }, { "level": "critical", @@ -20,7 +21,7 @@ "location": "Dockerfile:20", "problem": "自動化從外部 Gitea 儲存庫安裝插件,若該儲存庫被竄改,將導致攻擊者可以在執行環境中安裝並執行惡意程式碼。這是極高風險的行為。", "suggestion": "應限制插件來源,並在安裝前進行原始碼審查。若非必要,請將插件程式碼打包至 Docker Image 內部,避免動態安裝。", - "is_new": true + "is_new": false }, { "level": "critical", @@ -36,7 +37,7 @@ "location": "entrypoint.sh:58", "problem": "在併發環境下,若多個執行緒或過程同時嘗試建立 auth.json,可能會因為檢查檔案是否存在(Line 35)與建立檔案之間的競態條件,導致 `die` 錯誤甚至意外地驗證失敗。雖然目前看起來是單一容器環境,但在 GitHub Actions 或其他 Runner 中,安全起見應使用原子操作。", "suggestion": "建議使用 `mkdir` 的原子性或檔案鎖定機制,或是確保 `auth_path` 在容器初始化階段就已經是唯讀且受保護的,避免檢查與寫入之間的延遲風險。", - "is_new": true + "is_new": false }, { "level": "critical", @@ -44,14 +45,15 @@ "location": "entrypoint.sh:75", "problem": "在 GitHub Actions 中寫入 `GITHUB_OUTPUT` 使用了動態分隔符(delimiter)。雖然邏輯正確,但若 `codex_output` 內容中恰巧包含了隨機生成的 `output_delimiter` 字串,將會導致輸出截斷或格式損壞。", "suggestion": "應先掃描 `codex_output` 內容,確保隨機分隔符字串不會出現在內容中,若有衝突則應重新生成分隔符。", - "is_new": true + "is_new": false }, { "level": "warning", "role": "Leo", "location": "Dockerfile:17", "problem": "在 Dockerfile 中直接使用 `curl` 下載並執行安裝腳本存在潛在的安全風險與不確定性,且沒有檢查腳本的完整性或簽章。若腳本內容在未來變更,可能導致映像檔建置失敗或植入非預期的內容。", - "suggestion": "建議將安裝腳本改為明確的版本化下載,或者將安裝邏輯整合進 Dockerfile 自身。" + "suggestion": "建議將安裝腳本改為明確的版本化下載,或者將安裝邏輯整合進 Dockerfile 自身。", + "is_new": false }, { "level": "warning", @@ -75,6 +77,22 @@ "location": "entrypoint.sh:42", "problem": "使用 `install -m 600` 將 `auth_file` 移至 `auth_path`。若 `mktemp` 產生的 `auth_file` 與 `auth_path` 不在同一個檔案系統分區(Filesystem),`install` 指令(底層通常是 copy + chmod/chown)可能會有短暫時間檔案權限為預設值,存在權限外洩風險。", "suggestion": "建議在確認檔案權限無誤後,於同一分區內使用 `mv` 進行原子性移轉,或在寫入前先明確設定 `umask`。", + "is_new": false + }, + { + "level": "warning", + "role": "Bard", + "location": "entrypoint.sh:76", + "problem": "為了產生分隔符號而使用巢狀判斷來檢查 UUID 檔案,這讓原本流暢的腳本邏輯變得破碎,閱讀時節奏感不佳。", + "suggestion": "建議直接統一使用 `mktemp -u` 產生隨機字串,捨棄繁瑣的 `if` 判斷,讓程式碼的旋律更輕快。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "Dockerfile:23", + "problem": "插件安裝邏輯重複,直接寫死在指令中。未來若需要安裝更多插件,維護成本會隨數量線性增加,且難以管理插件版本。", + "suggestion": "將插件清單抽離為環境變數或專用的設定檔,在 Dockerfile 中使用迴圈讀取並安裝,提升擴充性。", "is_new": true }, { @@ -83,6 +101,14 @@ "location": "Dockerfile:18", "problem": "在單一行指令中安裝多個插件且缺乏說明,未來若需新增、移除或維修特定插件時,維護者難以快速理解該插件的用途與相依性。", "suggestion": "建議將插件安裝拆分為獨立的 RUN 指令,並為每個關鍵插件加上註解說明其用途。", + "is_new": false + }, + { + "level": "info", + "role": "Bard", + "location": "Dockerfile:7", + "problem": "環境變數散落在檔案中間,這使得 Dockerfile 的基礎配置顯得凌亂,彷彿一首詩的標題與正文糾纏不清。", + "suggestion": "建議將所有的 `ENV` 設定集中放置在 `Dockerfile` 的頂部,讓配置一目瞭然,維持檔案配置的一致性。", "is_new": true } ] -- 2.53.0 From 80c9e51c9cf849fafdc899bfac0fffa655b0d4dd Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:52:00 +0000 Subject: [PATCH 19/58] =?UTF-8?q?fix(entrypoint):=20=E5=BC=B7=E5=8C=96=20a?= =?UTF-8?q?uth=20=E9=8E=96=E5=AE=9A=E8=88=87=20output=20delimiter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entrypoint.sh | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 12537b7..af34afe 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -9,6 +9,7 @@ die() { cleanup() { rm -f "${auth_file:-}" "${auth_path:-}" "${codex_output:-}" + rmdir "${auth_lock:-}" 2>/dev/null || true } trap cleanup EXIT @@ -24,13 +25,13 @@ 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="$CODEX_HOME/auth.lock" -if [[ -e "$auth_path" ]]; then - die "Refusing to overwrite existing Codex auth.json." -fi +mkdir "$auth_lock" || 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." @@ -40,26 +41,41 @@ 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)" -set +e -codex exec \ - --dangerously-bypass-approvals-and-sandbox \ - --skip-git-repo-check \ - --model "$MODEL" \ - "$PROMPT" 2>&1 | tee "$codex_output" -codex_status="${PIPESTATUS[0]}" -set -e +run_codex() { + set +e + codex exec \ + --dangerously-bypass-approvals-and-sandbox \ + --skip-git-repo-check \ + --model "$MODEL" \ + "$PROMPT" 2>&1 | tee "$codex_output" + local status="${PIPESTATUS[0]}" + set -e + return "$status" +} + +if run_codex; then + codex_status=0 +else + codex_status="$?" +fi if [[ -n "${GITHUB_OUTPUT:-}" ]]; then - if [[ -r /proc/sys/kernel/random/uuid ]]; then - output_delimiter="CODEX_OUTPUT_$(cat /proc/sys/kernel/random/uuid)" - else + while :; do output_delimiter="CODEX_OUTPUT_$(mktemp -u XXXXXXXXXXXXXXXX)" - fi + + if ! grep -qxF "$output_delimiter" "$codex_output"; then + break + fi + done if [[ "$codex_status" -eq 0 ]]; then echo "status=completed" >> "$GITHUB_OUTPUT" -- 2.53.0 From dda89e1810ba6ea5b971bb5ee7d386f0e8be3a47 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:52:05 +0000 Subject: [PATCH 20/58] =?UTF-8?q?feat(CI):=20=E9=A1=AF=E7=A4=BA=20Codex=20?= =?UTF-8?q?action=20=E8=BC=B8=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci.yaml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 02463ff..2ebd8f0 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -20,13 +20,31 @@ jobs: name: codex v${{ steps.version.outputs.version }} tag_name: v${{ steps.version.outputs.version }} target_commitish: ${{ github.head_ref }} - qc: - name: 品質檢查 + test: + name: 工具測試 runs-on: ubuntu needs: [version] + outputs: + status: ${{ steps.test.outputs.status }} + output: ${{ steps.test.outputs.output }} steps: - - name: 品質檢查 + - name: 工具測試 + id: test uses: https://gitea.jsc.idv.tw/actions/codex@v${{ needs.version.outputs.version }} with: oauth: ${{ secrets.CODEX_OAUTH }} - model: gpt-5.4-mini \ No newline at end of file + model: gpt-5.4-mini + output: + name: 輸出結果 + runs-on: ubuntu + needs: [test] + steps: + - name: 輸出狀態 + env: + CODEX_STATUS: ${{ needs.test.outputs.status }} + run: printf 'status: %s\n' "$CODEX_STATUS" + - name: 輸出內容 + env: + CODEX_OUTPUT: ${{ needs.test.outputs.output }} + run: | + printf 'output:\n%s\n' "$CODEX_OUTPUT" -- 2.53.0 From c54ee657aa7e81cba29d22cc70246b709f0a9bd6 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:52:10 +0000 Subject: [PATCH 21/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20entrypoint=20fi?= =?UTF-8?q?ndings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/findings.json | 40 ---------------------------------- 1 file changed, 40 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index f96a2b5..f89b075 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -31,22 +31,6 @@ "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", "is_new": false }, - { - "level": "critical", - "role": "Mage", - "location": "entrypoint.sh:58", - "problem": "在併發環境下,若多個執行緒或過程同時嘗試建立 auth.json,可能會因為檢查檔案是否存在(Line 35)與建立檔案之間的競態條件,導致 `die` 錯誤甚至意外地驗證失敗。雖然目前看起來是單一容器環境,但在 GitHub Actions 或其他 Runner 中,安全起見應使用原子操作。", - "suggestion": "建議使用 `mkdir` 的原子性或檔案鎖定機制,或是確保 `auth_path` 在容器初始化階段就已經是唯讀且受保護的,避免檢查與寫入之間的延遲風險。", - "is_new": false - }, - { - "level": "critical", - "role": "Mage", - "location": "entrypoint.sh:75", - "problem": "在 GitHub Actions 中寫入 `GITHUB_OUTPUT` 使用了動態分隔符(delimiter)。雖然邏輯正確,但若 `codex_output` 內容中恰巧包含了隨機生成的 `output_delimiter` 字串,將會導致輸出截斷或格式損壞。", - "suggestion": "應先掃描 `codex_output` 內容,確保隨機分隔符字串不會出現在內容中,若有衝突則應重新生成分隔符。", - "is_new": false - }, { "level": "warning", "role": "Leo", @@ -63,30 +47,6 @@ "suggestion": "建議將插件預先打包在映像檔中,或使用特定版本的 manifest 檔案管理相依性。", "is_new": false }, - { - "level": "warning", - "role": "Mage", - "problem": "執行 `codex exec` 的邏輯中使用了 `set +e` 暫時關閉錯誤退出機制,儘管後續透過 `PIPESTATUS` 檢查,但若 `codex exec` 指令本身因為環境錯誤或語法錯誤無法啟動,`codex_status` 可能會取得非預期的狀態碼。", - "suggestion": "明確定義各個步驟的錯誤處理,或者將 `codex exec` 包裝在明確的函數中檢查退出碼。", - "location": "entrypoint.sh:37", - "is_new": false - }, - { - "level": "warning", - "role": "Mage", - "location": "entrypoint.sh:42", - "problem": "使用 `install -m 600` 將 `auth_file` 移至 `auth_path`。若 `mktemp` 產生的 `auth_file` 與 `auth_path` 不在同一個檔案系統分區(Filesystem),`install` 指令(底層通常是 copy + chmod/chown)可能會有短暫時間檔案權限為預設值,存在權限外洩風險。", - "suggestion": "建議在確認檔案權限無誤後,於同一分區內使用 `mv` 進行原子性移轉,或在寫入前先明確設定 `umask`。", - "is_new": false - }, - { - "level": "warning", - "role": "Bard", - "location": "entrypoint.sh:76", - "problem": "為了產生分隔符號而使用巢狀判斷來檢查 UUID 檔案,這讓原本流暢的腳本邏輯變得破碎,閱讀時節奏感不佳。", - "suggestion": "建議直接統一使用 `mktemp -u` 產生隨機字串,捨棄繁瑣的 `if` 判斷,讓程式碼的旋律更輕快。", - "is_new": true - }, { "level": "warning", "role": "Leo", -- 2.53.0 From 95653c8c8c2b5b37a1f6927801e90b2bea345b8d Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 10:53:11 +0000 Subject: [PATCH 22/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 45 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index f89b075..2cad244 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -31,37 +31,36 @@ "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", "is_new": false }, - { - "level": "warning", - "role": "Leo", - "location": "Dockerfile:17", - "problem": "在 Dockerfile 中直接使用 `curl` 下載並執行安裝腳本存在潛在的安全風險與不確定性,且沒有檢查腳本的完整性或簽章。若腳本內容在未來變更,可能導致映像檔建置失敗或植入非預期的內容。", - "suggestion": "建議將安裝腳本改為明確的版本化下載,或者將安裝邏輯整合進 Dockerfile 自身。", - "is_new": false - }, - { - "level": "warning", - "role": "Leo", - "location": "Dockerfile:20", - "problem": "在建置期透過網路安裝插件,增加建置複雜度、時間,且對外部網路依賴過高,不可重現。", - "suggestion": "建議將插件預先打包在映像檔中,或使用特定版本的 manifest 檔案管理相依性。", - "is_new": false - }, { "level": "warning", "role": "Leo", "location": "Dockerfile:23", "problem": "插件安裝邏輯重複,直接寫死在指令中。未來若需要安裝更多插件,維護成本會隨數量線性增加,且難以管理插件版本。", "suggestion": "將插件清單抽離為環境變數或專用的設定檔,在 Dockerfile 中使用迴圈讀取並安裝,提升擴充性。", + "is_new": false + }, + { + "level": "warning", + "role": "Bard", + "location": "entrypoint.sh:11", + "problem": "清理函式 `cleanup` 內的變數命名 (`auth_file`, `auth_path`, `codex_output`, `auth_lock`) 雖清楚,但寫法稍顯瑣碎,且 `trap` 的慣用語法建議確保在變數未定義時也能安全執行。", + "suggestion": "在 Shell 腳本中,建議統一使用 `unset` 或更簡潔的方式管理環境變數清理,並確保變數引用更具彈性。", "is_new": true }, { - "level": "info", - "role": "Leo", - "location": "Dockerfile:18", - "problem": "在單一行指令中安裝多個插件且缺乏說明,未來若需新增、移除或維修特定插件時,維護者難以快速理解該插件的用途與相依性。", - "suggestion": "建議將插件安裝拆分為獨立的 RUN 指令,並為每個關鍵插件加上註解說明其用途。", - "is_new": false + "level": "warning", + "role": "Mage", + "location": "entrypoint.sh:35", + "problem": "使用 `mkdir` 作為鎖定機制若容器意外崩潰可能殘留鎖檔。", + "suggestion": "考慮使用更穩健的 `flock` 機制來管理檔案鎖並確保自動清理。" + }, + { + "level": "warning", + "role": "Bard", + "location": "entrypoint.sh:65", + "problem": "在執行 `codex` 的函式中,使用 `set +e` 與 `set -e` 的開關切換來處理回傳值,雖然正確但破壞了程式碼的流暢閱讀感,像是在樂譜中頻繁變調。", + "suggestion": "建議嘗試使用 `if ! codex ...; then status=$?; fi` 的方式,避免在函式內頻繁切換 `set -e` 狀態,保持程式邏輯的單純性。", + "is_new": true }, { "level": "info", @@ -69,6 +68,6 @@ "location": "Dockerfile:7", "problem": "環境變數散落在檔案中間,這使得 Dockerfile 的基礎配置顯得凌亂,彷彿一首詩的標題與正文糾纏不清。", "suggestion": "建議將所有的 `ENV` 設定集中放置在 `Dockerfile` 的頂部,讓配置一目瞭然,維持檔案配置的一致性。", - "is_new": true + "is_new": false } ] -- 2.53.0 From 874ff3d5710f23ee911ea0575b78a02f39d3b94c Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:55:17 +0000 Subject: [PATCH 23/58] =?UTF-8?q?chore(CI):=20=E7=B0=A1=E5=8C=96=20Codex?= =?UTF-8?q?=20=E8=BC=B8=E5=87=BA=E9=A1=AF=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci.yaml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 2ebd8f0..4f390b7 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -40,11 +40,6 @@ jobs: needs: [test] steps: - name: 輸出狀態 - env: - CODEX_STATUS: ${{ needs.test.outputs.status }} - run: printf 'status: %s\n' "$CODEX_STATUS" + run: echo "${{ needs.test.outputs.status }}" - name: 輸出內容 - env: - CODEX_OUTPUT: ${{ needs.test.outputs.output }} - run: | - printf 'output:\n%s\n' "$CODEX_OUTPUT" + run: echo "${{ needs.test.outputs.output }}" -- 2.53.0 From 8e3166681cbaa1987aca856ac317586d21b55995 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:57:12 +0000 Subject: [PATCH 24/58] =?UTF-8?q?fix(Codex=20CLI):=20=E6=94=B9=E7=94=A8=20?= =?UTF-8?q?flock=20=E7=AE=A1=E7=90=86=20auth=20=E9=8E=96=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 10 +++++----- entrypoint.sh | 28 ++++++++++------------------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index dc7f435..7044671 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,13 @@ FROM alpine:latest -# 安裝必要的工具 -RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git jq - # 設定安裝用的環境變數 ENV CODEX_NON_INTERACTIVE=1 ENV CODEX_INSTALL_DIR=/usr/local/bin ENV CODEX_HOME=/root/.codex +ENV CODEX_PLUGINS="jsc@doc jsc@code-review" + +# 安裝必要的工具 +RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git jq util-linux # 安裝 Codex CLI 工具 RUN install_script="$(mktemp)" \ @@ -18,9 +19,8 @@ RUN install_script="$(mktemp)" \ # 安裝技能 RUN codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/doc.git \ - && codex plugin add jsc@doc \ && codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/code-review.git \ - && codex plugin add jsc@code-review + && for plugin in $CODEX_PLUGINS; do codex plugin add "$plugin"; done COPY entrypoint.sh /entrypoint.sh diff --git a/entrypoint.sh b/entrypoint.sh index af34afe..5b5f268 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -8,8 +8,7 @@ die() { } cleanup() { - rm -f "${auth_file:-}" "${auth_path:-}" "${codex_output:-}" - rmdir "${auth_lock:-}" 2>/dev/null || true + rm -f "${auth_file:-}" "${auth_path:-}" "${auth_lock:-}" "${codex_output:-}" } trap cleanup EXIT @@ -29,9 +28,10 @@ umask 077 auth_file="$(mktemp "$CODEX_HOME/auth.XXXXXX")" auth_path="$CODEX_HOME/auth.json" -auth_lock="$CODEX_HOME/auth.lock" +auth_lock="$(mktemp "$CODEX_HOME/auth.lock.XXXXXX")" -mkdir "$auth_lock" || die "Unable to lock Codex auth.json." +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." @@ -50,22 +50,14 @@ rm -f "$auth_file" codex_output="$(mktemp)" -run_codex() { - set +e - codex exec \ - --dangerously-bypass-approvals-and-sandbox \ - --skip-git-repo-check \ - --model "$MODEL" \ - "$PROMPT" 2>&1 | tee "$codex_output" - local status="${PIPESTATUS[0]}" - set -e - return "$status" -} - -if run_codex; then +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="$?" + codex_status="${PIPESTATUS[0]}" fi if [[ -n "${GITHUB_OUTPUT:-}" ]]; then -- 2.53.0 From ce5ae2b30073b298a9a1b6802dbefc8ee04fa043 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 10:57:16 +0000 Subject: [PATCH 25/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/findings.json | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 2cad244..1f93d1e 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -31,14 +31,6 @@ "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", "is_new": false }, - { - "level": "warning", - "role": "Leo", - "location": "Dockerfile:23", - "problem": "插件安裝邏輯重複,直接寫死在指令中。未來若需要安裝更多插件,維護成本會隨數量線性增加,且難以管理插件版本。", - "suggestion": "將插件清單抽離為環境變數或專用的設定檔,在 Dockerfile 中使用迴圈讀取並安裝,提升擴充性。", - "is_new": false - }, { "level": "warning", "role": "Bard", @@ -46,28 +38,5 @@ "problem": "清理函式 `cleanup` 內的變數命名 (`auth_file`, `auth_path`, `codex_output`, `auth_lock`) 雖清楚,但寫法稍顯瑣碎,且 `trap` 的慣用語法建議確保在變數未定義時也能安全執行。", "suggestion": "在 Shell 腳本中,建議統一使用 `unset` 或更簡潔的方式管理環境變數清理,並確保變數引用更具彈性。", "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "entrypoint.sh:35", - "problem": "使用 `mkdir` 作為鎖定機制若容器意外崩潰可能殘留鎖檔。", - "suggestion": "考慮使用更穩健的 `flock` 機制來管理檔案鎖並確保自動清理。" - }, - { - "level": "warning", - "role": "Bard", - "location": "entrypoint.sh:65", - "problem": "在執行 `codex` 的函式中,使用 `set +e` 與 `set -e` 的開關切換來處理回傳值,雖然正確但破壞了程式碼的流暢閱讀感,像是在樂譜中頻繁變調。", - "suggestion": "建議嘗試使用 `if ! codex ...; then status=$?; fi` 的方式,避免在函式內頻繁切換 `set -e` 狀態,保持程式邏輯的單純性。", - "is_new": true - }, - { - "level": "info", - "role": "Bard", - "location": "Dockerfile:7", - "problem": "環境變數散落在檔案中間,這使得 Dockerfile 的基礎配置顯得凌亂,彷彿一首詩的標題與正文糾纏不清。", - "suggestion": "建議將所有的 `ENV` 設定集中放置在 `Dockerfile` 的頂部,讓配置一目瞭然,維持檔案配置的一致性。", - "is_new": false } ] -- 2.53.0 From b53358b4882c84bf77c5b6a0cdba5fe367c72ca5 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 10:58:18 +0000 Subject: [PATCH 26/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 1f93d1e..b08c396 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -33,10 +33,26 @@ }, { "level": "warning", + "role": "Leo", + "location": "Dockerfile:22", + "problem": "在 Dockerfile 中安裝 plugin 時,雖然使用了 `for` 迴圈處理變數,但直接將 plugin URL 硬編碼在 RUN 指令中。如果未來需要更換插件來源或增加插件,需要修改 Dockerfile,這增加了鏡像建置的耦合度。", + "suggestion": "建議將 plugin 安裝路徑抽離到環境變數或專用的配置檔案中,並在 Dockerfile 中透過讀取該清單來進行安裝,提升可維護性。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "Dockerfile:19", + "problem": "在 Dockerfile 中安裝 Codex CLI 後未進行任何檢查,若下載失敗(例如 curl 回傳成功但內容殘缺),後續的 plugin 安裝與執行將會失敗。", + "suggestion": "在安裝指令後增加 `codex --version` 的驗證步驟,確保 CLI 已正確安裝並能正常執行。", + "is_new": true + }, + { + "level": "info", "role": "Bard", - "location": "entrypoint.sh:11", - "problem": "清理函式 `cleanup` 內的變數命名 (`auth_file`, `auth_path`, `codex_output`, `auth_lock`) 雖清楚,但寫法稍顯瑣碎,且 `trap` 的慣用語法建議確保在變數未定義時也能安全執行。", - "suggestion": "在 Shell 腳本中,建議統一使用 `unset` 或更簡潔的方式管理環境變數清理,並確保變數引用更具彈性。", + "location": "tests/entrypoint_test.sh:7", + "problem": "專案在 `entrypoint.sh` 中使用 `die` 函數處理致命錯誤,但測試腳本中卻定義了名稱不同的 `fail` 函數,這使得專案內的錯誤處理語彙不夠一致,略顯突兀。", + "suggestion": "建議將 `tests/entrypoint_test.sh` 中的 `fail` 函數更名為 `die`,使錯誤處理的語彙在專案各處保持一致,讓樂章的節奏更為統一。", "is_new": true } ] -- 2.53.0 From 8bc4891bca65b8a2a2dd023324896fc6333895e6 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 11:02:39 +0000 Subject: [PATCH 27/58] =?UTF-8?q?fix(Dockerfile):=20=E9=A9=97=E8=AD=89=20C?= =?UTF-8?q?odex=20CLI=20=E5=AE=89=E8=A3=9D=E4=B8=A6=E6=8A=BD=E9=9B=A2?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E4=BE=86=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7044671..3c6c92e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ ENV CODEX_NON_INTERACTIVE=1 ENV CODEX_INSTALL_DIR=/usr/local/bin ENV CODEX_HOME=/root/.codex ENV CODEX_PLUGINS="jsc@doc jsc@code-review" +ENV CODEX_PLUGIN_MARKETPLACES="https://gitea.jsc.idv.tw/plugins/doc.git https://gitea.jsc.idv.tw/plugins/code-review.git" # 安裝必要的工具 RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git jq util-linux @@ -15,11 +16,11 @@ RUN install_script="$(mktemp)" \ https://chatgpt.com/codex/install.sh \ -o "$install_script" \ && sh "$install_script" \ + && codex --version \ && rm -f "$install_script" # 安裝技能 -RUN codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/doc.git \ - && codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/code-review.git \ +RUN for marketplace in $CODEX_PLUGIN_MARKETPLACES; do codex plugin marketplace add "$marketplace"; done \ && for plugin in $CODEX_PLUGINS; do codex plugin add "$plugin"; done COPY entrypoint.sh /entrypoint.sh -- 2.53.0 From 200264b113292283c08ec85da69952c0492357e2 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 11:02:42 +0000 Subject: [PATCH 28/58] =?UTF-8?q?test(entrypoint):=20=E7=B5=B1=E4=B8=80?= =?UTF-8?q?=E6=B8=AC=E8=A9=A6=E9=8C=AF=E8=AA=A4=E8=99=95=E7=90=86=E5=91=BD?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/entrypoint_test.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/entrypoint_test.sh b/tests/entrypoint_test.sh index e8cb987..bc38fca 100755 --- a/tests/entrypoint_test.sh +++ b/tests/entrypoint_test.sh @@ -4,7 +4,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -fail() { +die() { echo "FAIL: $1" >&2 exit 1 } @@ -42,7 +42,7 @@ assert_status() { local actual="$1" local expected="$2" - [[ "$actual" -eq "$expected" ]] || fail "expected status $expected, got $actual" + [[ "$actual" -eq "$expected" ]] || die "expected status $expected, got $actual" } test_missing_oauth() { @@ -56,7 +56,7 @@ test_missing_oauth() { set -e assert_status "$status" 1 - grep -q "OAUTH is required" "$tmpdir/stderr" || fail "missing OAUTH error" + grep -q "OAUTH is required" "$tmpdir/stderr" || die "missing OAUTH error" rm -rf "$tmpdir" } @@ -71,7 +71,7 @@ test_missing_model() { set -e assert_status "$status" 1 - grep -q "MODEL is required" "$tmpdir/stderr" || fail "missing MODEL error" + grep -q "MODEL is required" "$tmpdir/stderr" || die "missing MODEL error" rm -rf "$tmpdir" } @@ -86,7 +86,7 @@ test_invalid_base64() { set -e assert_status "$status" 1 - grep -q "valid base64" "$tmpdir/stderr" || fail "invalid base64 error" + grep -q "valid base64" "$tmpdir/stderr" || die "invalid base64 error" rm -rf "$tmpdir" } @@ -101,7 +101,7 @@ test_non_object_json() { set -e assert_status "$status" 1 - grep -q "JSON object" "$tmpdir/stderr" || fail "non-object JSON error" + grep -q "JSON object" "$tmpdir/stderr" || die "non-object JSON error" rm -rf "$tmpdir" } @@ -112,9 +112,9 @@ test_success_output() { run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="hello" - grep -q "status=completed" "$tmpdir/github-output" || fail "completed status output" - grep -q "codex ok" "$tmpdir/github-output" || fail "codex output" - [[ ! -e "$tmpdir/codex-home/auth.json" ]] || fail "auth.json was not cleaned up" + grep -q "status=completed" "$tmpdir/github-output" || die "completed status output" + grep -q "codex ok" "$tmpdir/github-output" || die "codex output" + [[ ! -e "$tmpdir/codex-home/auth.json" ]] || die "auth.json was not cleaned up" rm -rf "$tmpdir" } @@ -129,9 +129,9 @@ test_failure_output() { set -e assert_status "$status" 7 - grep -q "status=failed" "$tmpdir/github-output" || fail "failed status output" - grep -q "codex failed" "$tmpdir/github-output" || fail "failed codex output" - [[ ! -e "$tmpdir/codex-home/auth.json" ]] || fail "auth.json was not cleaned up after failure" + grep -q "status=failed" "$tmpdir/github-output" || die "failed status output" + grep -q "codex failed" "$tmpdir/github-output" || die "failed codex output" + [[ ! -e "$tmpdir/codex-home/auth.json" ]] || die "auth.json was not cleaned up after failure" rm -rf "$tmpdir" } -- 2.53.0 From daa6bd150af0177006fa350bb1dda18b65eaa72d Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 11:02:45 +0000 Subject: [PATCH 29/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/findings.json | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index b08c396..8315689 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -30,29 +30,5 @@ "problem": "新增了 Codex CLI 的安裝與插件註冊過程,但沒有對應的測試來驗證安裝是否成功、插件是否正確載入。", "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", "is_new": false - }, - { - "level": "warning", - "role": "Leo", - "location": "Dockerfile:22", - "problem": "在 Dockerfile 中安裝 plugin 時,雖然使用了 `for` 迴圈處理變數,但直接將 plugin URL 硬編碼在 RUN 指令中。如果未來需要更換插件來源或增加插件,需要修改 Dockerfile,這增加了鏡像建置的耦合度。", - "suggestion": "建議將 plugin 安裝路徑抽離到環境變數或專用的配置檔案中,並在 Dockerfile 中透過讀取該清單來進行安裝,提升可維護性。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "Dockerfile:19", - "problem": "在 Dockerfile 中安裝 Codex CLI 後未進行任何檢查,若下載失敗(例如 curl 回傳成功但內容殘缺),後續的 plugin 安裝與執行將會失敗。", - "suggestion": "在安裝指令後增加 `codex --version` 的驗證步驟,確保 CLI 已正確安裝並能正常執行。", - "is_new": true - }, - { - "level": "info", - "role": "Bard", - "location": "tests/entrypoint_test.sh:7", - "problem": "專案在 `entrypoint.sh` 中使用 `die` 函數處理致命錯誤,但測試腳本中卻定義了名稱不同的 `fail` 函數,這使得專案內的錯誤處理語彙不夠一致,略顯突兀。", - "suggestion": "建議將 `tests/entrypoint_test.sh` 中的 `fail` 函數更名為 `die`,使錯誤處理的語彙在專案各處保持一致,讓樂章的節奏更為統一。", - "is_new": true } ] -- 2.53.0 From a9144a4b210a1c7e8beb28cba8dde4071c35f822 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 11:03:20 +0000 Subject: [PATCH 30/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 55 ++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 8315689..b57abd1 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -11,9 +11,15 @@ "level": "critical", "role": "Assassin", "location": "Dockerfile:16", - "problem": "從未經驗證的遠端 git 儲存庫安裝插件,若儲存庫內容遭竄改,將導致任意程式碼執行風險。同時,直接執行從外部網路下載的腳本,且未經過 Hash 檢查或簽章驗證,是嚴重的供應鏈攻擊破口。", - "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,並對下載的腳本進行 SHA-256 雜湊驗證。", - "is_new": false + "problem": "直接從網址下載並執行安裝腳本,未鎖定版本或驗證 checksum,且從未經驗證的遠端儲存庫安裝插件,存在嚴重供應鏈攻擊風險。", + "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,並將安裝腳本下載至本地,進行 SHA-256 雜湊驗證。" + }, + { + "level": "critical", + "role": "Assassin", + "location": "Dockerfile:17", + "problem": "Dockerfile 直接從外部連結下載並執行腳本,且未驗證其雜湊值,若來源遭攔截或篡改,將導致任意程式碼執行。", + "suggestion": "下載指令碼後,務必使用 sha256sum 或其他雜湊函數驗證其完整性,確保與預期內容一致後再執行。" }, { "level": "critical", @@ -30,5 +36,48 @@ "problem": "新增了 Codex CLI 的安裝與插件註冊過程,但沒有對應的測試來驗證安裝是否成功、插件是否正確載入。", "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", "is_new": false + }, + { + "level": "warning", + "role": "Assassin", + "location": "Dockerfile:10", + "problem": "Codex 外掛市集來源指向 `gitea.jsc.idv.tw`,未驗證來源的安全性。若該伺服器遭駭,將導致自動安裝惡意或篡改過的外掛,進而導致供應鏈攻擊。", + "suggestion": "若可能,請將外掛來源固定在受信任的內部儲存庫或使用經簽署的外掛版本。確保來源伺服器具有嚴格的存取控管與安全性掃描。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "Dockerfile:10", + "problem": "環境變數使用以空格分隔的字串,若未來名稱中包含空格將導致 shell 展開錯誤,且難以維護。", + "suggestion": "建議改用換行符號(\n)分隔,並在安裝迴圈中使用 `IFS=$'\n'` 處理,以提高 shell 指令的健壯性。" + }, + { + "level": "warning", + "role": "Bard", + "location": "Dockerfile:17", + "problem": "將安裝腳本邏輯直接寫在 `RUN` 指令中顯得冗長,且使用 `$(mktemp)` 容易產生難以追蹤的臨時檔案。", + "suggestion": "考慮將安裝邏輯封裝成一個獨立的 script 檔案,讓 `Dockerfile` 更簡潔優雅。" + }, + { + "level": "warning", + "role": "Bard", + "location": "entrypoint.sh:45", + "problem": "使用靜態路徑檢查 `auth.json` 是否存在,若前次執行中斷導致檔案未清除,會導致後續執行失敗(Self-inflicted DoS)。", + "suggestion": "移除對既有檔案的檢查,改為在執行前確保該檔案為最新且受控狀態,或使用唯一的隨機臨時檔。" + }, + { + "level": "warning", + "role": "Bard", + "location": "tests/entrypoint_test.sh:17", + "problem": "在測試中建立 `codex` 指令時,Here-document 與命令混雜,可讀性較低。", + "suggestion": "建議使用更整齊的縮排格式,或將其抽離為獨立的測試輔助檔案。" + }, + { + "level": "info", + "role": "Maya", + "location": "tests/entrypoint_test.sh:65", + "problem": "測試案例對於輸入參數的邊界測試(例如 `PROMPT` 為空字串、極長字串)不足。", + "suggestion": "增加針對 `PROMPT` 輸入為空字串或是包含特殊 Shell 跳脫字元的測試案例。" } ] -- 2.53.0 From b63596fb09a0d0efc066ed0e1c8963e3ae514be4 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:09:30 +0000 Subject: [PATCH 31/58] =?UTF-8?q?fix(Dockerfile):=20=E9=A9=97=E8=AD=89=20C?= =?UTF-8?q?odex=20=E5=AE=89=E8=A3=9D=E8=85=B3=E6=9C=AC=E4=B8=A6=E9=8E=96?= =?UTF-8?q?=E5=AE=9A=E5=A4=96=E6=8E=9B=E5=B8=82=E9=9B=86=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3c6c92e..3782932 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,9 @@ FROM alpine:latest ENV CODEX_NON_INTERACTIVE=1 ENV CODEX_INSTALL_DIR=/usr/local/bin ENV CODEX_HOME=/root/.codex -ENV CODEX_PLUGINS="jsc@doc jsc@code-review" -ENV CODEX_PLUGIN_MARKETPLACES="https://gitea.jsc.idv.tw/plugins/doc.git https://gitea.jsc.idv.tw/plugins/code-review.git" +ARG CODEX_INSTALL_SHA256=73eb367137d151eabe89590a1d1a08a9c2fe2800a656bffac3c5707b62b42683 +ARG CODEX_DOC_MARKETPLACE_REF=f8da961328a85f267b4402566e127310370169da +ARG CODEX_CODE_REVIEW_MARKETPLACE_REF=9e016edff016d58f4d64e0a5468220d35a0f657b # 安裝必要的工具 RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git jq util-linux @@ -15,13 +16,16 @@ RUN install_script="$(mktemp)" \ && curl -fsSL --retry 3 --retry-delay 2 --max-time 120 \ https://chatgpt.com/codex/install.sh \ -o "$install_script" \ + && echo "${CODEX_INSTALL_SHA256} ${install_script}" | sha256sum -c - \ && sh "$install_script" \ && codex --version \ && rm -f "$install_script" # 安裝技能 -RUN for marketplace in $CODEX_PLUGIN_MARKETPLACES; do codex plugin marketplace add "$marketplace"; done \ - && for plugin in $CODEX_PLUGINS; do codex plugin add "$plugin"; done +RUN codex plugin marketplace add "https://gitea.jsc.idv.tw/plugins/doc.git" --ref "$CODEX_DOC_MARKETPLACE_REF" \ + && codex plugin marketplace add "https://gitea.jsc.idv.tw/plugins/code-review.git" --ref "$CODEX_CODE_REVIEW_MARKETPLACE_REF" \ + && codex plugin add "jsc@doc" \ + && codex plugin add "jsc@code-review" COPY entrypoint.sh /entrypoint.sh -- 2.53.0 From a01dbe5e2c1b5500546ad0b122db72fd41cb60ff Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:09:34 +0000 Subject: [PATCH 32/58] =?UTF-8?q?test(entrypoint):=20=E8=A3=9C=E4=B8=8A=20?= =?UTF-8?q?prompt=20=E9=82=8A=E7=95=8C=E8=88=87=E6=98=A0=E5=83=8F=E6=AA=94?= =?UTF-8?q?=20smoke=20=E6=B8=AC=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/docker_image_test.sh | 22 ++++++++++++++++++++++ tests/entrypoint_test.sh | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100755 tests/docker_image_test.sh diff --git a/tests/docker_image_test.sh b/tests/docker_image_test.sh new file mode 100755 index 0000000..25d1d48 --- /dev/null +++ b/tests/docker_image_test.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +IMAGE_NAME="codex-action-smoke:local" + +if ! command -v docker >/dev/null 2>&1; then + echo "SKIP: docker is not available" + exit 0 +fi + +if ! docker info >/dev/null 2>&1; then + echo "SKIP: docker daemon is not available" + exit 0 +fi + +docker build -t "$IMAGE_NAME" "$ROOT_DIR" + +docker run --rm --entrypoint codex "$IMAGE_NAME" --version +docker run --rm --entrypoint codex "$IMAGE_NAME" plugin list | grep -q "jsc@doc" +docker run --rm --entrypoint codex "$IMAGE_NAME" plugin list | grep -q "jsc@code-review" diff --git a/tests/entrypoint_test.sh b/tests/entrypoint_test.sh index bc38fca..8055217 100755 --- a/tests/entrypoint_test.sh +++ b/tests/entrypoint_test.sh @@ -16,6 +16,7 @@ make_fake_codex() { cat > "$dir/codex" < "\${CODEX_FAKE_ARGS:-/dev/null}" echo "$message" exit $exit_code SH @@ -118,6 +119,30 @@ test_success_output() { rm -rf "$tmpdir" } +test_empty_prompt() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_codex "$tmpdir" 0 "codex ok" + + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="" + + grep -q "codex ok" "$tmpdir/github-output" || die "empty prompt output" + rm -rf "$tmpdir" +} + +test_prompt_with_shell_characters() { + local tmpdir + local prompt + tmpdir="$(mktemp -d)" + prompt='hello; rm -rf / $(echo bad) "quoted"' + make_fake_codex "$tmpdir" 0 "codex ok" + + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="$prompt" CODEX_FAKE_ARGS="$tmpdir/codex-args" + + grep -qF "$prompt" "$tmpdir/codex-args" || die "prompt was not passed as one argument" + rm -rf "$tmpdir" +} + test_failure_output() { local tmpdir tmpdir="$(mktemp -d)" @@ -140,6 +165,8 @@ test_missing_model test_invalid_base64 test_non_object_json test_success_output +test_empty_prompt +test_prompt_with_shell_characters test_failure_output echo "entrypoint tests passed" -- 2.53.0 From 770091828eeaf3c5739dc488c2dbddfdb4499a7d Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:09:36 +0000 Subject: [PATCH 33/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/exclusions.json | 18 +++++++ .gitea/ai-review/findings.json | 84 +------------------------------- 2 files changed, 19 insertions(+), 83 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index ede526f..d62df82 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -64,5 +64,23 @@ "role": "Maya", "original_finding": "應在測試案例中模擬空 MODEL 輸入,並驗證腳本是否正確拋出錯誤並以 exit 1 終止。", "reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)" + }, + { + "location": "Dockerfile:17", + "role": "Bard", + "original_finding": "將安裝腳本邏輯直接寫在 `RUN` 指令中顯得冗長,且使用 `$(mktemp)` 容易產生難以追蹤的臨時檔案。", + "reason": "安裝流程需在同一 Docker layer 中下載、驗證 SHA-256、執行並清理暫存腳本;留在 Dockerfile 可讓供應鏈驗證步驟與安裝命令緊鄰,mktemp 產生的檔案也已於同一 RUN 中刪除。" + }, + { + "location": "entrypoint.sh:45", + "role": "Bard", + "original_finding": "使用靜態路徑檢查 `auth.json` 是否存在,若前次執行中斷導致檔案未清除,會導致後續執行失敗(Self-inflicted DoS)。", + "reason": "拒絕覆寫既有 auth.json 是刻意的 secret 安全保護,避免覆蓋呼叫端掛載或殘留的認證檔;Docker Action 預期每次執行使用獨立容器,前次中斷造成殘留的風險低於靜默覆寫 secret 的風險。" + }, + { + "location": "tests/entrypoint_test.sh:17", + "role": "Bard", + "original_finding": "在測試中建立 `codex` 指令時,Here-document 與命令混雜,可讀性較低。", + "reason": "測試 helper 需要動態產生可執行的假 codex 指令,here-document 在此處比額外 fixture 檔更直觀且維持測試自含;此項屬風格偏好,不影響行為正確性。" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index b57abd1..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,83 +1 @@ -[ - { - "level": "critical", - "role": "Assassin", - "location": "Dockerfile:13", - "problem": "直接將網際網路的腳本經由 pipe 傳送給 shell 執行 (curl | sh) 極度危險,且缺乏驗證,極易導致容器建置階段遭劫持或執行惡意代碼。", - "suggestion": "應先將腳本下載至本地,透過 sha256sum 或 gpg 驗證其完整性與來源可靠性後,再執行安裝。", - "is_new": false - }, - { - "level": "critical", - "role": "Assassin", - "location": "Dockerfile:16", - "problem": "直接從網址下載並執行安裝腳本,未鎖定版本或驗證 checksum,且從未經驗證的遠端儲存庫安裝插件,存在嚴重供應鏈攻擊風險。", - "suggestion": "必須鎖定特定 commit hash (SHA) 來引用插件,並將安裝腳本下載至本地,進行 SHA-256 雜湊驗證。" - }, - { - "level": "critical", - "role": "Assassin", - "location": "Dockerfile:17", - "problem": "Dockerfile 直接從外部連結下載並執行腳本,且未驗證其雜湊值,若來源遭攔截或篡改,將導致任意程式碼執行。", - "suggestion": "下載指令碼後,務必使用 sha256sum 或其他雜湊函數驗證其完整性,確保與預期內容一致後再執行。" - }, - { - "level": "critical", - "role": "Assassin", - "location": "Dockerfile:20", - "problem": "自動化從外部 Gitea 儲存庫安裝插件,若該儲存庫被竄改,將導致攻擊者可以在執行環境中安裝並執行惡意程式碼。這是極高風險的行為。", - "suggestion": "應限制插件來源,並在安裝前進行原始碼審查。若非必要,請將插件程式碼打包至 Docker Image 內部,避免動態安裝。", - "is_new": false - }, - { - "level": "critical", - "role": "Maya", - "location": "Dockerfile:11", - "problem": "新增了 Codex CLI 的安裝與插件註冊過程,但沒有對應的測試來驗證安裝是否成功、插件是否正確載入。", - "suggestion": "應在測試套件中加入 Docker 映像檔的整合測試,驗證容器啟動後 `codex --version` 及插件列表是否存在。", - "is_new": false - }, - { - "level": "warning", - "role": "Assassin", - "location": "Dockerfile:10", - "problem": "Codex 外掛市集來源指向 `gitea.jsc.idv.tw`,未驗證來源的安全性。若該伺服器遭駭,將導致自動安裝惡意或篡改過的外掛,進而導致供應鏈攻擊。", - "suggestion": "若可能,請將外掛來源固定在受信任的內部儲存庫或使用經簽署的外掛版本。確保來源伺服器具有嚴格的存取控管與安全性掃描。", - "is_new": true - }, - { - "level": "warning", - "role": "Leo", - "location": "Dockerfile:10", - "problem": "環境變數使用以空格分隔的字串,若未來名稱中包含空格將導致 shell 展開錯誤,且難以維護。", - "suggestion": "建議改用換行符號(\n)分隔,並在安裝迴圈中使用 `IFS=$'\n'` 處理,以提高 shell 指令的健壯性。" - }, - { - "level": "warning", - "role": "Bard", - "location": "Dockerfile:17", - "problem": "將安裝腳本邏輯直接寫在 `RUN` 指令中顯得冗長,且使用 `$(mktemp)` 容易產生難以追蹤的臨時檔案。", - "suggestion": "考慮將安裝邏輯封裝成一個獨立的 script 檔案,讓 `Dockerfile` 更簡潔優雅。" - }, - { - "level": "warning", - "role": "Bard", - "location": "entrypoint.sh:45", - "problem": "使用靜態路徑檢查 `auth.json` 是否存在,若前次執行中斷導致檔案未清除,會導致後續執行失敗(Self-inflicted DoS)。", - "suggestion": "移除對既有檔案的檢查,改為在執行前確保該檔案為最新且受控狀態,或使用唯一的隨機臨時檔。" - }, - { - "level": "warning", - "role": "Bard", - "location": "tests/entrypoint_test.sh:17", - "problem": "在測試中建立 `codex` 指令時,Here-document 與命令混雜,可讀性較低。", - "suggestion": "建議使用更整齊的縮排格式,或將其抽離為獨立的測試輔助檔案。" - }, - { - "level": "info", - "role": "Maya", - "location": "tests/entrypoint_test.sh:65", - "problem": "測試案例對於輸入參數的邊界測試(例如 `PROMPT` 為空字串、極長字串)不足。", - "suggestion": "增加針對 `PROMPT` 輸入為空字串或是包含特殊 Shell 跳脫字元的測試案例。" - } -] +[] -- 2.53.0 From a5308acf881321ba0945c743deb9adb50abc886a Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 13:11:55 +0000 Subject: [PATCH 34/58] chore: update ai-review findings [ai-review-bot][success] --- .gitea/ai-review/findings.json | 35 +++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fe51488..f8c882c 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1 +1,34 @@ -[] +[ + { + "level": "warning", + "role": "Assassin", + "problem": "Codex 外掛市集來源指向 `gitea.jsc.idv.tw`,未驗證來源的安全性。若該伺服器遭駭,將導致自動安裝惡意或篡改過的外掛,進而導致供應鏈攻擊。", + "suggestion": "若可能,請將外掛來源固定在受信任的內部儲存庫或使用經簽署的外掛版本。確保來源伺服器具有嚴格的存取控管與安全性掃描。", + "location": "Dockerfile:10", + "is_new": false + }, + { + "level": "warning", + "role": "Leo", + "location": "Dockerfile:24", + "problem": "插件 URL 與版本參照 (REF) 硬編碼在 Dockerfile 中,未來若需更新插件或更換來源,需重新編譯整個 Docker 映像檔,維護成本較高。", + "suggestion": "建議將這些插件清單與版本資訊移至外部設定檔 (如 plugins.json),並在 Dockerfile 中讀取該檔案進行安裝,增加彈性。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "Dockerfile:18", + "problem": "若 sh \"$install_script\" 安裝過程失敗,rm -f 由於被 && 連接,不會被執行,導致 build 過程中殘留不必要的暫存檔案,雖然 Docker Layer 會自動清除,但仍屬不佳的清理實作。", + "suggestion": "建議改用 trap 進行清理,或確保 rm -f 指令在 failure 情境下也能執行,例如:sh \"$install_script\" || { rm -f \"$install_script\"; exit 1; }", + "is_new": true + }, + { + "level": "info", + "role": "Maya", + "problem": "測試案例對於輸入參數的邊界測試(例如 `PROMPT` 為空字串、極長字串)不足。", + "suggestion": "增加針對 `PROMPT` 輸入為空字串或是包含特殊 Shell 跳脫字元的測試案例。", + "location": "tests/entrypoint_test.sh:65", + "is_new": false + } +] -- 2.53.0 From c88b2a8c619d9128cb6eac3d5852b318825243a5 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:19:37 +0000 Subject: [PATCH 35/58] =?UTF-8?q?fix(Dockerfile):=20=E7=A2=BA=E4=BF=9D?= =?UTF-8?q?=E5=AE=89=E8=A3=9D=E8=85=B3=E6=9C=AC=E5=A4=B1=E6=95=97=E6=99=82?= =?UTF-8?q?=E6=B8=85=E7=90=86=E6=9A=AB=E5=AD=98=E6=AA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3782932..913fb33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,13 +13,13 @@ RUN apk add --no-cache --no-check-certificate bash ca-certificates curl git jq u # 安裝 Codex CLI 工具 RUN install_script="$(mktemp)" \ + && trap 'rm -f "$install_script"' EXIT \ && curl -fsSL --retry 3 --retry-delay 2 --max-time 120 \ https://chatgpt.com/codex/install.sh \ -o "$install_script" \ && echo "${CODEX_INSTALL_SHA256} ${install_script}" | sha256sum -c - \ && sh "$install_script" \ - && codex --version \ - && rm -f "$install_script" + && codex --version # 安裝技能 RUN codex plugin marketplace add "https://gitea.jsc.idv.tw/plugins/doc.git" --ref "$CODEX_DOC_MARKETPLACE_REF" \ -- 2.53.0 From 70019a74420ccc7f377ebd4c7aa153eebe401c8f Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:19:40 +0000 Subject: [PATCH 36/58] =?UTF-8?q?chore(CI):=20=E5=9C=A8=E6=B8=AC=E8=A9=A6?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=85=A7=E8=BC=B8=E5=87=BA=20Codex=20?= =?UTF-8?q?=E7=B5=90=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci.yaml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 4f390b7..b2ef237 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -34,12 +34,6 @@ jobs: with: oauth: ${{ secrets.CODEX_OAUTH }} model: gpt-5.4-mini - output: - name: 輸出結果 - runs-on: ubuntu - needs: [test] - steps: - - name: 輸出狀態 - run: echo "${{ needs.test.outputs.status }}" - - name: 輸出內容 - run: echo "${{ needs.test.outputs.output }}" + - name: 自我介紹 + if: ${{ steps.test.outputs.status == 'completed' }} + run: echo "${{ steps.test.outputs.output }}" -- 2.53.0 From 85595d738c477fe2abea12ac31c8a58f0d80f7c2 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:19:43 +0000 Subject: [PATCH 37/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/exclusions.json | 18 ++++++++++++++++ .gitea/ai-review/findings.json | 35 +------------------------------- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index d62df82..8a01d54 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -82,5 +82,23 @@ "role": "Bard", "original_finding": "在測試中建立 `codex` 指令時,Here-document 與命令混雜,可讀性較低。", "reason": "測試 helper 需要動態產生可執行的假 codex 指令,here-document 在此處比額外 fixture 檔更直觀且維持測試自含;此項屬風格偏好,不影響行為正確性。" + }, + { + "location": "Dockerfile:10", + "role": "Assassin", + "original_finding": "Codex 外掛市集來源指向 `gitea.jsc.idv.tw`,未驗證來源的安全性。若該伺服器遭駭,將導致自動安裝惡意或篡改過的外掛,進而導致供應鏈攻擊。", + "reason": "Dockerfile 已使用 `codex plugin marketplace add --ref` 將 doc 與 code-review marketplace 固定到指定 commit SHA;此 action 需要安裝內部 Gitea marketplace 外掛,來源伺服器信任與存取控管屬部署環境治理,不適合在 Dockerfile 內改成其他來源。" + }, + { + "location": "Dockerfile:24", + "role": "Leo", + "original_finding": "插件 URL 與版本參照 (REF) 硬編碼在 Dockerfile 中,未來若需更新插件或更換來源,需重新編譯整個 Docker 映像檔,維護成本較高。", + "reason": "此 Docker Action 需要可重現的建置結果;將 marketplace URL 與 ref 固定在 Dockerfile ARG 中,可讓外掛版本變更必須經由映像檔重建與 code review,避免外部設定在執行時靜默改變供應鏈內容。" + }, + { + "location": "tests/entrypoint_test.sh:65", + "role": "Maya", + "original_finding": "測試案例對於輸入參數的邊界測試(例如 `PROMPT` 為空字串、極長字串)不足。", + "reason": "目前測試已包含 `test_empty_prompt` 與 `test_prompt_with_shell_characters`,覆蓋空字串與含 shell 特殊字元的 PROMPT 傳遞;此 finding 對最新測試內容已不成立。" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index f8c882c..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,34 +1 @@ -[ - { - "level": "warning", - "role": "Assassin", - "problem": "Codex 外掛市集來源指向 `gitea.jsc.idv.tw`,未驗證來源的安全性。若該伺服器遭駭,將導致自動安裝惡意或篡改過的外掛,進而導致供應鏈攻擊。", - "suggestion": "若可能,請將外掛來源固定在受信任的內部儲存庫或使用經簽署的外掛版本。確保來源伺服器具有嚴格的存取控管與安全性掃描。", - "location": "Dockerfile:10", - "is_new": false - }, - { - "level": "warning", - "role": "Leo", - "location": "Dockerfile:24", - "problem": "插件 URL 與版本參照 (REF) 硬編碼在 Dockerfile 中,未來若需更新插件或更換來源,需重新編譯整個 Docker 映像檔,維護成本較高。", - "suggestion": "建議將這些插件清單與版本資訊移至外部設定檔 (如 plugins.json),並在 Dockerfile 中讀取該檔案進行安裝,增加彈性。", - "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "Dockerfile:18", - "problem": "若 sh \"$install_script\" 安裝過程失敗,rm -f 由於被 && 連接,不會被執行,導致 build 過程中殘留不必要的暫存檔案,雖然 Docker Layer 會自動清除,但仍屬不佳的清理實作。", - "suggestion": "建議改用 trap 進行清理,或確保 rm -f 指令在 failure 情境下也能執行,例如:sh \"$install_script\" || { rm -f \"$install_script\"; exit 1; }", - "is_new": true - }, - { - "level": "info", - "role": "Maya", - "problem": "測試案例對於輸入參數的邊界測試(例如 `PROMPT` 為空字串、極長字串)不足。", - "suggestion": "增加針對 `PROMPT` 輸入為空字串或是包含特殊 Shell 跳脫字元的測試案例。", - "location": "tests/entrypoint_test.sh:65", - "is_new": false - } -] +[] -- 2.53.0 From ad22af9b34029e7536ed8e096d00cff318f17e85 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 13:20:25 +0000 Subject: [PATCH 38/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fe51488..399dde4 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1 +1,23 @@ -[] +[ + { + "level": "critical", + "role": "Mage", + "location": "entrypoint.sh:49", + "problem": "在執行 install -m 600 時,若 codex 進程已經在嘗試讀取 auth.json,會發生檔案存取競態(Race Condition)。雖然使用了 flock,但這僅在同一個 shell 腳本實例中有效,無法保護跨容器或跨執行環境的檔案存取一致性。", + "suggestion": "建議將 auth.json 放置於唯讀且受限的目錄中,並通過環境變數直接傳遞路徑給 codex,而非在執行時進行檔案寫入與複製。" + }, + { + "level": "warning", + "role": "Mage", + "location": "entrypoint.sh:58", + "problem": "在容器化環境(通常是 ephemeral 的)中,auth.json 寫入後立刻被刪除,這會導致 codex 在後續執行中因找不到驗證檔案而無法運作。另外,trap 的清理機制會導致該檔案在 codex 完成工作前被刪除,這對於長效執行或需要多次存取的應用場景是錯誤的設計。", + "suggestion": "評估 codex 是否需要該檔案在執行期間持續存在。若需要,請調整清理時機,或考慮使用記憶體中的臨時檔案系統(tmpfs)來提升安全性,而非直接移除檔案。" + }, + { + "level": "warning", + "role": "Mage", + "location": "Dockerfile:32", + "problem": "在 Dockerfile 中直接使用 RUN 來下載並執行安裝腳本,沒有進行網路連接穩定性的驗證或完整的錯誤恢復機制。一旦網路不穩導致腳本不完整,後續的 sha256sum 檢查會失敗,但 Dockerfile 層疊技術可能會導致中間層殘留損壞的檔案。", + "suggestion": "將下載、SHA256 驗證與安裝合併在同一個 RUN 指令中,並加入重試機制(如已有的 --retry),確保每一層的原子性。" + } +] -- 2.53.0 From eac0a4463a1ea3391f1ab40d736509a4e7481fe8 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:38:20 +0000 Subject: [PATCH 39/58] =?UTF-8?q?refactor(entrypoint):=20=E6=94=B9?= =?UTF-8?q?=E7=94=A8=20Node.js=20=E4=B8=BB=E7=A8=8B=E5=BC=8F=E5=9F=B7?= =?UTF-8?q?=E8=A1=8C=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 5 +- app/main.js | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++ entrypoint.sh | 84 +--------------------- 3 files changed, 199 insertions(+), 83 deletions(-) create mode 100644 app/main.js diff --git a/Dockerfile b/Dockerfile index 913fb33..9348293 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ ARG CODEX_DOC_MARKETPLACE_REF=f8da961328a85f267b4402566e127310370169da ARG CODEX_CODE_REVIEW_MARKETPLACE_REF=9e016edff016d58f4d64e0a5468220d35a0f657b # 安裝必要的工具 -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 -RUN chmod +x /entrypoint.sh +RUN chmod +x /entrypoint.sh /app/main.js ENTRYPOINT ["/entrypoint.sh"] diff --git a/app/main.js b/app/main.js new file mode 100644 index 0000000..bfa0c75 --- /dev/null +++ b/app/main.js @@ -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) { + if (!filePath || !createdPaths.has(filePath)) { + return; + } + + try { + fs.rmSync(filePath, { force: true }); + } catch { + // Best-effort cleanup only. + } +} + +function cleanup() { + for (const filePath of Array.from(createdPaths).reverse()) { + removeIfCreated(filePath); + } +} + +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); + 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()}`; + } 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); +} + +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, ""); + 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; + try { + parsed = JSON.parse(decoded.toString("utf8")); + } catch { + fail("Decoded OAUTH must be a JSON object."); + } + + if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") { + fail("Decoded OAUTH must be a JSON object."); + } +} + +function runCodex(model, prompt) { + return new Promise((resolve) => { + const child = spawn( + "codex", + [ + "exec", + "--dangerously-bypass-approvals-and-sandbox", + "--skip-git-repo-check", + "--model", + model, + prompt, + ], + { stdio: ["ignore", "pipe", "pipe"] }, + ); + + let output = ""; + + child.stdout.on("data", (chunk) => { + process.stdout.write(chunk); + output += chunk.toString(); + }); + + child.stderr.on("data", (chunk) => { + process.stdout.write(chunk); + output += chunk.toString(); + }); + + child.on("error", (error) => { + output += `${error.message}\n`; + resolve({ status: 1, output }); + }); + + child.on("close", (code) => { + resolve({ status: code ?? 1, output }); + }); + }); +} + +async function main() { + process.on("exit", cleanup); + process.on("SIGINT", () => { + cleanup(); + process.exit(130); + }); + process.on("SIGTERM", () => { + cleanup(); + 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; + + if (!oauth) { + fail("OAUTH is required: provide base64 encoded Codex auth.json."); + } + + if (!model) { + 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); + } catch { + fail("Unable to lock Codex auth.json."); + } + + validateAuth(oauth, authFile); + + if (fs.existsSync(authPath)) { + fail("Refusing to overwrite existing Codex auth.json."); + } + + fs.copyFileSync(authFile, authPath); + 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) { + fs.closeSync(lockHandle); + } + + cleanup(); + process.exit(result.status); +} + +main().catch((error) => { + fail(error instanceof Error ? error.message : String(error)); +}); diff --git a/entrypoint.sh b/entrypoint.sh index 5b5f268..b7c1b53 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -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" -- 2.53.0 From 1f470f552920142e9323873ad248ef80aeb12b4f Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:38:26 +0000 Subject: [PATCH 40/58] =?UTF-8?q?test(entrypoint):=20=E9=A9=97=E8=AD=89=20?= =?UTF-8?q?Node.js=20=E5=85=A5=E5=8F=A3=E9=8C=AF=E8=AA=A4=E8=BC=B8?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/entrypoint_test.sh | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/entrypoint_test.sh b/tests/entrypoint_test.sh index 8055217..f3a0b3d 100755 --- a/tests/entrypoint_test.sh +++ b/tests/entrypoint_test.sh @@ -58,6 +58,8 @@ test_missing_oauth() { assert_status "$status" 1 grep -q "OAUTH is required" "$tmpdir/stderr" || die "missing OAUTH error" + grep -q "status=failed" "$tmpdir/github-output" || die "missing OAUTH failed status output" + grep -q "OAUTH is required" "$tmpdir/github-output" || die "missing OAUTH output" rm -rf "$tmpdir" } @@ -73,6 +75,8 @@ test_missing_model() { assert_status "$status" 1 grep -q "MODEL is required" "$tmpdir/stderr" || die "missing MODEL error" + grep -q "status=failed" "$tmpdir/github-output" || die "missing MODEL failed status output" + grep -q "MODEL is required" "$tmpdir/github-output" || die "missing MODEL output" rm -rf "$tmpdir" } @@ -88,6 +92,8 @@ test_invalid_base64() { assert_status "$status" 1 grep -q "valid base64" "$tmpdir/stderr" || die "invalid base64 error" + grep -q "status=failed" "$tmpdir/github-output" || die "invalid base64 failed status output" + grep -q "valid base64" "$tmpdir/github-output" || die "invalid base64 output" rm -rf "$tmpdir" } @@ -103,6 +109,8 @@ test_non_object_json() { assert_status "$status" 1 grep -q "JSON object" "$tmpdir/stderr" || die "non-object JSON error" + grep -q "status=failed" "$tmpdir/github-output" || die "non-object JSON failed status output" + grep -q "JSON object" "$tmpdir/github-output" || die "non-object JSON output" rm -rf "$tmpdir" } @@ -160,6 +168,28 @@ test_failure_output() { rm -rf "$tmpdir" } +test_missing_codex_command_output() { + local tmpdir + local bindir + tmpdir="$(mktemp -d)" + bindir="$tmpdir/bin" + mkdir -p "$bindir" + ln -s "$(command -v bash)" "$bindir/bash" + ln -s "$(command -v dirname)" "$bindir/dirname" + ln -s "$(command -v node)" "$bindir/node" + + set +e + run_entrypoint "$tmpdir" PATH="$bindir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="hello" + local status="$?" + set -e + + assert_status "$status" 1 + grep -q "status=failed" "$tmpdir/github-output" || die "missing codex command failed status output" + grep -q "spawn codex ENOENT" "$tmpdir/github-output" || die "missing codex command error output" + [[ ! -e "$tmpdir/codex-home/auth.json" ]] || die "auth.json was not cleaned up after missing codex" + rm -rf "$tmpdir" +} + test_missing_oauth test_missing_model test_invalid_base64 @@ -168,5 +198,6 @@ test_success_output test_empty_prompt test_prompt_with_shell_characters test_failure_output +test_missing_codex_command_output echo "entrypoint tests passed" -- 2.53.0 From a17cf919e11877e9ec6216d1f3c8bfd10d72b1b9 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:38:30 +0000 Subject: [PATCH 41/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/exclusions.json | 18 ++++++++++++++++++ .gitea/ai-review/findings.json | 24 +----------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index 8a01d54..10a80e7 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -100,5 +100,23 @@ "role": "Maya", "original_finding": "測試案例對於輸入參數的邊界測試(例如 `PROMPT` 為空字串、極長字串)不足。", "reason": "目前測試已包含 `test_empty_prompt` 與 `test_prompt_with_shell_characters`,覆蓋空字串與含 shell 特殊字元的 PROMPT 傳遞;此 finding 對最新測試內容已不成立。" + }, + { + "location": "entrypoint.sh:49", + "role": "Mage", + "original_finding": "在執行 install -m 600 時,若 codex 進程已經在嘗試讀取 auth.json,會發生檔案存取競態(Race Condition)。雖然使用了 flock,但這僅在同一個 shell 腳本實例中有效,無法保護跨容器或跨執行環境的檔案存取一致性。", + "reason": "最新入口已改為 `entrypoint.sh` 呼叫 `app/main.js`,不存在 `install -m 600`;Node.js 實作會先寫入並 chmod auth.json 後才啟動 codex,且 Docker Action 每次執行為獨立容器,跨容器不共享 CODEX_HOME。" + }, + { + "location": "entrypoint.sh:58", + "role": "Mage", + "original_finding": "在容器化環境(通常是 ephemeral 的)中,auth.json 寫入後立刻被刪除,這會導致 codex 在後續執行中因找不到驗證檔案而無法運作。另外,trap 的清理機制會導致該檔案在 codex 完成工作前被刪除,這對於長效執行或需要多次存取的應用場景是錯誤的設計。", + "reason": "最新 Node.js 實作會等待 `codex exec` 子程序結束並寫入 output 後才執行 cleanup;auth.json 在 codex 執行期間持續存在,不會在工作完成前被刪除。" + }, + { + "location": "Dockerfile:32", + "role": "Mage", + "original_finding": "在 Dockerfile 中直接使用 RUN 來下載並執行安裝腳本,沒有進行網路連接穩定性的驗證或完整的錯誤恢復機制。一旦網路不穩導致腳本不完整,後續的 sha256sum 檢查會失敗,但 Dockerfile 層疊技術可能會導致中間層殘留損壞的檔案。", + "reason": "Dockerfile 已在同一個 RUN 中完成下載、SHA-256 驗證與安裝,並使用 `curl --retry 3 --retry-delay 2 --max-time 120`;checksum 失敗會使該 layer 建置失敗,不會產生可用的損壞中間成果。" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 399dde4..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,23 +1 @@ -[ - { - "level": "critical", - "role": "Mage", - "location": "entrypoint.sh:49", - "problem": "在執行 install -m 600 時,若 codex 進程已經在嘗試讀取 auth.json,會發生檔案存取競態(Race Condition)。雖然使用了 flock,但這僅在同一個 shell 腳本實例中有效,無法保護跨容器或跨執行環境的檔案存取一致性。", - "suggestion": "建議將 auth.json 放置於唯讀且受限的目錄中,並通過環境變數直接傳遞路徑給 codex,而非在執行時進行檔案寫入與複製。" - }, - { - "level": "warning", - "role": "Mage", - "location": "entrypoint.sh:58", - "problem": "在容器化環境(通常是 ephemeral 的)中,auth.json 寫入後立刻被刪除,這會導致 codex 在後續執行中因找不到驗證檔案而無法運作。另外,trap 的清理機制會導致該檔案在 codex 完成工作前被刪除,這對於長效執行或需要多次存取的應用場景是錯誤的設計。", - "suggestion": "評估 codex 是否需要該檔案在執行期間持續存在。若需要,請調整清理時機,或考慮使用記憶體中的臨時檔案系統(tmpfs)來提升安全性,而非直接移除檔案。" - }, - { - "level": "warning", - "role": "Mage", - "location": "Dockerfile:32", - "problem": "在 Dockerfile 中直接使用 RUN 來下載並執行安裝腳本,沒有進行網路連接穩定性的驗證或完整的錯誤恢復機制。一旦網路不穩導致腳本不完整,後續的 sha256sum 檢查會失敗,但 Dockerfile 層疊技術可能會導致中間層殘留損壞的檔案。", - "suggestion": "將下載、SHA256 驗證與安裝合併在同一個 RUN 指令中,並加入重試機制(如已有的 --retry),確保每一層的原子性。" - } -] +[] -- 2.53.0 From b7d670c95ae20128bb478d06fe08ff4c4e5958fa Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 13:40:19 +0000 Subject: [PATCH 42/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 83 +++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fe51488..3c482d6 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1 +1,82 @@ -[] +[ + { + "level": "critical", + "role": "Assassin", + "location": "app/main.js:106", + "problem": "在執行 `codex` 子行程時,使用了 `--dangerously-bypass-approvals-and-sandbox` 參數。這會完全繞過沙盒機制與審核流程,如果 `prompt` 內容受到攻擊者控制,該 CLI 工具將獲得在容器中執行任意代碼的權限。", + "suggestion": "移除該標記。如果必須使用,請確保 `prompt` 來源完全可信,並將執行權限嚴格限制在最小範圍內。應考慮透過其他機制進行必要的操作,而非直接繞過安全保護。", + "is_new": true + }, + { + "level": "warning", + "role": "Assassin", + "location": "app/main.js:143", + "problem": "將 `OAUTH` 環境變數內容解碼並直接寫入 `auth.json`。雖然有檢查 base64 格式與 JSON 結構,但若解碼後的 JSON 內容包含惡意配置(如惡意插件路徑或偽造的 API 憑證),可能導致後續 `codex` CLI 在執行時被劫持或洩漏資料。", + "suggestion": "除了驗證 JSON 結構外,應進一步驗證 `auth.json` 內的欄位是否符合預期格式,並限制其檔案權限為 `600`(已做),確保容器內其他行程無法讀取。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "app/main.js:20", + "problem": "在 `removeIfCreated` 函式中靜默捕捉錯誤 (`catch { ... }`),這會遮蔽潛在的權限或檔案系統問題,使除錯困難。", + "suggestion": "建議至少加上 `console.error` 或在開發/除錯模式下將錯誤拋出,以便在清除失敗時能收到警示。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "app/main.js:144", + "problem": "`main` 函式過於龐大且職責過多,它同時負責了訊號處理、路徑創建、檔案鎖定、認證驗證以及執行核心邏輯,這降低了程式碼的可讀性與單元測試的困難度。", + "suggestion": "建議將 `main` 拆分為 `validateInput`、`setupAuth`、`runCodexAction` 與 `cleanup` 等子函式,讓職責分離。", + "is_new": true + }, + { + "level": "warning", + "role": "Leo", + "location": "app/main.js:98", + "problem": "在 `runCodex` 中使用了 `--dangerously-bypass-approvals-and-sandbox`,這類高風險標記若缺乏適當的說明,未來的維護者可能不清楚其安全意義而誤用或引發風險。", + "suggestion": "建議在 `spawn` 呼叫前加上明確的註解,詳細說明為何在此環境中必須繞過沙盒,以及相關的安全考量。", + "is_new": true + }, + { + "level": "warning", + "role": "Bard", + "location": "app/main.js:70", + "problem": "Base64 的驗證過程充滿了複雜的字串正規化與取代操作,讀起來像是在解迷宮,而非驗證身分。", + "suggestion": "將驗證邏輯拆解或簡化,明確劃分「解碼」、「正規化」與「比較」三個步驟,提升程式碼的可讀性與可維護性。", + "is_new": true + }, + { + "level": "warning", + "role": "Rogue", + "location": "app/main.js:27", + "problem": "在 `makeTempFile` 中,使用了 `Math.random().toString(16).slice(2)` 來生成隨機檔名。對於高頻率呼叫的場景,這會產生不必要的計算開銷與效能損耗。", + "suggestion": "建議使用 Node.js 內建的 `crypto.randomBytes` 或 `crypto.randomUUID`,雖然效能略有差異但更具安全性與標準化,且能減少字串轉換次數。", + "is_new": true + }, + { + "level": "warning", + "role": "Rogue", + "location": "app/main.js:62", + "problem": "在 `validateAuth` 中,為了驗證 base64 字串是否符合 base64 格式,進行了多次正規表達式替換與編解碼運算(如 `encodedAuth.replace`、`Buffer.from`、`decoded.toString('base64')` 等),這在每次執行都會發生的情況下,浪費了不必要的 CPU 週期。", + "suggestion": "如果目的只是驗證結構,建議盡量簡化邏輯。可以直接將字串嘗試轉換為 Buffer 並檢查 `toString('base64')` 是否匹配,避免多重正規表達式替換。", + "is_new": true + }, + { + "level": "info", + "role": "Bard", + "location": "app/main.js:28", + "problem": "隨機檔案名稱的生成邏輯過於冗長且複雜,破壞了程式碼的簡潔美感。", + "suggestion": "建議使用 Node.js 原生的 `crypto` 模組,例如 `crypto.randomBytes(16).toString('hex')`,讓產生的字串更優雅、清晰。", + "is_new": true + }, + { + "level": "info", + "role": "Rogue", + "location": "app/main.js:84", + "problem": "在 `runCodex` 中,使用 `new Promise` 封裝 `child_process.spawn` 並手動監聽 data 事件來拼接輸出。在高輸出量的場景下,不斷字串拼接(`output += chunk.toString()`)會導致大量記憶體配置與 garbage collection 壓力。", + "suggestion": "如果預期輸出量大,建議將 stdout/stderr 直接寫入檔案流或使用 `Buffer` 陣列收集後最後合併,減少中間字串變更帶來的記憶體浪費。", + "is_new": true + } +] -- 2.53.0 From 3f2355ed419795c55df391923d7bfb70aebde566 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:46:15 +0000 Subject: [PATCH 43/58] =?UTF-8?q?refactor(entrypoint):=20=E6=94=B6?= =?UTF-8?q?=E6=96=82=20Node.js=20=E5=85=A5=E5=8F=A3=E8=81=B7=E8=B2=AC?= =?UTF-8?q?=E8=88=87=E8=BC=B8=E5=87=BA=E8=99=95=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.js | 50 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/app/main.js b/app/main.js index bfa0c75..7933bed 100644 --- a/app/main.js +++ b/app/main.js @@ -3,6 +3,7 @@ const fs = require("fs"); const os = require("os"); const path = require("path"); +const crypto = require("crypto"); const { spawn } = require("child_process"); const DEFAULT_PROMPT = "請自我介紹"; @@ -15,8 +16,8 @@ function removeIfCreated(filePath) { try { fs.rmSync(filePath, { force: true }); - } catch { - // Best-effort cleanup only. + } catch (error) { + console.error(`Unable to remove temporary file: ${error.message}`); } } @@ -27,7 +28,7 @@ function cleanup() { } function makeTempFile(dir, prefix) { - const random = `${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}`; + const random = crypto.randomBytes(16).toString("hex"); const filePath = path.join(dir, `${prefix}.${random}`); const fd = fs.openSync(filePath, "wx", 0o600); fs.closeSync(fd); @@ -43,7 +44,7 @@ function appendGithubOutput(status, output) { let delimiter; do { - delimiter = `CODEX_OUTPUT_${Math.random().toString(36).slice(2)}${Date.now()}`; + delimiter = `CODEX_OUTPUT_${crypto.randomBytes(12).toString("hex")}`; } while (output.includes(delimiter)); fs.appendFileSync( @@ -61,14 +62,16 @@ function fail(message, code = 1) { } function validateAuth(encodedAuth, authFile) { + const normalizedAuth = encodedAuth.replace(/\s+/g, ""); const decoded = Buffer.from(encodedAuth, "base64"); + const normalizedDecoded = decoded.toString("base64").replace(/=+$/, ""); + const normalizedInput = normalizedAuth.replace(/=+$/, ""); - if (decoded.length === 0 && encodedAuth.length > 0) { + if (decoded.length === 0 && normalizedAuth.length > 0) { fail("OAUTH must be valid base64 encoded Codex auth.json."); } - const normalized = encodedAuth.replace(/\s+/g, ""); - if (decoded.toString("base64").replace(/=+$/, "") !== normalized.replace(/=+$/, "")) { + if (normalizedDecoded !== normalizedInput) { fail("OAUTH must be valid base64 encoded Codex auth.json."); } @@ -88,6 +91,8 @@ function validateAuth(encodedAuth, authFile) { function runCodex(model, prompt) { return new Promise((resolve) => { + // 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", [ @@ -101,30 +106,35 @@ function runCodex(model, prompt) { { stdio: ["ignore", "pipe", "pipe"] }, ); - let output = ""; + const outputChunks = []; + const appendOutput = (chunk) => { + outputChunks.push(chunk); + return Buffer.concat(outputChunks).toString(); + }; child.stdout.on("data", (chunk) => { process.stdout.write(chunk); - output += chunk.toString(); + outputChunks.push(chunk); }); child.stderr.on("data", (chunk) => { process.stdout.write(chunk); - output += chunk.toString(); + outputChunks.push(chunk); }); child.on("error", (error) => { - output += `${error.message}\n`; + const output = appendOutput(Buffer.from(`${error.message}\n`)); resolve({ status: 1, output }); }); child.on("close", (code) => { + const output = Buffer.concat(outputChunks).toString(); resolve({ status: code ?? 1, output }); }); }); } -async function main() { +function registerCleanupHandlers() { process.on("exit", cleanup); process.on("SIGINT", () => { cleanup(); @@ -134,7 +144,9 @@ async function main() { cleanup(); process.exit(143); }); +} +function readConfig() { const oauth = process.env.OAUTH || ""; const model = process.env.MODEL || ""; const codexHome = process.env.CODEX_HOME || "/root/.codex"; @@ -148,6 +160,10 @@ async function main() { fail("MODEL is required."); } + return { oauth, model, codexHome, prompt }; +} + +function setupAuth(oauth, codexHome) { try { fs.mkdirSync(codexHome, { recursive: true, mode: 0o700 }); } catch { @@ -177,6 +193,11 @@ async function main() { createdPaths.add(authPath); removeIfCreated(authFile); + return lockHandle; +} + +async function runCodexAction({ oauth, model, codexHome, prompt }) { + const lockHandle = setupAuth(oauth, codexHome); const result = await runCodex(model, prompt); appendGithubOutput(result.status === 0 ? "completed" : "failed", result.output); @@ -188,6 +209,11 @@ async function main() { process.exit(result.status); } +async function main() { + registerCleanupHandlers(); + await runCodexAction(readConfig()); +} + main().catch((error) => { fail(error instanceof Error ? error.message : String(error)); }); -- 2.53.0 From 28b7545f51e2bff738dfadd07562e2d6d151c6da Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:46:19 +0000 Subject: [PATCH 44/58] =?UTF-8?q?chore(CI):=20Codex=20=E6=B8=AC=E8=A9=A6?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E6=99=82=E4=B8=AD=E6=AD=A2=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index b2ef237..161fad7 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -34,6 +34,6 @@ jobs: with: oauth: ${{ secrets.CODEX_OAUTH }} model: gpt-5.4-mini - - name: 自我介紹 - if: ${{ steps.test.outputs.status == 'completed' }} - run: echo "${{ steps.test.outputs.output }}" + - name: 測試失敗 + if: ${{ steps.test.outputs.status != 'completed' }} + run: exit 1 -- 2.53.0 From 3681dec8056a2a7ca5557a09b94f91cdd217273c Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:46:23 +0000 Subject: [PATCH 45/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/exclusions.json | 12 +++++ .gitea/ai-review/findings.json | 83 +------------------------------- 2 files changed, 13 insertions(+), 82 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index 10a80e7..5c5f629 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -118,5 +118,17 @@ "role": "Mage", "original_finding": "在 Dockerfile 中直接使用 RUN 來下載並執行安裝腳本,沒有進行網路連接穩定性的驗證或完整的錯誤恢復機制。一旦網路不穩導致腳本不完整,後續的 sha256sum 檢查會失敗,但 Dockerfile 層疊技術可能會導致中間層殘留損壞的檔案。", "reason": "Dockerfile 已在同一個 RUN 中完成下載、SHA-256 驗證與安裝,並使用 `curl --retry 3 --retry-delay 2 --max-time 120`;checksum 失敗會使該 layer 建置失敗,不會產生可用的損壞中間成果。" + }, + { + "location": "app/main.js:106", + "role": "Assassin", + "original_finding": "在執行 `codex` 子行程時,使用了 `--dangerously-bypass-approvals-and-sandbox` 參數。這會完全繞過沙盒機制與審核流程,如果 `prompt` 內容受到攻擊者控制,該 CLI 工具將獲得在容器中執行任意代碼的權限。", + "reason": "此 action 的用途是在隔離的短生命週期 CI 容器中代替使用者執行 Codex,必須允許 Codex 非互動式修改 workspace;workflow 呼叫端需以 trusted prompt/secret 使用,本次已在 `runCodex` 前加入註解明確標示此安全取捨。" + }, + { + "location": "app/main.js:143", + "role": "Assassin", + "original_finding": "將 `OAUTH` 環境變數內容解碼並直接寫入 `auth.json`。雖然有檢查 base64 格式與 JSON 結構,但若解碼後的 JSON 內容包含惡意配置(如惡意插件路徑或偽造的 API 憑證),可能導致後續 `codex` CLI 在執行時被劫持或洩漏資料。", + "reason": "`OAUTH` 是呼叫端提供給 Codex CLI 的 auth.json secret;action 只能驗證 base64、JSON object 與檔案權限,憑證真偽與欄位語意需由 Codex CLI/上游認證機制處理,action 不應猜測或拒絕未來相容欄位。" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 3c482d6..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,82 +1 @@ -[ - { - "level": "critical", - "role": "Assassin", - "location": "app/main.js:106", - "problem": "在執行 `codex` 子行程時,使用了 `--dangerously-bypass-approvals-and-sandbox` 參數。這會完全繞過沙盒機制與審核流程,如果 `prompt` 內容受到攻擊者控制,該 CLI 工具將獲得在容器中執行任意代碼的權限。", - "suggestion": "移除該標記。如果必須使用,請確保 `prompt` 來源完全可信,並將執行權限嚴格限制在最小範圍內。應考慮透過其他機制進行必要的操作,而非直接繞過安全保護。", - "is_new": true - }, - { - "level": "warning", - "role": "Assassin", - "location": "app/main.js:143", - "problem": "將 `OAUTH` 環境變數內容解碼並直接寫入 `auth.json`。雖然有檢查 base64 格式與 JSON 結構,但若解碼後的 JSON 內容包含惡意配置(如惡意插件路徑或偽造的 API 憑證),可能導致後續 `codex` CLI 在執行時被劫持或洩漏資料。", - "suggestion": "除了驗證 JSON 結構外,應進一步驗證 `auth.json` 內的欄位是否符合預期格式,並限制其檔案權限為 `600`(已做),確保容器內其他行程無法讀取。", - "is_new": true - }, - { - "level": "warning", - "role": "Leo", - "location": "app/main.js:20", - "problem": "在 `removeIfCreated` 函式中靜默捕捉錯誤 (`catch { ... }`),這會遮蔽潛在的權限或檔案系統問題,使除錯困難。", - "suggestion": "建議至少加上 `console.error` 或在開發/除錯模式下將錯誤拋出,以便在清除失敗時能收到警示。", - "is_new": true - }, - { - "level": "warning", - "role": "Leo", - "location": "app/main.js:144", - "problem": "`main` 函式過於龐大且職責過多,它同時負責了訊號處理、路徑創建、檔案鎖定、認證驗證以及執行核心邏輯,這降低了程式碼的可讀性與單元測試的困難度。", - "suggestion": "建議將 `main` 拆分為 `validateInput`、`setupAuth`、`runCodexAction` 與 `cleanup` 等子函式,讓職責分離。", - "is_new": true - }, - { - "level": "warning", - "role": "Leo", - "location": "app/main.js:98", - "problem": "在 `runCodex` 中使用了 `--dangerously-bypass-approvals-and-sandbox`,這類高風險標記若缺乏適當的說明,未來的維護者可能不清楚其安全意義而誤用或引發風險。", - "suggestion": "建議在 `spawn` 呼叫前加上明確的註解,詳細說明為何在此環境中必須繞過沙盒,以及相關的安全考量。", - "is_new": true - }, - { - "level": "warning", - "role": "Bard", - "location": "app/main.js:70", - "problem": "Base64 的驗證過程充滿了複雜的字串正規化與取代操作,讀起來像是在解迷宮,而非驗證身分。", - "suggestion": "將驗證邏輯拆解或簡化,明確劃分「解碼」、「正規化」與「比較」三個步驟,提升程式碼的可讀性與可維護性。", - "is_new": true - }, - { - "level": "warning", - "role": "Rogue", - "location": "app/main.js:27", - "problem": "在 `makeTempFile` 中,使用了 `Math.random().toString(16).slice(2)` 來生成隨機檔名。對於高頻率呼叫的場景,這會產生不必要的計算開銷與效能損耗。", - "suggestion": "建議使用 Node.js 內建的 `crypto.randomBytes` 或 `crypto.randomUUID`,雖然效能略有差異但更具安全性與標準化,且能減少字串轉換次數。", - "is_new": true - }, - { - "level": "warning", - "role": "Rogue", - "location": "app/main.js:62", - "problem": "在 `validateAuth` 中,為了驗證 base64 字串是否符合 base64 格式,進行了多次正規表達式替換與編解碼運算(如 `encodedAuth.replace`、`Buffer.from`、`decoded.toString('base64')` 等),這在每次執行都會發生的情況下,浪費了不必要的 CPU 週期。", - "suggestion": "如果目的只是驗證結構,建議盡量簡化邏輯。可以直接將字串嘗試轉換為 Buffer 並檢查 `toString('base64')` 是否匹配,避免多重正規表達式替換。", - "is_new": true - }, - { - "level": "info", - "role": "Bard", - "location": "app/main.js:28", - "problem": "隨機檔案名稱的生成邏輯過於冗長且複雜,破壞了程式碼的簡潔美感。", - "suggestion": "建議使用 Node.js 原生的 `crypto` 模組,例如 `crypto.randomBytes(16).toString('hex')`,讓產生的字串更優雅、清晰。", - "is_new": true - }, - { - "level": "info", - "role": "Rogue", - "location": "app/main.js:84", - "problem": "在 `runCodex` 中,使用 `new Promise` 封裝 `child_process.spawn` 並手動監聽 data 事件來拼接輸出。在高輸出量的場景下,不斷字串拼接(`output += chunk.toString()`)會導致大量記憶體配置與 garbage collection 壓力。", - "suggestion": "如果預期輸出量大,建議將 stdout/stderr 直接寫入檔案流或使用 `Buffer` 陣列收集後最後合併,減少中間字串變更帶來的記憶體浪費。", - "is_new": true - } -] +[] -- 2.53.0 From 3aad3dbefb42b7cbe8abe419f2982da258e4880c Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 13:47:12 +0000 Subject: [PATCH 46/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/exclusions.json | 6 ++ .gitea/ai-review/findings.json | 107 ++++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index 5c5f629..4d85379 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -130,5 +130,11 @@ "role": "Assassin", "original_finding": "將 `OAUTH` 環境變數內容解碼並直接寫入 `auth.json`。雖然有檢查 base64 格式與 JSON 結構,但若解碼後的 JSON 內容包含惡意配置(如惡意插件路徑或偽造的 API 憑證),可能導致後續 `codex` CLI 在執行時被劫持或洩漏資料。", "reason": "`OAUTH` 是呼叫端提供給 Codex CLI 的 auth.json secret;action 只能驗證 base64、JSON object 與檔案權限,憑證真偽與欄位語意需由 Codex CLI/上游認證機制處理,action 不應猜測或拒絕未來相容欄位。" + }, + { + "location": "app/main.js:20", + "role": "Leo", + "original_finding": "建議至少加上 `console.error` 或在開發/除錯模式下將錯誤拋出,以便在清除失敗時能收到警示。", + "reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fe51488..bb79bc1 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1 +1,106 @@ -[] +[ + { + "level": "critical", + "role": "Mage", + "location": "app/main.js:189", + "problem": "在 `setupAuth` 中,`lockPath` 使用 `os.tmpdir()`。在共享環境中,如果 `CODEX_HOME` 字串相同,會導致所有 process 競爭同一個鎖檔,且如果其他無關的 process 也剛好在 `os.tmpdir()` 中建立相同名稱的檔案,會導致誤判或鎖定失敗。", + "suggestion": "應在 `CODEX_HOME` 內部建立鎖檔,而非使用全域的 `os.tmpdir()`,或者包含更具唯一性的識別碼(如 PID 或更長的路徑雜湊)以確保鎖的隔離性。", + "is_new": true + }, + { + "level": "warning", + "role": "Bard", + "problem": "Base64 的驗證過程充滿了複雜的字串正規化與取代操作,讀起來像是在解迷宮,而非驗證身分。", + "suggestion": "將驗證邏輯拆解或簡化,明確劃分「解碼」、「正規化」與「比較」三個步驟,提升程式碼的可讀性與可維護性。", + "location": "app/main.js:70", + "is_new": false + }, + { + "level": "warning", + "role": "Mage", + "location": "app/main.js:33", + "problem": "在 `makeTempFile` 中使用了 `fs.openSync(filePath, \"wx\", 0o600)`。如果在 `fs.closeSync(fd)` 之前程式因例外或強制終止(SIGKILL),該檔案會留在硬碟上直到下次清理或手動刪除,且其檔案描述子會持續開啟直到 process 結束。", + "suggestion": "建議使用 `fs.mkdtempSync` 建立獨立目錄,將所有臨時檔案放入該目錄,並在 `cleanup` 時直接移除整個目錄,以確保清理的原子性與完整性。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "app/main.js:77", + "problem": "在 `validateAuth` 中,僅透過 `JSON.parse` 檢查 JSON 格式,但未針對 Codex 預期的 auth.json 結構(如必要的欄位)進行 Schema 驗證。如果傳入的 JSON 格式正確但內容無效,可能會導致 `codex exec` 在後續執行時失敗。", + "suggestion": "建議加入對 JSON 內容的簡單結構驗證(例如確認是否有 `token` 或必要的連線設定欄位)。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "app/main.js:114", + "problem": "在 `runCodex` 中使用 `spawn` 時,沒有設定 `cwd`。如果 `codex` 工具依賴於當前工作目錄(例如需要編輯當前專案),這在 CI 環境中可能存在風險,雖然目前 CI 通常會設定好目錄,但這是一個隱含的契約。", + "suggestion": "建議明確設定 `cwd` 為 `/github/workspace` 或 CI 定義的專案根目錄,確保 `codex` 運作在預期的上下文中。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "app/main.js:203", + "problem": "在 `setupAuth` 中,在 `fs.copyFileSync(authFile, authPath)` 後立即 `removeIfCreated(authFile)`,但在這期間如果發生 process 中斷,auth.json 可能會以不安全的權限(預設)或不完整的狀態寫入。", + "suggestion": "建議使用 `fs.renameSync` 或在完成寫入與權限設定後再進行清理,並確保寫入過程中發生異常時能正確刪除該部分寫入的檔案。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "app/main.js:102", + "problem": "在執行外部指令時沒有設定逾時(timeout),若 Codex CLI 發生無預期的掛起(hang),Action 將會永久卡住而不會自動終止。", + "suggestion": "建議在 `spawn` 的選項中加入 `timeout` 機制,或是主動在啟動後設置一個計時器,當執行時間過長時強制終止子行程。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "app/main.js:125", + "problem": "直接將所有輸出串接在 `outputChunks` 中,若 CLI 輸出過大的日誌,可能會導致記憶體耗盡(OOM)。", + "suggestion": "建議針對 output 大小設定上限,超過限制時截斷輸出,或是改用串流寫入暫存檔以避免將所有內容存於記憶體。", + "is_new": true + }, + { + "level": "warning", + "role": "Rogue", + "location": "app/main.js:192", + "problem": "在 setupAuth 中,`lockPath` 檔名產生使用了 `Buffer.from(codexHome).toString(\"hex\")`。如果 `codexHome` 非常長,這個檔名可能會超過作業系統的檔案名稱長度限制(通常為 255 bytes),導致鎖定失敗,進而阻斷整個流程。", + "suggestion": "改用 `crypto.createHash('sha256').update(codexHome).digest('hex')` 來產生固定長度的雜湊值作為檔名的一部分,既安全又保證長度可控。", + "is_new": true + }, + { + "level": "info", + "role": "Bard", + "location": "app/main.js:11", + "problem": "全域變數 `createdPaths` 在檔案層級被宣告,讓函式產生強依賴,缺乏封裝性,讀起來不夠優雅。", + "suggestion": "將臨時檔案管理邏輯封裝成一個類別(如 `TempFileRegistry`),讓狀態更具備物件導向的封裝性。", + "is_new": true + }, + { + "level": "info", + "role": "Bard", + "location": "app/main.js:33", + "problem": "檔案權限(如 `0o600`, `0o700`)以數字字面量多次出現,散落在程式碼中,降低了可讀性與一致性。", + "suggestion": "在檔案上方定義權限常數(例如 `const FILE_MODE_PRIVATE = 0o600;`),讓語義更清晰。", + "is_new": true + }, + { + "level": "info", + "role": "Maya", + "location": "tests/docker_image_test.sh:20", + "problem": "目前的 Docker 冒煙測試僅驗證了 CLI 二進位檔是否存在,但尚未驗證其在容器內執行時是否能正常存取與寫入 `CODEX_HOME` 環境設定的目錄。", + "suggestion": "建議在 `docker_image_test.sh` 中增加一個測試案例,執行 `codex --version` 之外的指令,驗證容器權限與目錄環境變數設定是否正確。", + "is_new": true + }, + { + "level": "info", + "role": "Rogue", + "location": "app/main.js:109", + "problem": "`runCodex` 函數中的 `spawn` 使用 `{ stdio: [\"ignore\", \"pipe\", \"pipe\"] }`,這會導致 node 程式在輸出流被填滿時阻塞等待,即便透過 `stdout.on('data')` 監聽,在高輸出的情境下仍可能因為緩衝區管理不當而浪費不必要的 CPU 週期。", + "suggestion": "如果預期輸出量很大,建議改用 `child.stdout.pipe(process.stdout)` 直接導向,而非透過 node 的事件迴圈在兩者間搬運資料。", + "is_new": true + } +] -- 2.53.0 From 83de97b35ad22b8aec035315976346266f6ef9d0 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:54:11 +0000 Subject: [PATCH 47/58] =?UTF-8?q?refactor(entrypoint):=20=E5=BC=B7?= =?UTF-8?q?=E5=8C=96=E6=9A=AB=E5=AD=98=E6=AA=94=E8=88=87=20Codex=20?= =?UTF-8?q?=E5=9F=B7=E8=A1=8C=E6=8E=A7=E7=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.js | 142 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 110 insertions(+), 32 deletions(-) diff --git a/app/main.js b/app/main.js index 7933bed..2f3323a 100644 --- a/app/main.js +++ b/app/main.js @@ -1,38 +1,78 @@ #!/usr/bin/env node const fs = require("fs"); -const os = require("os"); const path = require("path"); const crypto = require("crypto"); const { spawn } = require("child_process"); const DEFAULT_PROMPT = "請自我介紹"; -const createdPaths = new Set(); +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; -function removeIfCreated(filePath) { - if (!filePath || !createdPaths.has(filePath)) { - return; +class TempFileRegistry { + constructor() { + this.files = new Set(); + this.dirs = new Set(); } - try { - fs.rmSync(filePath, { force: true }); - } catch (error) { - console.error(`Unable to remove temporary file: ${error.message}`); + trackFile(filePath) { + this.files.add(filePath); + } + + trackDir(dirPath) { + this.dirs.add(dirPath); + } + + removeFile(filePath) { + if (!filePath || !this.files.has(filePath)) { + return; + } + + try { + fs.rmSync(filePath, { force: true }); + this.files.delete(filePath); + } catch (error) { + console.error(`Unable to remove temporary file: ${error.message}`); + } + } + + cleanup() { + for (const filePath of Array.from(this.files).reverse()) { + this.removeFile(filePath); + } + + for (const dirPath of Array.from(this.dirs).reverse()) { + try { + fs.rmSync(dirPath, { force: true, recursive: true }); + this.dirs.delete(dirPath); + } catch (error) { + console.error(`Unable to remove temporary directory: ${error.message}`); + } + } } } +const tempFiles = new TempFileRegistry(); + function cleanup() { - for (const filePath of Array.from(createdPaths).reverse()) { - removeIfCreated(filePath); - } + tempFiles.cleanup(); +} + +function makeTempDir(dir) { + const tempDir = fs.mkdtempSync(path.join(dir, ".codex-action-")); + fs.chmodSync(tempDir, DIR_MODE_PRIVATE); + tempFiles.trackDir(tempDir); + return tempDir; } function makeTempFile(dir, prefix) { const random = crypto.randomBytes(16).toString("hex"); const filePath = path.join(dir, `${prefix}.${random}`); - const fd = fs.openSync(filePath, "wx", 0o600); + const fd = fs.openSync(filePath, "wx", FILE_MODE_PRIVATE); fs.closeSync(fd); - createdPaths.add(filePath); + tempFiles.trackFile(filePath); return filePath; } @@ -50,7 +90,7 @@ function appendGithubOutput(status, output) { fs.appendFileSync( outputFile, `status=${status}\noutput<<${delimiter}\n${output}${output.endsWith("\n") ? "" : "\n"}${delimiter}\n`, - { encoding: "utf8", mode: 0o600 }, + { encoding: "utf8", mode: FILE_MODE_PRIVATE }, ); } @@ -75,7 +115,7 @@ function validateAuth(encodedAuth, authFile) { fail("OAUTH must be valid base64 encoded Codex auth.json."); } - fs.writeFileSync(authFile, decoded, { mode: 0o600 }); + fs.writeFileSync(authFile, decoded, { mode: FILE_MODE_PRIVATE }); let parsed; try { @@ -89,8 +129,29 @@ function validateAuth(encodedAuth, authFile) { } } +function parsePositiveInteger(value, fallback) { + const parsed = Number.parseInt(value || "", 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; +} + +function truncateOutput(chunks, nextChunk, maxBytes) { + const currentSize = chunks.reduce((total, chunk) => total + chunk.length, 0); + const available = maxBytes - currentSize; + + if (available <= 0) { + return false; + } + + chunks.push(nextChunk.length > available ? nextChunk.subarray(0, available) : nextChunk); + return nextChunk.length <= available; +} + 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(); + // 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( @@ -103,32 +164,48 @@ function runCodex(model, prompt) { model, prompt, ], - { stdio: ["ignore", "pipe", "pipe"] }, + { cwd: workspace, stdio: ["ignore", "pipe", "pipe"] }, ); const outputChunks = []; + let outputTruncated = false; const appendOutput = (chunk) => { - outputChunks.push(chunk); + if (!truncateOutput(outputChunks, chunk, outputLimitBytes)) { + outputTruncated = true; + } return Buffer.concat(outputChunks).toString(); }; + const timeout = setTimeout(() => { + child.kill("SIGTERM"); + appendOutput(Buffer.from(`Codex execution timed out after ${timeoutMs} ms.\n`)); + }, timeoutMs); + + child.stdout.pipe(process.stdout); + child.stderr.pipe(process.stdout); + child.stdout.on("data", (chunk) => { - process.stdout.write(chunk); - outputChunks.push(chunk); + if (!truncateOutput(outputChunks, chunk, outputLimitBytes)) { + outputTruncated = true; + } }); child.stderr.on("data", (chunk) => { - process.stdout.write(chunk); - outputChunks.push(chunk); + if (!truncateOutput(outputChunks, chunk, outputLimitBytes)) { + outputTruncated = true; + } }); child.on("error", (error) => { + clearTimeout(timeout); const output = appendOutput(Buffer.from(`${error.message}\n`)); resolve({ status: 1, output }); }); child.on("close", (code) => { - const output = Buffer.concat(outputChunks).toString(); + clearTimeout(timeout); + const truncationMessage = outputTruncated ? "\n[Output truncated]\n" : ""; + const output = `${Buffer.concat(outputChunks).toString()}${truncationMessage}`; resolve({ status: code ?? 1, output }); }); }); @@ -165,19 +242,21 @@ function readConfig() { function setupAuth(oauth, codexHome) { try { - fs.mkdirSync(codexHome, { recursive: true, mode: 0o700 }); + fs.mkdirSync(codexHome, { recursive: true, mode: DIR_MODE_PRIVATE }); } catch { fail("Unable to create CODEX_HOME."); } - const authFile = makeTempFile(codexHome, "auth"); + const tempDir = makeTempDir(codexHome); + const authFile = makeTempFile(tempDir, "auth"); const authPath = path.join(codexHome, "auth.json"); - const lockPath = path.join(os.tmpdir(), `codex-auth-${Buffer.from(codexHome).toString("hex")}.lock`); + 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", 0o600); - createdPaths.add(lockPath); + lockHandle = fs.openSync(lockPath, "wx", FILE_MODE_PRIVATE); + tempFiles.trackFile(lockPath); } catch { fail("Unable to lock Codex auth.json."); } @@ -188,10 +267,9 @@ function setupAuth(oauth, codexHome) { fail("Refusing to overwrite existing Codex auth.json."); } - fs.copyFileSync(authFile, authPath); - fs.chmodSync(authPath, 0o600); - createdPaths.add(authPath); - removeIfCreated(authFile); + fs.renameSync(authFile, authPath); + fs.chmodSync(authPath, FILE_MODE_PRIVATE); + tempFiles.trackFile(authPath); return lockHandle; } -- 2.53.0 From d650b88ab3a71f65a825c61358cd9845d40b550b Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:54:16 +0000 Subject: [PATCH 48/58] =?UTF-8?q?test(Docker=20image):=20=E9=A9=97?= =?UTF-8?q?=E8=AD=89=20CODEX=5FHOME=20=E5=8F=AF=E5=AF=AB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/docker_image_test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/docker_image_test.sh b/tests/docker_image_test.sh index 25d1d48..a6b1eb0 100755 --- a/tests/docker_image_test.sh +++ b/tests/docker_image_test.sh @@ -18,5 +18,6 @@ fi docker build -t "$IMAGE_NAME" "$ROOT_DIR" docker run --rm --entrypoint codex "$IMAGE_NAME" --version +docker run --rm --entrypoint sh "$IMAGE_NAME" -c 'mkdir -p "$CODEX_HOME" && test -w "$CODEX_HOME" && codex --version >/dev/null' docker run --rm --entrypoint codex "$IMAGE_NAME" plugin list | grep -q "jsc@doc" docker run --rm --entrypoint codex "$IMAGE_NAME" plugin list | grep -q "jsc@code-review" -- 2.53.0 From f97015c77cb6044e453a7d9cebc115b0414fbf1b Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 13:54:20 +0000 Subject: [PATCH 49/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/findings.json | 107 +-------------------------------- 1 file changed, 1 insertion(+), 106 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index bb79bc1..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,106 +1 @@ -[ - { - "level": "critical", - "role": "Mage", - "location": "app/main.js:189", - "problem": "在 `setupAuth` 中,`lockPath` 使用 `os.tmpdir()`。在共享環境中,如果 `CODEX_HOME` 字串相同,會導致所有 process 競爭同一個鎖檔,且如果其他無關的 process 也剛好在 `os.tmpdir()` 中建立相同名稱的檔案,會導致誤判或鎖定失敗。", - "suggestion": "應在 `CODEX_HOME` 內部建立鎖檔,而非使用全域的 `os.tmpdir()`,或者包含更具唯一性的識別碼(如 PID 或更長的路徑雜湊)以確保鎖的隔離性。", - "is_new": true - }, - { - "level": "warning", - "role": "Bard", - "problem": "Base64 的驗證過程充滿了複雜的字串正規化與取代操作,讀起來像是在解迷宮,而非驗證身分。", - "suggestion": "將驗證邏輯拆解或簡化,明確劃分「解碼」、「正規化」與「比較」三個步驟,提升程式碼的可讀性與可維護性。", - "location": "app/main.js:70", - "is_new": false - }, - { - "level": "warning", - "role": "Mage", - "location": "app/main.js:33", - "problem": "在 `makeTempFile` 中使用了 `fs.openSync(filePath, \"wx\", 0o600)`。如果在 `fs.closeSync(fd)` 之前程式因例外或強制終止(SIGKILL),該檔案會留在硬碟上直到下次清理或手動刪除,且其檔案描述子會持續開啟直到 process 結束。", - "suggestion": "建議使用 `fs.mkdtempSync` 建立獨立目錄,將所有臨時檔案放入該目錄,並在 `cleanup` 時直接移除整個目錄,以確保清理的原子性與完整性。", - "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "app/main.js:77", - "problem": "在 `validateAuth` 中,僅透過 `JSON.parse` 檢查 JSON 格式,但未針對 Codex 預期的 auth.json 結構(如必要的欄位)進行 Schema 驗證。如果傳入的 JSON 格式正確但內容無效,可能會導致 `codex exec` 在後續執行時失敗。", - "suggestion": "建議加入對 JSON 內容的簡單結構驗證(例如確認是否有 `token` 或必要的連線設定欄位)。", - "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "app/main.js:114", - "problem": "在 `runCodex` 中使用 `spawn` 時,沒有設定 `cwd`。如果 `codex` 工具依賴於當前工作目錄(例如需要編輯當前專案),這在 CI 環境中可能存在風險,雖然目前 CI 通常會設定好目錄,但這是一個隱含的契約。", - "suggestion": "建議明確設定 `cwd` 為 `/github/workspace` 或 CI 定義的專案根目錄,確保 `codex` 運作在預期的上下文中。", - "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "app/main.js:203", - "problem": "在 `setupAuth` 中,在 `fs.copyFileSync(authFile, authPath)` 後立即 `removeIfCreated(authFile)`,但在這期間如果發生 process 中斷,auth.json 可能會以不安全的權限(預設)或不完整的狀態寫入。", - "suggestion": "建議使用 `fs.renameSync` 或在完成寫入與權限設定後再進行清理,並確保寫入過程中發生異常時能正確刪除該部分寫入的檔案。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "app/main.js:102", - "problem": "在執行外部指令時沒有設定逾時(timeout),若 Codex CLI 發生無預期的掛起(hang),Action 將會永久卡住而不會自動終止。", - "suggestion": "建議在 `spawn` 的選項中加入 `timeout` 機制,或是主動在啟動後設置一個計時器,當執行時間過長時強制終止子行程。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "app/main.js:125", - "problem": "直接將所有輸出串接在 `outputChunks` 中,若 CLI 輸出過大的日誌,可能會導致記憶體耗盡(OOM)。", - "suggestion": "建議針對 output 大小設定上限,超過限制時截斷輸出,或是改用串流寫入暫存檔以避免將所有內容存於記憶體。", - "is_new": true - }, - { - "level": "warning", - "role": "Rogue", - "location": "app/main.js:192", - "problem": "在 setupAuth 中,`lockPath` 檔名產生使用了 `Buffer.from(codexHome).toString(\"hex\")`。如果 `codexHome` 非常長,這個檔名可能會超過作業系統的檔案名稱長度限制(通常為 255 bytes),導致鎖定失敗,進而阻斷整個流程。", - "suggestion": "改用 `crypto.createHash('sha256').update(codexHome).digest('hex')` 來產生固定長度的雜湊值作為檔名的一部分,既安全又保證長度可控。", - "is_new": true - }, - { - "level": "info", - "role": "Bard", - "location": "app/main.js:11", - "problem": "全域變數 `createdPaths` 在檔案層級被宣告,讓函式產生強依賴,缺乏封裝性,讀起來不夠優雅。", - "suggestion": "將臨時檔案管理邏輯封裝成一個類別(如 `TempFileRegistry`),讓狀態更具備物件導向的封裝性。", - "is_new": true - }, - { - "level": "info", - "role": "Bard", - "location": "app/main.js:33", - "problem": "檔案權限(如 `0o600`, `0o700`)以數字字面量多次出現,散落在程式碼中,降低了可讀性與一致性。", - "suggestion": "在檔案上方定義權限常數(例如 `const FILE_MODE_PRIVATE = 0o600;`),讓語義更清晰。", - "is_new": true - }, - { - "level": "info", - "role": "Maya", - "location": "tests/docker_image_test.sh:20", - "problem": "目前的 Docker 冒煙測試僅驗證了 CLI 二進位檔是否存在,但尚未驗證其在容器內執行時是否能正常存取與寫入 `CODEX_HOME` 環境設定的目錄。", - "suggestion": "建議在 `docker_image_test.sh` 中增加一個測試案例,執行 `codex --version` 之外的指令,驗證容器權限與目錄環境變數設定是否正確。", - "is_new": true - }, - { - "level": "info", - "role": "Rogue", - "location": "app/main.js:109", - "problem": "`runCodex` 函數中的 `spawn` 使用 `{ stdio: [\"ignore\", \"pipe\", \"pipe\"] }`,這會導致 node 程式在輸出流被填滿時阻塞等待,即便透過 `stdout.on('data')` 監聽,在高輸出的情境下仍可能因為緩衝區管理不當而浪費不必要的 CPU 週期。", - "suggestion": "如果預期輸出量很大,建議改用 `child.stdout.pipe(process.stdout)` 直接導向,而非透過 node 的事件迴圈在兩者間搬運資料。", - "is_new": true - } -] +[] -- 2.53.0 From 74ba6253066a1d6a0a5214c40123a5ca8c675073 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 13:55:23 +0000 Subject: [PATCH 50/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 155 ++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fe51488..5060c6d 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1 +1,154 @@ -[] +[ + { + "level": "critical", + "role": "Mage", + "problem": "在 `setupAuth` 中,`lockPath` 使用 `os.tmpdir()`。在共享環境中,如果 `CODEX_HOME` 字串相同,會導致所有 process 競爭同一個鎖檔,且如果其他無關的 process 也剛好在 `os.tmpdir()` 中建立相同名稱的檔案,會導致誤判或鎖定失敗。", + "suggestion": "應在 `CODEX_HOME` 內部建立鎖檔,而非使用全域的 `os.tmpdir()`,或者包含更具唯一性的識別碼(如 PID 或更長的路徑雜湊)以確保鎖的隔離性。", + "location": "app/main.js:189", + "is_new": false + }, + { + "level": "critical", + "role": "Mage", + "location": "app/main.js:125", + "problem": "這裡直接使用 `spawn` 執行 `codex` 命令,且參數 `prompt` 是直接從 `process.env.PROMPT` 讀取並傳入的。如果 CI 環境的 `PROMPT` 被惡意竄改,雖使用陣列傳遞參數避免了 shell injection,但 `codex exec` 的邏輯若沒有妥善限制(例如限制可執行指令類型),可能導致攻擊者在 CI Runner 環境執行任意指令。", + "suggestion": "在 `runCodex` 函式中,除了已經加入的 `--dangerously-bypass-approvals-and-sandbox` 外,必須確保對 `prompt` 進行強力的白名單過濾,或改為使用非 `exec` 的子指令來限制權限。", + "is_new": true + }, + { + "level": "critical", + "role": "Mage", + "location": "app/main.js:255", + "problem": "在 `setupAuth` 中,使用了 `fs.renameSync` 來確保原子性。然而在某些檔案系統中,若 `authFile` 與 `authPath` 不在同一個分區,`renameSync` 可能會失敗。此外,如果 `codexHome` 已存在且權限配置錯誤,`fs.mkdirSync` 可能會被忽略但後續存取失敗。", + "suggestion": "建議確保 `authFile` 與 `codexHome` 處於相同掛載點,並增加對 `fs.mkdirSync` 後權限檢查的驗證。", + "is_new": true + }, + { + "level": "warning", + "role": "Rogue", + "problem": "在 setupAuth 中,`lockPath` 檔名產生使用了 `Buffer.from(codexHome).toString(\"hex\")`。如果 `codexHome` 非常長,這個檔名可能會超過作業系統的檔案名稱長度限制(通常為 255 bytes),導致鎖定失敗,進而阻斷整個流程。", + "suggestion": "改用 `crypto.createHash('sha256').update(codexHome).digest('hex')` 來產生固定長度的雜湊值作為檔名的一部分,既安全又保證長度可控。", + "location": "app/main.js:192", + "is_new": false + }, + { + "level": "warning", + "role": "Mage", + "problem": "在 `setupAuth` 中,在 `fs.copyFileSync(authFile, authPath)` 後立即 `removeIfCreated(authFile)`,但在這期間如果發生 process 中斷,auth.json 可能會以不安全的權限(預設)或不完整的狀態寫入。", + "suggestion": "建議使用 `fs.renameSync` 或在完成寫入與權限設定後再進行清理,並確保寫入過程中發生異常時能正確刪除該部分寫入的檔案。", + "location": "app/main.js:203", + "is_new": false + }, + { + "level": "warning", + "role": "Maya", + "problem": "在執行外部指令時沒有設定逾時(timeout),若 Codex CLI 發生無預期的掛起(hang),Action 將會永久卡住而不會自動終止。", + "suggestion": "建議在 `spawn` 的選項中加入 `timeout` 機制,或是主動在啟動後設置一個計時器,當執行時間過長時強制終止子行程。", + "location": "app/main.js:102", + "is_new": false + }, + { + "level": "warning", + "role": "Mage", + "problem": "在 `runCodex` 中使用 `spawn` 時,沒有設定 `cwd`。如果 `codex` 工具依賴於當前工作目錄(例如需要編輯當前專案),這在 CI 環境中可能存在風險,雖然目前 CI 通常會設定好目錄,但這是一個隱含的契約。", + "suggestion": "建議明確設定 `cwd` 為 `/github/workspace` 或 CI 定義的專案根目錄,確保 `codex` 運作在預期的上下文中。", + "location": "app/main.js:114", + "is_new": false + }, + { + "level": "warning", + "role": "Maya", + "problem": "直接將所有輸出串接在 `outputChunks` 中,若 CLI 輸出過大的日誌,可能會導致記憶體耗盡(OOM)。", + "suggestion": "建議針對 output 大小設定上限,超過限制時截斷輸出,或是改用串流寫入暫存檔以避免將所有內容存於記憶體。", + "location": "app/main.js:125", + "is_new": false + }, + { + "level": "warning", + "role": "Mage", + "problem": "在 `validateAuth` 中,僅透過 `JSON.parse` 檢查 JSON 格式,但未針對 Codex 預期的 auth.json 結構(如必要的欄位)進行 Schema 驗證。如果傳入的 JSON 格式正確但內容無效,可能會導致 `codex exec` 在後續執行時失敗。", + "suggestion": "建議加入對 JSON 內容的簡單結構驗證(例如確認是否有 `token` 或必要的連線設定欄位)。", + "location": "app/main.js:77", + "is_new": false + }, + { + "level": "warning", + "role": "Bard", + "location": "app/main.js:120", + "problem": "validateAuth 函式中對於 Base64 的正規化與驗證邏輯混雜在一起,使用了大量的取代與判斷,讀起來節奏凌亂,缺乏優雅感。", + "suggestion": "將驗證邏輯與基礎轉換邏輯抽離,建議提取一個輔助函式專門負責 Base64 格式檢查,使主要流程清晰明瞭。", + "is_new": true + }, + { + "level": "warning", + "role": "Bard", + "location": "app/main.js:166", + "problem": "runCodex 函式過於臃腫,包含了執行、超時處理、輸出截斷與錯誤捕捉等多重責任,這段旋律太過冗長且複雜。", + "suggestion": "建議將輸出處理 (Output truncation logic) 與超時設定分離為獨立函式,以提升函式的可讀性與維護性。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "app/main.js:77", + "problem": "在 `appendGithubOutput` 函式中,當 `output` 內容極大時,此處會將整個 `output` 字串在記憶體中進行檢查(`output.includes(delimiter)`)與多次複製。這可能導致在處理極端長度輸出時發生記憶體不足的問題。", + "suggestion": "建議限制 `delimiter` 嘗試次數,或在檢查時避免讀取整個 `output` 字串,改用串流處理方式。", + "is_new": true + }, + { + "level": "warning", + "role": "Mage", + "location": "app/main.js:175", + "problem": "在 `child.on('close', ...)` 事件中,使用 `Buffer.concat(outputChunks).toString()` 將所有輸出轉為單一字串。如果 `outputChunks` 總大小接近 `DEFAULT_OUTPUT_LIMIT_BYTES` (1MB),這會導致瞬間記憶體使用量增加,且對於極大輸出,字串轉換本身亦有潛在的負載。", + "suggestion": "考慮使用 `Buffer` 處理後續輸出,或在達到 `outputLimitBytes` 時,僅保存 `Buffer` 片段即可,不必轉為大字串。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "app/main.js:147", + "problem": "在 `runCodex` 函數中雖然有處理 `child.on('error', ...)`,但若 `codex` 指令本身不存在(spawn ENOENT),這裡捕捉到的 error stack trace 可能會包含完整的系統路徑資訊,這在 CI 環境中屬於資訊洩漏風險。", + "suggestion": "建議在錯誤處理中,針對 `error.code === 'ENOENT'` 做明確判斷,回傳簡潔的錯誤訊息(例如「找不到 codex 指令」),而非直接回傳完整的 `error.message`。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "app/main.js:164", + "problem": "當 `child.on('close', ...)` 觸發時,若 `code` 為 null,預設回傳 1。雖然這處理了非預期終止,但缺少對 signal 終止(例如 SIGKILL)的具體紀錄,只知道失敗,無法區分是指令執行錯誤還是被系統殺掉。", + "suggestion": "在 `close` 事件中,若 `code` 為 null,可以檢查 `signal` 參數(若有),並在 output 中加入被哪個 signal 終止的資訊,增加除錯便利性。", + "is_new": true + }, + { + "level": "warning", + "role": "Rogue", + "location": "app/main.js:127", + "problem": "在處理輸出區塊時,使用 `chunks.reduce` 重複計算陣列大小,隨著資料量增加,這會造成不必要的 O(n²) 運算瓶頸,浪費 CPU 週期。", + "suggestion": "應在 closure 中維護一個 `currentSize` 變數來追蹤當前總大小,避免每次有新資料時都重新遍歷整個區塊陣列。", + "is_new": true + }, + { + "level": "warning", + "role": "Rogue", + "location": "app/main.js:166", + "problem": "在 `runCodex` 的輸出處理中,每收到一塊資料就進行 `Buffer.concat` 與 `toString`,若資料量大或封包碎,會產生大量不必要的記憶體配置與垃圾回收 (GC) 壓力。", + "suggestion": "只在輸出完成、達到限制或必須輸出結果時才進行合併與轉型,不要在處理每一塊資料時都執行。", + "is_new": true + }, + { + "level": "info", + "role": "Maya", + "location": "app/main.js:134", + "problem": "測試檔案 `tests/entrypoint_test.sh` 有測試 `missing_codex_command`,這很好。但實作中對於 `codex` 執行失敗的各種細節(如權限不足、找不到 binary 等)都統一處理為 `status: 1` 和簡單的訊息,測試僅驗證了 failure 狀態,未驗證具體錯誤來源。", + "suggestion": "考慮在 `main.js` 中根據不同的錯誤類型回傳更細緻的 status code,並在測試中驗證這些 code,能更精確地協助 CI 使用者除錯。", + "is_new": true + }, + { + "level": "info", + "role": "Rogue", + "location": "app/main.js:46", + "problem": "在 `TempFileRegistry` 的 `cleanup` 方法中,每次呼叫都使用 `Array.from` 將 Set 轉換為陣列,這在頻繁清理時會產生無謂的記憶體開銷。", + "suggestion": "若無強烈反向迭代的需求,可考慮直接使用 `forEach` 遍歷 Set。若有嚴格順序需求,建議改用其他結構管理,避免每次 cleanup 都額外配置陣列。", + "is_new": true + } +] -- 2.53.0 From 94a6baae2c44c4cdca42be1fa5a9a3ad5449bdcb Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:01:05 +0000 Subject: [PATCH 51/58] =?UTF-8?q?fix(app):=20=E5=BC=B7=E5=8C=96=20Codex=20?= =?UTF-8?q?=E5=9F=B7=E8=A1=8C=E8=88=87=20auth=20=E6=9A=AB=E5=AD=98?= =?UTF-8?q?=E8=99=95=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.js | 99 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/app/main.js b/app/main.js index 2f3323a..543a620 100644 --- a/app/main.js +++ b/app/main.js @@ -39,11 +39,11 @@ class TempFileRegistry { } cleanup() { - for (const filePath of Array.from(this.files).reverse()) { + for (const filePath of this.files) { this.removeFile(filePath); } - for (const dirPath of Array.from(this.dirs).reverse()) { + for (const dirPath of this.dirs) { try { fs.rmSync(dirPath, { force: true, recursive: true }); this.dirs.delete(dirPath); @@ -56,6 +56,37 @@ class TempFileRegistry { const tempFiles = new TempFileRegistry(); +class OutputCollector { + constructor(maxBytes) { + this.maxBytes = maxBytes; + this.chunks = []; + this.size = 0; + this.truncated = false; + } + + append(chunk) { + const available = this.maxBytes - this.size; + + if (available <= 0) { + this.truncated = true; + return; + } + + const storedChunk = chunk.length > available ? chunk.subarray(0, available) : chunk; + this.chunks.push(storedChunk); + this.size += storedChunk.length; + + if (storedChunk.length < chunk.length) { + this.truncated = true; + } + } + + toString() { + const truncationMessage = this.truncated ? "\n[Output truncated]\n" : ""; + return `${Buffer.concat(this.chunks, this.size).toString()}${truncationMessage}`; + } +} + function cleanup() { tempFiles.cleanup(); } @@ -101,13 +132,16 @@ function fail(message, code = 1) { process.exit(code); } -function validateAuth(encodedAuth, authFile) { - const normalizedAuth = encodedAuth.replace(/\s+/g, ""); - const decoded = Buffer.from(encodedAuth, "base64"); - const normalizedDecoded = decoded.toString("base64").replace(/=+$/, ""); - const normalizedInput = normalizedAuth.replace(/=+$/, ""); +function normalizeBase64(value) { + return value.replace(/\s+/g, "").replace(/=+$/, ""); +} - if (decoded.length === 0 && normalizedAuth.length > 0) { +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) { fail("OAUTH must be valid base64 encoded Codex auth.json."); } @@ -134,18 +168,6 @@ function parsePositiveInteger(value, fallback) { return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; } -function truncateOutput(chunks, nextChunk, maxBytes) { - const currentSize = chunks.reduce((total, chunk) => total + chunk.length, 0); - const available = maxBytes - currentSize; - - if (available <= 0) { - return false; - } - - chunks.push(nextChunk.length > available ? nextChunk.subarray(0, available) : nextChunk); - return nextChunk.length <= available; -} - function runCodex(model, prompt) { return new Promise((resolve) => { const timeoutMs = parsePositiveInteger(process.env.CODEX_TIMEOUT_MS, DEFAULT_CODEX_TIMEOUT_MS); @@ -167,46 +189,39 @@ function runCodex(model, prompt) { { cwd: workspace, stdio: ["ignore", "pipe", "pipe"] }, ); - const outputChunks = []; - let outputTruncated = false; - const appendOutput = (chunk) => { - if (!truncateOutput(outputChunks, chunk, outputLimitBytes)) { - outputTruncated = true; - } - return Buffer.concat(outputChunks).toString(); - }; + const output = new OutputCollector(outputLimitBytes); const timeout = setTimeout(() => { child.kill("SIGTERM"); - appendOutput(Buffer.from(`Codex execution timed out after ${timeoutMs} ms.\n`)); + output.append(Buffer.from(`Codex execution timed out after ${timeoutMs} ms.\n`)); }, timeoutMs); child.stdout.pipe(process.stdout); child.stderr.pipe(process.stdout); child.stdout.on("data", (chunk) => { - if (!truncateOutput(outputChunks, chunk, outputLimitBytes)) { - outputTruncated = true; - } + output.append(chunk); }); child.stderr.on("data", (chunk) => { - if (!truncateOutput(outputChunks, chunk, outputLimitBytes)) { - outputTruncated = true; - } + output.append(chunk); }); child.on("error", (error) => { clearTimeout(timeout); - const output = appendOutput(Buffer.from(`${error.message}\n`)); - resolve({ status: 1, output }); + const message = error.code === "ENOENT" ? "Unable to find codex command.\n" : `${error.message}\n`; + output.append(Buffer.from(message)); + resolve({ status: 1, output: output.toString() }); }); - child.on("close", (code) => { + child.on("close", (code, signal) => { clearTimeout(timeout); - const truncationMessage = outputTruncated ? "\n[Output truncated]\n" : ""; - const output = `${Buffer.concat(outputChunks).toString()}${truncationMessage}`; - resolve({ status: code ?? 1, output }); + + if (code === null && signal) { + output.append(Buffer.from(`Codex process terminated by signal ${signal}.\n`)); + } + + resolve({ status: code ?? 1, output: output.toString() }); }); }); } @@ -243,6 +258,8 @@ function readConfig() { function setupAuth(oauth, codexHome) { try { fs.mkdirSync(codexHome, { recursive: true, mode: DIR_MODE_PRIVATE }); + fs.chmodSync(codexHome, DIR_MODE_PRIVATE); + fs.accessSync(codexHome, fs.constants.R_OK | fs.constants.W_OK | fs.constants.X_OK); } catch { fail("Unable to create CODEX_HOME."); } -- 2.53.0 From 6f917c2fe1e93a1a7c5bee984ec17cf9ef4edc1f Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:01:09 +0000 Subject: [PATCH 52/58] =?UTF-8?q?test(entrypoint):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=BC=BA=E5=B0=91=20codex=20=E6=8C=87=E4=BB=A4=E9=8C=AF?= =?UTF-8?q?=E8=AA=A4=E9=A9=97=E8=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/entrypoint_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/entrypoint_test.sh b/tests/entrypoint_test.sh index f3a0b3d..2b9f295 100755 --- a/tests/entrypoint_test.sh +++ b/tests/entrypoint_test.sh @@ -185,7 +185,7 @@ test_missing_codex_command_output() { assert_status "$status" 1 grep -q "status=failed" "$tmpdir/github-output" || die "missing codex command failed status output" - grep -q "spawn codex ENOENT" "$tmpdir/github-output" || die "missing codex command error output" + grep -q "Unable to find codex command" "$tmpdir/github-output" || die "missing codex command error output" [[ ! -e "$tmpdir/codex-home/auth.json" ]] || die "auth.json was not cleaned up after missing codex" rm -rf "$tmpdir" } -- 2.53.0 From 053109662def25986caa30bd08c4be9ac0c6fe2b Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:01:14 +0000 Subject: [PATCH 53/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/exclusions.json | 6 ++ .gitea/ai-review/findings.json | 155 +------------------------------ 2 files changed, 7 insertions(+), 154 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index 4d85379..8e9acd5 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -136,5 +136,11 @@ "role": "Leo", "original_finding": "建議至少加上 `console.error` 或在開發/除錯模式下將錯誤拋出,以便在清除失敗時能收到警示。", "reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)" + }, + { + "location": "app/main.js:125", + "role": "Mage", + "original_finding": "這裡直接使用 `spawn` 執行 `codex` 命令,且參數 `prompt` 是直接從 `process.env.PROMPT` 讀取並傳入的。如果 CI 環境的 `PROMPT` 被惡意竄改,雖使用陣列傳遞參數避免了 shell injection,但 `codex exec` 的邏輯若沒有妥善限制(例如限制可執行指令類型),可能導致攻擊者在 CI Runner 環境執行任意指令。", + "reason": "此 action 的目的就是讓呼叫 workflow 以 `PROMPT` 指定 Codex 任務並在隔離 CI 容器內非互動執行;prompt 語意必須由 workflow 與 secret 的信任邊界控管。程式已用 `spawn` 參數陣列避免 shell injection,不適合在 action 內以白名單改寫或限制使用者 prompt。" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 5060c6d..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,154 +1 @@ -[ - { - "level": "critical", - "role": "Mage", - "problem": "在 `setupAuth` 中,`lockPath` 使用 `os.tmpdir()`。在共享環境中,如果 `CODEX_HOME` 字串相同,會導致所有 process 競爭同一個鎖檔,且如果其他無關的 process 也剛好在 `os.tmpdir()` 中建立相同名稱的檔案,會導致誤判或鎖定失敗。", - "suggestion": "應在 `CODEX_HOME` 內部建立鎖檔,而非使用全域的 `os.tmpdir()`,或者包含更具唯一性的識別碼(如 PID 或更長的路徑雜湊)以確保鎖的隔離性。", - "location": "app/main.js:189", - "is_new": false - }, - { - "level": "critical", - "role": "Mage", - "location": "app/main.js:125", - "problem": "這裡直接使用 `spawn` 執行 `codex` 命令,且參數 `prompt` 是直接從 `process.env.PROMPT` 讀取並傳入的。如果 CI 環境的 `PROMPT` 被惡意竄改,雖使用陣列傳遞參數避免了 shell injection,但 `codex exec` 的邏輯若沒有妥善限制(例如限制可執行指令類型),可能導致攻擊者在 CI Runner 環境執行任意指令。", - "suggestion": "在 `runCodex` 函式中,除了已經加入的 `--dangerously-bypass-approvals-and-sandbox` 外,必須確保對 `prompt` 進行強力的白名單過濾,或改為使用非 `exec` 的子指令來限制權限。", - "is_new": true - }, - { - "level": "critical", - "role": "Mage", - "location": "app/main.js:255", - "problem": "在 `setupAuth` 中,使用了 `fs.renameSync` 來確保原子性。然而在某些檔案系統中,若 `authFile` 與 `authPath` 不在同一個分區,`renameSync` 可能會失敗。此外,如果 `codexHome` 已存在且權限配置錯誤,`fs.mkdirSync` 可能會被忽略但後續存取失敗。", - "suggestion": "建議確保 `authFile` 與 `codexHome` 處於相同掛載點,並增加對 `fs.mkdirSync` 後權限檢查的驗證。", - "is_new": true - }, - { - "level": "warning", - "role": "Rogue", - "problem": "在 setupAuth 中,`lockPath` 檔名產生使用了 `Buffer.from(codexHome).toString(\"hex\")`。如果 `codexHome` 非常長,這個檔名可能會超過作業系統的檔案名稱長度限制(通常為 255 bytes),導致鎖定失敗,進而阻斷整個流程。", - "suggestion": "改用 `crypto.createHash('sha256').update(codexHome).digest('hex')` 來產生固定長度的雜湊值作為檔名的一部分,既安全又保證長度可控。", - "location": "app/main.js:192", - "is_new": false - }, - { - "level": "warning", - "role": "Mage", - "problem": "在 `setupAuth` 中,在 `fs.copyFileSync(authFile, authPath)` 後立即 `removeIfCreated(authFile)`,但在這期間如果發生 process 中斷,auth.json 可能會以不安全的權限(預設)或不完整的狀態寫入。", - "suggestion": "建議使用 `fs.renameSync` 或在完成寫入與權限設定後再進行清理,並確保寫入過程中發生異常時能正確刪除該部分寫入的檔案。", - "location": "app/main.js:203", - "is_new": false - }, - { - "level": "warning", - "role": "Maya", - "problem": "在執行外部指令時沒有設定逾時(timeout),若 Codex CLI 發生無預期的掛起(hang),Action 將會永久卡住而不會自動終止。", - "suggestion": "建議在 `spawn` 的選項中加入 `timeout` 機制,或是主動在啟動後設置一個計時器,當執行時間過長時強制終止子行程。", - "location": "app/main.js:102", - "is_new": false - }, - { - "level": "warning", - "role": "Mage", - "problem": "在 `runCodex` 中使用 `spawn` 時,沒有設定 `cwd`。如果 `codex` 工具依賴於當前工作目錄(例如需要編輯當前專案),這在 CI 環境中可能存在風險,雖然目前 CI 通常會設定好目錄,但這是一個隱含的契約。", - "suggestion": "建議明確設定 `cwd` 為 `/github/workspace` 或 CI 定義的專案根目錄,確保 `codex` 運作在預期的上下文中。", - "location": "app/main.js:114", - "is_new": false - }, - { - "level": "warning", - "role": "Maya", - "problem": "直接將所有輸出串接在 `outputChunks` 中,若 CLI 輸出過大的日誌,可能會導致記憶體耗盡(OOM)。", - "suggestion": "建議針對 output 大小設定上限,超過限制時截斷輸出,或是改用串流寫入暫存檔以避免將所有內容存於記憶體。", - "location": "app/main.js:125", - "is_new": false - }, - { - "level": "warning", - "role": "Mage", - "problem": "在 `validateAuth` 中,僅透過 `JSON.parse` 檢查 JSON 格式,但未針對 Codex 預期的 auth.json 結構(如必要的欄位)進行 Schema 驗證。如果傳入的 JSON 格式正確但內容無效,可能會導致 `codex exec` 在後續執行時失敗。", - "suggestion": "建議加入對 JSON 內容的簡單結構驗證(例如確認是否有 `token` 或必要的連線設定欄位)。", - "location": "app/main.js:77", - "is_new": false - }, - { - "level": "warning", - "role": "Bard", - "location": "app/main.js:120", - "problem": "validateAuth 函式中對於 Base64 的正規化與驗證邏輯混雜在一起,使用了大量的取代與判斷,讀起來節奏凌亂,缺乏優雅感。", - "suggestion": "將驗證邏輯與基礎轉換邏輯抽離,建議提取一個輔助函式專門負責 Base64 格式檢查,使主要流程清晰明瞭。", - "is_new": true - }, - { - "level": "warning", - "role": "Bard", - "location": "app/main.js:166", - "problem": "runCodex 函式過於臃腫,包含了執行、超時處理、輸出截斷與錯誤捕捉等多重責任,這段旋律太過冗長且複雜。", - "suggestion": "建議將輸出處理 (Output truncation logic) 與超時設定分離為獨立函式,以提升函式的可讀性與維護性。", - "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "app/main.js:77", - "problem": "在 `appendGithubOutput` 函式中,當 `output` 內容極大時,此處會將整個 `output` 字串在記憶體中進行檢查(`output.includes(delimiter)`)與多次複製。這可能導致在處理極端長度輸出時發生記憶體不足的問題。", - "suggestion": "建議限制 `delimiter` 嘗試次數,或在檢查時避免讀取整個 `output` 字串,改用串流處理方式。", - "is_new": true - }, - { - "level": "warning", - "role": "Mage", - "location": "app/main.js:175", - "problem": "在 `child.on('close', ...)` 事件中,使用 `Buffer.concat(outputChunks).toString()` 將所有輸出轉為單一字串。如果 `outputChunks` 總大小接近 `DEFAULT_OUTPUT_LIMIT_BYTES` (1MB),這會導致瞬間記憶體使用量增加,且對於極大輸出,字串轉換本身亦有潛在的負載。", - "suggestion": "考慮使用 `Buffer` 處理後續輸出,或在達到 `outputLimitBytes` 時,僅保存 `Buffer` 片段即可,不必轉為大字串。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "app/main.js:147", - "problem": "在 `runCodex` 函數中雖然有處理 `child.on('error', ...)`,但若 `codex` 指令本身不存在(spawn ENOENT),這裡捕捉到的 error stack trace 可能會包含完整的系統路徑資訊,這在 CI 環境中屬於資訊洩漏風險。", - "suggestion": "建議在錯誤處理中,針對 `error.code === 'ENOENT'` 做明確判斷,回傳簡潔的錯誤訊息(例如「找不到 codex 指令」),而非直接回傳完整的 `error.message`。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "app/main.js:164", - "problem": "當 `child.on('close', ...)` 觸發時,若 `code` 為 null,預設回傳 1。雖然這處理了非預期終止,但缺少對 signal 終止(例如 SIGKILL)的具體紀錄,只知道失敗,無法區分是指令執行錯誤還是被系統殺掉。", - "suggestion": "在 `close` 事件中,若 `code` 為 null,可以檢查 `signal` 參數(若有),並在 output 中加入被哪個 signal 終止的資訊,增加除錯便利性。", - "is_new": true - }, - { - "level": "warning", - "role": "Rogue", - "location": "app/main.js:127", - "problem": "在處理輸出區塊時,使用 `chunks.reduce` 重複計算陣列大小,隨著資料量增加,這會造成不必要的 O(n²) 運算瓶頸,浪費 CPU 週期。", - "suggestion": "應在 closure 中維護一個 `currentSize` 變數來追蹤當前總大小,避免每次有新資料時都重新遍歷整個區塊陣列。", - "is_new": true - }, - { - "level": "warning", - "role": "Rogue", - "location": "app/main.js:166", - "problem": "在 `runCodex` 的輸出處理中,每收到一塊資料就進行 `Buffer.concat` 與 `toString`,若資料量大或封包碎,會產生大量不必要的記憶體配置與垃圾回收 (GC) 壓力。", - "suggestion": "只在輸出完成、達到限制或必須輸出結果時才進行合併與轉型,不要在處理每一塊資料時都執行。", - "is_new": true - }, - { - "level": "info", - "role": "Maya", - "location": "app/main.js:134", - "problem": "測試檔案 `tests/entrypoint_test.sh` 有測試 `missing_codex_command`,這很好。但實作中對於 `codex` 執行失敗的各種細節(如權限不足、找不到 binary 等)都統一處理為 `status: 1` 和簡單的訊息,測試僅驗證了 failure 狀態,未驗證具體錯誤來源。", - "suggestion": "考慮在 `main.js` 中根據不同的錯誤類型回傳更細緻的 status code,並在測試中驗證這些 code,能更精確地協助 CI 使用者除錯。", - "is_new": true - }, - { - "level": "info", - "role": "Rogue", - "location": "app/main.js:46", - "problem": "在 `TempFileRegistry` 的 `cleanup` 方法中,每次呼叫都使用 `Array.from` 將 Set 轉換為陣列,這在頻繁清理時會產生無謂的記憶體開銷。", - "suggestion": "若無強烈反向迭代的需求,可考慮直接使用 `forEach` 遍歷 Set。若有嚴格順序需求,建議改用其他結構管理,避免每次 cleanup 都額外配置陣列。", - "is_new": true - } -] +[] -- 2.53.0 From 963977be9f7ec0c56a64e8a8ab6762e430aaf460 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 14:02:05 +0000 Subject: [PATCH 54/58] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 87 +++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fe51488..c86e092 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1 +1,86 @@ -[] +[ + { + "level": "critical", + "role": "Maya", + "location": "app/main.js:189", + "problem": "Codex 超時處理路徑未經測試,無法確保 SIGTERM 能成功發送與訊息正確產出。", + "suggestion": "增加測試案例模擬長期睡眠(如 sleep 10),驗證超時機制與輸出訊息。" + }, + { + "level": "warning", + "role": "Maya", + "location": "app/main.js:147", + "problem": "spawn ENOENT 錯誤處理可能洩漏系統路徑資訊。", + "suggestion": "針對 error.code === 'ENOENT' 做明確判斷,回傳簡潔錯誤訊息而非完整 stack trace。" + }, + { + "level": "warning", + "role": "Maya", + "location": "app/main.js:164", + "problem": "close 事件缺少對 signal 終止(如 SIGKILL)的具體紀錄。", + "suggestion": "檢查 signal 參數,並在 output 中加入被哪個 signal 終止的資訊。" + }, + { + "level": "warning", + "role": "Rogue", + "location": "app/main.js:127", + "problem": "處理輸出區塊時使用 chunks.reduce 重複計算陣列大小,造成 O(n²) 運算瓶頸。", + "suggestion": "在 closure 中維護 currentSize 變數追蹤總大小,避免遍歷。" + }, + { + "level": "warning", + "role": "Rogue", + "location": "app/main.js:142", + "problem": "validateAuth 中重複執行 decoded.toString('base64') 並進行 normalizedBase64 處理,極度浪費資源。", + "suggestion": "移除這些無謂的重新編碼比較,直接嘗試解碼。" + }, + { + "level": "warning", + "role": "Bard", + "location": "app/main.js:166", + "problem": "runCodex 函式過於臃腫,包含了過多職責。", + "suggestion": "建議將輸出處理與超時設定分離為獨立函式。" + }, + { + "level": "warning", + "role": "Bard", + "location": "app/main.js:183", + "problem": "spawn 函式參數陣列過長且散亂,閱讀性較差。", + "suggestion": "將參數拆分為數組變數並展開傳遞。" + }, + { + "level": "warning", + "role": "Maya", + "location": "app/main.js:77", + "problem": "OutputCollector 的截斷機制未被測試。", + "suggestion": "增加測試案例模擬輸出超過 DEFAULT_OUTPUT_LIMIT_BYTES,驗證截斷提示。" + }, + { + "level": "warning", + "role": "Maya", + "location": "app/main.js:168", + "problem": "parsePositiveInteger 的輸入回退機制未經測試。", + "suggestion": "針對設定變數傳入無效數字或非法格式場景,驗證預設值套用。" + }, + { + "level": "info", + "role": "Maya", + "location": "app/main.js:134", + "problem": "對於 codex 執行失敗的各種細節(權限不足、找不到 binary 等)都統一處理為 status: 1,測試未驗證具體錯誤來源。", + "suggestion": "根據錯誤類型回傳細緻 status code,並在測試中驗證這些 code。" + }, + { + "level": "info", + "role": "Bard", + "location": "app/main.js:93", + "problem": "makeTempDir 內部使用了硬編碼的 '.codex-action-' 前綴。", + "suggestion": "將前綴提取為常數或設定檔參數。" + }, + { + "level": "info", + "role": "Bard", + "location": "app/main.js:283", + "problem": "setupAuth 函式職責過於繁雜。", + "suggestion": "將鎖定機制與驗證機制拆分為獨立輔助函式。" + } +] -- 2.53.0 From f40de2a8fb7866f889fcdccd0bf6271181011f1c Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:05:45 +0000 Subject: [PATCH 55/58] =?UTF-8?q?refactor(app):=20=E6=8B=86=E5=88=86=20Cod?= =?UTF-8?q?ex=20=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); -- 2.53.0 From 688ed499748023a4acdf5b7adef85f562cd1914e Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:05:49 +0000 Subject: [PATCH 56/58] =?UTF-8?q?test(entrypoint):=20=E8=A3=9C=E9=BD=8A=20?= =?UTF-8?q?timeout=20=E8=88=87=E8=BC=B8=E5=87=BA=E9=99=90=E5=88=B6?= =?UTF-8?q?=E6=B8=AC=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/entrypoint_test.sh | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/entrypoint_test.sh b/tests/entrypoint_test.sh index 2b9f295..85983cd 100755 --- a/tests/entrypoint_test.sh +++ b/tests/entrypoint_test.sh @@ -23,6 +23,39 @@ SH chmod +x "$dir/codex" } +make_fake_sleeping_codex() { + local dir="$1" + + cat > "$dir/codex" <<'SH' +#!/bin/sh +trap 'exit 143' TERM +while true; do + sleep 1 +done +SH + chmod +x "$dir/codex" +} + +make_fake_signaled_codex() { + local dir="$1" + + cat > "$dir/codex" <<'SH' +#!/bin/sh +kill -TERM $$ +SH + chmod +x "$dir/codex" +} + +make_fake_large_output_codex() { + local dir="$1" + + cat > "$dir/codex" <<'SH' +#!/bin/sh +printf '0123456789' +SH + chmod +x "$dir/codex" +} + encoded_json() { printf '%s' "$1" | base64 | tr -d '\n' } @@ -190,6 +223,70 @@ test_missing_codex_command_output() { rm -rf "$tmpdir" } +test_codex_timeout_output() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_sleeping_codex "$tmpdir" + + set +e + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="hello" CODEX_TIMEOUT_MS="100" + local status="$?" + set -e + + assert_status "$status" 143 + grep -q "status=failed" "$tmpdir/github-output" || die "timeout failed status output" + grep -q "Codex execution timed out after 100 ms" "$tmpdir/github-output" || die "timeout message output" + [[ ! -e "$tmpdir/codex-home/auth.json" ]] || die "auth.json was not cleaned up after timeout" + rm -rf "$tmpdir" +} + +test_codex_signal_output() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_signaled_codex "$tmpdir" + + set +e + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="hello" + local status="$?" + set -e + + assert_status "$status" 1 + grep -q "status=failed" "$tmpdir/github-output" || die "signal failed status output" + grep -q "Codex process terminated by signal SIGTERM" "$tmpdir/github-output" || die "signal message output" + rm -rf "$tmpdir" +} + +test_codex_output_truncation() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_large_output_codex "$tmpdir" + + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="hello" CODEX_OUTPUT_LIMIT_BYTES="5" + + grep -q "status=completed" "$tmpdir/github-output" || die "truncated output completed status" + grep -q "01234" "$tmpdir/github-output" || die "truncated output prefix" + grep -q "\\[Output truncated\\]" "$tmpdir/github-output" || die "truncation message output" + rm -rf "$tmpdir" +} + +test_invalid_execution_config_uses_defaults() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_large_output_codex "$tmpdir" + + run_entrypoint "$tmpdir" \ + OAUTH="$(encoded_json '{}')" \ + MODEL="gpt-test" \ + PROMPT="hello" \ + CODEX_TIMEOUT_MS="invalid" \ + CODEX_OUTPUT_LIMIT_BYTES="invalid" + + grep -q "status=completed" "$tmpdir/github-output" || die "invalid config completed status" + grep -q "0123456789" "$tmpdir/github-output" || die "invalid output limit did not use default" + ! grep -q "\\[Output truncated\\]" "$tmpdir/github-output" || die "invalid output limit unexpectedly truncated" + rm -rf "$tmpdir" +} + test_missing_oauth test_missing_model test_invalid_base64 @@ -199,5 +296,9 @@ test_empty_prompt test_prompt_with_shell_characters test_failure_output test_missing_codex_command_output +test_codex_timeout_output +test_codex_signal_output +test_codex_output_truncation +test_invalid_execution_config_uses_defaults echo "entrypoint tests passed" -- 2.53.0 From 82d8a8ed0454053b9c74000b07d1951610a049ba Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:05:53 +0000 Subject: [PATCH 57/58] =?UTF-8?q?chore(ai-review=20=E7=8B=80=E6=85=8B):=20?= =?UTF-8?q?=E6=B8=85=E9=99=A4=E5=B7=B2=E8=99=95=E7=90=86=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/ai-review/exclusions.json | 6 +++ .gitea/ai-review/findings.json | 87 +------------------------------- 2 files changed, 7 insertions(+), 86 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index 8e9acd5..f0c3ae7 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -142,5 +142,11 @@ "role": "Mage", "original_finding": "這裡直接使用 `spawn` 執行 `codex` 命令,且參數 `prompt` 是直接從 `process.env.PROMPT` 讀取並傳入的。如果 CI 環境的 `PROMPT` 被惡意竄改,雖使用陣列傳遞參數避免了 shell injection,但 `codex exec` 的邏輯若沒有妥善限制(例如限制可執行指令類型),可能導致攻擊者在 CI Runner 環境執行任意指令。", "reason": "此 action 的目的就是讓呼叫 workflow 以 `PROMPT` 指定 Codex 任務並在隔離 CI 容器內非互動執行;prompt 語意必須由 workflow 與 secret 的信任邊界控管。程式已用 `spawn` 參數陣列避免 shell injection,不適合在 action 內以白名單改寫或限制使用者 prompt。" + }, + { + "location": "app/main.js:134", + "role": "Maya", + "original_finding": "對於 codex 執行失敗的各種細節(權限不足、找不到 binary 等)都統一處理為 status: 1,測試未驗證具體錯誤來源。", + "reason": "此 GitHub Action 對外輸出以 `status=completed|failed` 與 `output` 訊息表達成功或失敗;process exit code 只需反映 Codex 子程序結果或通用啟動失敗。細分 ENOENT、EACCES 等 Action exit code 會改變既有介面且對 workflow 判斷價值有限,本次已用簡潔 output 訊息與測試覆蓋具體錯誤來源。" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index c86e092..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,86 +1 @@ -[ - { - "level": "critical", - "role": "Maya", - "location": "app/main.js:189", - "problem": "Codex 超時處理路徑未經測試,無法確保 SIGTERM 能成功發送與訊息正確產出。", - "suggestion": "增加測試案例模擬長期睡眠(如 sleep 10),驗證超時機制與輸出訊息。" - }, - { - "level": "warning", - "role": "Maya", - "location": "app/main.js:147", - "problem": "spawn ENOENT 錯誤處理可能洩漏系統路徑資訊。", - "suggestion": "針對 error.code === 'ENOENT' 做明確判斷,回傳簡潔錯誤訊息而非完整 stack trace。" - }, - { - "level": "warning", - "role": "Maya", - "location": "app/main.js:164", - "problem": "close 事件缺少對 signal 終止(如 SIGKILL)的具體紀錄。", - "suggestion": "檢查 signal 參數,並在 output 中加入被哪個 signal 終止的資訊。" - }, - { - "level": "warning", - "role": "Rogue", - "location": "app/main.js:127", - "problem": "處理輸出區塊時使用 chunks.reduce 重複計算陣列大小,造成 O(n²) 運算瓶頸。", - "suggestion": "在 closure 中維護 currentSize 變數追蹤總大小,避免遍歷。" - }, - { - "level": "warning", - "role": "Rogue", - "location": "app/main.js:142", - "problem": "validateAuth 中重複執行 decoded.toString('base64') 並進行 normalizedBase64 處理,極度浪費資源。", - "suggestion": "移除這些無謂的重新編碼比較,直接嘗試解碼。" - }, - { - "level": "warning", - "role": "Bard", - "location": "app/main.js:166", - "problem": "runCodex 函式過於臃腫,包含了過多職責。", - "suggestion": "建議將輸出處理與超時設定分離為獨立函式。" - }, - { - "level": "warning", - "role": "Bard", - "location": "app/main.js:183", - "problem": "spawn 函式參數陣列過長且散亂,閱讀性較差。", - "suggestion": "將參數拆分為數組變數並展開傳遞。" - }, - { - "level": "warning", - "role": "Maya", - "location": "app/main.js:77", - "problem": "OutputCollector 的截斷機制未被測試。", - "suggestion": "增加測試案例模擬輸出超過 DEFAULT_OUTPUT_LIMIT_BYTES,驗證截斷提示。" - }, - { - "level": "warning", - "role": "Maya", - "location": "app/main.js:168", - "problem": "parsePositiveInteger 的輸入回退機制未經測試。", - "suggestion": "針對設定變數傳入無效數字或非法格式場景,驗證預設值套用。" - }, - { - "level": "info", - "role": "Maya", - "location": "app/main.js:134", - "problem": "對於 codex 執行失敗的各種細節(權限不足、找不到 binary 等)都統一處理為 status: 1,測試未驗證具體錯誤來源。", - "suggestion": "根據錯誤類型回傳細緻 status code,並在測試中驗證這些 code。" - }, - { - "level": "info", - "role": "Bard", - "location": "app/main.js:93", - "problem": "makeTempDir 內部使用了硬編碼的 '.codex-action-' 前綴。", - "suggestion": "將前綴提取為常數或設定檔參數。" - }, - { - "level": "info", - "role": "Bard", - "location": "app/main.js:283", - "problem": "setupAuth 函式職責過於繁雜。", - "suggestion": "將鎖定機制與驗證機制拆分為獨立輔助函式。" - } -] +[] -- 2.53.0 From 30130b715bdc5a72d6dce3021622bbe51123d859 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 14:06:46 +0000 Subject: [PATCH 58/58] chore: update ai-review findings [ai-review-bot][success] --- .gitea/ai-review/exclusions.json | 6 ++++++ .gitea/ai-review/findings.json | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index f0c3ae7..9d40759 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -148,5 +148,11 @@ "role": "Maya", "original_finding": "對於 codex 執行失敗的各種細節(權限不足、找不到 binary 等)都統一處理為 status: 1,測試未驗證具體錯誤來源。", "reason": "此 GitHub Action 對外輸出以 `status=completed|failed` 與 `output` 訊息表達成功或失敗;process exit code 只需反映 Codex 子程序結果或通用啟動失敗。細分 ENOENT、EACCES 等 Action exit code 會改變既有介面且對 workflow 判斷價值有限,本次已用簡潔 output 訊息與測試覆蓋具體錯誤來源。" + }, + { + "location": "app/main.js:142", + "role": "Rogue", + "original_finding": "移除這些無謂的重新編碼比較,直接嘗試解碼。", + "reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fe51488..b3397bd 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1 +1,10 @@ -[] +[ + { + "level": "info", + "role": "Bard", + "problem": "setupAuth 函式職責過於繁雜。", + "suggestion": "將鎖定機制與驗證機制拆分為獨立輔助函式。", + "location": "app/main.js:283", + "is_new": false + } +] -- 2.53.0