AI가 추천한 패키지가 진짜 있는지 확인하기: requirements·package.json 검증 스크립트
이 글의 핵심
AI가 적어 준 의존성 이름이 레지스트리에 없을 수 있습니다. 설치 전에 PyPI와 npm에 이름이 실제로 있는지, 몇 개 버전이 있고 언제 처음 올라왔는지를 조회하는 48줄짜리 스크립트와 실행 결과를 정리했습니다. 이름이 있다는 사실이 안전하다는 뜻은 아니라는 한계도 함께 적었습니다.
AI 코딩 도구에게 “JWT 인증이 있는 API 서버를 만들어 줘”라고 시키면 requirements.txt나 package.json까지 같이 써 준다. 그 목록의 이름이 모두 실제 패키지라는 보장은 없다. 설치하기 전에 이름이 레지스트리에 있는지 확인하는 일은 몇 줄이면 된다. 이 글은 그 확인 스크립트를 만들고, 실제로 돌린 결과와 한계를 적는다.
확인한 환경은 macOS의 Python 3.9.6, pip 25.0, npm 11.19.1이다. 조회는 2026-11-11에 PyPI와 npm 공개 레지스트리에 직접 했다.
왜 이름부터 확인하나
AI는 그럴듯한 이름을 만들어 낼 수 있다. fastapi-easy-auth-helper처럼 라이브러리 이름 규칙에 맞는 문자열은 실재 여부와 무관하게 자연스럽게 나온다. 이름이 없으면 설치가 실패해서 바로 알 수 있다. 위험한 쪽은 반대다. 누군가 그럴듯한 이름을 미리 등록해 두었다면 설치가 성공한다. 그래서 설치 명령을 실행하기 전에 이름을 따로 조회해 본다.
없는 이름을 설치하려고 하면 실제로는 이렇게 끝난다.
$ python3 -m pip install --dry-run fastapi-easy-auth-helper
ERROR: No matching distribution found for fastapi-easy-auth-helper
$ npm view express-rate-limit-redis-lite version
npm error code E404
npm error 404 Not Found - GET https://registry.npmjs.org/express-rate-limit-redis-lite - Not found
확인 스크립트
PyPI는 https://pypi.org/pypi/<이름>/json, npm은 https://registry.npmjs.org/<이름>이 JSON을 돌려준다. 없는 이름이면 404다. 표준 라이브러리만 쓴다.
#!/usr/bin/env python3
"""requirements.txt / package.json 의존성이 레지스트리에 실제로 있는지, 얼마나 오래됐는지 확인한다."""
import json, sys, re, urllib.request, urllib.error
from datetime import datetime, timezone
def get(url):
req = urllib.request.Request(url, headers={"User-Agent": "pkg-check/0.1"})
try:
with urllib.request.urlopen(req, timeout=20) as r:
return r.status, json.load(r)
except urllib.error.HTTPError as e:
return e.code, None
def age_days(iso):
t = datetime.fromisoformat(iso.replace("Z", "+00:00"))
return (datetime.now(timezone.utc) - t).days
def pypi(name):
st, d = get(f"https://pypi.org/pypi/{name}/json")
if st != 200:
return st, None
files = [f for fs in d["releases"].values() for f in fs]
first = min(f["upload_time_iso_8601"] for f in files) if files else None
return 200, {"versions": len(d["releases"]), "latest": d["info"]["version"],
"first_upload_days": age_days(first) if first else None}
def npm(name):
st, d = get(f"https://registry.npmjs.org/{name.replace('/', '%2F')}")
if st != 200:
return st, None
return 200, {"versions": len(d["versions"]), "latest": d["dist-tags"].get("latest"),
"first_upload_days": age_days(d["time"]["created"])}
def main(path):
if path.endswith("package.json"):
j = json.load(open(path)); names = list({**j.get("dependencies", {}), **j.get("devDependencies", {})})
look = npm
else:
names = [re.split(r"[=<>~!\[; ]", l.strip())[0] for l in open(path) if l.strip() and not l.startswith("#")]
look = pypi
for n in names:
st, info = look(n)
if info is None:
print(f"{n:35s} 없음 (HTTP {st})")
else:
print(f"{n:35s} 있음 버전 {info['versions']}개, 최신 {info['latest']}, 첫 업로드 {info['first_upload_days']}일 전")
main(sys.argv[1])
실행 결과
requirements.txt에는 실제 패키지 4개와, 내가 지어낸 이름 2개, 철자를 틀린 이름 1개를 섞었다. 지어낸 이름은 AI가 만든 것이 아니라 이 실험을 위해 내가 만든 문자열이다.
$ python3 check_pkgs.py requirements.txt
requests 있음 버전 163개, 최신 2.34.2, 첫 업로드 5748일 전
flask 있음 버전 64개, 최신 3.1.3, 첫 업로드 6052일 전
flask-jwt-extended 있음 버전 92개, 최신 4.7.4, 첫 업로드 3702일 전
python-dotenv 있음 버전 52개, 최신 1.2.4, 첫 업로드 4446일 전
fastapi-easy-auth-helper 없음 (HTTP 404)
pydantic-settings-vault-loader 없음 (HTTP 404)
reqeusts 없음 (HTTP 404)
package.json도 같은 방식으로 돌렸다.
$ python3 check_pkgs.py package.json
express 있음 버전 290개, 최신 5.3.0, 첫 업로드 5795일 전
zod 있음 버전 1013개, 최신 4.6.7, 첫 업로드 2439일 전
express-rate-limit-redis-lite 없음 (HTTP 404)
@types/node 있음 버전 2382개, 최신 26.6.6, 첫 업로드 3829일 전
스코프 패키지(@types/node)는 슬래시를 %2F로 바꿔야 조회된다. 위 코드에서 name.replace('/', '%2F')가 그 일을 한다.
결과를 읽는 방법
- 없음이 나온 이름은 설치하지 않는다. AI에게 어떤 라이브러리를 의도했는지 물어서 실제 이름을 받거나, 공식 문서에서 직접 찾는다.
- 있음이어도 버전 수와 첫 업로드 시점을 본다. 설치하려는 라이브러리가 오래 쓰이던 것이라면 버전이 수십 개이고 첫 업로드가 몇 년 전이다. 버전이 1~2개이고 첫 업로드가 며칠 전인 이름은 이름만 선점한 것일 수 있어서 따로 확인한다.
- 위 출력에서
reqeusts는requests의 철자 오타인데 PyPI에 없었다. 이 이름이 앞으로도 비어 있을지는 알 수 없다. 오타 이름은 언제든 누군가 등록할 수 있다.
한계
- 이 스크립트는 이름의 실재 여부와 업로드 이력만 본다. 그 패키지가 내가 원한 라이브러리인지, 악성 코드가 들어 있는지는 판별하지 못한다.
- 버전 수가 많고 오래된 이름도 하이재킹 등으로 나중에 바뀔 수 있다. 의존성을 고정(lock 파일)하고 변경 시 검토하는 절차가 따로 필요하다.
- 사설 레지스트리에만 있는 패키지는 공개 레지스트리에서 없음으로 나온다. 사내 패키지는 대상에서 빼야 한다.
- 이 실험에서 AI가 실제로 존재하지 않는 이름을 얼마나 자주 내놓는지는 측정하지 않았다. 빈도는 모델과 프롬프트에 따라 다를 것이고, 이 글은 그 수치를 주장하지 않는다.
쓰는 순서
- AI가 만든
requirements.txt나package.json을 설치하기 전에 스크립트를 돌린다. - 없음이 하나라도 있으면 그 이름만 AI에게 되물어 실제 이름을 정한다.
- 처음 보는 이름은 공식 문서나 저장소 링크로 대조한다.
- 설치 후에는 lock 파일을 커밋해서 같은 버전이 다시 설치되게 한다.