Files
OpenShell/.github/workflows/codex-security.yml
2026-09-21 18:51:37 +00:00

316 lines
12 KiB
YAML

# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Codex Security Release Qualification
on:
workflow_call:
inputs:
candidate_ref:
description: Pre-release tag to scan (vMAJOR.MINOR.PATCH-pre.N)
required: true
type: string
stable_ref:
description: Optional previous stable tag override
required: false
default: ""
type: string
allow_full_bootstrap:
description: Allow a full scan when no previous stable tag exists
required: false
default: false
type: boolean
fail-on-findings:
description: Fail on HIGH/CRITICAL security findings
default: false
type: boolean
upload_sarif:
description: Upload results to Code Scanning when called from a manual workflow
default: true
type: boolean
outputs:
base_sha:
description: Previous stable commit, empty for a full bootstrap scan
value: ${{ jobs.analyze.outputs.base_sha }}
candidate_sha:
description: Qualified pre-release commit
value: ${{ jobs.analyze.outputs.candidate_sha }}
category:
description: Code Scanning category for the release train
value: ${{ jobs.analyze.outputs.category }}
train:
description: Stable version targeted by the pre-release train
value: ${{ jobs.analyze.outputs.train }}
secrets:
CODEX_SECURITY_API_KEY:
required: true
workflow_dispatch:
inputs:
candidate_ref:
description: Pre-release tag to scan (vMAJOR.MINOR.PATCH-pre.N)
required: true
type: string
stable_ref:
description: Optional previous stable tag override
required: false
type: string
upload_sarif:
description: Upload manual-run results to Code Scanning
required: false
default: false
type: boolean
allow_full_bootstrap:
description: Allow a full scan when no previous stable tag exists
required: false
default: false
type: boolean
permissions:
actions: read
contents: read
security-events: write
concurrency:
group: codex-security-release-qualification
cancel-in-progress: true
env:
CODEX_SECURITY_MAX_CONCURRENT_THREADS: "8"
CODEX_SECURITY_REASONING_EFFORT: medium
NVIDIA_INFERENCE_BASE_URL: https://inference-api.nvidia.com/v1
NVIDIA_INFERENCE_MODEL: openai/openai/gpt-5.6-sol
jobs:
analyze:
name: Codex Security (${{ inputs.candidate_ref || github.ref_name }})
# The agent executes no shell commands on the repository self-hosted runner,
# so its preflight never scopes the diff and it seals no draft.
runs-on: ubuntu-latest
timeout-minutes: 120
outputs:
base_sha: ${{ steps.range.outputs.base_sha }}
candidate_sha: ${{ steps.range.outputs.candidate_sha }}
category: ${{ steps.range.outputs.category }}
train: ${{ steps.range.outputs.train }}
steps:
# Install the trusted scanner before repository-controlled files exist in
# the workspace, and invoke it later through its absolute path.
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "26"
package-manager-cache: false
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
# Codex confines model-run commands with bubblewrap, which needs
# unprivileged user namespaces. Ubuntu 24.04 restricts those through
# AppArmor, so bubblewrap cannot set up the sandbox network namespace and
# the scan agent executes nothing at all.
- name: Allow unprivileged user namespaces
run: |
set -euo pipefail
if [ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
- name: Install Codex Security
run: |
set -euo pipefail
npm install \
--prefix "$RUNNER_TEMP/codex-security" \
--ignore-scripts \
--no-audit \
--no-fund \
@openai/codex-security@0.1.24
- name: Verify Codex Security
env:
CODEX_SECURITY_BIN: ${{ runner.temp }}/codex-security/node_modules/.bin/codex-security
run: |
set -euo pipefail
test -x "$CODEX_SECURITY_BIN"
"$CODEX_SECURITY_BIN" --version
# The range resolver has to come from the workflow's own revision. A
# scanned candidate predates it, and running the resolver from the
# revision under scan would let that revision pick its own scan range.
- name: Check out the workflow revision
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: workflow-revision
sparse-checkout: tasks/scripts
persist-credentials: false
- name: Stage the range resolver
run: |
set -euo pipefail
install -d -m 700 "$RUNNER_TEMP/range-resolver"
cp workflow-revision/tasks/scripts/release.py \
workflow-revision/tasks/scripts/codex_security_range.py \
"$RUNNER_TEMP/range-resolver/"
rm -rf workflow-revision
- name: Check out the pre-release
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.candidate_ref || github.ref }}
fetch-depth: 0
persist-credentials: false
- name: Resolve release range
id: range
env:
ALLOW_FULL_BOOTSTRAP: ${{ inputs.allow_full_bootstrap || false }}
CANDIDATE_REF: ${{ inputs.candidate_ref || github.ref_name }}
STABLE_REF: ${{ inputs.stable_ref || '' }}
run: |
set -euo pipefail
git show-ref --verify --quiet refs/remotes/origin/main
args=(
--candidate "$CANDIDATE_REF"
--main-ref origin/main
)
if [ -n "$STABLE_REF" ]; then
args+=(--stable "$STABLE_REF")
fi
if [ "$ALLOW_FULL_BOOTSTRAP" = "true" ]; then
args+=(--allow-full-bootstrap)
fi
python3 "$RUNNER_TEMP/range-resolver/codex_security_range.py" "${args[@]}"
- name: Scan changes since the previous stable
env:
BASE_SHA: ${{ steps.range.outputs.base_sha }}
CODEX_SECURITY_BIN: ${{ runner.temp }}/codex-security/node_modules/.bin/codex-security
CODEX_SECURITY_STATE_DIR: ${{ runner.temp }}/codex-security-state-${{ github.run_id }}-${{ github.run_attempt }}
HEAD_SHA: ${{ steps.range.outputs.candidate_sha }}
NVIDIA_INFERENCE_API_KEY: ${{ secrets.CODEX_SECURITY_API_KEY }}
OPENAI_API_KEY: ${{ secrets.CODEX_SECURITY_API_KEY }}
SCAN_DIR: ${{ runner.temp }}/codex-security-results-${{ github.run_id }}-${{ github.run_attempt }}
SCAN_SCOPE: ${{ steps.range.outputs.scan_scope }}
run: |
set -euo pipefail
install -d -m 700 "$CODEX_SECURITY_STATE_DIR" "$SCAN_DIR"
target_args=()
if [ "$SCAN_SCOPE" = "diff" ]; then
target_args=(--diff "$BASE_SHA" --head "$HEAD_SHA")
elif [ "$SCAN_SCOPE" != "full" ]; then
echo "::error::Unsupported Codex Security scan scope: $SCAN_SCOPE"
exit 2
fi
"$CODEX_SECURITY_BIN" scan . \
"${target_args[@]}" \
--auth api-key \
--model "$NVIDIA_INFERENCE_MODEL" \
--effort "$CODEX_SECURITY_REASONING_EFFORT" \
--codex 'model_provider="nvidia"' \
--codex 'model_providers.nvidia.name="NVIDIA Inference"' \
--codex "model_providers.nvidia.base_url=\"$NVIDIA_INFERENCE_BASE_URL\"" \
--codex 'model_providers.nvidia.env_key="NVIDIA_INFERENCE_API_KEY"' \
--codex 'model_providers.nvidia.wire_api="responses"' \
--codex 'model_providers.nvidia.supports_websockets=false' \
--codex "features.multi_agent_v2.max_concurrent_threads_per_session=$CODEX_SECURITY_MAX_CONCURRENT_THREADS" \
--codex 'approval_policy="never"' \
--output-dir "$SCAN_DIR" \
--headless > /dev/null
- name: Export SARIF
env:
CODEX_SECURITY_BIN: ${{ runner.temp }}/codex-security/node_modules/.bin/codex-security
SARIF_FILE: ${{ runner.temp }}/codex-security.sarif
SCAN_DIR: ${{ runner.temp }}/codex-security-results-${{ github.run_id }}-${{ github.run_attempt }}
run: |
set -euo pipefail
"$CODEX_SECURITY_BIN" export "$SCAN_DIR" \
--export-format sarif \
--source-root "$GITHUB_WORKSPACE" \
--output "$SARIF_FILE"
- name: Summarize findings
id: findings
env:
BASE_TAG: ${{ steps.range.outputs.base_tag }}
CANDIDATE_TAG: ${{ steps.range.outputs.candidate_tag }}
COMMIT_COUNT: ${{ steps.range.outputs.commit_count }}
SARIF_FILE: ${{ runner.temp }}/codex-security.sarif
SCAN_SCOPE: ${{ steps.range.outputs.scan_scope }}
TRAIN: ${{ steps.range.outputs.train }}
FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings || false }}
run: |
set -euo pipefail
finding_count=$(jq '[.runs[].results[]] | length' "$SARIF_FILE")
high_count=$(jq '
[ .runs[].results[]
| select(all(.suppressions[]?; .status != "accepted"))
| select(.properties.severity | ascii_downcase | IN("high", "critical"))
] | length
' "$SARIF_FILE")
echo "high_count=$high_count" >> "$GITHUB_OUTPUT"
{
echo "### Codex Security release qualification"
echo
echo "- Train: \`$TRAIN\`"
echo "- Candidate: \`$CANDIDATE_TAG\`"
echo "- Maximum concurrent agent threads: $CODEX_SECURITY_MAX_CONCURRENT_THREADS"
if [ "$SCAN_SCOPE" = "diff" ]; then
echo "- Previous stable: \`$BASE_TAG\`"
echo "- Commits in cumulative diff: $COMMIT_COUNT"
else
echo "- Scope: approved full bootstrap scan"
fi
echo "- Coverage: complete"
echo "- Findings: $finding_count"
echo "- HIGH/CRITICAL: $high_count"
echo
echo "Fail on HIGH/CRITICAL: $FAIL_ON_FINDINGS"
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload SARIF to Code Scanning
if: ${{ github.event_name != 'workflow_dispatch' || inputs.upload_sarif }}
uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
sarif_file: ${{ runner.temp }}/codex-security.sarif
ref: refs/heads/main
sha: ${{ steps.range.outputs.candidate_sha }}
category: ${{ steps.range.outputs.category }}
- name: Enforce HIGH/CRITICAL threshold
if: ${{ !cancelled() && steps.findings.outcome == 'success' }}
env:
HIGH_COUNT: ${{ steps.findings.outputs.high_count }}
FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings || false }}
run: |
if [ "$HIGH_COUNT" -gt 0 ]; then
if [ "$FAIL_ON_FINDINGS" = "true" ]; then
echo "::error::Codex reported $HIGH_COUNT HIGH/CRITICAL findings."
exit 1
fi
echo "::warning::Codex reported $HIGH_COUNT HIGH/CRITICAL findings; enforcement is disabled."
fi
result:
name: ${{ inputs.fail-on-findings && 'OpenShell / Codex Security' || 'OpenShell / Codex Security (informational)' }}
if: ${{ always() }}
needs: analyze
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Evaluate scanner execution
env:
ANALYZE_RESULT: ${{ needs.analyze.result }}
run: |
set -euo pipefail
if [ "$ANALYZE_RESULT" != "success" ]; then
echo "::error::Codex Security release qualification did not complete successfully."
exit 1
fi
echo "Codex Security completed and the configured finding threshold passed."