AI 어시스턴트가 작성하는 Pydantic v2 검증(Validation) 버그
요약
AI 어시스턴트가 Pydantic v1 데이터로 학습되어 v2 환경에서 잘못된 검증 코드를 생성하는 문제를 다룹니다. v2의 변경된 데코레이터, 설정 방식, 타입 변환 규칙을 AI가 제대로 반영하지 못해 발생하는 런타임 오류와 안티패턴을 분석합니다.
핵심 포인트
- AI는 v1 패턴에 치중된 학습 데이터로 인해 v2 구문을 잘못 생성함
- @validator 대신 @field_validator 사용 및 ConfigDict 설정 필요
- 잘못된 타입 반환 시 런타임 오류나 조용한 강제 변환 발생 위험
- Optional[str] 사용 시 None 체크 누락 등 논리적 안티패턴 주의
- Semgrep 스캐너 등을 활용해 구식 v1 패턴을 탐지하는 것이 권장됨
2023년 이전의 Python 코드를 주로 학습한 AI 어시스턴트들은 Pydantic 문제에 직면해 있습니다. 2023년 6월에 출시된 v2 버전은 재작성된 검증(validator) 코어를 탑재했습니다. 이는 실행 순서, 강제 변환(coercion) 규칙, 설정 키의 명칭이 모두 달라졌음을 의미하지만, 공개된 저장소(repository)에는 여전히 v2보다 v1 코드가 훨씬 더 많이 포함되어 있습니다. 오늘날 Copilot이나 Claude Code가 FastAPI 라우트를 생성할 때, 출력 결과로 @validator 데코레이터나 orm_mode = True를 사용하는 경우가 빈번합니다. 이 두 가지는 모두 v2에서 다르게 동작하거나 아예 실패하는 v1 패턴입니다. 이러한 패턴들은 타입 체크(type-check)를 통과하며, 많은 설정에서 오류 없이 실행됩니다. 하지만 그 중 일부는 귀하의 API가 받도록 설계되지 않은 데이터를 조용히 수락하게 됩니다.
AI 어시스턴트가 놓치는 Pydantic v2의 변경 사항
Pydantic v2는 AI가 생성한 모델을 조용히 망가뜨리는 방식으로 검증 동작을 변경했습니다. v1의 validator 데코레이터는 실행 순서가 다른 field_validator로 대체되었고, orm_mode = True는 model_config = ConfigDict(from_attributes=True)로 변경되었으며, 숫자 타입에 대한 강제 변환(coercion) 동작도 바뀌었습니다. 이 모든 차이점은 v1 패턴에 치중하여 학습된 AI가 틀리게 작성할 수밖에 없는 부분들입니다. BrassCoders의 Semgrep 스캐너는 v2 코드베이스 내의 권장되지 않는(deprecated) v1 패턴을 찾아내어, AI 어시스턴트가 구식 구문을 사용했을 때 CI(지속적 통합)에 결정적인 신호를 제공합니다.
가장 중대한 변화는 검증(validator) 실행 타이밍입니다. v1에서는 @validator가 Pydantic이 값을 확정하기 전, 필드 할당 주기의 초기에 실행되었습니다. v2에서 @field_validator는 정의된 mode에 따라 실행되며, 해당 모드가 함수가 어떤 값을 받는지와 어떤 타입을 반환해야 하는지를 결정합니다. 자신의 모드에 맞지 않는 잘못된 타입을 반환하는 AI 생성 검증기는 런타임(runtime)에 오류를 발생시키거나, 어노테이션(annotation)이 설명하지 않는 값으로 조용히 강제 변환(coerce)해 버립니다.
AI 어시스턴트들은 생성 시점에 Pydantic v2 마이그레이션 가이드를 참고하지 않습니다. 대신 v1에 가중치가 실린 학습 데이터에 따라 패턴 매칭(pattern-match)을 수행합니다. 이는 우연이 아닌 구조적인 문제입니다. 동일한 프롬프트에 대해 여러 AI 어시스턴트에서 똑같은 패턴이 반복되는 것을 볼 수 있을 것입니다.
AI가 생성하는 세 가지 검증 안티패턴
BrassCoders의 Semgrep 스캐너는 v2 코드베이스에서 사용되지 않는 Pydantic v1 패턴을 플래그할 수 있지만, AI가 만들어내는 더 깊은 검증 안티패턴은 다음과 같습니다. 필드 할당 전에 실행되어 잘못된 타입을 조용히 반환하는 유효성 검사기(validators), API 로직에서는 필수로 취급하지만 Optional[str]로 지정된 필드, 그리고 타입 변환(coercion)이 잘못된 입력을 조용히 수용하는 필드에 strict=True가 누락되는 경우입니다.
Optional[str] 안티패턴은 가장 신뢰할 수 있게 위험합니다. 사용자 프로필 모델을 생성한 AI 어시스턴트는 비즈니스 규칙상 null이 허용된다면 이메일 필드를 종종 Optional[str]로 선언합니다 — 이는 기술적으로는 정확하지만 — 이후 라우트 핸들러(route handler)가 None 체크 없이 값에 대해 .split('@')을 호출하는 경우 문제가 발생합니다. 모델은 None을 수용합니다. 하지만 라우트는 AttributeError를 발생시킵니다. 어노테이션(annotation)은 둘 다 유효하다고 약속했지만, 실제 컨텍스트에서는 하나만 유효합니다.
strict=True의 허점은 더 미묘합니다. 기본적으로 Pydantic v2는 int 필드에 대해 `
구체적인 실패 모드(failure mode): mypy는 Optional[str]을 평가하고 str | None이 유효한 타입임을 확인합니다. 하지만 mypy는 라우트 핸들러(route handler) 내의 호출 코드가 문자열 메서드에 접근하기 전에 None에 대한 방어 코드(guard)를 갖추고 있는지 추적하지는 않습니다. 어노테이션(annotation)은 구조적으로 건전합니다. 런타임(runtime) 동작은 그렇지 않지만, mypy는 두 가지 모두에 대해 이의를 제기하지 않습니다.
Pysa는 다른 접근 방식을 취합니다. Pysa는 HTTP 요청 본문(request body) — 즉 오염원(taint source) — 으로부터 Pydantic 모델 할당을 거쳐 다운스트림(downstream) 코드로 이어지는 데이터의 흐름을 추적합니다. 사용자 제어 입력이 정화 함수(sanitizing function)를 거치지 않고 데이터베이스 쿼리, 파일 경로 또는 서브프로세스 호출(subprocess call)에 도달하면 Pysa는 이를 플래그(flag)로 표시합니다. FastAPI의 요청 검증(request validation) 문서는 Pydantic이 유입(ingress) 시점에 무엇을 검증하는지 설명하며, Pysa는 검증을 통과한 후에 어떤 일이 발생하는지를 설명합니다.
코드는 정적으로는 올바르게 보입니다. 하지만 런타임에는 그렇지 않으며, 이러한 차이는 잘못된 형식의 요청이 실제 라우트에 도달할 때에만 드러납니다.
BrassCoders가 FastAPI 및 Pydantic 코드에서 찾아내는 것들
BrassCoders의 Semgrep 및 Bandit 스캐너는 AI가 생성한 FastAPI/Pydantic 코드에서 보안과 밀접한 관련이 있는 패턴들을 찾아냅니다: 라우트 핸들러 내부의 생(raw) SQL 쿼리, 누락된 인증 의존성(authentication dependencies), 그리고 v1에서 v2로의 마이그레이션이 동작을 업데이트하지 않은 AI에 의해 처리되었음을 나타내는 권장되지 않는(deprecated) Pydantic 패턴들입니다.
Semgrep 규칙은 @validator와 orm_mode를 v1 패턴 신호로 타겟팅합니다. 이 둘은 v2에서 모두 권장되지 않으며(deprecated), 오래된 학습 데이터로 작업하는 AI 어시스턴트에 의해 안정적으로 생성되는 패턴들입니다. Bandit 스캐너는 라우트 핸들러 내의 생(raw) f-string 또는 .format() 스타일의 SQL 생성 방식을 포착합니다. 이는 느슨한 검증 가정을 인젝션 벡터(injection vector)로 변질시키는 패턴입니다.
설치는 단 하나의 명령어로 가능합니다: pip install brasscoders && brasscoders scan .. 오픈 소스 (OSS) 코어는 외부 데이터 유출 없이 12개의 모든 스캐너를 로컬에서 실행합니다. BrassCoders의 공개된 벤치마크에 따르면, Bandit은 단독으로 AI가 생성한 12개의 보안 버그 중 6개를 포착했습니다. 반면 12개 스캐너 전체를 통과하면 12개 중 11개를 포착합니다. 개발자당 월 $12인 BrassCoders Paid 플랜은 호스팅된 게이트웨이를 통한 임베딩 기반 (embedding-based) 강화 패스를 추가하여, 가공되지 않은 결과물들을 가장 먼저 분류 (triage)할 가치가 있는 하위 집합으로 축소해 줍니다. 무료 체험은 없습니다. brasscoders portal을 통해 언제든 취소할 수 있습니다.
Pydantic v2 검증 (validation) 격차는 학습 데이터 (training-data) 문제입니다. AI 어시스턴트가 쓰기 권한을 가지고 있고, 검토자가 mypy 실행 결과가 깨끗하다는 것을 보안 태세 (security posture)가 깨끗하다는 의미로 간주하는 모든 프로젝트에서 재현될 것입니다. v2 코드베이스에 있는 단 하나의 @validator는 조기에 포착할 가치가 있는 신호입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기