/ AWS, GENAI, SECURITY, LAB

Amazon Bedrock 기반 지능형 PR 보안 분석 with Codex Security

코드 리뷰를 하다 보면 인증 여부는 확인하지만, 요청한 사용자와 조회 대상의 관계까지는 검증하지 않는 코드를 발견할 때가 있습니다. 각 줄만 보면 자연스럽지만, 객체 단위 권한 확인이라는 업무 규칙이 구현 과정에서 빠진 경우입니다.

이런 문제는 정해진 Pattern을 빠르게 찾는 기존 정적 코드 분석만으로 설명하기 어렵습니다. 반대로 언어 모델에 Repository 전체를 던지고 막연히 취약점을 찾아달라고 하는 것도 Pipeline에 넣기에는 범위와 결과가 불명확합니다.

Codex Security CLI는 이 사이에서 Repository 또는 Git 변경분을 보안 관점으로 검토하고, Finding뿐 아니라 근거와 Coverage를 구조화된 Artifact로 남깁니다. 특히 Base와 Head Revision을 지정하는 Diff Scan과 Amazon Bedrock Provider를 함께 사용하면 Pull Request에서 바뀐 코드에 초점을 맞춘 분석을 AWS 코드 파이프라인 안에서 실행할 수 있습니다.


🛡️ Codex Security CLI는 어떻게 분석할까?

Codex Security CLI는 Repository 전체, 특정 Path, Git Diff 또는 Working Tree를 대상으로 보안 Scan을 실행하는 명령줄 도구입니다. 결과는 사람이 읽는 report.md와 함께 findings.json, coverage.json, scan-manifest.json, SARIF(Static Analysis Results Interchange Format) 같은 구조화 문서로 남습니다. SARIF는 정적 분석 결과를 다른 보안 도구나 코드 리뷰 시스템에 전달하기 위한 표준 JSON 형식입니다.

Codex Security CLI와 Plugin은 역할을 나눠 갖습니다. CLI는 Scan 대상, Model과 Provider, 실행 옵션과 Artifact를 관리하고, 실제 보안 리뷰 절차는 함께 배포되는 Plugin의 Skills와 References에 정의되어 있습니다. Codex Security Overview는 Desktop과 Codex CLI가 이 Plugin을 사용하며, CLI와 SDK도 같은 Scanner를 사용한다고 설명합니다.

Plugin 내부에는 Scan 유형별 실행 절차를 정의한 skills/와, 분석 기준과 Artifact 규약을 정리한 references/가 함께 있습니다. Security Diff Scan Skill은 변경 파일을 모두 검토하고, 바뀐 동작을 이해하는 데 필요한 Supporting Code까지 따라가도록 Diff Scan의 실행 순서를 정의합니다. 반면 Core Security Scan은 별도의 Scanner가 아니라 같은 Plugin의 references/에 포함된 Repository 감사 절차입니다. 여기에는 공격자, Entry Point, Trust Boundary, 공격자 입력의 흐름, 깨진 Control, Sensitive Operation, Counterevidence와 실제 영향을 확인하는 기준이 정리되어 있습니다. 같은 references 디렉터리에는 Threat Model, Finding 구조, Coverage, Artifact와 최종 Report의 기준도 함께 정의되어 있습니다.

따라서 Codex Security Scan은 CLI가 Source를 모델에 전달하고 답을 받는 단순 호출이 아니라, Agent가 Plugin에 정의된 보안 리뷰 절차를 따라 Source를 조사하고 판단 근거를 구조화하는 Workflow로 보는 편이 정확합니다. Codex Security Plugin의 Scan 문서는 그 과정을 다음과 같이 설명합니다.

단계 하는 일
Threat modeling Asset, Entry Point, Trust Boundary와 Security Invariant를 정리
Finding discovery 요청한 코드에서 깨진 Control과 Source-to-Sink Path 후보를 탐색
Validation 후보의 Source Evidence와 Counterevidence를 확인하고 Proof Gap을 기록
Impact and path analysis 현실적인 Attack Path, 영향과 Severity를 평가
Reporting 검증된 Finding, Coverage와 Scan Metadata를 Artifact로 생성

기존 Scanner의 결과가 있다면 연결 방법도 있습니다. Triage 문서read-only static analysis Workflow에 SARIF, Scanner Ticket, GitHub Code Scanning Finding 등을 입력하면 Source Code와 대조해 검토할 수 있습니다. Standard Scan은 Source에서 후보를 찾는 경로, Triage는 이미 있는 Finding을 검토하는 경로로 구분할 수 있습니다.


🏗️ CodeCommit에서 CodeBuild까지

AWS Code Services에서 Codex Security PR Review까지의 처리 흐름

처리 흐름

  1. Revision 수집 — CodeCommit PR Event에서 Base·Head Commit을 읽습니다. Branch 경로에서는 CodePipeline이 Source Revision을 선택합니다.

  2. 실행 연결 — EventBridge는 PR Build를 시작하고, CodePipeline은 Branch 경로를 CodeBuild까지 연결합니다.

  3. Diff 계산 — CodeBuild가 Git History에서 git merge-base를 계산해 BASE_REVISIONSOURCE_COMMIT_ID를 확정합니다.

  4. 보안 분석 — Codex Security가 변경분과 판단에 필요한 Supporting Code를 읽고 Amazon Bedrock 모델로 보안 전제, 경로와 근거를 검토합니다.

  5. 원본 Artifact 생성 — Security Scan이 report.md, findings.json, coverage.json, scan-manifest.jsonScanArtifact로 남깁니다.

  6. 한국어 문서 생성 — 별도 Localization CodeBuild가 완성된 ScanArtifact만 입력받아 report.ko.mdfindings.ko.json을 추가합니다.

  7. 검토 전달 — CodePipeline의 LocalizedArtifact는 S3에 보관하고, PR 실행 경로에서는 요약과 변경 라인 Comment를 CodeCommit Pull Request에 남깁니다.

Bedrock 호출에는 API Key 대신 CodeBuild Service Role의 임시 AWS 자격 증명을 사용했습니다. Codex Security CLI의 Bedrock Provider는 Responses API 경로를 사용하므로 bedrock-mantle:CreateInference 권한이 필요합니다. 일반 Bedrock Runtime의 Model Invocation Logging은 이 호출 경로에 적용되지 않으므로, 실행은 CodeBuild Log와 Codex Security Artifact로 확인했습니다.


🔎 PR 변경분을 Scan 범위로 지정하기

변경 범위를 정하는 로직은 실제 CodeBuild BuildSpec에 넣었습니다. 별도의 buildspec.yml 대신 CloudFormation의 CodeBuild Project에 Inline BuildSpec으로 정의했습니다. CodePipeline 경로에서는 Source Action이 선택한 Revision을 Head로 삼고 기본 Branch와의 공통 조상을 Base로 계산합니다.

pre_build:
  commands:
    - |
      set -euo pipefail
      git fetch origin "$DEFAULT_BRANCH"
      export HEAD_REVISION="$CODEBUILD_RESOLVED_SOURCE_VERSION"
      export BASE_REVISION="$(
        git merge-base "origin/${DEFAULT_BRANCH}" "$HEAD_REVISION"
      )"

build:
  commands:
    - |
      "$CODEX_SECURITY_BIN" scan . \
        --diff "$BASE_REVISION" \
        --head "$HEAD_REVISION" \
        --provider amazon-bedrock \
        --model openai.gpt-5.6-luna \
        --effort medium \
        --output-dir "$SCAN_DIR" \
        --json > "$JSON_FILE"

PR Event가 직접 시작하는 Review 경로도 원리는 같습니다. EventBridge가 CodeCommit Event의 sourceCommitdestinationCommit을 CodeBuild에 전달합니다. sourceCommit만 받아 바로 이전 Commit과 비교하면 PR의 마지막 Commit에서 변경된 내용만 남습니다. 대신 Source와 Destination Commit의 공통 조상부터 Source Commit까지 비교하면 PR에 누적된 전체 변경분을 검토할 수 있습니다.

{
  "sourceVersion": <sourceCommit>,
  "environmentVariablesOverride": [
    {"name": "DESTINATION_COMMIT_ID", "value": <destinationCommit>},
    {"name": "SOURCE_COMMIT_ID", "value": <sourceCommit>}
  ]
}

BuildSpec은 두 값이 실제 Git Object인지 확인하고 공통 조상인 BASE_REVISION을 계산합니다. Codex Security에는 이 값을 Base로, SOURCE_COMMIT_ID를 Head로 넘겼습니다. Codex Security CLI Reference--diff BASE는 Base부터 --head까지의 Committed Change를 Scan합니다. CI Guide도 Base와 Head History를 가져와 Merge Base를 계산하고, 두 Revision을 명시적으로 전달하도록 안내합니다.

--diff는 Base부터 Head까지 누적된 변경분을 Scan 대상으로 지정합니다. OpenAI Docs는 Security Diff Scan이 Repository Context로 변경을 이해하고, Finding Discovery와 Validation을 Diff와 직접 관련된 Supporting Code에 집중한다고 설명합니다.

분석 결과를 확인하기 위해 객체 소유권 검증이 누락된 API 예제를 PR에 추가했습니다.

def get_invoice(
    invoice_id: int,
    authenticated_user_id: int,
) -> dict[str, object]:
    invoice = INVOICES[invoice_id]

    if not authenticated_user_id:
        raise PermissionError("Authentication required")

    return invoice

이 코드는 로그인 여부만 확인합니다. invoice["owner_id"]authenticated_user_id를 비교하지 않으므로, 다른 고객의 Invoice ID를 알면 해당 객체를 읽을 수 있습니다.


🧪 Codex Security Diff Scan 결과

Diff Scan은 변경된 get_invoice()와 판단에 필요한 주변 코드를 함께 검토해 객체 소유권 검사 누락을 하나의 Finding으로 만들었습니다.

PR Review 실행 결과는 다음과 같습니다.

항목 결과
Finding 다른 고객의 Invoice를 노출하는 소유권 검사 누락
분류 Authorization bypass / IDOR
Severity Low
Confidence High
Coverage Complete

invoice_id는 호출자가 선택할 수 있고, 함수는 authenticated_user_id가 비어 있지 않은지만 확인한 뒤 Invoice를 반환합니다. invoice.owner_id와 인증된 사용자 ID를 비교하지 않기 때문에 객체 단위 권한 검사가 빠져 있습니다.

이번 구성은 Finding을 자동 차단 조건으로 사용하지 않고, 경로와 Evidence를 사람이 검토하는 Advisory 방식으로 두었습니다. CodeBuild 화면에는 실행 성공 여부가, 실제 보안 결과는 S3 Artifact와 CodeCommit Review에 남도록 책임도 분리했습니다.


💬 분석 결과를 한국어 PR Review로 전달하기

Codex Security의 기본 report.md는 영어로 생성됩니다. 처음에는 Scan Session의 후속 지침으로 번역까지 처리했지만, 보안 분석과 번역의 성공 여부가 한 작업에 묶이는 문제가 있었습니다. 최종 구성에서는 CodePipeline에 Localization Stage를 분리했습니다.

SourceArtifact
  └─ SecurityScan
       └─ ScanArtifact
            ├─ results/report.md
            ├─ results/findings.json
            ├─ results/coverage.json
            └─ results/scan-manifest.json
                 └─ Localization
                      └─ LocalizedArtifact
                           ├─ results/report.ko.md
                           ├─ results/findings.ko.json
                           └─ localization-status.txt

Localization은 codex-security scan을 한 번 더 실행하지 않습니다. 일반 Codex Agent가 완성된 report.mdfindings.json만 받아 한국어 파생 문서를 만듭니다. 입력 문서는 JSON 문자열로 Prompt 안에 넣고, 문서 안의 내용은 지시가 아닌 신뢰하지 않는 데이터로 취급하도록 제한했습니다. CodeBuild 환경에서 Read-only Sandbox가 파일을 직접 읽게 했을 때 Linux Namespace 권한 오류가 발생했기 때문에, Sandbox 권한을 넓히는 대신 필요한 입력만 명시적으로 전달하는 방식으로 바꿨습니다.

Localization 지침에는 변환 범위를 다음처럼 명시했습니다.

1. report.ko.md에는 원본 Finding ID, Severity, Confidence,
   파일 경로, 줄 번호, Evidence, Proof Gap, Remediation을 보존한다.
2. findings.ko.json에는 findingId, title, summary, remediation만 기록한다.
3. 원본 report.md, findings.json, coverage.json, scan-manifest.json과
   Source Code는 수정하지 않는다.

findings.ko.json은 같은 Finding을 PR Review에 한국어로 표시하기 위한 보조 파일입니다. title, summary, remediation만 한국어로 작성하고 Severity, Confidence, 파일과 줄 번호는 원본 findings.json을 기준으로 삼았습니다. 변환 후에는 원본과 한국어 JSON의 findingId 집합이 정확히 일치할 때만 두 파일을 저장합니다.

최종 CodePipeline 실행에서는 Source, SecurityScan, Localization Stage가 모두 성공했고, localization-status.txtcomplete였습니다. S3의 LocalizedArtifact ZIP 내부에서 다음 두 파일과 원본 Artifact를 함께 확인했습니다.

s3://<artifact-bucket>/slens-codex-security/LocalizedA/<artifact-key>
└─ codex-security-artifacts/
   ├─ localization-status.txt
   └─ results/
      ├─ report.md
      ├─ report.ko.md
      ├─ findings.json
      └─ findings.ko.json

Localization은 Advisory 단계라 번역 실패만으로 Pipeline을 중단하지 않습니다. 따라서 CodePipeline의 Succeeded만 확인하면 부족하고, 소비 단계에서 localization-status.txtcomplete인지 함께 확인해야 합니다.

CodeCommit Review로 연결

CodeCommit Pull Request 요약에 추가한 한국어 보고서 링크

CodeCommit에는 PostCommentForPullRequest API로 두 종류의 Comment를 남겼습니다.

  • 일반 Comment에는 Coverage, Finding 개수, 한국어 요약, CodeBuild 링크와 report.ko.md 링크를 기록합니다.
  • Finding의 줄이 실제 PR 추가 라인과 겹칠 때만 해당 파일과 줄에 Inline Comment를 기록합니다.

CodeCommit Pull Request 변경 라인에 남은 Codex Security 한국어 Inline Review

Inline Review는 Report의 Finding을 실제 PR 변경 지점에 연결합니다. invoice_api.py의 32번 줄은 소유권을 확인하지 않은 채 invoice를 반환하는 Sink입니다. 캡처는 첫 PR Review 실행 결과로, Comment에는 한국어 Finding 제목, 당시의 Severity low, Confidence high, 소유권 비교가 빠졌다는 설명과 invoice.owner_id를 인증 사용자와 비교하라는 권장 조치를 표시했습니다.

PR 화면에는 수정 판단에 필요한 요약과 Remediation만 표시하고, 전체 내용은 report.ko.md 링크에서 확인하도록 했습니다.


🌐 Custom Provider도 사용할 수 있을까?

Codex Security CLI Reference에는 openai, openrouter, fireworks, amazon-bedrock가 공식 Provider로 나옵니다. 하지만 선택지가 이 목록에만 묶이는 것은 아닙니다. Codex Custom Model Provider에서 base_url, wire_api, 인증 방식을 따로 지정할 수 있어 Responses API를 지원하는 Endpoint도 연결할 수 있습니다.

실제로 CLI Provider는 openai로 두고, Model 요청만 Custom Provider를 거쳐 Bedrock Mantle로 보내봤습니다. 검증 대상은 OS Command Injection을 심어 둔 세 파일짜리 Repository였습니다. 이 실험은 탐지 성능보다 Model 호출부터 Scan 완료와 Artifact 생성까지 전체 경로가 동작하는지 확인하는 데 초점을 맞췄습니다.

@openai/codex-security@0.1.21에서는 Custom Provider가 env_key를 직접 읽는 방식이 동작하지 않아 auth.command를 사용했습니다. AWS short-term Bedrock Token Generator를 Credential Helper로 연결하면 Token을 설정 파일이나 명령행에 남기지 않고 Mantle에 전달할 수 있습니다.

export OPENAI_API_KEY="<cli-preflight-placeholder>"

codex-security scan . \
  --provider openai \
  --auth api-key \
  --model openai.gpt-5.6-luna \
  --codex 'model_provider="bedrock_mantle_custom"' \
  --codex 'model_providers.bedrock_mantle_custom.base_url="https://bedrock-mantle.us-east-2.api.aws/openai/v1"' \
  --codex 'model_providers.bedrock_mantle_custom.wire_api="responses"' \
  --codex 'model_providers.bedrock_mantle_custom.auth.command="/path/to/fetch-bedrock-token"' \
  --json

Scan은 2분 만에 끝났고 Coverage는 complete였습니다. 의도한 OS Command Injection을 High Finding으로 찾았으며 report.md, JSON, SARIF도 생성됐습니다. Worker 위임은 중간에 실패했지만 Parent Agent가 이어받아 세 파일을 모두 검토하고 Scan을 마쳤습니다.

이 실행으로 Responses API를 지원하는 Endpoint라면 Custom Model Provider를 적용할 수 있다는 점을 확인했습니다. Endpoint마다 Streaming, Tool Calling, 인증 방식이 다르므로 작은 Repository에서 Scan 완료와 Artifact 생성까지 먼저 확인하는 편이 안전합니다.


Outro

이번에는 CodeCommit의 PR 변경분을 Codex Security로 분석하고, 결과를 S3 Artifact와 PR Comment로 전달하는 흐름을 구성했습니다. 원본 Report와 한국어 Report를 분리해 보관하고, PR에서는 Finding의 요약과 수정할 위치를 바로 확인할 수 있도록 했습니다.

Plugin 내부의 Skills와 References를 살펴보는 것도 흥미로웠습니다. Threat Model을 세우고, Finding 후보의 근거와 반대 근거를 확인하며, 실제 영향과 Attack Path를 판단하는 과정이 문서로 정리되어 있어 보안 코드리뷰의 흐름을 공부하는 자료로도 활용할 수 있습니다.

이번 글에서는 PR 변경분을 직접 검토하는 Diff Scan에 집중해 Triage 기능까지는 사용하지 않았습니다. Codex Security의 Triage는 기존 Scanner가 만든 SARIF나 Finding을 Source와 다시 대조해 근거와 실제 영향을 검토하는 흐름입니다. 다음에는 기존 정적 분석 결과를 Triage에 연결하고, Finding이 어떻게 걸러지고 보강되는지도 소개할 수 있기를 바라며 글을 마칩니다.

잘못된 내용은 지적해주세요! 😃


References