AI 코더들이 실수하는 5가지 FastAPI 보안 패턴
요약
AI 코딩 어시스턴트가 FastAPI 개발 시 빈번하게 생성하는 5가지 보안 취약 패턴을 분석합니다. 특히 학습 데이터의 한계로 인해 발생하는 SQL 인젝션 위험성을 경고하며, 이를 탐지하기 위한 스캐너 활용의 중요성을 강조합니다.
핵심 포인트
- AI는 가독성 중심의 취약한 튜토리얼 코드를 학습하여 보안 실수를 재현함
- f-string을 사용한 SQL 쿼리 생성은 심각한 SQL 인젝션 취약점을 유발함
- 프롬프팅보다 생성된 코드를 검증하는 스캐너(Bandit 등) 사용이 더 효과적임
- BrassCoders는 AI가 생성한 안티 패턴을 탐지하는 오픈 소스 도구를 제공함
AI 코딩 어시스턴트(AI coding assistants)는 몇 분 만에 작동하는 FastAPI 엔드포인트를 생성합니다. 하지만 이들은 코드베이스 전반에 걸쳐 동일한 다섯 가지 보안 실수를 재현 가능하게 생성하기도 합니다. 이는 FastAPI가 보안에 취약하기 때문이 아닙니다 — FastAPI의 공식 보안 문서(security documentation)는 매개변수화된 쿼리(parameterized queries)와 인증 패턴(authentication patterns)을 명확하게 다루고 있습니다. 이러한 실수는 학습 데이터(training data)에서 비롯됩니다. AI는 수백만 개의 튜토리얼 예제와 StackOverflow 답변으로부터 학습했는데, 그중 상당수는 간결함을 위해 보안에 취약한 패턴을 사용합니다. AI는 다음에 올 내용을 예측하며, 튜토리얼에서 다음에 오는 내용은 종종 f"SELECT * FROM users WHERE id = {user_id}"와 같은 형태입니다.
BrassCoders는 자체 오픈 소스(OSS) 코어 스캐너인 Bandit, Yelp의 detect-secrets, 그리고 AI가 도입한 안티 패턴(anti-patterns)을 위한 세 가지 커스텀 탐지기를 사용하여 아래의 다섯 가지 패턴을 단 한 번의 패스(pass)로 모두 탐지합니다. 유료 플랜은 필요하지 않습니다.
AI 어시스턴트가 이러한 패턴을 생성하는 이유
BrassCoders는 특정한 관찰을 바탕으로 구축되었습니다: AI 코딩 어시스턴트는 패턴 완성 엔진(pattern-completion engines)이며, 이들이 가장 자주 접한 패턴이 항상 올바른 패턴은 아니라는 점입니다. Python 웹 API를 위한 학습 데이터는 튜토리얼과 퀵스타트(quick-start) 예제에 치우쳐 있습니다 — 즉, 보안을 위해서가 아니라 가독성을 위해 작성된 코드들입니다. f-string SQL 쿼리를 사용한 FastAPI 라우트를 보여주는 StackOverflow 답변은 명확성 덕분에 추천(upvote)을 받습니다. 하지만 AI 어시스턴트에 의해 대규모로 재현된 동일한 코드는 운영 환경(production)에 SQL 인젝션(SQL injection) 취약점을 배포하게 됩니다.
이는 구조적인 문제입니다. 프롬프팅(Prompting)을 통해 이를 우회하는 것은 미미한 도움만 줄 뿐입니다. 더 확실한 해결책은 생성 후에 실행되어 AI 어시스턴트가 틀리는 특정 패턴을 표시해 주는 스캐너(scanner)를 사용하는 것입니다.
패턴 1 — f-String을 통한 SQL 인젝션 (SQL Injection)
BrassCoders는 Bandit 규칙 B608을 사용하여 문자열 포매팅(string formatting)을 통해 구축된 SQL 쿼리를 탐지합니다. 이 규칙은 사용자 제어 값(user-controlled values)이 쿼리 문자열에 직접 보간(interpolated)되는 모든 탐지된 SQL 문에서 실행됩니다.
생성된 코드는 다음과 같습니다:
from fastapi import FastAPI
import databases
...
문제점: user_id가 FastAPI에 의해 int 타입으로 지정되어 있어 가장 명백한 문자열 주입 (string-injection) 페이로드는 차단하지만, 모든 페이로드를 차단하는 것은 아닙니다. 더 심각한 문제는, 이 패턴이 AI(그리고 이를 읽는 개발자)에게 SQL 내에서 f-string을 사용하는 것이 허용된다는 잘못된 학습을 시킨다는 점입니다. 동일한 패턴이 문자열 타입의 필드에 복사되어 붙여넣어지면 전형적인 SQL 인젝션 (SQL injection)이 됩니다. OWASP API Security Top 10은 인젝션을 API8:2023으로 분류하며, 이는 데이터베이스 쿼리, 셸 명령 (shell commands), 템플릿 엔진 (template engines) 전반에서 나타납니다.
해결책은 매개변수화된 쿼리 (parameterized queries)를 사용하는 것입니다:
@app.get("/users/{user_id}")
async def get_user(user_id: int):
query = "SELECT * FROM users WHERE id = :user_id"
...
BrassCoders는 Bandit B608을 통해 f-string 버전을 탐지합니다. 매개변수화된 버전은 탐지 결과(finding)를 생성하지 않습니다.
패턴 2 — 설정 파일 내 하드코딩된 자격 증명 (Hardcoded Credentials in Config)
BrassCoders는 Yelp의 detect-secrets 라이브러리를 사용하여 하드코딩된 자격 증명 — API 키, 데이터베이스 연결 문자열, 서명 비밀값 (signing secrets) — 을 탐지합니다. 이 라이브러리는 OpenAI 키, AWS 액세스 키, GitHub PAT, Stripe 라이브 키 및 엔트로피가 높은 문자열을 포함하여 20개 이상의 자격 증명 형식을 지원합니다.
생성된 코드는 다음과 같습니다:
# config.py
DATABASE_URL = "postgresql://admin:hunter2@localhost/mydb"
OPENAI_API_KEY = "sk-proj-abc123..."
...
이 패턴은 AI가 생성한 코드에서 끊임없이 나타납니다. AI는 "개발자에게 작동하는 예시를 제공한다"는 것에 최적화되어 있으며, 실제 값일 것이라고 예상하지 않는 플레이스홀더 (placeholder) 값을 하드코딩합니다. 개발자는 이 설정을 복사하여 실제 값으로 교체한 뒤 커밋합니다. 이렇게 되면 해당 파일이 나중에 삭제되더라도 자격 증명은 git 히스토리에 영구적으로 남게 됩니다.
해결책은 모든 비밀값 (secrets)을 환경 변수 (environment variables)로 옮기는 것입니다:
import os
from functools import lru_cache
from pydantic_settings import BaseSettings
...
FastAPI의 공식 보안 문서(security documentation)는 모든 비밀 정보(secrets)에 대해 환경 기반 설정을 권장합니다. BrassCoders는 detect-secrets를 통해 하드코딩된 버전을 잡아냅니다. BaseSettings 패턴은 탐지 결과(finding)를 생성하지 않습니다.
패턴 3 — 리포트 실행기에서의 셸 인젝션 (Shell Injection)
BrassCoders는 Bandit 규칙 B602 및 B603을 통해 shell=True가 포함된 subprocess 호출을 탐지하며, 이는 사용자 제어 데이터가 셸 명령 문자열로 흐를 때마다 발생합니다.
생성된 코드는 다음과 같습니다:
from fastapi import FastAPI
import subprocess
...
report_type=pdf; rm -rf /와 같은 요청은 두 명령을 모두 실행합니다. shell=True 플래그는 운영체제(OS)에 해당 문자열을 /bin/sh를 통해 파싱하도록 지시하며, 이는 셸 메타문자(shell metacharacters)를 해석하게 됩니다. 해당 문자열 내의 사용자 제어 값은 임의 코드 실행(arbitrary code execution)으로 이어집니다. Bandit의 B602 문서는 이를 HIGH 심각도로 분류합니다.
해결책은 인자를 리스트(list)로 전달하여 셸을 완전히 우회하는 것입니다:
import subprocess
import shlex
...
shell=True를 사용하지 않습니다. 문자열 보간(string interpolation)도 사용하지 않습니다. BrassCoders는 B602/B603을 통해 원본 코드를 잡아내고 HIGH 심각도로 표시합니다.
패턴 4 — 환각된 미들웨어 패키지 (Hallucinated Middleware Package)
BrassCoders의 AI 패턴 스캐너는 PyPI에 존재하지 않는 패키지의 임포트(import)를 탐지하여, 모델이 한 번도 게시된 적 없는 그럴듯한 패키지 이름을 지어내는 AI 생성 버그 유형을 잡아냅니다.
생성된 코드는 다음과 같습니다:
from fastapi import FastAPI
from fastapi_auth_middleware import AuthMiddleware
from fastapi_rate_limiter import RateLimiterMiddleware
...
이러한 패키지 중 일부는 실제로 존재하지만, 일부는 존재하지 않으며, 일부는 존재하더라도 임포트 경로(import paths)나 클래스 이름이 다를 수 있습니다. AI는 자신이 학습한 패턴을 바탕으로 존재할 법한 코드를 생성합니다. Python의 임포트 오류는 컴파일 타임(compile-time)이 아닌 런타임(runtime)에 발생하기 때문에, 생성된 코드는 린터(linter)를 통과할 수 있습니다. 개발자의 IDE는 실제 패키지 API와 일치하지 않는 스텁(stubs)을 기반으로 자동 완성(autocomplete)을 제공할 수도 있습니다.
위험은 단순히 런타임의 ModuleNotFoundError에 그치지 않습니다. PyPI에 존재하지 않는 패키지 이름은 주인이 없는 네임스페이스(namespace)가 됩니다. 공격자는 해당 이름으로 악성 패키지를 게시할 수 있으며, 이 기술은 의존성 혼란(dependency confusion) 또는 슬롭스쿼팅(slopsquatting)으로 알려져 있습니다. AI가 제안한 패키지를 기대하며 pip install fastapi_auth_middleware를 실행하는 개발자는 공격자의 코드를 설치하게 됩니다.
해결책은 패키지를 사용하기 전에 PyPI에서 모든 패키지를 검증하고, 유지보수 이력이 확실한 패키지를 사용하며, 스캐너(scanner)를 통해 해결할 수 없는 임포트가 프로덕션(production) 환경에 도달하기 전에 식별하는 것입니다. BrassCoders의 팬텀-API 탐지기(phantom-API detector)는 임포트를 PyPI와 교차 참조하여 해결되지 않는 패키지를 식별합니다.
패턴 5 — O(N²) 응답 생성
BrassCoders의 성능 스캐너는 Python에서 두 가지 특정 O(N²) 패턴을 식별합니다: 루프 내부의 results += [item]와 루프 내부의 list.insert(0, item)입니다. 이 두 패턴은 AI가 생성한 리스트 구축 코드에서 일관되게 나타납니다.
생성된 코드는 다음과 같습니다:
from fastapi import FastAPI
from sqlalchemy.orm import Session
...
또는 리스트를 역순으로 만드는 변형 방식입니다:
ordered = []
for item in db.query(Item).order_by(Item.created_at.desc()).all():
ordered.insert(0, item.to_dict())
...
results += [item]은 매 반복(iteration)마다 새로운 리스트를 생성합니다. insert(0, ...)는 매 반복마다 기존의 모든 요소를 오른쪽으로 이동시킵니다. 10개의 행을 반환하는 데이터베이스에서는 둘 다 문제가 되지 않습니다. 하지만 사용자 테이블이나 이벤트 피드에서 흔히 볼 수 있는 100,000개의 행을 반환하는 데이터베이스에서는 두 방식 모두 이차 시간(quadratic time) 및 메모리 동작을 보입니다. 특히 insert(0, ...) 방식은 매우 느립니다. 호출당 O(N)이며, 전체적으로는 O(N²)입니다.
해결 방법은 간단합니다:
# += 패턴에 대한 수정
results = [{"id": user.id, "email": user.email} for user in users]
...
BrassCoders의 성능 스캐너는 루프 내부의 += [item]과 insert(0, ...)를 모두 잡아내어 YAML 출력 결과에 성능 탐지 사항(performance findings)으로 보고합니다.
FastAPI 프로젝트에서 BrassCoders 실행하기
BrassCoders는 Bandit 보안 규칙, Yelp의 detect-secrets 자격 증명 패턴, 그리고 AI가 유발하는 안티 패턴(anti-patterns)을 위한 세 가지 커스텀 BrassCoders 탐지기를 결합하여, 위에서 언급한 다섯 가지 패턴을 단 한 번의 오프라인 스캔으로 모두 다룹니다. 스캔 결과는 .brass/ai_instructions.yaml로 생성되며, 이는 Claude Code나 Cursor에 직접 붙여넣을 수 있도록 형식이 지정된 순위별 탐지 목록입니다.
설치 및 실행:
pip install brasscoders
brasscoders --offline scan .
--offline 플래그는 외부 네트워크 호출을 전혀 허용하지 않습니다. 오픈 소스(OSS) 코어는 Apache 2.0 라이선스를 따르며, 계정 생성이나 텔레메트리(telemetry) 전송이 필요하지 않습니다.
더 큰 프로젝트를 위해, BrassCoders 유료 버전($12/month)은 파일 간의 유사한 탐지 결과를 제거하여 분류(triage) 전 스캔 출력물을 더욱 줄여주는 의미론적 중복 제거(semantic deduplication) 단계를 추가합니다. coppersun.dev/pricing에서 구독한 후 brasscoders activate로 활성화할 수 있습니다.
위의 다섯 가지 패턴은 AI 특화적입니다. 이것들은 FastAPI의 버그도 아니고 개발자의 실수도 아닙니다. 튜토리얼 데이터로 학습된 모델이 스캐너를 루프에 포함하지 않은 채 프로덕션 코드(production code)를 생성할 때 발생하는 현상입니다.
스캐너를 추가하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기