Skip to content

Latest commit

 

History

History
544 lines (410 loc) · 16 KB

File metadata and controls

544 lines (410 loc) · 16 KB

GitHub and Codex Credentials Setup

  • Path: docs/setup/ubuntu/auth.md
  • Template Version: 20260508

Purpose

This document describes how to configure credentials for GitHub CLI and Codex CLI when running Codex agents through GitHub Flows.

The credentials are used by Docker-isolated agent executions.

This document covers:

  • GitHub personal access token storage;
  • GitHub App authentication via hostScript with short-lived installation tokens;
  • GitHub CLI authentication through GH_TOKEN;
  • Codex authentication through persisted Codex auth state;
  • host-side preparation of execution-scoped credentials or helper files;
  • profile configuration for passing credentials into the agent container.

This document does not describe Docker image creation, application deployment, Apache configuration, or GitHub webhook setup.

Runtime Assumptions

The runtime user is referenced as user.

The application is deployed into:

/home/user/app/github-flows/

The runtime workspace is located under:

/home/user/app/github-flows/var/work/

The Codex agent image is:

github-flows-agent-codex:latest

The long-lived GitHub token file is:

/home/user/.secrets/gh-token

The Codex auth state directory is:

/home/user/.secrets/codex/

Inside the container, only selected run inputs should appear:

/run/secrets/gh-token      execution-scoped GitHub token file or bind mount
/home/user/.codex          Codex auth state
/workspace                 per-run workspace

Before the container starts, a selected runtime profile may also create execution-scoped files on the host through hostScript. Those files should be mounted only into the run that needs them.

GitHub Account Access

The GitHub account used by the agent must have access to the target repository.

For a private organization repository, organization membership alone may not be enough. The account must have repository access through direct repository permissions, team membership, or another approved organization access policy.

For a Codex agent that reads issues, modifies files, pushes branches, and opens pull requests, the GitHub account typically needs write access to the target repository.

GitHub Token

Create a fine-grained personal access token for the GitHub account used by the agent.

Recommended repository access:

Only selected repositories:
  owner/repository

Recommended repository permissions:

Metadata: Read
Contents: Read and write
Issues: Read and write
Pull requests: Read and write

If the repository belongs to an organization, the token may require organization owner approval before it can access private organization resources.

For organization approval, use the organization settings:

Organization
  -> Settings
  -> Personal access tokens
  -> Pending requests
  -> select the token request
  -> Approve

Store the GitHub Token

Run under the runtime user:

sudo -iu user

Create the secrets directory:

mkdir -p /home/user/.secrets
chmod 700 /home/user/.secrets

Create the token file:

nano /home/user/.secrets/gh-token
chmod 600 /home/user/.secrets/gh-token

The file must contain only the raw GitHub token value.

This host-local secret is a source input for host-side preparation. It should not be copied into the runtime workspace as a durable file.

Check that the runtime user can read the token:

test -r /home/user/.secrets/gh-token && echo gh-token-ok

Verify GitHub Token In A Selected Container Run

Check the GitHub identity exposed by the token:

docker run --rm \
  --mount type=bind,src=/home/user/.secrets/gh-token,dst=/run/secrets/gh-token,readonly \
  -e GH_TOKEN_FILE=/run/secrets/gh-token \
  github-flows-agent-codex:latest \
  bash -lc 'export GH_TOKEN="$(tr -d "\r\n" < "$GH_TOKEN_FILE")"; export GITHUB_TOKEN="$GH_TOKEN"; gh api user --jq .login'

Check repository access:

docker run --rm \
  --mount type=bind,src=/home/user/.secrets/gh-token,dst=/run/secrets/gh-token,readonly \
  -e GH_TOKEN_FILE=/run/secrets/gh-token \
  github-flows-agent-codex:latest \
  bash -lc 'export GH_TOKEN="$(tr -d "\r\n" < "$GH_TOKEN_FILE")"; export GITHUB_TOKEN="$GH_TOKEN"; gh repo view owner/repository'

The tr -d "\r\n" command removes line endings from the token file before exporting the token.

Preparation Model

Use this split when preparing credentials for a run:

  • hostScript keeps long-lived secrets on the host, derives any temporary execution-scoped files, and decides which files are mounted into the selected container;
  • setupScript consumes only the already-mounted run inputs from inside the container.

Examples of execution-scoped artifacts:

  • a short-lived token file derived from a host-local source secret;
  • a temporary context snapshot prepared for one run;
  • a run-local helper file needed only by the selected profile.

Remove such artifacts after the run. Do not promote them into long-lived host storage or public inspection paths.

GitHub App Authentication via hostScript

Instead of a long-lived personal access token, you can use a GitHub App to generate short-lived installation tokens for each agent run. The token is produced by hostScript before the container starts and written into the per-run workspace, making it available inside the container without any extra Docker mount.

Benefits

  • Tokens expire after 1 hour (vs. static PAT that never expires).
  • Each token is scoped to one installation — no user-bound PAT to rotate.
  • The App private key is never mounted into the container.
  • Works with parallel agent executions — each run gets its own isolated token file inside its own workspace.

Create a GitHub App

  1. Go to GitHub Settings → Developer settings → GitHub Apps → New GitHub App.
  2. Give it a name, e.g. github-flows-agent.
  3. Disable webhook (the app is only used for token generation).
  4. Set Permissions:
    • Contents: Read and write
    • Issues: Read and write
    • Pull requests: Read and write
    • Metadata: Read
  5. Set Where can this GitHub App be installed?Any account.
  6. Click Create GitHub App.
  7. On the app page, scroll to Private keysGenerate a private key. A .pem file is downloaded. Keep it secure.

Install the App

  1. Go to the GitHub App page → Install App → select the target account or organization.
  2. Choose Only selected repositories and pick the repository where agents will operate.
  3. Click Install.
  4. Note the Installation ID — it appears in the URL after installation: https://github.com/settings/installations/<INSTALLATION_ID>.

Store the Private Key

sudo -iu user

mkdir -p /home/user/.secrets
chmod 700 /home/user/.secrets

# Upload the downloaded PEM file
mv ~/github-flows-agent.2025-01-01.private-key.pem /home/user/.secrets/github-app.pem
chmod 600 /home/user/.secrets/github-app.pem

Configure the Profile

Add hostScript and App-related env vars to the profile runtime section. The hostScript calls bin/gh-app-token.sh (shipped with the application) which generates the token. The GH_TOKEN_FILE is set to a relative path so the token lands inside the per-run workspace.

{
  "execution": {
    "runtime": {
      "hostScript": "/home/user/app/github-flows/bin/gh-app-token.sh",
      "env": {
        "LOG_LEVEL": "info",
        "GH_TOKEN_FILE": ".gh-token",
        "GH_APP_ID": "123456",
        "GH_APP_INSTALLATION_ID": "789012",
        "GH_APP_KEY_FILE": "/home/user/.secrets/github-app.pem"
      },
      "dockerArgs": [
        "--mount",
        "type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex"
      ]
    }
  }
}

Note: the gh-token Docker mount is no longer needed — the token file lives inside the workspace which is already mounted as /workspace.

The setupScript reads the token from the workspace-relative path:

export GH_TOKEN="$(tr -d '\r\n' < "$GH_TOKEN_FILE")"
export GITHUB_TOKEN="$GH_TOKEN"

Pipeline Summary

GitHub Webhook Event
  → runtime selects profile
  → workspacePath created at <workspaceRoot>/ws/<owner>/<repo>/<event>/<id>/
  → hostScript: bin/gh-app-token.sh
      → reads GH_APP_KEY_FILE, GH_APP_ID, GH_APP_INSTALLATION_ID
      → builds JWT (RS256, 10 min validity)
      → POST /app/installations/{id}/access_tokens
      → writes token to workspacePath/.gh-token
  → Docker: mount workspacePath → /workspace
  → setupScript: reads /workspace/.gh-token
  → codex runs with short-lived GH_TOKEN

After the run, the workspace (including the token file) is cleaned up by the runtime.

Codex Authentication

Codex CLI supports authentication through a ChatGPT account or through an OpenAI API key.

For subscription-based Codex use, authenticate Codex through ChatGPT sign-in and persist the Codex auth state outside the container image.

Create the Codex auth directory:

sudo -iu user

mkdir -p /home/user/.secrets/codex
chmod 700 /home/user/.secrets/codex

Run Codex login interactively:

docker run --rm -it \
  --name github-flows-codex-login \
  --mount type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex \
  --mount type=bind,src=/home/user/app/github-flows/var/work,dst=/workspace \
  -w /workspace \
  github-flows-agent-codex:latest \
  codex login

Complete the browser or device authentication flow shown by Codex.

After login, check that Codex auth state was written:

find /home/user/.secrets/codex -maxdepth 3 -type f -ls

Choose and Persist Codex Model

Run Codex interactively with the persisted auth state:

docker run --rm -it \
  --name github-flows-codex-interactive \
  --mount type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex \
  --mount type=bind,src=/home/user/app/github-flows/var/work,dst=/workspace \
  -w /workspace \
  github-flows-agent-codex:latest \
  codex

Inside Codex, select the model:

/model

Check the active session settings:

/status

The selected model and Codex settings are stored in the mounted Codex auth/config directory.

Profile Credential Configuration

A profile can mount both long-lived credential sources into the container:

{
  "runtime": {
    "env": {
      "LOG_LEVEL": "info",
      "GH_TOKEN_FILE": "/run/secrets/gh-token"
    },
    "dockerArgs": [
      "--mount",
      "type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex",
      "--mount",
      "type=bind,src=/home/user/.secrets/gh-token,dst=/run/secrets/gh-token,readonly"
    ]
  }
}

This does not automatically create GH_TOKEN.

The host-side preparation step is responsible for deciding what GH_TOKEN_FILE points to for the selected run. The container command must then read GH_TOKEN_FILE and export GH_TOKEN before starting Codex.

In the newer runtime model, prefer splitting host-side and container-side work:

  • hostScript prepares execution-scoped artifacts on the host, such as a copied token file under a run-specific temporary directory;
  • setupScript performs lightweight container checks after the mounts are in place;
  • the execution command exports GH_TOKEN and starts Codex.

Profile Example

This profile launches Codex through bash -lc, uses hostScript to prepare an execution-scoped token file on the host, uses setupScript for an in-container sanity check, then reads the mounted token and starts Codex. The bind-mount src value remains a host path, even though the destination is inside the container.

{
  "trigger": {
    "repository": "owner/repository",
    "event": "issue_comment",
    "action": "created"
  },
  "execution": {
    "handler": {
      "type": "agent",
      "command": ["bash", "-lc"],
      "args": [
        "export GH_TOKEN=\"$(tr -d '\\r\\n' < \"$GH_TOKEN_FILE\")\"; export GITHUB_TOKEN=\"$GH_TOKEN\"; exec codex exec --dangerously-bypass-approvals-and-sandbox -C /workspace/repo"
      ],
      "promptRef": "prompt.md",
      "promptVariables": {
        "REPOSITORY": "event.repository.full_name",
        "ISSUE_NUMBER": "event.issue.number",
        "ISSUE_TITLE": "event.issue.title",
        "ISSUE_BODY": "event.issue.body",
        "ISSUE_AUTHOR": "event.issue.user.login"
      }
    },
    "runtime": {
      "image": "github-flows-agent-codex:latest",
      "hostScript": "set -euo pipefail; exec_root=\"/home/user/app/github-flows/var/work/tmp/gh-auth/${EVENT_ID}\"; rm -rf \"$exec_root\"; mkdir -p \"$exec_root\"; install -m 600 /home/user/.secrets/gh-token \"$exec_root/gh-token\"",
      "setupScript": "test -d repo && test -r /run/secrets/gh-token",
      "timeoutSec": 1800,
      "env": {
        "LOG_LEVEL": "info",
        "GH_TOKEN_FILE": "/run/secrets/gh-token"
      },
      "dockerArgs": [
        "--mount",
        "type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex",
        "--mount",
        "type=bind,src=/home/user/app/github-flows/var/work/tmp/gh-auth/${EVENT_ID}/gh-token,dst=/run/secrets/gh-token,readonly"
      ]
    }
  }
}

The exact placeholder syntax for values such as ${EVENT_ID} depends on the runtime package version and its profile-templating model. The important repository-level rule is the split of responsibilities:

  • hostScript prepares a run-specific file on the host;
  • setupScript validates the mounted result in the container;
  • the long-lived source secret stays outside the workspace-visible runtime state.

Manual Container Check

Prepare a test workspace:

mkdir -p /home/user/app/github-flows/var/work/auth-test

Run a container with both GitHub and Codex credentials mounted:

docker run --rm -it \
  --name github-flows-auth-test \
  --mount type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex \
  --mount type=bind,src=/home/user/.secrets/gh-token,dst=/run/secrets/gh-token,readonly \
  --mount type=bind,src=/home/user/app/github-flows/var/work/auth-test,dst=/workspace \
  -e GH_TOKEN_FILE=/run/secrets/gh-token \
  -w /workspace \
  github-flows-agent-codex:latest \
  bash -lc 'export GH_TOKEN="$(tr -d "\r\n" < "$GH_TOKEN_FILE")"; export GITHUB_TOKEN="$GH_TOKEN"; gh api user --jq .login && codex --help'

Security Rules

Do not mount the runtime user home directory:

/home/user

Do not mount SSH configuration:

/home/user/.ssh/

Do not mount the full user configuration directory:

/home/user/.config/

Do not mount the application .env file into the agent workspace:

/home/user/app/github-flows/.env

Do not mount the Docker socket:

/var/run/docker.sock

Do not place credentials inside:

/home/user/app/github-flows/var/work/

The runtime workspace may be visible through logs or debugging tools. It must not contain long-lived credentials.

If host-side preparation creates execution-scoped files for a selected run, clean them up after the run completes and keep them out of long-lived indexed or public paths.

If hostScript creates a temporary credential file under a run-specific workspace directory, remove it after the run and do not reuse it across executions.

Result

After this setup:

  • the GitHub token is stored in /home/user/.secrets/gh-token;
  • the GitHub token file is readable only by the runtime user;
  • host-side preparation may derive temporary execution-scoped token files from that host-local source when a selected run needs them;
  • the Codex auth state is stored in /home/user/.secrets/codex/;
  • GitHub CLI receives GH_TOKEN and GITHUB_TOKEN from the mounted token file;
  • Codex CLI uses the mounted Codex auth state from /home/user/.codex;
  • a selected profile may create an execution-scoped token copy for one run without moving the long-lived source secret into the workspace;
  • profile execution can authenticate to GitHub without mounting the host home directory;
  • profile execution can authenticate Codex without storing Codex credentials in the image;
  • credentials are mounted into the container only at runtime;
  • execution-scoped artifacts should be removed after the run;
  • the per-run workspace remains separated from long-lived credentials.