بسته‌ی @openai/codex-security یک خط فرمان مستقل برای اسکن امنیتی کد است که بدون نشست تعاملی کدکس کار می‌کند: ۲۲ زیر‌دستور دارد، روی Node 22.13 به بالا اجرا می‌شود و گزارش SARIF می‌سازد. در این مقاله کل مسیر را روی یک ریپوی نمونه اجرا می‌کنیم، از بررسی ورودی‌ها با --dry-run تا کد خروج ۱ با --fail-on-severity و قلاب pre-commit. لحظه‌ی خواندن همه‌ی عددها: ۱۳ مهر ۱۴۰۵، برابر با ۵ اکتبر ۲۰۲۶.

سه شماره‌ی نسخه که نباید با هم اشتباه شوند

بسته‌ی عمومی است و نصبش هیچ حسابی نمی‌خواهد. رجیستری npm برای @openai/codex-security نسخه‌ی 0.1.31 را به‌عنوان latest می‌دهد و یک فایل اجرایی به نام codex-security تعریف می‌کند.

محدوده‌ی نسخه‌های Node در همان رجیستری نوشته شده و با چیزی که مستندات می‌گویند جور است: ^22.13.0 || ^24.0.0 || ^26.0.0. اسکن، خروجی‌گرفتن، تاریخچه و یافته‌های ذخیره‌شده به Python 3.10 به بالا هم نیاز دارند. روی این ماشین node نسخه‌ی v26.7.0 و python3 نسخه‌ی 3.14.7 است، پس هر دو شرط برقرارند.

نکته‌ای که همه را غافلگیر می‌کند این است که یک ابزار، سه شماره‌ی نسخه دارد و این سه با هم بالا نمی‌روند. آنچه info گزارش می‌کند در این اجرا چنین بود:

شمارهکجا آمدچه چیزی را توصیف می‌کند
0.1.31رجیستری npm و کلید cliVersionنسخه‌ی بسته و خط فرمان
0.1.95کلید bundledPluginVersionافزونه‌ی همراه بسته
0.156.1کلید codexVersionکدکسی که بسته با خود حمل می‌کند
0.158.0خروجی codex --versionکدکس نصب‌شده‌ی جداگانه‌ی این ماشین

آخرین سطر جدول مهم است. کدکس نصب‌شده‌ی این ماشین دو نسخه از کدکس داخل بسته تازه‌تر است، و این هیچ اثری روی اسکن ندارد. اگر بخواهید بدانید اسکن با کدام کدکس اجرا می‌شود، به codex --version نگاه نکنید؛ به info --json نگاه کنید.

اولین اجرا: info و سپس بررسی ورودی‌ها

قبل از هر اسکنی، info بزنید. این زیر‌دستور نه اسکن می‌کند و نه هزینه‌ای دارد و تنها پیکربندی حل‌شده را نشان می‌دهد.

$ npx -y @openai/codex-security info --json
{
  "sdkVersion": "0.1.31",
  "bundledPluginVersion": "0.1.95",
  "scanMcp": false,
  "cancellationNote": "Scans are CLI-only because the MCP transport cannot cancel active commands.",
  "cliVersion": "0.1.31",
  "codexVersion": "0.156.1",
  "codexSdkVersion": "0.156.1",
  "model": "gpt-5.6-sol",
  "reasoningEffort": "xhigh",
  "nextStep": "codex-security scan . --dry-run",
  "configuration": {
    "settings": { "auth": "auto", "mode": "standard", "target": "repository" },
    "sources": { "auth": "default", "scan.mode": "default" }
  }
}

سه چیز را از همین خروجی باید برداشت. مدل پیش‌فرض gpt-5.6-sol با تلاش استدلال xhigh است، نه مدلی که فکر می‌کنید. بخش sources نشان می‌دهد هر تنظیم از کجا آمده: مقدار default یعنی هیچ فایل پیکربندی آن را بازنویسی نکرده است. و cancellationNote توضیح می‌دهد چرا لغو اسکن فقط از خط فرمان ممکن است.

گام بعدی --dry-run است. این اسکن را شروع نمی‌کند و فقط ورودی‌ها را می‌سنجد، پس ارزان‌ترین راه برای پیدا کردن اشتباه پیکربندی است.

$ npx -y @openai/codex-security scan . --dry-run --headless
[00:00] Validating scan inputs
[00:00] Preflight complete
dryRun: true
repository: /root/.hermes/cache/scratch/sec-demo
target:
  kind: repository
  paths: []
mode: standard
outputDir: null
authentication:
  method: stored_credentials
  verified: false
model: gpt-5.6-sol
reasoningEffort: xhigh

به target.paths: [] نگاه کنید: آرایه‌ی خالی یعنی کل مخزن، نه هیچ فایلی. اگر می‌خواهید اسکن به یک پوشه محدود شود باید مسیر را با --path بدهید. همچنین verified: false یعنی هیچ اعتباری تأیید نشده است؛ پیش‌بررسی با این وضعیت سبز شد، ولی اسکن واقعی به دسترسی Codex Security نیاز دارد.

اسکن می‌گوید فایل پیکربندی نمی‌خواهد. اگر بخواهید تنظیمات را در مخزن نگه دارید، init یک فایل ۹۸۶ بایتی می‌سازد که تمام گزینه‌هایش کامنت شده است و چیزی را فعال نمی‌کند.

اجرای بدون مدل، و عددی که نباید باور کنید

اگر دسترسی Codex Security ندارید یا می‌خواهید خط لوله را بدون هزینه بیازمایید، پرچم --mock یافته‌های ساختگی می‌سازد و اصلا مدلی صدا نمی‌زند. این کل اجرا بود:

$ npx -y @openai/codex-security scan . --mock --headless
[00:00] Preparing scan
codex-security: Mock scan: generating synthetic findings; no security analysis or LLM calls.
[00:00] Running scan
[00:01] Scan complete · 18c4da10

  REPORT    /root/.codex/state/plugins/codex-security/scans/sec-demo/codex-security-sec-demo-QF9wwf/report.md

  FINDINGS  12 (12 confirmed this scan; 0 previously found; 2 critical, 4 high, 3 medium, 2 low, 1 informational)
  COVERAGE  complete
  ELAPSED   0s
  TOKENS    unavailable uncached input, 0 cache reads, unavailable cache writes, 0 output, 0 total
  RESULTS   /root/.codex/state/plugins/codex-security/scans/sec-demo/codex-security-sec-demo-QF9wwf

دو سطر از این خروجی دروغ نمی‌گویند و دو سطر دیگر درباره‌ی همین اسکن دروغ نمی‌گویند. ELAPSED 0s و TOKENS unavailable یعنی هیچ مدلی اجرا نشده است. اما COVERAGE complete در کنارشان گمراه‌کننده است: کامل بودن پوشش یعنی فهرست فایل‌ها کامل بوده، نه اینکه فایل‌ها تحلیل شده‌اند.

راستش را همان اول گفته‌اند. مکان یافته‌ها به مسیرهای خیالی اشاره می‌کند، از جمله mock/command-injection.ts و mock/sql-injection.ts، در حالی که ریپوی من دو فایل app/db.py و app/handler.py داشت. دلیل را خود ابزار در فیلد اطمینان نوشته است: Deterministic fixture; not a conclusion about the scanned repository. یعنی این ۱۲ یافته درباره‌ی کد من نیست و حتی تزریق SQLی که عمدا در app/db.py گذاشته بودم هم پیدا نشد، چون هیچ‌وقت نگاهش نکردند.

یافته‌ها، پوشش و خروجی SARIF

هر اسکن کامل یک پوشه می‌سازد که سه فایل مرجع دارد: scan-manifest.json برای هدف و دامنه، findings.json برای یافته‌ها و coverage.json برای پوشش. کنارشان یک report.md هم نوشته می‌شود که به‌جای عدد دلخواه، همان سه فایل را نمایش می‌دهد.

اینجا یک ناهماهنگی هست که اگر متوجه نشوید، دوباره شمارش می‌کنید و به نتیجه‌ی غلط می‌رسید. کنسول ۱۲ یافته می‌گوید، ولی report.md عدد ۱۱ را در سطر Reportable findings می‌نویسد. علت این است که یافته‌ی informational شمرده نمی‌شود و ترکیب شدت در همان گزارش چهار سطح را نشان می‌دهد، نه پنج سطح را. جمع ۲ و ۴ و ۳ و ۲ می‌شود ۱۱، و آن دوازدهمی همان یافته‌ی اطلاعاتی است.

خروجی قابل ماشین‌خواندن را با export می‌گیرید. قالب پیش‌فرض SARIF است و همان قالبی است که اسکن کد گیت‌هبل می‌پذیرد.

$ npx -y @openai/codex-security export --export-format sarif
$ ls -l results.sarif
-rw------- 1 root root 31071 Oct  5 00:11 results.sarif
$ jq -r '[.runs[].results[]]|length' results.sarif
12
$ jq -r '[.runs[].results[].level]|group_by(.)|map("\(.[0])=\(length)")|join(" ")' results.sarif
error=6 note=3 warning=3
$ jq -r '.runs[0].properties' results.sarif
{
  "codexSecuritySchemaVersion": "1.0",
  "codexSecurityTargetKind": "git_worktree"
}

نگاشت سه سطح SARIF به پنج سطح شدت، دلیل ریاضی این اعداد است و از روی همین فایل قابل بازسازی است. شش error برابر است با دو critical به‌علاوه‌ی چهار high. سه warning برابر است با سه medium. سه note برابر است با دو low به‌علاوه‌ی یک informational. جمع 6 + 3 + 3 می‌شود ۱۲، یعنی هر دو شمارش روی یک اسکن می‌نشینند.

اگر می‌خواهید هزینه‌ی توکن یک اسکن را پیش از اجرا تخمین بزنید، همان فرمان با --token-count به‌جای خروجی، تعداد توکن را برمی‌گرداند. روی همین ریپوی نمونه عدد ۶۸ بود.

کد خروج و قلاب pre-commit

مستندات یک قاعده‌ی صریح دارند: اسکن به‌صورت پیش‌فرض فقط گزارش می‌دهد و هیچ‌وقت خط لوله را متوقف نمی‌کند. اندازه‌گیری این اجرا هم همین را نشان داد. بدون آستانه، کد خروج صفر بود. با --fail-on-severity high روی اسکنی که دو یافته‌ی critical داشت، کد خروج یک شد. با --fail-on-severity critical هم به همان دلیل یک شد.

شرایطکد خروجاین عدد از کجا آمد
گزارش بدون آستانه0اندازه‌گیری در این اجرا
--fail-on-severity high با دو یافته‌ی بحرانی1اندازه‌گیری در این اجرا
پوشش ناقص یا نامعلوم2مستندات، بدون اندازه‌گیری در این اجرا

سطر آخر را جدا نگه دارید. کد ۲ یعنی اسکن تمام نشد، نه اینکه آسیب‌پذیری پیدا شد. اگر خط لوله‌ی شما ۲ را هم شکست تلقی کند، یک اسکن نیمه‌کاره به مرحله‌ی بعد می‌رود و آن‌وقت تصمیم امنیتی روی داده‌ی ناقص گرفته می‌شود.

برای گرفتن همین آستانه در کامیت محلی، یک زیر‌دستور کافی است. فایل قلابی که می‌نویسد دقیقا همین است:

$ npx -y @openai/codex-security install-hook
hook: /root/.hermes/cache/scratch/sec-demo-ci/.git/hooks/pre-commit
failOnSeverity: high
$ cat .git/hooks/pre-commit
#!/bin/sh
set -eu
# مسیر مطلق به بسته‌ی موقت npx، نه یک دستور عمومی روی PATH
exec '/root/.hermes/tools/node-26.7.0-linux-x64/bin/node' '/root/.npm/_npx/f4a9a6fe740fa973/node_modules/@openai/codex-security/dist/cli.js' scan . --working-tree --fail-on-severity high

آن توضیح فارسی داخل بلوک، یادداشت من است و در فایل واقعی وجود ندارد؛ فایل تولیدشده سه خط بیشتر ندارد. مهم‌ترین نکته‌ی این سه خط، سطر آخر است: مسیر مطلق به کش موقت npx داخل قلاب نوشته می‌شود. اگر آن کش پاک شود یا روی ماشین دیگری کامیت بزنید، قلاب می‌شکند، و شکستش هم بی‌صدا نیست: هر کامیت با خطا متوقف می‌شود. برای همین راهنمای رسمی در CI تاکید می‌کند بسته را با npm install --prefix پیش از چک‌اوت نصب کنید و مسیر مطلق همان نصب را صدا بزنید، و پوشه‌ی وضعیت را با CODEX_SECURITY_STATE_DIR بیرون از مخزن بگذارید تا فایل اجرایی مخزن به اعتبار اسکن نزدیک نشود.

برای ریپوهای بزرگ، bulk-scan فهرست مخازن را پیدا می‌کند و اسکن‌های قابل‌ادامه می‌سازد، و زیر‌دستور --patch با --patch-severity یافته‌های تأییدشده را وصله می‌کند و سپس راستی‌آزمایی می‌کند. این دو را در این اجرا نسنجیدم، چون هر دو به اسکن واقعی نیاز دارند.

قبل از این، لایه‌ی مجوز را ببینید

اگر تازه‌وارد این حوزه‌اید، اسکن امنیتی بدون کنترل دسترسی مثل دویدن در مهتاب است. پست لایه‌ی مجوز ایجنت اعداد واقعی یک بازبین خودکار را نشان می‌دهد و پروفایل مجوز کدکس به شما می‌گوید سندباکس را چطور واقعا ببندید. این ابزار دروازه‌بان است، نه نگهبان؛ جلوی یافته‌ی تازه را نمی‌گیرد.

منابع

  1. راهنمای شروع سریع خط فرمان codex-security؛ پیش‌نیازهای Node و Python و شکل گزارش پایان اسکن
  2. مرجع کامل زیر‌دستورهای خط فرمان، از جمله پرچم‌های fail-on-severity و patch
  3. اجرای اسکن در CI؛ نصب پیش از چک‌اوت، پوشه‌ی وضعیت بیرون از مخزن و بارگذاری SARIF
  4. تاریخچه‌ی افزونه‌ی Codex Security؛ نسخه‌های 0.1.19 تا 0.1.25 با تاریخ انتشار
  5. مستندات افزونه‌ی Codex Security
  6. نسخه‌ی latest در رجیستری npm؛ توضیح بسته، فایل اجرایی و محدوده‌ی نسخه‌های Node
  7. تاریخچه‌ی رسمی کدکس
  8. مخزن رسمی کدکس
  9. اسکیمای SARIF نسخه‌ی ۲٫۱٫۰ که خروجی export با آن مطابق است
  10. بارگذاری فایل SARIF در اسکن کد گیت‌هبل
  11. تعریف CWE-78 برای تزریق فرمان در سیستم‌عامل
  12. صفحه‌ی تزریق فرمان در OWASP