본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 31. 12:45

4가지 보안 도메인에서의 Claude vs Gemini: 막상막하의 결과 — 그리고 AI 코드의 63%가 놓치는 보안 강화 조치

요약

Claude와 Gemini의 보안 코드 생성 능력을 비교 분석한 결과, 두 모델 모두 유사한 보안 취약점을 노출하며 막상막하의 성능을 보였습니다. 특히 AI 생성 함수의 63%가 보안 결함을 포함하고 있어, 모델의 성능 비교보다 보안 강화 조치를 검증하는 프로세스가 더 중요함을 강조합니다.

핵심 포인트

  • Claude와 Gemini는 보안 도메인에서 통계적으로 유사한 성능을 보임
  • AI 생성 코드의 약 63%가 보안 취약점을 포함하고 있음
  • 단순 모델 비교보다 CWE 기반의 보안 검증 도구 활용이 필수적임
  • 모델의 성능 격차보다 공통적인 보안 실패 모드에 주목해야 함

흥미로운 결과는 누가 이겼느냐가 아닙니다. 4가지 보안 도메인 전반에 걸쳐 Claude와 Gemini가 동일한 보안 강화 (hardening) 단계를 놓쳤다는 점입니다. 만약 당신이 올해 AI가 생성한 인증 미들웨어 (auth middleware)를 배포했다면, 당신의 코드에도 거의 확실히 동일한 결함이 존재하며, 당신의 리뷰 과정에서도 이를 잡아내지 못했을 것입니다.

기록을 위해 점수판을 공개하자면: Gemini 1승, 2무, 1분할 — 통계적으로 막상막하 (dead heat)의 결과입니다. 이 글에서 '승자'가 중요하게 다뤄지는 것은 이번이 마지막입니다.

어떤 리더보드보다 당신을 더 괴롭혀야 할 숫자는 이것입니다: 제가 곧 사용할 규칙에 따라 점수를 매긴 700개의 AI 생성 함수 중, 63%가 취약점을 포함한 채 배포되었습니다. 따라서 "어떤 모델이 더 안전한 코드를 작성하는가?"라는 질문은 대체로 잘못된 질문입니다. 저는 직접 그 리더보드를 실행해 보았고, 그것이 잘못된 프레임이라고 주장했습니다. 하지만 사람들은 계속해서 그 질문을 던지기에, 저는 무엇이 실제로 중요한지 보여주기 위해 제대로 실행해 보았습니다. 즉, 이러한 버그를 잡아내기 위해 제가 특별히 작성한 CWE (Common Weakness Enumeration)별 ESLint 보안 플러그인(security plugins)을 사용하여 테스트했습니다.

설정 (The setup)

4개의 도메인, 그리고 제가 만든 4개의 플러그인입니다. 각 도메인에 대해, 기능만을 요구하는 동일한 프롬프트(사용자들이 실제로 이 도구들을 사용하는 방식인 "보안을 강화해줘"와 같은 힌트는 제외함)를 사용하여, Gemini CLI를 통한 Gemini 2.5 FlashClaude CLI를 통한 Claude Sonnet 4.6에서 각각 한 번씩 생성했습니다. 그 후 해당 도메인의 플러그인을 recommended 설정으로 린트(lint)했습니다.

방법론의 정직성: 이것은 Gemini Flash 대 Claude Sonnet의 대결입니다. 이는 각 벤더의 CLI가 기본값으로 설정하는, 비교 가능한 가격/지연 시간(latency) 계층입니다 (Pro와 Opus는 별도의 범주이며, 이에 대해서는 아래에서 더 자세히 다룹니다). 이는 통제된 디코딩(decoding) 하의 순수 모델이 아니라, 시스템 프롬프트(system prompt)를 포함한 CLI 도구 자체를 비교한 것입니다. 도메인당 n=1이지만, JWT 라운드를 다시 실행했을 때 두 모델 모두 동일한 핵심 실수를 하며 다시 5개의 결과(findings)를 냈습니다. 따라서 이를 절대적인 복음(gospel)이 아닌, 안정적인 실패 모드(failure modes)를 보여주는 방향성 있는 지표로 취급하십시오.

점수판 (The scorecard)

도메인 (Domain)프롬프트 (Prompt)플러그인 (Plugin)GeminiClaude
NestJS 서비스users + auth + adminnestjs-security26
...
Gemini 1승, 무승부 2회, 판정승 1회. 최첨단 보안 격차는 담론에서 주장하는 것보다 작으며, 단순히 승수를 세는 것은 여기서 가장 흥미롭지 않은 수치입니다.

하단 표 범례: = 해당 규칙 1회 위반, ✗✗ = 2회, ✗✗✗ = 3회, = 규칙 미발생 (clean).

라운드 1 — NestJS: Gemini의 관용적인 스캐폴딩(scaffolding) 승리

단 하나의 깔끔한 승리는 별도로 상세히 기록되었습니다. 요약하자면: 사용자(users) 서비스를 요청했을 때, Gemini의 CLI는 관용적인 (idiomatic) NestJS 방식을 채택했습니다 — 클래스 레벨의 @UseGuards, 비밀번호 필드에 대한 @Exclude(), 그리고 모든 DTO에 적용된 class-validator 등이 포함되었습니다. nestjs-security2개의 이슈를 발견했습니다. Claude는 이러한 스캐폴딩 없이 기능적으로 동일한 코드를 작성했으나 6개의 이슈가 발견되었습니다.

주관이 뚜렷한 프레임워크에서 Gemini는 보안 관용구(secure idiom)를 기본값으로 사용합니다. 이 점을 유념하십시오.

라운드 2 — JWT: 동일한 RFC 8725 단계를 놓친 5–5 무승부

두 모델 모두 깔끔한 jsonwebtoken 코드를 작성했습니다: 서명된 로그인 토큰, 그리고 (단순한 jwt.decode 지름길이나 alg: none, 하드코딩된 비밀키 없이) 검증(verifies)을 수행하는 미들웨어 — 두 모델 모두 치명적인 JWT 실수(footgun)를 모두 피했습니다. 그 후 두 모델 모두 정확히 동일한 지점에서 멈췄습니다:

jwt ruleCWEGeminiClaude
require-algorithm-whitelistCWE-757
...

이것이 검토를 통과하는 이유입니다: jwt.verify(token, secret)를 읽는 검토자는 검증(verify) 호출을 보고 그대로 배포합니다. 아무도 다음 질문을 던지지 않습니다 — 누구를 위한 검증인가? audience 옵션이 없으면, 귀하의 서비스가 다른 API를 위해 발행한 토큰이 그대로 통과됩니다. 이러한 사각지대가 바로 require-audience-validation이 인코딩하는 내용이며, 두 모델 모두 — 그리고 대부분의 인간 검토자도 — 이를 그냥 지나치는 이유입니다. 이번 라운드는 5-5로 비깁니다.

라운드 3 — MongoDB: 둘 다 비밀번호를 유출했으나, 둘 다 인젝션은 당하지 않음

가장 먼저 본인의 저장소(repo)를 점검해봐야 할 결과가 나왔습니다: 두 모델 모두 프로젝션(projection) 없이 비밀번호 해시를 포함한 전체 문서를 반환하도록 검색 코드를 작성했습니다.

// 두 모델 모두 본질적으로 다음과 같이 작성함:
const results = await User.find(filter);   // 호출자에게 passwordHash를 전달함
// 두 모델 모두 작성하지 못한 수정 사항:
...

이는 양측 모두에서 require-projection (CWE-200) 및 no-select-sensitive-fields 규칙이 위반된 것입니다. 기분 좋은 놀라움은 다음과 같습니다: 프롬프트가 사용자 제공 검색 객체를 Mongoose 쿼리에 직접 전달하여 — 교과서적인 $where/연산자 인젝션 (operator-injection) 함정 — 을 만들었음에도 불구하고, 두 모델 모두 이를 피해갔습니다. 양측 모두 no-operator-injection, no-unsafe-where, no-unsafe-query 위반이 전혀 없었습니다. 최첨단 모델들은

mongodb-security 규칙CWEGeminiClaude
require-schema-validationCWE-20✗✗✗
...

분포는 다르지만 총합은 동일합니다 (8–8). 하지만 한 셀은 솔직하게 짚고 넘어갈 필요가 있습니다. 왜냐하면 이 결과가 제 제목의 논조와 _상충_하기 때문입니다: require-schema-validationGemini에서 세 번, Claude에서 한 번 발생했습니다. 이 지점에서는 Claude가 더 절제된 모습을 보였습니다. Gemini가 느슨한 타이핑 (looser typing)에 의존한 반면, Claude는 Mongoose의 스키마 수준 검증 (schema-level validation)을 더 많이 연결했습니다. "Gemini가 최첨단 (frontier-grade) 모델이다"라는 말이 "Gemini가 모든 셀에서 승리한다"는 뜻은 아닙니다. 이 셀은 Gemini가 패배한 지점입니다. (그리고 맞습니다, require-lean-queries는 고전적인 인젝션이 아니라 CWE-400입니다. .lean()은 하이드레이션된 (hydrated) Mongoose 문서 대신 일반 객체를 반환하며, 제한 없는 검색 시 이는 실제 메모리 고갈 (memory-exhaustion) 레버가 될 수 있습니다. 그렇기에 이것은 권장 사항이 아닌 리소스 제어 (resource control) 항목으로 점수가 매겨집니다.)

라운드 4 — 일반 인젝션 (General injection): 수치의 함정

*별표. 인젝션에 취약한 원시 API (JSON/XML 임포트, 동적 검색, 비밀번호 재설정)에서 secure-coding은 Gemini 9건, Claude 13건을 식별했습니다. 하지만 이 수치는 거꾸로 해석해야 합니다. Claude의 추가 발견은 Claude가 더 많은 것을 수행했기 때문에 발생했습니다. Claude는 XML DOCTYPE/ENTITY를 명시적으로 거부했고 (XXE 방어 강화), 검색 필드를 화이트리스트 (allowlist) 처리했으며, 실제로 토큰 검증을 구현했습니다. 그리고 여기서 솔직한 부분을 말씀드리자면, Claude는 그중 일부를 안전하지 않게 구현했습니다:

// Claude의 재설정 흐름 — CWE-208, 타이밍 공격에 취약 (timing-unsafe):
if (providedToken === storedToken) { /* ...재설정... */ }

...

Claude는 해당 === 비교를 다섯 번 작성했습니다 (no-insecure-comparison, CWE-208). 이는 이 전체 벤치마크를 통틀어 두 모델 중 어느 한 모델이 도입한 유일한 실제 취약점입니다. 그리고 이 취약점은 역설적으로 Claude가 검증 영역 (verification surface)을 구축했기 때문에 존재하게 되었습니다. Gemini의 더 간결한 97줄 코드는 토큰을 발행하기만 하고 비교하지 않았으므로, 틀릴 영역 자체가 없었습니다. 수치상으로는 Gemini가 유리했지만, 실질적인 내용은 진정으로 엇갈립니다. Claude는 더 많은 부분을 강화(hardened)했 동시에 유일한 실제 버그를 배포했습니다.

솔직한 주의사항: 작업 유형이 모든 것을 바꾼다

누군가

  1. Gemini는 프런티어급(frontier-grade)의 보안 기본값(secure default)을 제공합니다. Gemini는 4개 도메인 중 3개에서 Claude와 대등하거나 앞섰으며, 프레임워크 라운드에서 압도적인 승리를 거두었습니다. 또한 어떤 도메인에서도 심각도가 높은 인젝션(injection)이나 인증 우회(auth-bypass) 버그를 발생시키지 않았습니다. 즉, NoSQL 연산자 인젝션(NoSQL operator injection), alg: none, 검증 없는 jwt.decode, eval, 하드코딩된 자격 증명(hardcoded credentials) 등이 전혀 발견되지 않았습니다. (유일하게 새로 발생한 취약점은 Claude의 타이밍에 취약한 토큰 비교 — CWE-208이었습니다. 공정하게 말하자면 이는 아마도 여기서 가장 낮은 위험도의 발견일 것입니다. DB 조회 후 비교되는 높은 엔트로피의 토큰은 네트워크 지터(network jitter)를 통해 공격하기 어렵기 때문입니다. 또한 두 모델이 공통적으로 가진 잠재적인 격차 — aud/iss 검증이 없는 고정되지 않은 JWT 알고리즘 — 가 애플리케이션 보안(appsec) 엔지니어들이 가장 먼저 패치할 부분입니다. 이를 단순히 "강화(Hardening)"라고 표현하기에는 부족합니다. 저는 이를 무해한 것이 아니라, 누락된 통제 항목(missing control)으로 지적하는 것입니다.) Gemini로 개발한다면, 신뢰할 수 있는 보안 기준선(security baseline)에서 시작하는 셈입니다.
  2. **어떤 프런티어 모델도 보안적으로 완전(security-complete)하지는 않습니다. 놓친 부분들은 무작위가 아니었습니다. 그것들은 바로 프롬프트(prompt)가 명시하지 않았기 때문에 어떤 모델도 기능 프롬프트로부터 추론해내지 못한 동일한 부정적 공간의 강화 조치들(알고리즘 허용 목록(allowlists), 대상(audience) 검증, 쿼리 프로젝션(query projections), 스키마 검증(schema validation), 인증(auth))이었습니다. 이 격차는 더 나은 모델이 나온다고 해서 좁혀지지 않습니다. 당신이 적어 넣지 않은 제약 사항을 확인해 주는 도구가 있어야만 좁혀집니다.

이것이 바로 정적 분석(static analysis)의 핵심입니다. 정적 분석은 당신의 프롬프트가 묻지 않은 질문을 던집니다.

설정 (두 모델의 출력물에 대해 실행 가능)

// eslint.config.mjs
import jwt from 'eslint-plugin-jwt';
import mongodbSecurity from 'eslint-plugin-mongodb-security';
...
npm install --save-dev eslint-plugin-jwt eslint-plugin-mongodb-security \
  eslint-plugin-nestjs-security eslint-plugin-secure-coding
npx eslint src/

모든 규칙은 CWE에 매핑되어 있어, AI 에이전트와 사람이 동일한 신호를 읽을 수 있습니다. 전체 문서는 eslint.interlace.tools에서 확인할 수 있습니다.

당신이 생성한 AI 코드는 어떤 강화 (hardening) 단계를 가장 많이 건너뛰나요? 알고리즘 허용 목록 (algorithm allowlist), 대상 확인 (audience check), 아니면 쿼리 프로젝션 (query projection) 중 무엇인가요? 파일을 열어 직접 확인해 보세요. 아마 세 가지 중 최소 두 가지는 해당될 것이라고 확신합니다. 어떤 것인지 저에게 알려주세요. 점수표 (scorecards)를 수집하고 있습니다.

AI 보안 벤치마크 시리즈 (AI Security Benchmark Series)의 일부:
동일한 NestJS 프롬프트. Claude는 6개의 보안 오류를, Gemini는 2개의 오류를 기록했습니다. · Frontier Dead Heat (현재 위치) · 다음 → (곧 공개 예정)

📦 eslint-plugin-jwt · eslint-plugin-mongodb-security · eslint-plugin-nestjs-security · eslint-plugin-secure-coding · 규칙 문서 (Rule docs)

⭐ GitHub에서 Star 받기

GitHub | X | LinkedIn | Dev.to | ofriperetz.dev

👇 아래에 당신의 점수표를 남겨주세요 — 알고리즘 허용 목록 (algorithm allowlist), 대상 확인 (audience check), 또는 쿼리 프로젝션 (query projection) 중 당신의 AI 생성 코드가 건너뛰는 것은 무엇인가요? 수집 중입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0