mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-03 07:58:25 +08:00
316 lines
12 KiB
YAML
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."
|