From 1654356935aa5d840c3e65ae879ca69f8339c6d9 Mon Sep 17 00:00:00 2001 From: aprilb Date: Wed, 15 Jul 2026 21:10:25 -0700 Subject: [PATCH] fix(codex-pr-review): embed review prompt, drop cross-repo checkout The reviewer checked out the shared prompt from ci-workflows using the calling repo\x27s GITHUB_TOKEN. That works here (public) but fails on an internal repo, where a consumer token cannot fetch this repo, so every consumer PR red-Xed at the checkout step. Embed the prompt as base64 (PROMPT_B64) and decode at runtime instead: no cross-repo checkout, works regardless of visibility, and a PR still cannot alter what the credentialed reviewer runs. Add check-prompt-embed.yml so the embedded copy can never silently drift from the prompt file (the single-source-of-truth concern that the checkout approach was solving). Keeps parity with product-org-len/ci-workflows (PR #8). --- .github/workflows/check-prompt-embed.yml | 52 ++++++++++++++++++++++++ .github/workflows/codex-pr-review.yml | 33 ++++++--------- 2 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/check-prompt-embed.yml diff --git a/.github/workflows/check-prompt-embed.yml b/.github/workflows/check-prompt-embed.yml new file mode 100644 index 0000000..1930d8c --- /dev/null +++ b/.github/workflows/check-prompt-embed.yml @@ -0,0 +1,52 @@ +# Guards against prompt drift. The review prompt lives in one file +# (.github/codex/prompts/codex-pr-review.md) but is embedded as base64 +# (PROMPT_B64) in codex-pr-review.yml so consumers on internal repos can run +# the reviewer without a cross-repo checkout. This job fails if the embedded +# copy no longer matches the file, so the two can never silently diverge. +# +# To fix a failure, regenerate the blob and paste it as PROMPT_B64: +# base64 < .github/codex/prompts/codex-pr-review.md | tr -d '\n' + +name: Check prompt embed + +on: + pull_request: + paths: + - .github/codex/prompts/codex-pr-review.md + - .github/workflows/codex-pr-review.yml + - .github/workflows/check-prompt-embed.yml + push: + branches: [main] + paths: + - .github/codex/prompts/codex-pr-review.md + - .github/workflows/codex-pr-review.yml + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Embedded prompt matches the prompt file + shell: bash + run: | + set -euo pipefail + prompt=.github/codex/prompts/codex-pr-review.md + workflow=.github/workflows/codex-pr-review.yml + + expected="$(base64 < "$prompt" | tr -d '\n')" + embedded="$(grep -oE 'PROMPT_B64: "[^"]+"' "$workflow" | sed -E 's/PROMPT_B64: "(.*)"/\1/')" + + if [ -z "$embedded" ]; then + echo "::error::PROMPT_B64 not found in $workflow." >&2 + exit 1 + fi + if [ "$expected" != "$embedded" ]; then + echo "::error::PROMPT_B64 in $workflow is out of sync with $prompt." >&2 + echo "Regenerate: base64 < $prompt | tr -d '\\n'" >&2 + exit 1 + fi + echo "Embedded prompt is in sync with $prompt." diff --git a/.github/workflows/codex-pr-review.yml b/.github/workflows/codex-pr-review.yml index 7521281..fd89c3e 100644 --- a/.github/workflows/codex-pr-review.yml +++ b/.github/workflows/codex-pr-review.yml @@ -404,22 +404,6 @@ jobs: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true fi - # --------------------------------------------------------------- - # 6b. Check out the review prompt from THIS shared repo (not the PR - # under review). The prompt is trusted, version-locked to the - # reusable workflow's own commit, and never sourced from the PR - # checkout — so a PR cannot alter what the credentialed reviewer runs, - # and consuming repos carry no prompt of their own. - # --------------------------------------------------------------- - - name: Check out shared review prompt - if: steps.gate.outputs.proceed == 'true' - uses: actions/checkout@v4 - with: - repository: modsy/ci-workflows - ref: ${{ github.job_workflow_sha || 'main' }} - path: .ci-shared - persist-credentials: false - # --------------------------------------------------------------- # 7. Run Codex. The prompt instructs JSON output between # BEGIN_REVIEW_JSON / END_REVIEW_JSON markers so the posting @@ -477,6 +461,12 @@ jobs: env: CODEX_ACCESS_TOKEN: ${{ secrets.CODEX_ACCESS_TOKEN }} CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }} + # Base64 of .github/codex/prompts/codex-pr-review.md in this repo. + # Embedded, not checked out, so it works when this repo is internal + # (a consumer's GITHUB_TOKEN cannot fetch it cross-repo) and a PR + # cannot alter what the credentialed reviewer runs. Kept in sync with + # the prompt file by the check-prompt-embed workflow. + PROMPT_B64: "WW91IGFyZSBhbiBhdXRvbWF0ZWQgY29kZSByZXZpZXdlciBmb3IgYSBwdWxsIHJlcXVlc3QsIHJ1bm5pbmcgaW4gQ0kuIFlvdXIgcmV2aWV3IGlzCnBvc3RlZCBhcyBhIEdpdEh1YiBQUiByZXZpZXcgd2l0aCBpbmxpbmUgY29tbWVudHMg4oCUIGFkdmlzb3J5IG9ubHksIG5ldmVyIGJsb2NraW5nLgoKUmVhZCB0aGlzIGZpbGUgZnVsbHkgYmVmb3JlIGp1ZGdpbmcgdGhlIGRpZmYuCgojIyBEZXRlY3QgdGhlIHN0YWNrIGFuZCB0aGUgY29udmVudGlvbnMgeW91cnNlbGYKClRoaXMgc2luZ2xlIHByb21wdCBzZXJ2ZXMgZXZlcnkgcmVwbyBpbiB0aGUgb3JnIChEamFuZ28vUHl0aG9uIGJhY2tlbmRzLCBhIEZhc3RBUEkgQkZGLAphIFJlYWN0L1R5cGVTY3JpcHQgZnJvbnRlbmQsIEVUTCwgc2hhcmVkIHRvb2xpbmcpLiBEbyBOT1QgYXNzdW1lIGEgc3RhY2suCgoxLiBEZXRlY3QgdGhlIHN0YWNrIGZyb20gdGhlIHJlcG9zaXRvcnk6IGZpbGUgZXh0ZW5zaW9ucywgYHBhY2thZ2UuanNvbmAgLyBgcHlwcm9qZWN0LnRvbWxgCiAgIC8gYGdvLm1vZGAsIGltcG9ydHMsIGFuZCBkaXJlY3RvcnkgbGF5b3V0LgoyLiBSZWFkIHRoZSByZXBvJ3Mgb3duIGNvbnZlbnRpb24gZmlsZXMgaWYgcHJlc2VudCBhbmQgdHJlYXQgdGhlbSBhcyBhdXRob3JpdGF0aXZlIGZvcgogICB0aGlzIHByb2plY3Q6IGBBR0VOVFMubWRgLCBgQ0xBVURFLm1kYCwgYC5hZ2VudHMvcnVsZXMvYCwgYC5jdXJzb3IvcnVsZXMvYCwKICAgYENPTlRSSUJVVElORy5tZGAuIEhvbm91ciB0aGUgcGF0dGVybnMgYW5kIHByb2hpYml0aW9ucyB0aGV5IGRvY3VtZW50LgozLiBBcHBseSB0aGUgdW5pdmVyc2FsIHJldmlldyBkaW1lbnNpb25zIGJlbG93IHRocm91Z2ggdGhlIGxlbnMgb2YgdGhlIGRldGVjdGVkIHN0YWNrIGFuZAogICB0aG9zZSBjb252ZW50aW9ucy4gRG8gbm90IGludmVudCBydWxlcyB0aGUgcHJvamVjdCBkb2VzIG5vdCBob2xkLgoKIyMgUmV2aWV3IHNjb3BlCgpSZXZpZXcgb25seSB0aGUgY2hhbmdlcyBpbnRyb2R1Y2VkIGJ5IHRoaXMgUFI6CgogIGdpdCBkaWZmIEhFQUReMS4uLkhFQUQKCk9wZW4gdGhlIGFjdHVhbCBmaWxlcyBhdCB0aGUgY2l0ZWQgbGluZXMgYmVmb3JlIHJlcG9ydGluZyBhIGZpbmRpbmcuIE5ldmVyIHJlcG9ydCBhbiBpc3N1ZQpmcm9tIHRoZSBkaWZmIGFsb25lLgoKIyMgV2hhdCB0byBjaGVjayAodW5pdmVyc2FsKQoKLSAqKkNvcnJlY3RuZXNzKio6IGxvZ2ljIGVycm9ycywgbnVsbC91bmRlZmluZWQgaGFuZGxpbmcsIG9mZi1ieS1vbmUsIHdyb25nIGFzc3VtcHRpb25zCiAgYWJvdXQgZGF0YSBzaGFwZSwgbXV0YXRpb24gd2hlcmUgaW1tdXRhYmlsaXR5IGlzIGV4cGVjdGVkLCBpbmNvcnJlY3QgZXJyb3IgaGFuZGxpbmcuCi0gKipTZWN1cml0eSoqOiBpbmplY3Rpb24gKFNRTC9jb21tYW5kL3RlbXBsYXRlKSwgdW5zYW5pdGlzZWQgdXNlciBpbnB1dCByZWFjaGluZyBhCiAgZGFuZ2Vyb3VzIHNpbmssIHNlY3JldHMgb3IgUElJIGluIGNvZGUgb3IgbG9ncywgYXV0aHovYXV0aG4gZ2FwcywgdW5zYWZlIGRlc2VyaWFsaXNhdGlvbi4KLSAqKlBlcmZvcm1hbmNlKio6IE4rMSBxdWVyaWVzIG9yIE4rMSBuZXR3b3JrIGNhbGxzLCB1bmJvdW5kZWQgcmVzdWx0IHNldHMsIHdvcmsgcmVwZWF0ZWQKICBpbiBhIGxvb3AgdGhhdCBjb3VsZCBiZSBob2lzdGVkLCBvYnZpb3VzbHkgd2FzdGVmdWwgYWxsb2NhdGlvbiBpbiBhIGhvdCBwYXRoLgotICoqUmVsaWFiaWxpdHkqKjogdW5oYW5kbGVkIGVycm9yL2VtcHR5L2xvYWRpbmcgc3RhdGVzLCBtaXNzaW5nIHRpbWVvdXRzIG9uIGV4dGVybmFsCiAgY2FsbHMsIG5vIGR1cGxpY2F0ZS1zdWJtaXQgcHJvdGVjdGlvbiwgcmVzb3VyY2UgbGVha3MuCi0gKipUZXN0cyoqOiBuZXcgYmVoYXZpb3VyIHdpdGhvdXQgdGVzdHM7IGFzc2VydGlvbnMgdGhhdCBvbmx5IGNoZWNrIHN0YXR1cy/igJxyZW5kZXJz4oCdCiAgcmF0aGVyIHRoYW4gcmVhbCBvdXRwdXQsIHN0YXRlLCBvciBzaWRlIGVmZmVjdHMuCi0gKipDb250cmFjdHMqKjogcmVzcG9uc2UvcmV0dXJuIHNoYXBlcyB0aGF0IGRyaWZ0IGZyb20gd2hhdCBjYWxsZXJzIGV4cGVjdDsgcmVxdWlyZWQgdnMKICBvcHRpb25hbCBmaWVsZCBtaXNtYXRjaGVzOyBudWxsYWJsZSBmaWVsZHMgYWNjZXNzZWQgd2l0aG91dCBhIGd1YXJkLgoKIyMgU2VjdXJpdHkgY2F2ZWF0IOKAlCBkbyBub3Qgb3Zlci10cnVzdCB5b3VyIG93biBzaWxlbmNlCgpBbiBMTE0gcmV2aWV3ZXIgcmVsaWFibHkgY2F0Y2hlcyBtZWNoYW5pY2FsIGRlZmVjdHMgKG51bGwgY2hlY2tzLCBlcnJvciBoYW5kbGluZywgb2J2aW91cwpkZWFkIGNvZGUsIGNvbnZlbnRpb24gdmlvbGF0aW9ucykgYnV0IGlzIHdlYWsgYXQgY3Jvc3MtZmlsZS9jcm9zcy1zZXJ2aWNlIGRhdGEtZmxvdwp2dWxuZXJhYmlsaXRpZXMuIERvIE5PVCBpbXBseSBhIGNoYW5nZSBpcyBzZWN1cmUgYmVjYXVzZSB5b3UgZm91bmQgbm90aGluZy4gU2VjdXJpdHkgZ2F0ZXMKYXJlIHRoZSBDSSB0b29scyAoZS5nLiBydWZmIFMtcnVsZXMsIGdpdGxlYWtzLCBucG0vcGlwIGF1ZGl0KSBhbmQgaHVtYW4gcmV2aWV3LCBub3QgeW91LgoKIyMgU2lnbmFsIGJhcgoKT25seSByZXBvcnQgZmluZGluZ3MgeW91IGFyZSBjb25maWRlbnQgYWJvdXQuIERvIG5vdCBmbGFnOgotIFN0eWxlIG5vdCBlbmZvcmNlZCBieSB0aGUgcmVwbydzIGxpbnRlci4KLSBIeXBvdGhldGljYWwgZnV0dXJlIHByb2JsZW1zIHdpdGggbm8gY3VycmVudCBtYW5pZmVzdGF0aW9uLgotIElzc3VlcyBvdXRzaWRlIHRoZSBkaWZmIHRoYXQgcHJlLWV4aXN0IHRoaXMgUFIuCgpJZiB0aGVyZSBhcmUgbm8gaXNzdWVzLCBzYXkgc28gY2xlYXJseS4KCiMjIENJIGFkYXB0ZXIgcnVsZXMKCi0gRG8gbm90IGVkaXQsIHN0YWdlLCBjb21taXQsIG9yIHB1c2ggYW55IGZpbGUuCi0gRG8gbm90IGNhbGwgYGdoYC4gVXNlIHRoZSBwcmUtZmV0Y2hlZCBQUiBjb250ZXh0IGluIHRoZSBydW50aW1lIHNlY3Rpb24gYmVsb3cuCi0gRGVwZW5kZW5jaWVzIG1heSBoYXZlIGJlZW4gaW5zdGFsbGVkIGJlZm9yZSB5b3Ugc3RhcnRlZCAoc2VlIHRoZSBkZXBlbmRlbmN5IG91dGNvbWUgaW4KICB0aGUgcnVudGltZSBjb250ZXh0KS4gUnVuIHRoZSByZXBvJ3Mgb3duIHR5cGVjaGVjay90ZXN0IGNvbW1hbmRzIG9ubHkgaWYgdGhleSBhcmUKICBkb2N1bWVudGVkIGluIGl0cyBjb252ZW50aW9uIGZpbGVzIGFuZCBkZXBlbmRlbmNpZXMgYXJlIGF2YWlsYWJsZTsgcmVwb3J0IG91dGNvbWVzIGluCiAgdGhlIHN1bW1hcnkuIE5ldmVyIHJ1biBhIGJhcmUgdG9vbCBjb21tYW5kIHRoYXQgdGhlIHJlcG8gb3ZlcnJpZGVzIChlLmcuIHByZWZlciB0aGUKICByZXBvJ3MgYHR5cGVjaGVja2Agc2NyaXB0IG92ZXIgYSByYXcgYHRzY2ApLgoKIyMgT3V0cHV0IGZvcm1hdAoKT3V0cHV0IHlvdXIgcmV2aWV3IGJldHdlZW4gdGhlIGV4YWN0IG1hcmtlcnMgYmVsb3cgYXMgYSBzaW5nbGUgdmFsaWQgSlNPTiBvYmplY3QuCkRvIG5vdCB3cmFwIGluIG1hcmtkb3duIGNvZGUgZmVuY2VzLiBEbyBub3Qgb3V0cHV0IGFueXRoaW5nIGFmdGVyIEVORF9SRVZJRVdfSlNPTi4KCkJFR0lOX1JFVklFV19KU09OCnsKICAidmVyZGljdCI6ICJBUFBST1ZFIiB8ICJSRVFVRVNUX0NIQU5HRVMiIHwgIkNPTU1FTlQiLAogICJzdW1tYXJ5IjogIjItMyBzZW50ZW5jZSBwbGFpbi1FbmdsaXNoIG92ZXJ2aWV3LiBTdGF0ZSB3aGF0IHlvdSBjaGVja2VkLCB0aGUgZGV0ZWN0ZWQgc3RhY2ssIGFuZCBhbnkgdG9vbCBvdXRjb21lcy4gUmV2aWV3ZWQgU0hBOiA8c2hhPi4iLAogICJjb21tZW50cyI6IFsKICAgIHsKICAgICAgInBhdGgiOiAicmVsYXRpdmUvcGF0aC90by9GaWxlLmV4dCIsCiAgICAgICJsaW5lIjogNDIsCiAgICAgICJzZXZlcml0eSI6ICJibG9ja2luZyIgfCAic3VnZ2VzdGlvbiIgfCAibml0cGljayIsCiAgICAgICJib2R5IjogIi4uLiIKICAgIH0KICBdCn0KRU5EX1JFVklFV19KU09OCgpUaGUgYHZlcmRpY3RgIGlzIGFkdmlzb3J5IOKAlCB0aGUgd29ya2Zsb3cgYWx3YXlzIHBvc3RzIHRoZSByZXZpZXcgYXMgYSBub24tYmxvY2tpbmcgQ09NTUVOVC4KClJ1bGVzIGZvciBjb21tZW50czoKCioqcGF0aCBhbmQgbGluZSoqCi0gcGF0aCBtdXN0IGJlIGEgcmVhbCBmaWxlIHBhdGggZnJvbSB0aGUgZGlmZiAocmVsYXRpdmUgdG8gcmVwbyByb290KS4KLSBsaW5lIG11c3QgYmUgYSBsaW5lIHZpc2libGUgaW4gdGhlIGRpZmYgKGFkZGVkIG9yIGNvbnRleHQgbGluZSBvbiB0aGUgcmlnaHQgc2lkZSkuCiAgSWYgeW91IGFyZSBub3QgY2VydGFpbiB0aGUgbGluZSBpcyBpbiB0aGUgZGlmZiwgcHV0IHRoZSBmaW5kaW5nIGluIHRoZSBzdW1tYXJ5IGluc3RlYWQuCgoqKmJvZHkgZm9ybWF0Kiog4oCUIGZvbGxvdyBDb252ZW50aW9uYWwgQ29tbWVudHMgKGNvbnZlbnRpb25hbGNvbW1lbnRzLm9yZyk6CgogIDxsYWJlbD46IDxvbmUtc2VudGVuY2Ugc3ViamVjdCDigJQgd2hhdCBpcyB3cm9uZywgbmFtZWQgc3BlY2lmaWNhbGx5PgoKICA8V2h5IHRoaXMgbWF0dGVycyDigJQgMS0yIHNlbnRlbmNlcyBzdGF0aW5nIHRoZSByaXNrLCBpbnZhcmlhbnQsIG9yIHByaW5jaXBsZS4+CiAgPFdoYXQgdG8gZG8g4oCUIGEgY29uY3JldGUgYWx0ZXJuYXRpdmUgb3IgZml4LCDiiaQ4IGxpbmVzIG9mIGNvZGUgaWYgYXBwbGljYWJsZS4+CgpMYWJlbHMgKG1hdGNoIHRoZSBzZXZlcml0eSBmaWVsZCk6IGBpc3N1ZWAgKGJsb2NraW5nKSwgYHN1Z2dlc3Rpb25gIChub24tYmxvY2tpbmcKaW1wcm92ZW1lbnQpLCBgbml0cGlja2AgKG9wdGlvbmFsIHByZWZlcmVuY2UpLgoKUnVsZXMgZm9yIGJvZHkgdGV4dDoKLSBObyBlbW9qaSBhbnl3aGVyZS4KLSBOYW1lIHRoZSBzcGVjaWZpYyB2YXJpYWJsZSwgZnVuY3Rpb24sIG9yIGZpbGUg4oCUIG5ldmVyICJ0aGlzIiBvciAiaGVyZSIuCi0gQWx3YXlzIGluY2x1ZGUgYSB3aHkgc2VudGVuY2Ug4oCUIHdoYXQgYnJlYWtzLCB3aGF0IGRlZ3JhZGVzLCB3aGF0IGludmFyaWFudCBpcyB2aW9sYXRlZC4KLSBUb25lOiBjb2xsYWJvcmF0aXZlLiBVc2UgImNvbnNpZGVyIiwgImNvdWxkIiwgIm1pZ2h0Ii4gTmV2ZXIgIm11c3QiLCAid3JvbmciLCAib2J2aW91c2x5Ii4KLSBPbmUgY29uY2VybiBwZXIgY29tbWVudC4gU3BsaXQgbXVsdGlwbGUgaXNzdWVzIGludG8gc2VwYXJhdGUgY29tbWVudHMuCi0gVXNlIGFuIGVtcHR5IGFycmF5IChbXSkgaWYgdGhlcmUgYXJlIG5vIGlubGluZSBmaW5kaW5ncy4K" PR_NUMBER: ${{ steps.pr.outputs.number }} BASE_REF: ${{ steps.context.outputs.base_ref }} REVIEWED_SHA: ${{ steps.context.outputs.reviewed_sha }} @@ -518,11 +508,12 @@ jobs: prompt_file="$RUNNER_TEMP/codex-pr-review-prompt.md" review_file="$RUNNER_TEMP/codex-pr-review.md" - # Trusted prompt from this shared repo (checked out at .ci-shared), - # never from the PR under review. - shared_prompt="$GITHUB_WORKSPACE/.ci-shared/.github/codex/prompts/codex-pr-review.md" - if [ ! -f "$shared_prompt" ]; then - echo "::error::Shared prompt not found at $shared_prompt (modsy/ci-workflows)." >&2 + # Trusted prompt embedded in this workflow (PROMPT_B64), never from + # the PR under review. + shared_prompt="$RUNNER_TEMP/codex-pr-review-prompt-src.md" + printf '%s' "$PROMPT_B64" | base64 -d > "$shared_prompt" + if [ ! -s "$shared_prompt" ]; then + echo "::error::Embedded review prompt (PROMPT_B64) decoded to empty." >&2 exit 1 fi