#!/bin/bash # codex-security-review.sh - Run an AI-powered security vulnerability review using Codex CLI # Usage: codex-security-review.sh [OPTIONS] # # Runs codex exec in read-only sandbox mode with a security-focused review prompt. # Outputs findings as JSON and optionally posts them to a PR. set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/common.sh" # Defaults PR_NUMBER="" BASE_BRANCH="main" COMMIT_SHA="" OUTPUT_FILE="" POST_TO_PR=false UNCOMMITTED=false REVIEW_MODE="" show_help() { cat <<'EOF' Usage: codex-security-review.sh [OPTIONS] Run an AI-powered security vulnerability review using OpenAI Codex CLI. Options: -n, --pr PR number (auto-enables posting findings to PR) -b, --base Base branch to diff against (default: main) -c, --commit Review a specific commit -o, --output Write JSON results to file --post-to-pr Post findings as PR comment (requires -n) --uncommitted Review uncommitted changes (staged + unstaged + untracked) -h, --help Show this help Examples: # Security review uncommitted changes codex-security-review.sh --uncommitted # Security review a PR and post findings codex-security-review.sh -n 42 # Security review against main, save JSON codex-security-review.sh -b main -o security.json # Security review a specific commit codex-security-review.sh -c abc123f EOF exit 0 } # Parse arguments while [[ $# -gt 0 ]]; do case $1 in -n|--pr) PR_NUMBER="$2" POST_TO_PR=true REVIEW_MODE="pr" shift 2 ;; -b|--base) BASE_BRANCH="$2" REVIEW_MODE="base" shift 2 ;; -c|--commit) COMMIT_SHA="$2" REVIEW_MODE="commit" shift 2 ;; -o|--output) OUTPUT_FILE="$2" shift 2 ;; --post-to-pr) POST_TO_PR=true shift ;; --uncommitted) UNCOMMITTED=true REVIEW_MODE="uncommitted" shift ;; -h|--help) show_help ;; *) echo "Unknown option: $1" >&2 echo "Run with --help for usage" >&2 exit 1 ;; esac done # Validate if [[ -z "$REVIEW_MODE" ]]; then echo "Error: Specify a review mode: --uncommitted, --base , --commit , or --pr " >&2 exit 1 fi if [[ "$POST_TO_PR" == true && -z "$PR_NUMBER" ]]; then echo "Error: --post-to-pr requires -n " >&2 exit 1 fi check_codex check_jq # Verify we're in a git repo if ! git rev-parse --is-inside-work-tree &>/dev/null; then echo "Error: Not inside a git repository" >&2 exit 1 fi # Get the diff context echo "Gathering diff context..." >&2 case "$REVIEW_MODE" in uncommitted) DIFF_CONTEXT=$(build_diff_context "uncommitted" "") ;; base) DIFF_CONTEXT=$(build_diff_context "base" "$BASE_BRANCH") ;; commit) DIFF_CONTEXT=$(build_diff_context "commit" "$COMMIT_SHA") ;; pr) DIFF_CONTEXT=$(build_diff_context "pr" "$PR_NUMBER") ;; esac if [[ -z "$DIFF_CONTEXT" ]]; then echo "No changes found to review." >&2 exit 0 fi # Build the security review prompt REVIEW_PROMPT=$(cat <<'PROMPT' You are an expert application security engineer performing a security-focused code review. Your goal is to identify vulnerabilities, security anti-patterns, and data exposure risks. ## Security Review Scope ### OWASP Top 10 (2021) - A01: Broken Access Control — missing authorization checks, IDOR, privilege escalation - A02: Cryptographic Failures — weak algorithms, plaintext secrets, missing encryption - A03: Injection — SQL, NoSQL, OS command, LDAP, XPath injection - A04: Insecure Design — missing threat modeling, unsafe business logic - A05: Security Misconfiguration — debug mode, default credentials, unnecessary features - A06: Vulnerable Components — known CVEs in dependencies - A07: Authentication Failures — weak auth, missing MFA, session issues - A08: Data Integrity Failures — deserialization, unsigned updates - A09: Logging Failures — sensitive data in logs, missing audit trails - A10: SSRF — unvalidated URLs, internal service access ### Additional Checks - Hardcoded secrets, API keys, tokens, passwords - Insecure direct object references - Missing input validation at trust boundaries - Cross-Site Scripting (XSS) — reflected, stored, DOM-based - Cross-Site Request Forgery (CSRF) protection - Insecure file handling (path traversal, unrestricted upload) - Race conditions and TOCTOU vulnerabilities - Information disclosure (stack traces, verbose errors) - Supply chain risks (typosquatting, dependency confusion) ## Severity Guide - **critical**: Exploitable vulnerability with immediate impact (RCE, auth bypass, data breach) - **high**: Significant vulnerability requiring prompt fix (injection, XSS, secrets exposure) - **medium**: Vulnerability with limited exploitability or impact (missing headers, weak config) - **low**: Minor security concern or hardening opportunity (informational, defense-in-depth) ## Rules - Include CWE IDs when applicable - Include OWASP category when applicable - Provide specific remediation steps for every finding - Only report findings you are confident about - Do NOT flag non-security code quality issues - If no security issues found, say so clearly PROMPT ) # Set up temp files for output and diff TEMP_OUTPUT=$(mktemp /tmp/codex-security-XXXXXX.json) TEMP_DIFF=$(mktemp /tmp/codex-diff-XXXXXX.txt) trap 'rm -f "$TEMP_OUTPUT" "$TEMP_DIFF"' EXIT SCHEMA_FILE="$SCRIPT_DIR/schemas/security-review-schema.json" # Write diff to temp file echo "$DIFF_CONTEXT" > "$TEMP_DIFF" echo "Running Codex security review..." >&2 echo " Diff size: $(wc -l < "$TEMP_DIFF") lines" >&2 # Build full prompt with diff reference FULL_PROMPT="${REVIEW_PROMPT} Here are the code changes to security review: \`\`\`diff $(cat "$TEMP_DIFF") \`\`\`" # Run codex exec with prompt from stdin to avoid arg length limits echo "$FULL_PROMPT" | codex exec \ --sandbox read-only \ --output-schema "$SCHEMA_FILE" \ -o "$TEMP_OUTPUT" \ - 2>&1 | while IFS= read -r line; do echo " [codex] $line" >&2 done # Check output was produced if [[ ! -s "$TEMP_OUTPUT" ]]; then echo "Error: Codex produced no output" >&2 exit 1 fi # Validate JSON if ! jq empty "$TEMP_OUTPUT" 2>/dev/null; then echo "Error: Codex output is not valid JSON" >&2 cat "$TEMP_OUTPUT" >&2 exit 1 fi # Save output if requested if [[ -n "$OUTPUT_FILE" ]]; then cp "$TEMP_OUTPUT" "$OUTPUT_FILE" echo "Results saved to: $OUTPUT_FILE" >&2 fi # Post to PR if requested if [[ "$POST_TO_PR" == true && -n "$PR_NUMBER" ]]; then echo "Posting findings to PR #$PR_NUMBER..." >&2 post_to_pr "$PR_NUMBER" "$TEMP_OUTPUT" "security" echo "Posted security review to PR #$PR_NUMBER" >&2 fi # Always print results to stdout print_results "$TEMP_OUTPUT" "security"