CI에서 Codex Security 실행하기
풀 리퀘스트 및 머지 리퀘스트의 변경 사항을 스캔하고, 구조화된 결과를 보존하고, SARIF를 업로드하고, 심각도 정책을 설정합니다.
CI에서 Codex Security CLI를 실행하여 풀 리퀘스트 또는 머지 리퀘스트의 정확한 변경 사항을 검토하고, 발견 항목과 커버리지를 유지하며, 선택한 심각도에 따라 선택적으로 검사를 실패 처리할 수 있습니다. 먼저 참고용 결과로 시작하여 스캔 품질과 실행 시간을 검토한 다음, 리포지토리에 적합한 심각도 정책을 추가하세요.
이 가이드에는 GitHub Actions와 GitLab CI/CD 예제가 포함되어 있습니다. 동일한 스캔 및 내보내기 명령을 다른 CI 시스템에서도 사용할 수 있습니다.
워크플로 준비하기
CI 제공업체의 보안 비밀 저장소에 OpenAI API key를 CODEX_SECURITY_API_KEY(으)로 저장하세요.
이 보안 비밀을 스캔 단계의 OPENAI_API_KEY 환경 변수에 직접 매핑하세요. 자격 증명의 범위를 스캔 프로세스로 제한하고 --auth api-key을(를) 사용하여 명시적으로 선택하세요.
러너에는 다음 항목이 필요합니다.
- Node.js 22 이상.
- Python 3.10 이상.
- 리포지토리 체크아웃 외부에 설치된 공개
@openai/codex-security패키지. - Git이 병합 기준점을 계산할 수 있도록 풀 리퀘스트 또는 머지 리퀘스트의 헤드 및 베이스 기록.
GitHub Actions 워크플로 추가하기
비공개 또는 내부 리포지토리의 경우 SARIF를 업로드하기 전에 GitHub Code Security를 활성화하세요.
.github/workflows/codex-security.yml을(를) 만드세요. 풀 리퀘스트를 체크아웃하기 전에 신뢰할 수 있는 실행 파일을 $RUNNER_TEMP/codex-security/node_modules/.bin/codex-security에서 사용할 수 있도록 @openai/codex-security을(를) $RUNNER_TEMP/codex-security 아래에 설치하세요.
name: Codex Security scan
on:
pull_request:
jobs:
codex-security:
if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: "26"
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.14"
- name: Install Codex Security
run: |
set -euo pipefail
npm install \
--prefix "$RUNNER_TEMP/codex-security" \
--ignore-scripts \
--no-audit \
--no-fund \
@openai/codex-security
- 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
- name: Check out the pull request
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
persist-credentials: false
- name: Scan the pull request
env:
OPENAI_API_KEY: ${{ secrets.CODEX_SECURITY_API_KEY }}
CODEX_SECURITY_BIN: ${{ runner.temp }}/codex-security/node_modules/.bin/codex-security
CODEX_SECURITY_STATE_DIR: ${{ runner.temp }}/codex-security-state
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
SCAN_DIR: ${{ runner.temp }}/codex-security-results
run: |
set -euo pipefail
BASE_REVISION="$(git merge-base "$BASE_SHA" "$HEAD_SHA")"
"$CODEX_SECURITY_BIN" scan . \
--diff "$BASE_REVISION" \
--head "$HEAD_SHA" \
--auth api-key \
--output-dir "$SCAN_DIR" \
--json > "$RUNNER_TEMP/codex-security.json"
- name: Export SARIF
id: export-sarif
if: always()
env:
CODEX_SECURITY_BIN: ${{ runner.temp }}/codex-security/node_modules/.bin/codex-security
SCAN_DIR: ${{ runner.temp }}/codex-security-results
SARIF_FILE: ${{ runner.temp }}/codex-security.sarif
run: |
set -euo pipefail
if test -f "$SCAN_DIR/scan-manifest.json"; then
"$CODEX_SECURITY_BIN" export "$SCAN_DIR" \
--export-format sarif \
--source-root "$GITHUB_WORKSPACE" \
--output "$SARIF_FILE"
echo "available=true" >> "$GITHUB_OUTPUT"
fi
- name: Upload SARIF
if: always() && steps.export-sarif.outputs.available == 'true'
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
with:
sarif_file: ${{ runner.temp }}/codex-security.sarif
ref: refs/pull/${{ github.event.pull_request.number }}/head
sha: ${{ github.event.pull_request.head.sha }}
category: codex-security
- name: Preserve scan results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codex-security-results
path: |
${{ runner.temp }}/codex-security-results
${{ runner.temp }}/codex-security.json
if-no-files-found: warn
retention-days: 7워크플로는 풀 리퀘스트 헤드를 체크아웃하고 병합 기준점을 계산한 후 해당 리비전 사이에 커밋된 변경 사항을 스캔합니다. 전체 기록을 사용하면 대상을 정확하게 유지할 수 있습니다. persist-credentials: false은(는) 체크아웃된 Git 구성에 리포지토리 토큰이 포함되지 않도록 합니다. 체크아웃 전에 CLI를 설치하고 절대 경로로 실행하면 리포지토리에서 제어하는 실행 파일이 스캔 자격 증명에 접근하지 못합니다. --auth api-key은(는) 범위가 제한된 API key를 명시적으로 선택합니다. 스캔 기록은 리포지토리 외부의 쓰기 가능한 상태 디렉터리에 저장됩니다.
--json은(는) 완전한 JSON 문서 하나를 stdout에 쓰므로 워크플로에서 이를 직접 저장할 수 있습니다. 진행 상황, 완료 요약, 오류는 stderr에 계속 기록됩니다. 이는 JSON Lines 이벤트 스트림을 내보내는 codex exec --json과(와) 다릅니다.
내보내기 단계에서는 완료되고 봉인된 스캔을 읽어 SARIF를 작성합니다. Codex 런타임과 자격 증명은 변경하지 않습니다. 스캔 아티팩트에는 취약한 소스 코드 조각, 증거, 수정 세부 정보가 포함될 수 있습니다. 리포지토리에 적합한 액세스 제어와 짧은 보존 기간을 선택하세요.
GitLab CI/CD 파이프라인 추가하기
GitLab Ultimate 19.2 이상에서는 GitLab이 SARIF 2.1.0 보고서를 수집할 수 있습니다. 파이프라인을 실행하기 전에 마스킹되고 숨겨진 CODEX_SECURITY_API_KEY CI/CD 변수를 추가하세요.
루트 .gitlab-ci.yml에 security 단계와 Codex Security 작업을 추가하세요. 파일에 있는 기존 단계와 작업은 모두 유지하세요. 이 예제는 기본적으로 머지 리퀘스트 변경 사항을 스캔합니다. 기본 브랜치 전체도 스캔하려면 CODEX_SECURITY_FULL_SCAN_DEFAULT_BRANCH을(를) "true"(으)로 설정하세요.
variables:
CODEX_SECURITY_FULL_SCAN_DEFAULT_BRANCH: "false"
stages:
- test
- security
codex-security:
stage: security
image: node:26-bookworm-slim
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_PROJECT_ID == $CI_PROJECT_ID'
variables:
CODEX_SECURITY_SCAN_SCOPE: "diff"
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CODEX_SECURITY_FULL_SCAN_DEFAULT_BRANCH == "true"'
variables:
CODEX_SECURITY_SCAN_SCOPE: "full"
variables:
GIT_DEPTH: "0"
CODEX_SECURITY_CLI_DIR: "/tmp/codex-security-cli"
before_script:
- |
set -eu
apt-get update -qq
apt-get install -y -qq --no-install-recommends \
ca-certificates \
git \
python3 \
ripgrep
npm install \
--prefix "$CODEX_SECURITY_CLI_DIR" \
--ignore-scripts \
--no-audit \
--no-fund \
@openai/codex-security
export CODEX_SECURITY_BIN="$CODEX_SECURITY_CLI_DIR/node_modules/.bin/codex-security"
test -x "$CODEX_SECURITY_BIN"
"$CODEX_SECURITY_BIN" --version
script:
- |
set -eu
if test -z "${CODEX_SECURITY_API_KEY:-}"; then
echo "Set the CODEX_SECURITY_API_KEY CI/CD variable." >&2
exit 2
fi
codex_security_api_key="$CODEX_SECURITY_API_KEY"
unset CODEX_SECURITY_API_KEY
case "${CODEX_SECURITY_SCAN_SCOPE:-}" in
diff)
BASE_SHA="$CI_MERGE_REQUEST_DIFF_BASE_SHA"
HEAD_SHA="$CI_COMMIT_SHA"
BASE_REVISION="$(git merge-base "$BASE_SHA" "$HEAD_SHA")"
set -- --diff "$BASE_REVISION" --head "$HEAD_SHA"
echo "Scanning committed changes from $BASE_REVISION to $HEAD_SHA."
;;
full)
set -- --mode standard
echo "Scanning the complete default branch at $CI_COMMIT_SHA."
;;
*)
echo "Unsupported Codex Security scan scope: ${CODEX_SECURITY_SCAN_SCOPE:-unset}" >&2
exit 2
;;
esac
export CODEX_SECURITY_STATE_DIR="/tmp/codex-security-state-$CI_JOB_ID"
SCAN_DIR="/tmp/codex-security-results-$CI_JOB_ID"
JSON_FILE="/tmp/codex-security-$CI_JOB_ID.json"
SARIF_FILE="/tmp/codex-security-$CI_JOB_ID.sarif"
install -d -m 700 "$CODEX_SECURITY_STATE_DIR" "$SCAN_DIR"
set +e
OPENAI_API_KEY="$codex_security_api_key" \
"$CODEX_SECURITY_BIN" scan . \
"$@" \
--auth api-key \
--output-dir "$SCAN_DIR" \
--json > "$JSON_FILE"
scan_exit="$?"
set -e
unset codex_security_api_key
install -d -m 700 codex-security-artifacts/results
cp -R "$SCAN_DIR"/. codex-security-artifacts/results/
if test -s "$JSON_FILE"; then
cp "$JSON_FILE" codex-security-artifacts/codex-security.json
fi
printf '%s\n' "$scan_exit" > codex-security-artifacts/scan-exit-code.txt
export_exit=0
if test -f "$SCAN_DIR/scan-manifest.json"; then
set +e
"$CODEX_SECURITY_BIN" export "$SCAN_DIR" \
--export-format sarif \
--source-root "$CI_PROJECT_DIR" \
--output "$SARIF_FILE"
export_exit="$?"
set -e
if test -s "$SARIF_FILE"; then
cp "$SARIF_FILE" codex-security-artifacts/codex-security.sarif
fi
fi
if test "$scan_exit" -ne 0; then
exit "$scan_exit"
fi
exit "$export_exit"
artifacts:
when: always
access: maintainer
expire_in: 7 days
paths:
- codex-security-artifacts/
reports:
sarif: codex-security-artifacts/codex-security.sarif기본적으로 작업은 동일한 프로젝트의 브랜치에서 생성된 머지 리퀘스트에 대해서만 실행되므로 포크 파이프라인에는 스캔 자격 증명이 제공되지 않습니다. 기본 브랜치에서 표준 전체 스캔도 실행하려면 그룹, 프로젝트 또는 파이프라인 수준에서 CODEX_SECURITY_FULL_SCAN_DEFAULT_BRANCH을(를) "true"(으)로 설정하세요. 전체 스캔은 차이 스캔보다 오래 걸리고 비용도 더 많이 듭니다.
GIT_DEPTH: "0"은(는) 머지 리퀘스트 스캔에서 CI_MERGE_REQUEST_DIFF_BASE_SHA 및 CI_COMMIT_SHA을(를) 기준으로 병합 기준점을 계산하는 데 필요한 기록을 제공합니다.
작업은 /tmp 아래에 CLI를 설치하고 절대 경로로 실행하며 API key를 스캔 프로세스에만 노출합니다. artifacts: when: always은(는) 스캔이 실패해도 SARIF 보고서를 보존하고, artifacts:access: maintainer은(는) 상세 스캔 결과에 대한 액세스를 제한합니다.
.gitlab-ci.yml의 변경 사항은 CI/CD 변수를 노출할 수 있으므로 작업을 실행하기 전에 파이프라인 변경 사항을 검토하세요. CODEX_SECURITY_API_KEY을(를) 보호하면 GitLab은 보호된 브랜치 간의 동일 프로젝트 머지 리퀘스트에 대해서만, 그리고 사용자가 대상 브랜치에 액세스할 수 있을 때만 이를 제공합니다.
심각도 정책 선택하기
두 예제 모두 --fail-on-severity을(를) 생략하므로 보고만 수행합니다. 발견 항목이 검사 결과에 영향을 주도록 할 준비가 되면 스캔 명령에 임곗값을 추가하세요.
"$CODEX_SECURITY_BIN" scan . \
--diff origin/main \
--output-dir /path/outside/repository/results \
--fail-on-severity high지원되는 임곗값은 critical, high, medium, low입니다. 임곗값에는 해당 심각도 이상의 발견 항목이 포함됩니다.
스캔 단계에서는 다음 종료 코드를 사용합니다.
| 종료 | 의미 |
|---|---|
0 |
스캔이 완전한 커버리지로 완료되었으며 구성된 모든 정책을 통과했습니다. |
1 |
완료된 스캔에 임곗값 이상의 발견 항목이 포함되어 있습니다. |
2 |
CLI에서 입력 또는 런타임 오류를 발견했거나 완료된 스캔의 커버리지가 불완전합니다. |
130 |
Ctrl-C로 스캔이 중단되었습니다. |
143 |
SIGTERM으로 스캔이 종료되었습니다. |
커버리지가 partial 또는 unknown인 스캔은 심각도 정책이 없어도 2을(를) 반환합니다. CLI는 사용 가능한 발견 항목과 커버리지를 계속 기록합니다. 검사를 확정적인 결과로 간주하기 전에 coverage.json의 보류된 영역을 검토하세요.
기존 결과 디렉터리로 다시 시도하기
각 CI 작업에는 새로운 러너 디렉터리를 사용하세요. 영구 또는 자체 호스팅 러너에서는 --archive-existing을(를) 사용하여 이전 결과를 보존하세요.
"$CODEX_SECURITY_BIN" scan . \
--diff origin/main \
--output-dir /path/outside/repository/results \
--archive-existing이 명령은 이전 결과를 보관하고 빈 스캔 디렉터리에서 시작합니다.
CI 스캔 문제 해결하기
- 알 수 없는 Git 참조 또는 예상치 못한 차이: 베이스 및 헤드 기록을 가져오고 병합 기준점을 계산한 다음 두 리비전을 명시적으로 전달하세요.
- 보호되었거나 비어 있지 않은 출력 디렉터리: 이를 포함하는 Git 작업 트리 외부의 비공개 디렉터리를 선택하세요. 디렉터리에 결과가 이미 포함되어 있으면
--archive-existing을(를) 사용하세요. - 자격 증명 누락: 신뢰할 수 있는 워크플로 또는 파이프라인에서
CODEX_SECURITY_API_KEY을(를) 사용할 수 있고 스캔 프로세스의OPENAI_API_KEY환경 변수에 직접 매핑되어 있는지 확인하세요. - 스캔 기록 오류:
CODEX_SECURITY_STATE_DIR을(를) 리포지토리 외부의 쓰기 가능한 디렉터리로 설정하세요. - Python 설정 오류: 러너에서 Python 3.10 이상을 사용하는지 확인하세요.
- 불완전한 커버리지: 보류된 영역과 미해결 질문을 포함하여
coverage.json을(를) 검토한 다음 적절한 대상 또는 환경으로 다시 실행하세요. - SARIF 내보내기 오류: 스캔이 완료되었고 전체 스캔 디렉터리를 사용할 수 있는지 확인하세요. 내보내기는 SARIF를 작성하기 전에 봉인된 아티팩트의 유효성을 검사합니다.
- SARIF 업로드 오류: GitHub Actions의 경우 조직에서 리포지토리에 GitHub Code Security를 활성화했고 워크플로에
actions: read,contents: read,security-events: write권한이 부여되어 있는지 확인하세요. GitLab CI/CD의 경우 프로젝트에서 GitLab Ultimate 19.2 이상을 사용하고 작업이artifacts:reports:sarif을(를) 통해 SARIF 2.1.0 파일을 업로드하는지 확인하세요.
모든 명령, 플래그, 아티팩트, 출력 필드는 CLI 참조에서 확인하세요. 대화형 플러그인 기반 CI 검토는 보안을 위한 코드 변경 사항 검토를 참조하세요.