Codex in CI is useful exactly to the extent its rights are granted narrowly, and OpenAI accounts for this in the tool itself. For GitHub Actions the official openai/codex-action is recommended instead of manually installing the CLI and passing the API key to the whole job. The difference is not cosmetic: the action runs a proxy and gives a separate safety strategy, whereas a manual install with a job-wide key opens the secret to everything that runs in the job. The right tool here is part of the right boundary.
The main thing in CI is not what the agent can do but what it is allowed to do. Least privilege here is not a recommendation but a condition of security: each extra right repeats on every run and multiplies the cost of a mistake. It helps to see a whole minimal workflow once. Below is a pull request review with permissions of contents: read, checkout with persist-credentials: false, a call to openai/codex-action and the key only from GitHub Secrets. It does exactly what is needed for review and nothing beyond.
Secret isolation is the first principle of CI configuration. The key is kept only in GitHub Secrets and given through the action, not set at the whole-job level. The reason is concrete: if the job runs scripts controlled by the repository - dependency hooks, build steps - then a secret available to the whole job can be read by untrusted or compromised code. The proxy the action raises serves exactly to let the agent work with the model without handing the key to the whole run environment.
Permissions are granted strictly for the required behavior, not with a reserve. A review is satisfied with contents: read - it does not need to write. Commenting on a pull request will need pull-requests: write; committing and pushing - contents: write. Granting write "just in case" is not allowed: it opens to the agent the ability to change the protected where the task does not require it. Each right in the workflow answers a concrete need, and if there is no need there is no right, because in CI an extra right is costly.
A separate caution is fork PRs and untrusted code. GitHub has its own secret model for forks, and running a privileged workflow on a pull request from a fork without a separate design is dangerous: untrusted code must not get access to your secrets. The trigger is configured so that code from a fork does not end up with privileged rights. This is not paranoia but a direct consequence of the fact that a fork PR is code written by someone outside, and it must be treated accordingly.
Patches and output are another boundary easy to forget. The run's result - be it a patch, a report or a comment - must not contain secrets and must have a clear path of human review before application. An automatically generated patch is reviewed like any other code: CI generates a proposal, while the merge decision stays with a human. Least privilege concerns what the run produces outward too, not only what it can read.
The point of CI discipline is that a rights error here is costlier than anywhere, because it repeats automatically. An extra right granted once fires on every run, not once. So the CI configuration is reviewed like code on every change: whether the permissions expanded, where the action and its version come from, whether the secret is isolated, whether there are limits. The official action, minimal rights and a human at the merge are not over-insurance but basic automation hygiene.
The typical CI failures are predictable and costly. Installing the CLI manually and setting the API key for the whole job instead of the action with isolation. Granting contents: write "just in case" and letting the agent push to a protected branch. Running a privileged workflow on a fork PR with no separate security model. And applying a generated patch without human review. Use the official action, grant minimal permissions, isolate the secret, handle forks carefully and keep a human at the merge.
name: Codex review
on:
pull_request:
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read # the minimum for review
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: openai/codex-action@v1
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
prompt: |
Review this pull request for correctness risks. Do not modify files.