# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 name: Codex Security Release Qualification on: push: tags: - "v*.*.*-pre.*" 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."