package.json의 diff에서 당신이 한 번도 입력해 본 적 없는 이름, 즉 에이전트가 요약 끝부분 근처에서 언급하는 작은 날짜 파싱 라이브러리 하나를 발견했다. 버그는 수정되었고 테스트는 통과했다. 하지만 lockfile은 수백 줄 늘어났다.
당신은 그 패키지를 누가 게시했는지, 언제 처음 나타났는지, 또는 설치할 때 무엇을 실행하는지 알지 못한다. USENIX Security 2025 논문에서는 16개 모델로 576,000개의 코드 샘플을 생성했으며, 상업용 모델이 제안한 패키지 중 최소 5.2%가 존재하지 않았음(오픈 소스 모델의 경우 21.7%)을 발견했다. 이는 가짜 이름을 누구나 등록할 수 있게 만든다. 그리고 2026년 3월 31일 (UTC)에, 주간 다운로드 수가 1억 개가 넘는 라이브러리에 두 개의 오염된 axios 릴리스가 배포되었다. Trend Micro에 따르면,
에이전트의 작업 트리(worktree)에서, 해당 브랜치가 체크아웃된 상태로 이 명령을 실행하세요. git diff의 세 점 표기법은 해당 브랜치가 main에서 분기된 이후 변경된 내용만을 보여줍니다:
git diff --stat main...HEAD
# dependencies 또는 devDependencies 아래의 새 이름들
...
마지막 grep이 가장 중요합니다. lockfile에서 hasInstallScript는 "패키지에 preinstall, install, 또는 postinstall 스크립트가 있음을 나타내는 플래그"입니다. 이 값이 어떤 내용을 출력하든, 전체 diff에서 해당 항목을 찾아보세요.
직접 의존성 하나만 가져와도 자체적인 종속성을 포함하기 때문에 lockfile 목록은 더 길 것이라고 예상해야 합니다. axios 공격이 바로 여기에 있었습니다: package.json에는 새로운 내용이 없지만, lockfile에 새로운 간접(transitive) 이름이 생겼습니다. 만약 에이전트가 아직 커밋하지 않았다면, 점들(git diff main -- package.json)을 제거하여 작업 트리를 main과 비교하세요.
npm 12에서는 package.json diff에서 새로운 allowScripts 키도 확인해야 합니다. 이 키는 어떤 종속성이 설치 스크립트를 실행할 수 있는지 나열하며, npm install-scripts approve가 여기에 기록합니다. 만약 에이전트가 승인한 것이 있다면, 그 줄을 읽어볼 수 있습니다.
npm ci 및 npm explain: 아무것도 실행하지 않고 설치하기
npm ci --ignore-scripts
npm explain name-from-the-diff
npm ci는 node_modules를 삭제하고 lockfile에 명시된 내용만을 정확하게 설치합니다. 이 명령어는 "절대로 package.json이나 package-lock.json을 수정하지 않으며," 만약 두 파일이 불일치하면 오류로 종료되는데, 이는 lockfile이 재생성되지 않았음에도 package.json이 변경되었음을 알려줍니다. ignore-scripts는 "npm이 package.json 파일에 지정된 스크립트를 실행하지 않음"을 의미하므로, 패키지를 검토한다고 해서 그것이 실제로 실행되는 것은 아닙니다. npm 12는 이미 승인되지 않은 종속성 스크립트를 차단하며, 이 플래그 또한 에이전트가 승인한 모든 스크립트를 건너뜁니다.
npm explain은 주어진 패키지가 설치되는 원인(dependency chain)을 출력합니다. 직접적인 의존성(direct dependency)의 경우 체인은 프로젝트에서 끝납니다. 간접적인 의존성의 경우 부모(parent)를 명시하는데, 예를 들어 plain-crypto-js는 axios를 지목했을 것이며, axios 소스 코드는 이를 가져온 적이 없습니다.
npm view: 연령, 소유자 및 출처 확인
npm view는 레지스트리(registry)의 메타데이터를 읽어오며, 마침표(.)로 하위 필드를 선택합니다:
PKG=diff에서 가져온 이름
VER=lockfile에서 가져온 버전
...
답변을 해석하는 방법:
time.created 및 time[$VER]: 지난주에 처음 게시되었고, 한두 개의 버전을 가진 패키지는 더 신중하게 살펴볼 가치가 있습니다. 마찬가지로 axios가 이를 의존하기 시작했을 때의 [email protected]처럼 몇 분밖에 안 된 버전도 그렇습니다.
maintainers 및 repository.url: 누가 게시할 수 있는지, 그리고 코드가 있다고 추정되는 위치입니다. 레포지토리 필드는 게시자가 입력한 것이므로 열어보고 $VER와 일치하는 태그가 있는지 확인해야 합니다. Trend Micro는 axios 레포에서 양쪽 오염된 버전 모두에 대해
npm pack은 레지스트리 이름과 버전을 받아들이며, dry-run을 사용하면 npm이 "수행할 작업만 보고"하기 때문에 파일을 저장하지 않고도 타르볼(tarball)의 파일들을 볼 수 있습니다. 이 타르볼은 node_modules에 배치되는 것이며, 레포지토리와 다를 수 있습니다. 날짜 라이브러리는 네이티브 바이너리나 소스 코드가 없는 최소화된 블롭(minified blob), 또는 axios 페이로드의 형태인 postinstall에 연결된 setup.js 같은 것을 배포할 이유가 없습니다.
npm audit signatures는 설치된 모든 패키지의 레지스트리 서명(registry signature)을 확인하며, "다운로드된 패키지의 출처 증명서(provenance attestations)도 검증"하므로 npm ci 단계에서 나온 node_modules가 필요합니다. npm의 출처 증명 가이드는 출력 형태를 보여주는데, "1267개의 패키지가 검증된 레지스트리 서명을 가지고 있음", "74개의 패키지가 검증된 출처 증명서를 가지고 있음"과 같습니다. 서명 문서에 따르면, CLI는 패키지가 서명을 제공하는 레지스트리에서 서명이 부족할 경우 오류를 발생시키는데, 이는 "공격자가 서명 검증을 우회하려고 시도하고 있을 수 있음"을 의미할 수 있습니다.
출처 증명(Provenance)은 어떤 저장소와 빌드가 특정 버전을 생성했는지 알려주며, 이것이 바로 오염된 axios 릴리스가 부족했던 정보입니다. 해당 코드를 실행하는 것이 안전한지는 여전히 사용자의 판단에 달려 있습니다.
리뷰 데스크에서 확인하세요
저는 Hermes IDE를 구축했으므로 이 부분을 염두에 두고 읽어보시기 바랍니다. Hermes는 Claude Code나 Codex와 같은 각 에이전트를 자체 터미널의 고유한 hermes/ 브랜치 및 작업 디렉토리에서 실행하며, 위 명령어들은 그곳에서 실행됩니다.
⌘G (Windows 및 Linux에서는 Ctrl+Shift+G)를 누르면 Review Desk가 열립니다. README에 따르면, 이곳은 '턴별 또는 파일별로 위험 플래그와 함께 변경 사항을 보여줍니다.' By turn 뷰에서 package-lock.json의 변경 사항은 해당 변경을 만든 턴 아래에 나타납니다.
The Hermes Review Desk in By turn view, listing two agent turns with their file and line counts, the diff of turn 1, the changed-files list and a drafted commit message
orders-api의 Claude Code 태스크에 대한 Review Desk (⌘G): 두 개의 턴(T1: 3개 파일 +69 −1, T2: 1개 파일 +1 −0), src/db.ts의 턴 1 차이점, 변경된 파일 및 해당 턴에서 작성된 커밋 메시지.
모든 에이전트 턴은 저장되므로, 되돌리기(undo) 기능을 사용하여 해당 단일 턴을 비교하거나 복원할 수 있습니다. 또는 package.json 라인에 댓글을 남겨서 누가 작성했는지 추적할 수도 있습니다: '내장된 Date 및 Intl API를 사용하고, 새로운 의존성은 없습니다.'
사용자 자신의 검사로도 태스크가 완료되었는지 결정할 수 있으며, worktree recipes doc에서 리포지토리별 설정을 다루고 있습니다. npm test와 npm audit signatures는 이러한 검사에 좋은 후보입니다. 이후 커밋을 하거나, 풀 리퀘스트를 열거나, 태스크가 시작된 브랜치에 로컬로 병합할 수 있으며, 이는 되돌릴 수 있습니다.
Hermes는 BSL 1.1하에 무료로 소스 코드를 공개하며, macOS, Windows 및 Linux에서 사용할 수 있습니다. 현재 릴리스 버전은 2026년 10월 15일 기준 2.3.4입니다. Anthropic, OpenAI, Nous Research 또는 기타 에이전트 공급업체와는 관련이 없습니다. 다운로드하거나 GitHub에서 코드를 읽어보세요.
다음 작업을 더 일찍 중단하기
이 모든 과정은 에이전트가 npm install을 실행한 후에 발생합니다. npm 11에서는 의존성 설치 스크립트가 기본적으로 실행되므로, 검토하는 시점에는 이미 악의적인 postinstall이 실행된 상태입니다. 커밋된 .npmrc에 두 줄만 추가하면 이 간격을 좁힐 수 있습니다.
ignore-scripts=true
min-release-age=7
`min-release-age`는 2026년 2월 11일 [npm 11.10.0](https://github.com/npm/cli/releases/tag/v11.10.0)에 도입되었으며, 주어진 일수보다 오래된 버전만 설치합니다. 7일을 설정하면 오염된 axios 버전과 `[email protected]` 모두를 새로 설치하는 과정에서 제외할 수 있습니다. `ignore-scripts`는 설치 시 네이티브 코드를 컴파일하는 특이한 패키지에서는 문제가 발생할 수 있으니, 해당 패키지들은 하나씩 처리해야 합니다.
2026년 7월 8일에 출시된 [npm 12](https://github.com/npm/cli/releases/tag/v12.0.0)는 기본적으로 "루트 패키지의 allowScripts 정책에 의해 허용되지 않는 한" 의존성 라이프사이클 스크립트를 차단합니다. 이 결정은 `package.json`에 위치하며, 여기서 `git diff` 검사가 이를 확인합니다. 참고로, `AGENTS.md` 파일에 한 줄을 추가하여 의존성을 추가하기 전에 질문하도록 요청해 보세요.
에이전트가 아무도 요청하지 않은 패키지를 추가했을 때 팀은 어떻게 대응하나요? 댓글로 알려주세요.
## 유용했다면
위의 모든 검사는 에이전트의 행동에 대해 하나의 질문을 던집니다: 무엇을 변경했고, 되돌릴 수 있습니까? _AI That Acts_는 빌더(builder) 측면에서 이 질문을 합니다: TypeScript를 사용한 툴 호출(tool calling) 및 함수 기능까지, 설정된 한계 내에서 작동하는 최초의 에이전트까지 다룹니다. 만약 에이전트가 `npm install`을 실행하는 것을 보고 그 툴 호출이 어떻게 연결되는지 궁금했다면, 거기서부터 시작해 보세요.
[](https://www.amazon.com/dp/B0HBTDV75G)
이는 _AI in TypeScript_의 3번째 책이며, 최초의 LLM 호출부터 프로덕션 환경의 에이전트에 이르기까지 총 다섯 권의 시리즈입니다: [전체 시리즈](https://xgabriel.com/ai-in-typescript). 그리고 매일 에이전트 작업을 검토한다면, [Hermes IDE](https://hermes-ide.com/download)는 변경 사항을 적용하기 전에 각 에이전트의 변화를 단계별로 보여줍니다.