Русский

Запуск Codex Security в CI

Сканируйте изменения в запросах на включение и слияние, сохраняйте структурированные результаты, загружайте SARIF и задавайте политику уровней серьёзности.

Запускайте Codex Security CLI в CI, чтобы проверять конкретные изменения в запросе на включение или слияние, сохранять обнаруженные проблемы и данные о покрытии, а также при необходимости завершать проверку с ошибкой при выбранном уровне серьёзности. Начните с результатов рекомендательного характера, оцените качество и продолжительность сканирования, а затем добавьте политику уровней серьёзности, подходящую вашему репозиторию.

В этом руководстве приведены примеры для GitHub Actions и GitLab CI/CD. Те же команды сканирования и экспорта работают и в других системах CI.

Подготовка рабочего процесса

Сохраните API key OpenAI в хранилище секретов поставщика CI под именем CODEX_SECURITY_API_KEY.

Напрямую сопоставьте этот секрет с переменной окружения OPENAI_API_KEY шага сканирования. Ограничьте область действия учётных данных процессом сканирования и используйте --auth api-key, чтобы явно выбрать их.

Среде выполнения необходимы:

  • Node.js 22 или более поздней версии.
  • Python 3.10 или более поздней версии.
  • Опубликованный пакет @openai/codex-security, установленный вне рабочей копии репозитория.
  • История главной и базовой ветвей запроса на включение или слияние, чтобы Git мог вычислить базу слияния.

Добавление рабочего процесса GitHub Actions

Для частных или внутренних репозиториев включите GitHub Code Security перед загрузкой SARIF.

Создайте .github/workflows/codex-security.yml. Перед получением рабочей копии запроса на включение установите @openai/codex-security в $RUNNER_TEMP/codex-security, чтобы доверенный исполняемый файл был доступен по адресу $RUNNER_TEMP/codex-security/node_modules/.bin/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 записывает в stdout один полный документ JSON, поэтому рабочий процесс может сохранить его напрямую. Ход выполнения, итоговые сводки и ошибки остаются в stderr. Это отличается от codex exec --json, который выводит поток событий JSON Lines.

Шаг экспорта считывает завершённое, запечатанное сканирование и записывает SARIF. Он не изменяет среду выполнения Codex и учётные данные. Артефакты сканирования могут содержать уязвимые фрагменты исходного кода, доказательства и сведения об устранении проблем. Выберите средства управления доступом и короткий срок хранения, подходящие для вашего репозитория.

Добавление конвейера GitLab CI/CD

GitLab может принимать отчёты SARIF 2.1.0 в GitLab Ultimate 19.2 или более поздней версии. Перед запуском конвейера добавьте замаскированную и скрытую переменную CI/CD CODEX_SECURITY_API_KEY.

Добавьте этап security и задание Codex Security в корневой файл .gitlab-ci.yml. Сохраните все существующие этапы и задания файла. По умолчанию пример сканирует изменения запроса на слияние. Задайте для 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 при сканировании запросов на слияние.

Задание устанавливает CLI в /tmp, запускает его по абсолютному пути и предоставляет 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 или более поздней версии и что задание загружает файл SARIF 2.1.0 через artifacts:reports:sarif.

Сведения обо всех командах, флагах, артефактах и полях вывода см. в справочнике CLI. Интерактивная проверка в CI на основе плагина описана в разделе Проверка изменений кода на безопасность.