Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ jobs:
ref: refs/pull/${{ github.event.pull_request.number }}/merge

- name: Pre-fetch base and head refs for the PR
env:
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
# Pass GitHub expressions through env and quote shell expansions.
git fetch --no-tags origin \
${{ github.event.pull_request.base.ref }} \
+refs/pull/${{ github.event.pull_request.number }}/head
"$PR_BASE_REF" \
"+refs/pull/$PR_NUMBER/head"

# If you want Codex to build and run code, install any dependencies that
# need to be downloaded before the "Run Codex" step because Codex's
Expand Down
97 changes: 64 additions & 33 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ runs:
- name: Validate Windows safety strategy
if: ${{ runner.os == 'Windows' }}
shell: bash
env:
SAFETY_STRATEGY: ${{ inputs['safety-strategy'] }}
run: |
if [ "${{ inputs['safety-strategy'] }}" != "unsafe" ]; then
if [ "$SAFETY_STRATEGY" != "unsafe" ]; then
echo "On Windows, inputs['safety-strategy'] must be 'unsafe'" >&2
echo "because no viable sandboxing options are available at this time." >&2
exit 1
Expand All @@ -123,46 +125,63 @@ runs:

- name: Check repository write access
env:
ACTION_PATH: ${{ github.action_path }}
GITHUB_TOKEN: ${{ github.token }}
ALLOW_BOTS: ${{ inputs['allow-bots'] }}
ALLOW_USERS: ${{ inputs['allow-users'] }}
shell: bash
run: |
node "${{ github.action_path }}/dist/main.js" check-write-access \
--allow-bots "${{ inputs['allow-bots'] }}" \
--allow-users "${{ inputs['allow-users'] }}"
node "$ACTION_PATH/dist/main.js" check-write-access \
--allow-bots "$ALLOW_BOTS" \
--allow-users "$ALLOW_USERS"

- name: Install Codex CLI
shell: bash
run: npm install -g "@openai/codex@${{ inputs['codex-version'] }}"
env:
CODEX_VERSION: ${{ inputs['codex-version'] }}
run: npm install -g "@openai/codex@${CODEX_VERSION}"

- name: Install Codex Responses API proxy
shell: bash
run: npm install -g "@openai/codex-responses-api-proxy@${{ inputs['codex-version'] }}"
env:
CODEX_VERSION: ${{ inputs['codex-version'] }}
run: npm install -g "@openai/codex-responses-api-proxy@${CODEX_VERSION}"

- name: Resolve Codex home
id: resolve_home
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
CODEX_HOME_OVERRIDE: ${{ inputs['codex-home'] }}
SAFETY_STRATEGY: ${{ inputs['safety-strategy'] }}
CODEX_USER: ${{ inputs['codex-user'] }}
CODEX_RUN_ID: ${{ github.run_id }}
run: |
node "${{ github.action_path }}/dist/main.js" resolve-codex-home \
--codex-home-override "${{ inputs['codex-home'] }}" \
--safety-strategy "${{ inputs['safety-strategy'] }}" \
--codex-user "${{ inputs['codex-user'] }}" \
--github-run-id "${{ github.run_id }}"
node "$ACTION_PATH/dist/main.js" resolve-codex-home \
--codex-home-override "$CODEX_HOME_OVERRIDE" \
--safety-strategy "$SAFETY_STRATEGY" \
--codex-user "$CODEX_USER" \
--github-run-id "$CODEX_RUN_ID"

- name: Determine server info path
id: derive_server_info
shell: bash
env:
CODEX_HOME: ${{ steps.resolve_home.outputs.codex-home }}
CODEX_RUN_ID: ${{ github.run_id }}
run: |
server_info_file="${{ steps.resolve_home.outputs.codex-home }}/${{ github.run_id }}.json"
server_info_file="$CODEX_HOME/$CODEX_RUN_ID.json"
echo "server_info_file=$server_info_file" >> "$GITHUB_OUTPUT"

- name: Check Responses API proxy status
id: start_proxy
if: ${{ inputs['openai-api-key'] != '' }}
shell: bash
env:
SERVER_INFO_FILE: ${{ steps.derive_server_info.outputs.server_info_file }}
run: |
server_info_file="${{ steps.derive_server_info.outputs.server_info_file }}"
if [ -s "$server_info_file" ]; then
echo "Responses API proxy already appears to be running (found $server_info_file)."
if [ -s "$SERVER_INFO_FILE" ]; then
echo "Responses API proxy already appears to be running (found $SERVER_INFO_FILE)."
echo "server_info_file_exists=true" >> "$GITHUB_OUTPUT"
else
echo "server_info_file_exists=false" >> "$GITHUB_OUTPUT"
Expand All @@ -175,19 +194,19 @@ runs:
- name: Start Responses API proxy
if: ${{ inputs['openai-api-key'] != '' && steps.start_proxy.outputs.server_info_file_exists == 'false' }}
env:
SERVER_INFO_FILE: ${{ steps.derive_server_info.outputs.server_info_file }}
PROXY_API_KEY: ${{ inputs['openai-api-key'] }}
UPSTREAM_URL: ${{ inputs['responses-api-endpoint'] }}
shell: bash
run: |
upstream_url="${{ inputs['responses-api-endpoint'] }}"

args=(
codex-responses-api-proxy
--http-shutdown
--server-info "${{ steps.derive_server_info.outputs.server_info_file }}"
--server-info "$SERVER_INFO_FILE"
)

if [ -n "$upstream_url" ]; then
args+=(--upstream-url "$upstream_url")
if [ -n "$UPSTREAM_URL" ]; then
args+=(--upstream-url "$UPSTREAM_URL")
fi

(
Expand All @@ -197,50 +216,61 @@ runs:
- name: Wait for Responses API proxy
if: ${{ inputs['openai-api-key'] != '' && steps.start_proxy.outputs.server_info_file_exists == 'false' }}
shell: bash
env:
SERVER_INFO_FILE: ${{ steps.derive_server_info.outputs.server_info_file }}
run: |
server_info_file="${{ steps.derive_server_info.outputs.server_info_file }}"
for _ in {1..10}; do
if [ -s "$server_info_file" ]; then
if [ -s "$SERVER_INFO_FILE" ]; then
break
fi
sleep 1
done
if [ ! -s "$server_info_file" ]; then
if [ ! -s "$SERVER_INFO_FILE" ]; then
echo "responses-api-proxy did not write server info" >&2
exit 1
fi

if [ "${RUNNER_OS}" != "Windows" ]; then
sudo chmod 444 "$server_info_file"
sudo chown root "$server_info_file"
sudo chmod 444 "$SERVER_INFO_FILE"
sudo chown root "$SERVER_INFO_FILE"
fi

# This step has an output named `port`.
- name: Read server info
id: read_server_info
if: ${{ inputs['openai-api-key'] != '' || inputs.prompt != '' || inputs['prompt-file'] != '' }}
shell: bash
run: node "${{ github.action_path }}/dist/main.js" read-server-info "${{ steps.derive_server_info.outputs.server_info_file }}"
env:
ACTION_PATH: ${{ github.action_path }}
SERVER_INFO_FILE: ${{ steps.derive_server_info.outputs.server_info_file }}
run: node "$ACTION_PATH/dist/main.js" read-server-info "$SERVER_INFO_FILE"

- name: Write Codex proxy config
if: ${{ inputs['openai-api-key'] != '' }}
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
CODEX_HOME: ${{ steps.resolve_home.outputs.codex-home }}
PROXY_PORT: ${{ steps.read_server_info.outputs.port }}
SAFETY_STRATEGY: ${{ inputs['safety-strategy'] }}
run: |
node "${{ github.action_path }}/dist/main.js" write-proxy-config \
--codex-home "${{ steps.resolve_home.outputs.codex-home }}" \
--port "${{ steps.read_server_info.outputs.port }}" \
--safety-strategy "${{ inputs['safety-strategy'] }}"
node "$ACTION_PATH/dist/main.js" write-proxy-config \
--codex-home "$CODEX_HOME" \
--port "$PROXY_PORT" \
--safety-strategy "$SAFETY_STRATEGY"

- name: Drop sudo privilege, if appropriate
if: ${{ inputs['safety-strategy'] == 'drop-sudo' && inputs['openai-api-key'] != '' }}
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
run: |
case "${RUNNER_OS}" in
Linux)
node "${{ github.action_path }}/dist/main.js" drop-sudo --user runner --group sudo
node "$ACTION_PATH/dist/main.js" drop-sudo --user runner --group sudo
;;
macOS)
node "${{ github.action_path }}/dist/main.js" drop-sudo --user runner --group admin
node "$ACTION_PATH/dist/main.js" drop-sudo --user runner --group admin
;;
*)
echo "Unsupported OS for drop-sudo: ${RUNNER_OS}" >&2
Expand Down Expand Up @@ -275,10 +305,11 @@ runs:
CODEX_EFFORT: ${{ inputs.effort }}
CODEX_SAFETY_STRATEGY: ${{ inputs['safety-strategy'] }}
CODEX_USER: ${{ inputs['codex-user'] }}
ACTION_PATH: ${{ github.action_path }}
FORCE_COLOR: 1
shell: bash
run: |
node "${{ github.action_path }}/dist/main.js" run-codex-exec \
node "$ACTION_PATH/dist/main.js" run-codex-exec \
--prompt "${CODEX_PROMPT}" \
--prompt-file "${CODEX_PROMPT_FILE}" \
--output-file "$CODEX_OUTPUT_FILE" \
Expand Down
14 changes: 14 additions & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ There is a lot of valuable context that can be used to fuel your invocation of C
- **Commit messages**: a pull request can be composed of many commits. The messages for individual commits often go unnoticed, but could read by Codex.
- **Screenshots** screenshots and other media have been known to be used as vehicles for prompt injection.

## Avoid shell injection in workflow steps

GitHub Actions expands `${{ ... }}` expressions before the shell runs your `run:` script. If you splice untrusted values such as branch names, issue titles, comment bodies, or action inputs directly into the script, those values can break shell quoting and execute arbitrary commands.

Instead, pass those values through `env:` and quote the shell variables that consume them:

```yaml
- name: Safe shell usage
env:
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
git fetch origin "$PR_BASE_REF"
```

<!-- TODO ## Protecting secrets -->

## Look out for API key abuse
Expand Down
4 changes: 3 additions & 1 deletion examples/test-sandbox-protections.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ jobs:
safety-strategy: ${{ matrix.safety-strategy }}

- name: Try to dump the key from the codex-responses-api-proxy process
env:
CODEX_RUN_ID: ${{ github.run_id }}
run: |
# Find the PID for the codex-responses-api-proxy process.
SERVER_INFO_FILE="$HOME/.codex/${{ github.run_id }}.json"
SERVER_INFO_FILE="$HOME/.codex/$CODEX_RUN_ID.json"
PID=$(jq .pid < "$SERVER_INFO_FILE")

# Using standard filesystem read operations (albeit privileged ones),
Expand Down
Loading