从写代码,到创作下一幕

探索 字节跳动 - 火山方舟 的 AI 编程与视频创作活动。

Agent Plan & Coding Plan

一站体验多款热门模型,为 AI 编程与智能体开发提供更多选择。新用户可联系(微信: goo_lvyouyou)免费体验 9.9 agent plan。

Seedance 2.5

让创意,跃然成片。探索 30 秒视频、多模态参考与局部编辑,把脑海中的画面变成下一支作品。

中文

在 CI 中运行 Codex Security

扫描 PR 和合并请求中的更改,保留结构化结果,上传 SARIF,并设置严重性策略。

在 CI 中运行 Codex Security CLI,以评审 PR 或合并请求中的确切更改、保留发现和覆盖范围,并可选择在达到指定严重性时使检查失败。建议先使用仅供参考的结果,评估扫描质量和运行时间,然后添加适合你仓库的严重性策略。

本指南包含 GitHub Actions 和 GitLab CI/CD 示例。同样的扫描和导出命令也适用于其他 CI 系统。

准备工作流

将 OpenAI API key 作为 CODEX_SECURITY_API_KEY 存储在 CI 提供商的密钥存储中。

将此密钥直接映射到扫描步骤的 OPENAI_API_KEY 环境 变量。将凭据的作用域限制在扫描进程内,并使用 --auth api-key 显式选择该凭据。

仅对你信任的仓库和 PR 运行工作流。扫描会使用 运行器的本地权限,且不会暂停以等待批准。扫描进程 可能继承作业环境,因此不要在其中放置无关的令牌和云服务 凭据。

运行器需要:

  • Node.js 22(22.13.0 或更高版本)、24 或 26。
  • Python 3.10 或更高版本。
  • 已发布的 @openai/codex-security 软件包,且安装在 仓库检出目录之外。
  • PR 或合并请求的头部和基准历史记录,以便 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

该工作流会检出 PR 头部,计算其合并基准,并 扫描这些修订版本之间已提交的更改。完整历史记录可确保 目标准确。persist-credentials: false 可避免仓库令牌进入 已检出的 Git 配置。在检出前安装 CLI 并 通过绝对路径运行它,可防止仓库控制的可执行文件接触 扫描凭据。--auth api-key 会显式选择限定作用域的 API key。 扫描会将历史记录保存在仓库之外可写的状态目录中。

--json 会向 stdout 写入一份完整的 JSON 文档,因此工作流可以直接 保存它。进度、完成摘要和错误仍会写入 stderr。这 不同于 codex exec --json,后者会发出 JSON Lines 事件流。

导出步骤会读取已完成且已封存的扫描并写入 SARIF。它不会改动 Codex 运行时和凭据。扫描工件可能包含易受攻击的 源代码片段、证据和修复详情。请根据你的仓库选择适当的访问控制和 较短的保留期限。

添加 GitLab CI/CD 流水线

如需包含受保护默认分支扫描、可选择启用的定时 深度扫描、独立的 SARIF 策略门控以及可选的已验证草稿合并 请求的生产工作流,请参阅在 GitLab CI/CD 中运行 Codex Security

GitLab Ultimate 19.2 或更高版本可以接收 SARIF 2.1.0 报告。 运行流水线前,请添加一个经过掩码处理且隐藏的 CODEX_SECURITY_API_KEY CI/CD 变量。

以下最小示例会向根目录的 .gitlab-ci.yml 添加一个仅扫描的 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@0.1.20
      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

默认情况下,该作业仅针对同一项目内分支发起的合并请求运行, 因此来自 fork 仓库的流水线不会收到扫描凭据。在组、项目或 流水线层级将 CODEX_SECURITY_FULL_SCAN_DEFAULT_BRANCH 设置为 "true", 还可在默认分支上运行标准完整扫描。完整 扫描比差异扫描耗时更长、成本更高。

GIT_DEPTH: "0" 提供从 CI_MERGE_REQUEST_DIFF_BASE_SHACI_COMMIT_SHA 计算合并请求扫描所需合并基准的历史记录。

该作业会将 CLI 安装到 /tmp 下,通过绝对路径运行它,并仅向 扫描进程提供 API key。artifacts: when: always 会在扫描失败时保留 SARIF 报告,而 artifacts:access: maintainer 会限制对详细扫描结果的访问。

.gitlab-ci.yml 的更改可能暴露 CI/CD 变量,因此请在运行作业前 评审流水线更改。如果你 保护 CODEX_SECURITY_API_KEY, GitLab 只会为受保护分支之间、同一项目内的合并请求提供该变量, 并且仅限能够访问目标分支的用户使用。

专门的 GitLab 指南将此最小作业扩展为本节开头所链接的 生产工作流。

选择严重性策略

两个示例都省略了 --fail-on-severity,因此仅生成报告。当你 准备让发现影响检查结果时,请向扫描 命令添加阈值:

"$CODEX_SECURITY_BIN" scan . \
  --diff origin/main \
  --output-dir /path/outside/repository/results \
  --fail-on-severity high

支持的阈值为 criticalhighmediumlow。 阈值涵盖当前扫描中处于该严重性及以上级别的发现。 仓库摘要中显示的早期未解决发现不会影响该策略。

扫描步骤使用以下退出代码:

退出代码 含义
0 扫描以完整覆盖范围完成,且所有已配置的策略均通过。
1 已完成的扫描包含严重性达到或超过阈值的发现。
2 CLI 遇到输入或运行时错误,或者已完成扫描的覆盖范围不完整。
130 Ctrl-C 中断了扫描。
143 SIGTERM 终止了扫描。

覆盖范围为 partialunknown 的扫描会返回 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: readcontents: readsecurity-events: write。对于 GitLab CI/CD,请确认项目使用 GitLab Ultimate 19.2 或更高版本,并且 作业通过 artifacts:reports:sarif 上传 SARIF 2.1.0 文件。

有关每个命令、标志、工件和输出字段的信息,请参阅 CLI 参考。有关基于插件的交互式 CI 评审,请参阅评审代码更改中的安全问题