本文へ移動
🛡️

Codex Security CLIを見つけたとき、最初に迷うのは「既存のCodex Security pluginと何が違うのか」「CIに入れてよいのか」「exit code 1や2をどう扱うのか」です。security scanは導入しただけでは安全になりません。むしろ、coverageが不完全なのにgreen扱いする、CI runnerのsecretをscan processへ渡しすぎる、SARIFだけ保存して判断根拠を失う、といった事故が起きやすくなります。

この記事では、Codex Security CLIをterminalとCIのsecurity gateとして使う設計に限定します。Codex内でSecurity plugin skillを呼び、review laneを分ける話は Codex Security plugin 実践ガイド が担当します。CIでcodex execを安全に回す一般論は Codex CI / non-interactive 設計ガイド、Codex自体の実行権限は Codex sandbox / approvals 設計ガイド、scan結果をmerge前に受け入れる判断は Codex レビュー完了ゲート設計ガイド に分けます。

今回の論点は、local scan、PR差分scan、SARIF export、severity policy、coverage、bulk scanを、同じ「セキュリティ確認」という言葉へ潰さないことです。

先に結論: CLIはscanの実行面、pluginはCodex内のreview lane

Codex Security CLIとCodex Security pluginは近い名前ですが、運用上の入口が違います。

目的向いている入口成果物注意点
手元でrepositoryをscanするCodex Security CLIreport.md、findings.json、coverage.jsonlocal OS権限と環境変数を継承しうる
PR差分をCIで確認するCodex Security CLIJSON、SARIF、coverage、exit code最初はadvisory運用にする
Codex作業中にsecurity laneを呼ぶCodex Security pluginplugin workflowの結果terminal / CI gateとは分ける
通常のPR reviewerを回すCodex code reviewreview commentsecurity scanの代替にしない

私なら、CLIは「scanを再現可能な成果物として残す道具」、pluginは「Codex内のsecurity review laneを明示する道具」として分けます。この分け方にすると、CI gate、ローカル調査、Codex上の修正依頼が混線しません。

導入前に3つだけ確認する

公式docsでは、@openai/codex-security packageはpublicですが、scan実行にはCodex Security accessが必要です。さらにCLIはNode.js 22.13.0以降の22系、24、26と、scan / export / history用にPython 3.10以降を要求します。

導入前の確認は次で十分です。

npx @openai/codex-security --version
npx @openai/codex-security info --json
npx @openai/codex-security --help

local利用ではChatGPT sign-inを使えます。CIやheadless runnerではAPI keyをscan stepだけに渡し、--auth api-keyで明示します。

npx @openai/codex-security login
npx @openai/codex-security scan . --auth chatgpt

CIで使うcredentialは、build、deploy、cloud adminのtokenと同じjob環境へ混ぜない方がよいです。scan processはlocal permissionでrepositoryを読み、環境変数を継承しうるため、不要なsecretを持ったrunnerで動かすほど結果の扱いが難しくなります。

local scanは結果ディレクトリをrepo外に置く

最初のscanでやるべきことは、脆弱性を全部見つけることではありません。repositoryをどう見たか、何を見られなかったか、どの成果物をreviewに渡せるかを確認することです。

REPOSITORY=/path/to/repository
SCAN_DIR=/path/outside/repository/codex-security-results

npx @openai/codex-security scan "$REPOSITORY" \
  --output-dir "$SCAN_DIR" \
  --dry-run

npx @openai/codex-security scan "$REPOSITORY" \
  --output-dir "$SCAN_DIR"

--dry-runは、repository、target、knowledge base、output pathなどの入力を確認します。長いscanを始める前に、出力先がrepo内になっていないか、対象pathが想定どおりか、credentialを使う段階まで進まない形で見るために使います。

scan結果はprivateなrepo外ディレクトリへ置きます。結果にはsource excerpt、finding detail、validation logが入ることがあるため、dist/やPR差分へ混ぜるものではありません。

artifactsはSARIFだけで終わらせない

CLIのcompleted scanは、読み物としてのreport.mdと構造化artifactをまとめて保存します。

codex-security-results/
  scan-manifest.json
  findings.json
  coverage.json
  report.md
  artifacts/
  exports/
    results.sarif

それぞれ役割が違います。

ファイル使い道
scan-manifest.jsonscanの対象、状態、producer、artifact record
findings.jsonfinding ID、severity、confidence、location、evidence、validation
coverage.json見たsurface、除外、deferred work、open question、coverage completeness
report.md人間が読むsummary
results.sarifGitHub code scanningなどへの連携

SARIFはtriage UIに載せるには便利ですが、scanの根拠全部ではありません。特にcoverage、deferred surface、open questionはcoverage.jsonを見ないと失われます。CIでSARIFだけuploadして「security checked」とするのは弱いです。

exit codeはmerge判定の語彙に変換する

Codex Security CLIのexit codeは、そのままmerge可否にせず、チームの語彙へ変換します。

exit code意味CIでの扱い
0完了し、coverage completeでseverity policyも通過pass候補
1完了scanでpolicy以上のfindingがあるsecurity findingあり
2入力、runtime、export、bulk error、またはpartial / unknown coverage判定不能としてfail
130 / 143interrupt / SIGTERM判定不能としてfail

重要なのは2です。partial coverageやunknown coverageは「高severity findingがなかった」ではありません。見切れていない、またはscanとして信頼できない状態です。coverage.jsonでdeferred surfaceとopen questionを確認し、必要ならtargetを絞って再scanします。

PR CIは最初からblockしない

CIではPR差分を対象にします。repository全体のdeep scanを毎回PRで回すと、既存負債や未確認surfaceが混ざり、merge判断が遅くなります。

BASE_REVISION="$(git merge-base "$BASE_SHA" "$HEAD_SHA")"

codex-security scan . \
  --diff "$BASE_REVISION" \
  --head "$HEAD_SHA" \
  --auth api-key \
  --output-dir "$SCAN_DIR" \
  --json > "$RUNNER_TEMP/codex-security.json"

最初の2週間は、私ならadvisoryにします。

段階CI挙動人間の見るもの
導入週結果を保存してsummaryだけcommentreport.md、coverage、scan時間
安定後--fail-on-severity highを設定high以上、coverage gap
成熟後SARIF uploadとowner routingを追加finding owner、期限、再検証

いきなり--fail-on-severity mediumのように広く止めると、toolの信頼性検証より先に運用が詰まります。まずcomplete coverageが安定して出るか、findingsがreview可能な粒度か、runtimeとcostがCIの予算内に収まるかを見ます。

severity policyはcoverageとセットで読む

--fail-on-severity highを設定すると、complete scanでhigh以上のfindingが出たときにexit code 1になります。

npx @openai/codex-security scan . \
  --diff origin/main \
  --fail-on-severity high

ただし、severity policyはcoverageの代わりではありません。scanがpartialなら、policy以上のfindingがなくてもexit code 2です。この仕様はむしろ良い設計です。不完全なscanをgreenへ落とすと、セキュリティ確認ではなく「未確認を隠すCI」になります。

CI commentでは、severityだけでなく次を必ず出します。

{
  "security_gate": "needs_review",
  "coverage": "partial",
  "policy": "fail_on_high",
  "confirmed_findings": 0,
  "blocking_reason": "coverage_not_complete",
  "next_action": "Review coverage.json deferred surfaces and rerun targeted scan"
}

この形なら、findingがないのか、scanが見切れていないのかをreviewerが区別できます。

CI runnerのsecretを最小化する

CI guideでは、API keyをCI secret storeに置き、scan stepのOPENAI_API_KEYへ直接mapする構成が示されています。このとき、同じjobにdeploy key、cloud admin credential、package publish tokenを置かない方が安全です。

最小構成は次です。

permissions:
  actions: read
  contents: read
  security-events: write

env:
  OPENAI_API_KEY: ${{ secrets.CODEX_SECURITY_API_KEY }}
  CODEX_SECURITY_STATE_DIR: ${{ runner.temp }}/codex-security-state

actions: read、contents: read、security-events: writeはSARIF uploadまで含めた最小permissionです。checkoutではpersist-credentials: falseを使い、scan packageはrepository checkoutの外、たとえば$RUNNER_TEMP/codex-securityへ入れます。scanがrepository内のnode_modulesやpackage scriptへ依存しない形にするためです。

local developer hookは軽く使う

FAQには、staged / unstaged change向けのpre-commit checkとしてinstall-hookが示されています。

npx @openai/codex-security install-hook

ただし、hookは開発者の手元の速度を壊しやすいので、最初は重要repositoryやsecurity-sensitive directoryに限定します。全員のpre-commitでdeep scanを走らせるのは過剰です。

私なら次の分担にします。

場所役割
pre-commit明らかなPR前事故を早めに見る
PR CImerge前の差分gate
scheduled full scanrepository全体の変化と既存findingの再確認
bulk scan複数repoの棚卸し

developer hookはCIの代替ではありません。手元でpassしても、CIのbase / head / credential / OS / dependency状態は違います。

bulk scanはinventoryを固定してから使う

複数repositoryを見たい場合、Codex Security CLIにはbulk scan導線があります。ここで避けたいのは、organization全体をいきなり広くscanして、結果を誰も読めない状態にすることです。

bulk scan前に、inventoryへ最低限これを持たせます。

id,repository,revision,scope,mode,prompt
api,https://github.com/example/api.git,0123456789abcdef0123456789abcdef01234567,src,standard,Review authorization boundaries.
admin,https://github.com/example/admin.git,fedcba9876543210fedcba9876543210fedcba98,.,deep,Review privileged admin workflows.

必須列はid、repository、revisionです。特にrevisionはfull commit hashで固定します。「先週のscanと今週のscanで何が変わったか」を見るには、同じbranch名ではなく、対象commitを記録する必要があります。scan結果を比較するときも、同じ範囲がcoverageされているかを先に確認します。

fix確認はscan比較だけで終わらせない

findingを直したあと、元scanをrerunし、before / afterをcompareできます。

npx @openai/codex-security scans rerun BEFORE_SCAN_ID
npx @openai/codex-security scans compare BEFORE_SCAN_ID AFTER_SCAN_ID

ただし、FAQが強調しているように、findingが消えたことやscan comparisonだけでは修正証明になりません。元のpathがcoverageされているかを確認し、重要findingはcurrent checkoutに対して直接recheckします。

npx @openai/codex-security validate /path/to/original/findings.json \
  "Recheck the authorization bypass in src/billing.ts against the current code"

PR本文には、次を残します。

codex_security_evidence:
  before_scan: "scan_..."
  after_scan: "scan_..."
  coverage: "complete"
  finding_status: "resolved"
  direct_recheck: "passed"
  regression_test: "pnpm test authz"
  remaining_gap: "runtime tenant policy not exercised in this scan"

これで、AI scanの結果をそのまま信じるのではなく、review可能な証拠として扱えます。

TypeScript SDKはproduct組み込み用に残す

Codex SecurityにはTypeScript SDKもあります。SDKは、アプリケーションや社内developer toolからscanを起動し、typed findings、coverage、artifact path、progress、cost control、cancellationを扱うための入口です。

ただし、最初からSDKへ行く必要はありません。

やりたいこと入口
手元でscanしたいCLI
GitHub ActionsでPR差分を見るCLI
GitHub code scanningへSARIFを送るCLI
社内portalからscan lifecycleを管理するTypeScript SDK
Codex task内のsecurity review laneを使うSecurity plugin

SDKを使う場合も、local OS permission、環境変数継承、repo外のprivate result directory、coverage completenessの確認はCLIと同じです。

最小運用テンプレート

小さなチームなら、最初はこのくらいで十分です。

codex_security_cli_policy:
  local_scan:
    output_dir: "repo外のprivate directory"
    auth: "chatgpt or api-keyを明示"
    share:
      - report.md
      - findings.json
      - coverage.json
  pull_request_ci:
    target: "merge-base..head"
    mode: "standard"
    initial_behavior: "advisory"
    later_policy: "fail-on-severity high"
    fail_if:
      - coverage is partial
      - coverage is unknown
      - exit code is 2
  artifacts:
    keep:
      - scan-manifest.json
      - findings.json
      - coverage.json
      - report.md
      - results.sarif
    retention_days: 30
  secrets:
    provide_only:
      - CODEX_SECURITY_API_KEY
    forbid:
      - deploy credentials
      - cloud admin tokens
      - production database URLs

よくある失敗

1. exit 2をwarningにする

exit 2は不完全なscanやruntime/export errorを含みます。warningへ落とすと、coverage gapを見落とします。CIでは判定不能としてfailさせ、coverage.jsonを読む方がよいです。

2. SARIF uploadだけで完了にする

SARIFは結果の連携形式です。scanの対象、coverage、deferred work、open question、artifact provenanceまで残すにはscan-manifest.json、coverage.json、report.mdも必要です。

3. plugin記事の設定をCLIに流用する

$codex-security:security-diff-scanはCodex plugin laneの入口です。CLIではnpx @openai/codex-security scan . --diff ...のように、terminal / CIの再現性を中心に考えます。

4. scan jobに余計なsecretを渡す

scanはlocal permissionで動き、環境を継承しうる作業です。security scanだから強いcredentialを渡す、ではなく、security scanだからcredentialを減らします。

5. fix後に「findingが消えた」だけで閉じる

scan comparisonは便利ですが、coverageが変わっていれば意味が弱くなります。direct recheck、regression test、owner reviewを合わせて修正完了にします。

参考

Codex Security CLIは「AIが見たから安心」の道具ではありません。scan対象、coverage、finding、artifact、exit code、credential境界をそろえて、初めてCIのsecurity gateとして使えます。

最初はlocal scanとadvisory CIで十分です。coverageが安定し、findingsをreviewできるようになってからseverity policyでmergeを止める。この順番なら、セキュリティ確認を形だけのcheckではなく、チームが読める証拠にできます。

WRITTEN BY nidoneko

Full-stack engineer with 8+ years of experience in TypeScript, React, Node.js, and cloud-native development across healthcare, finance, HR, and IoT domains.

View Profile →