Claude Code한테 "이거 구현해줘" 했더니 import fastjson 썼습니다. PyPI에서 설치하려는데 진짜 있더라고요. 근데 어제 공격자가 등록한 겁니다. 이게 Slopsquatting입니다.
[핵심 요약]
→ Slopsquatting: AI가 환각한 패키지 이름을 공격자가 선점 등록하는 공급망 공격
→ 용어 창시: Python Software Foundation 보안 연구원 Seth Larson
→ 규모: 16개 AI 코딩 모델 테스트 → 생성 코드의 약 20%에 환각 패키지 포함
→ 반복성: 환각 패키지 이름의 58% 이상이 여러 번 반복 → 공격자가 예측 가능
→ 실제 사례: react-codeshift (jscodeshift + react-codemod 합성) — 2026년 1월
→ 타겟: npm(JS), PyPI(Python), RubyGems 등 공개 패키지 레지스트리
→ 위험: CI/CD 파이프라인 자동 설치 → 탐지 전 프로덕션 배포
왜 지금 위험한가
기존 타이포스쿼팅:
→ 공격자가 패키지 이름 직접 추측
→ 개발자가 오타 낼 확률에 의존
→ 방어: 패키지 이름 주의해서 타이핑
Slopsquatting:
→ AI가 환각 패키지를 자신 있게 추천
→ 개발자는 AI 코드를 그대로 복붙
→ 패키지 이름이 그럴듯하게 보임
→ 공격자는 AI 출력 모니터링만 하면 됨
→ Vibe Coding 확산 = 공격 표면 폭발적 증가
2026년 1월 실제 사례, Aikido 보안 연구원 Charlie Eriksen이 발견했습니다.
react-codeshift 사건:
→ LLM이 jscodeshift + react-codemod 두 패키지를 합성
→ react-codeshift라는 없는 패키지 이름 생성
→ AI가 생성한 에이전트 스킬 47개 커밋에서 발견
→ 패키지 없다가 → 공격자 등록 → 실행 시 악성코드
실전 1 — 환각 패키지 탐지 스크립트
AI가 생성한 코드의 패키지를 설치 전에 검증합니다.
# check_packages.py
import subprocess
import sys
import re
import requests
from pathlib import Path
def extract_imports(code: str) -> list[str]:
"""코드에서 import 패키지명 추출"""
patterns = [
r'^import\s+([\w\-]+)',
r'^from\s+([\w\-]+)\s+import',
r'pip install ([\w\-]+)',
r'npm install ([\w\-]+)',
]
packages = set()
for line in code.split('\n'):
for pattern in patterns:
match = re.match(pattern.strip(), line.strip())
if match:
packages.add(match.group(1))
return list(packages)
def verify_pypi(package: str) -> dict:
"""PyPI에 패키지 존재 여부 + 메타데이터 확인"""
try:
res = requests.get(
f"https://pypi.org/pypi/{package}/json",
timeout=5
)
if res.status_code == 404:
return {"exists": False, "package": package}
data = res.json()
info = data["info"]
releases = data["releases"]
return {
"exists": True,
"package": package,
"version": info["version"],
"author": info["author"],
"release_count": len(releases),
# ⚠️ 릴리즈가 1~2개면 최근 등록 의심
"suspicious": len(releases) <= 2,
"pypi_url": f"https://pypi.org/project/{package}/",
}
except Exception as e:
return {"exists": False, "package": package, "error": str(e)}
def verify_npm(package: str) -> dict:
"""npm에 패키지 존재 여부 확인"""
try:
res = requests.get(
f"https://registry.npmjs.org/{package}",
timeout=5
)
if res.status_code == 404:
return {"exists": False, "package": package}
data = res.json()
versions = list(data.get("versions", {}).keys())
return {
"exists": True,
"package": package,
"latest": data.get("dist-tags", {}).get("latest"),
"version_count": len(versions),
"suspicious": len(versions) <= 2,
"npm_url": f"https://www.npmjs.com/package/{package}",
}
except Exception:
return {"exists": False, "package": package}
def audit_ai_code(code: str, ecosystem: str = "pypi") -> None:
"""AI 생성 코드의 패키지 전부 검증"""
packages = extract_imports(code)
print(f"\n🔍 {len(packages)}개 패키지 검증 중...\n")
dangerous = []
suspicious = []
for pkg in packages:
if ecosystem == "pypi":
result = verify_pypi(pkg)
else:
result = verify_npm(pkg)
if not result["exists"]:
dangerous.append(pkg)
print(f"❌ {pkg} — PyPI/npm에 존재하지 않음 (환각 패키지 의심)")
elif result.get("suspicious"):
suspicious.append(pkg)
print(f"⚠️ {pkg} — 릴리즈 수 적음, 최근 등록 가능성")
else:
print(f"✅ {pkg} — 정상")
print(f"\n{'='*40}")
print(f"❌ 위험 패키지: {dangerous}")
print(f"⚠️ 의심 패키지: {suspicious}")
if dangerous or suspicious:
print("\n⛔ 설치 전 수동 검증 필요!")
sys.exit(1)
# 사용 예시
ai_generated_code = """
import fastjson
from aws-helper-sdk import S3Client
import react-codeshift
"""
audit_ai_code(ai_generated_code, ecosystem="pypi")
[탐지 기준]
→ 존재하지 않는 패키지: 즉시 차단
→ 릴리즈 1~2개: 최근 등록 의심 → 수동 검토
→ 작성자 정보 없음: 의심
→ GitHub 링크 없음: 의심
→ 다운로드 수 0: 신규 등록 가능성 높음
실전 2 — CI/CD 파이프라인 방어
# .github/workflows/security-check.yml
name: Slopsquatting 방어
on:
pull_request:
paths:
- 'requirements*.txt'
- 'package*.json'
- '*.py'
- '*.ts'
- '*.js'
jobs:
package-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Python 패키지 검증
run: |
pip install requests
python check_packages.py requirements.txt
- name: npm 패키지 검증
run: |
# 존재하지 않는 패키지 설치 시도 차단
npm install --dry-run 2>&1 | grep -i "404\|not found" && exit 1 || true
- name: pip-audit 실행
run: |
pip install pip-audit
pip-audit -r requirements.txt
- name: Socket 보안 스캔 (npm)
uses: nicolo-ribaudo/npm-package-socket-security-check@v1
# Socket은 환각 패키지 전용 탐지 DB 보유
# pre-commit hook — 커밋 전 자동 검사
# .git/hooks/pre-commit
#!/usr/bin/env python3
import subprocess
import sys
def check_new_imports():
"""스테이징된 파일에서 새로 추가된 import 검사"""
result = subprocess.run(
['git', 'diff', '--cached', '--name-only'],
capture_output=True, text=True
)
changed_files = result.stdout.strip().split('\n')
for file in changed_files:
if file.endswith('.py'):
diff = subprocess.run(
['git', 'diff', '--cached', file],
capture_output=True, text=True
).stdout
# 새로 추가된 import 줄만 추출
new_imports = [
line[1:] for line in diff.split('\n')
if line.startswith('+import ') or line.startswith('+from ')
]
if new_imports:
print(f"⚠️ {file}에 새 import 발견:")
for imp in new_imports:
print(f" {imp}")
print("PyPI/npm 존재 여부를 확인하세요.")
check_new_imports()
[CI/CD 방어 레이어]
→ PR 단계: requirements.txt, package.json 변경 시 자동 검증
→ 커밋 단계: pre-commit hook으로 새 import 경고
→ 빌드 단계: pip-audit, npm audit 실행
→ 런타임 단계: Socket, Snyk으로 실시간 모니터링
→ 핵심: 자동 설치 파이프라인에 검증 게이트 추가
실전 3 — Claude Code / Cursor 사용 시 방어 설정
AI 코딩 툴 자체에서 환각 패키지를 줄이는 설정입니다.
# CLAUDE.md — 패키지 환각 방지 규칙
## 패키지 사용 규칙
새 패키지를 추천하기 전에 반드시:
1. 해당 패키지가 실제 존재하는지 확인
2. PyPI(https://pypi.org) 또는 npm(https://npmjs.com)에서 검색
3. 공식 문서 링크 제공
4. 마지막 업데이트 날짜 확인
절대 하지 말 것:
- 확인하지 않은 패키지 이름 사용
- 두 패키지를 합성한 이름 생성 (예: requests + httpx → requestx)
- 패키지 존재 여부 확실하지 않으면 표준 라이브러리 사용
패키지 추천 시 반드시 포함:
- PyPI/npm URL
- GitHub 저장소 링크
- 주간 다운로드 수 (인기도 확인용)
# pip 설치 전 확인 스크립트
# 팀 공통 alias로 등록
# ~/.bashrc 또는 ~/.zshrc
alias safe-pip='python -c "
import sys, requests
pkg = sys.argv[1]
r = requests.get(f\"https://pypi.org/pypi/{pkg}/json\")
if r.status_code == 404:
print(f\"❌ {pkg} — PyPI에 없는 패키지!\")
sys.exit(1)
data = r.json()
releases = len(data[\"releases\"])
print(f\"✅ {pkg} v{data[\"info\"][\"version\"]} — {releases}개 릴리즈\")
if releases <= 2:
print(\"⚠️ 릴리즈 수 적음 — 수동 검토 권장\")
input(\"계속하려면 Enter...\")
" "$1" && pip install "$1"'
# 사용: safe-pip requests
[AI 코딩 툴별 환각 위험도]
→ Claude Code (Sonnet 4.x): 낮음 — 최신 모델, 환각률 상대적으로 낮음
→ GitHub Copilot: 중간
→ 오픈소스 모델 (CodeLlama 등): 높음 — 연구에서 환각률 가장 높음
→ 공통: 어떤 모델도 100% 안전하지 않음 → 항상 검증 필요
마무리
✅ 반드시 해야 하는 것
→ AI 생성 코드의 패키지 PyPI/npm에서 수동 검색
→ CI/CD에 패키지 검증 게이트 추가
→ CLAUDE.md/CURSOR.md에 패키지 확인 규칙 명시
→ pip install 전 릴리즈 수·다운로드 수 확인
→ Socket, Snyk 같은 공급망 보안 도구 도입
❌ 절대 하면 안 되는 것
→ AI 추천 패키지 아무 확인 없이 바로 설치
→ CI/CD에서 패키지 자동 설치 후 검증 없이 배포
→ 릴리즈 1~2개짜리 신규 패키지 무조건 신뢰
→ "유명한 패키지 이름 같으니까 괜찮겠지" 판단
→ Vibe Coding 결과물을 검토 없이 프로덕션 배포
관련 글
https://cell-devlog.tistory.com/161
5개국 "에이전트 AI 보안 가이드" 완전 분석 — 정부가 경고한 AI 에이전트 5가지 위험과 개발자 체
CISA, NSA, 영국, 호주, 캐나다, 뉴질랜드가 함께 경고했습니다. AI 에이전트는 이미 핵심 인프라에서 돌아가고 있고, 대부분의 조직이 아무도 실시간으로 감사할 수 없을 만큼 많은 권한을 줬다고.[
cell-devlog.tistory.com
https://cell-devlog.tistory.com/98
AI 코딩 툴 보안 실전 — Claude Code 소스 유출 이후 달라진 공격 지형
2026년 3월 31일, Claude Code 소스코드가 npm에 실수로 노출됐어요.유출 규모:→ 51만 2천 줄 TypeScript→ 1,906개 파일→ 24시간 만에 GitHub 포크 41,500개공격자들의 반응 속도:→ 유출 24시간 이내: 악성 "leak
cell-devlog.tistory.com
https://cell-devlog.tistory.com/27
AI 에이전트 보안 완전 정리 — Prompt Injection 공격과 방어 완전 가이드
AI 에이전트를 프로덕션에 올리면 이런 일이 생겨요."에이전트가 갑자기 이상한 행동을 해. 아무도 그런 지시를 안 했는데."이건 버그가 아니에요. 공격이에요. OWASP 2025 LLM Top 10에서 Prompt Injection
cell-devlog.tistory.com
'AI Agent' 카테고리의 다른 글
| uv + Ruff 완전 가이드 — OpenAI가 인수한 Python 툴링의 새 표준 (0) | 2026.05.18 |
|---|---|
| Harness Engineering 완전 가이드 — AI가 더 잘 짜도록 환경 자체를 설계하는 법 (0) | 2026.05.18 |
| PydanticAI 완전 가이드 — LLM 출력을 문자열 말고 타입으로 받는 법 (0) | 2026.05.18 |
| Mastra AI 완전 가이드 — TypeScript로 AI 에이전트 만드는 LangChain 대안 (0) | 2026.05.18 |
| Firebase Genkit + MCP 완전 가이드 — Gemini에 외부 툴을 붙이는 Google 공식 방법 (0) | 2026.05.18 |