다음 명령어를 입력하셨습니다. npm install 을 수천 번이나 입력해 보셨을 겁니다. 아마 패키지 이름을 적어도 한 번은 잘못 입력한 적이 있을 텐데, 운 좋게도 문제가 없었을 수도 있습니다. 하지만 모든 사람이 그런 건 아닙니다. 공격자들은 바로 그 한 번의 실수—무의식적으로 입력하는 습관에 대한 믿음, 무심코 누르는 엔터 키—를 노립니다. 자바스크립트 개발을 이토록 빠르게 만들어 주는 바로 그 패키지 생태계가 타이포스쿼팅을 그토록 효과적으로 만드는 요인이기도 합니다.
이 글에서는 타이포스쿼팅이 무엇인지, 이를 어떻게 식별할 수 있는지, 그리고 여러분의 프로젝트를 보호하기 위해 실제로 취할 수 있는 조치에 대해 자세히 설명합니다.
타이포스쿼팅이란 무엇인가요?
타이포스쿼팅이란, 유명하고 합법적인 패키지 이름과 유사한 이름을 사용하여 악성 패키지를 배포하는 행위로, 개발자가 이름을 잘못 입력하거나, 잘못 읽거나, 신뢰할 수 없는 출처에서 복사하거나, 설치 전에 다시 확인하지 않을 것을 기대하는 것입니다.
몇 가지 일반적인 패턴은 다음과 같습니다:
- 캐릭터 교체 또는 누락/오타: lodash 가 lodahs 또는 loadsh
- 하이픈 활용 요령: babel-core vs babelcore
- 복수형 만들기: 색상 vs color
- 범위 및 네임스페이스 활용법: @types/react (실제 스코프가 지정된 패키지) 대 가짜이며 스코프가 지정되지 않은 types-react
- 동형어: 소문자 l 을 대문자 I로 바꾸거나, 다른 닮은 글자를 사용하는 것
이 패키지들을 설치하면, 실제 npm 패키지가 할 수 있는 모든 작업을 수행할 수 있습니다. 환경 변수를 읽거나, .env 파일이나 SSH 키를 유출하거나, 백도어를 설치하거나, 빌드 출력물에 악성 코드를 은밀하게 삽입하는 등 무엇이든 할 수 있습니다. npm 라이프사이클 스크립트는 설치 과정에서 실행될 수 있으므로, 코드를 확인하기도 전에 이미 피해가 발생할 수 있습니다.
타이포스쿼팅은 패키지에만 국한되지 않습니다. URL에서도 나타납니다. 예를 들어, guthub.com을 예로 들어 봅시다. “github.com”을 입력하려다가 실수로 “guthub.com”에 접속한 적이 있다면, 운이 좋았던 것입니다. 해당 도메인은 실제 사이트로 리디렉션되기 때문입니다. 하지만 이 리디렉션은 누군가가 친절하게 설정해 둔 것이지, 보장된 것은 아닙니다. 흔히 틀리기 쉬운 철자로 도메인을 등록한 사람이 그 도메인의 연결처를 제어하며, 만약 악의적인 세력이 이를 소유하고 있다면, 단 한 번의 키 입력 실수만으로도 자격 증명을 탈취하는 복제 사이트로 연결될 수 있습니다. 패키지 생태계에서는 이러한 위험이 훨씬 더 직접적입니다. 왜냐하면 같은 종류의 실수로 인해 실행 가능한 코드가 프로젝트로 바로 유입될 수 있기 때문입니다.
왜 하필 자바스크립트인가?
이는 자바스크립트에만 국한된 현상은 아니지만, npm 생태계는 특히 매력적인 표적이 됩니다. 레지스트리의 규모가 방대하고, 많은 패키지가 소규모 팀이나 개인에 의해 관리되며, 중첩된 전이적 의존성으로 인해 현대의 의존성 트리는 수백 개에서 수천 개에 달하는 패키지로 깊게 뻗어 있을 수 있습니다. 여기에 낮은 게시 장벽과 의존성 설치가 대개 대충 훑어보고 바로 진행하는 식이라는 점이 더해지면서, 타이포스쿼팅이 성공할 여지가 충분합니다.
패키지 관리자를 불문하고 동일한 위험이 따릅니다. 달라지는 것은 명령어뿐입니다:
yarn add package_name pnpm add 패키지명 bun add 패키지명
어떤 실을 사용하든 실, pnpm, 혹은 bun를 사용하든, 구문은 npm과 다릅니다. 이 세 가지 모두 add 를 사용하며 install대신add를 사용하지만, 모두 동일한 소스인 npm 레지스트리에서 패키지를 대신add를 사용합니다. 하지만 이 세 가지 모두 동일한 소스인 npm 레지스트리에서 패키지를 가져오기 때문에, 명령어는 달라지더라도 신뢰성 문제는 여전히 존재합니다. 어떤 패키지 관리자를 사용하든, 심지어 직접 npm install을 입력한 적이 전혀 없더라도, 오타가 난 패키지나 악의적으로 삽입된 패키지가 프로젝트에 유입될 수 있습니다.
또한 npm은 계정이 활동이 중단되었다는 이유만으로 유지보수자의 게시 권한을 박탈하지 않으며, 2026년 6월 발생한 Mastra 공격은 이러한 정책이 어떤 결과를 초래할 수 있는지 보여주었습니다. 공격자들은 @mastra 스코프에 대한 접근 권한이 한 번도 취소된 적이 없는 전직 Mastra 기여자의 계정을 탈취한 뒤, 인기 있는 dayjs 라이브러리의 타이포스쿼트인 ‘easy-day-js’를 140개 이상의 Mastra 패키지에 주입했습니다. 신뢰할 수 있는 스코프가 악성 유사 의존성을 전달하는 경로가 되어버린 것입니다.
타이포스쿼팅된 패키지를 식별하는 방법
경고 신호가 항상 뚜렷한 것은 아니지만, 몇 가지 확인 절차를 거치면 의심스러운 패키지가 프로젝트에 포함되기 전에 이를 파악하는 데 도움이 될 수 있습니다:
다운로드 횟수와 게시 내역을 확인해 보세요. “인기 있는” 라이브러리라고 주장하지만 다운로드 수가 수백 건에 불과하고 지난주에야 게시된 패키지는 의심스러운 신호입니다. npmjs.com에서 확인할 수 있는 실제 패키지의 통계와 비교해 보세요.
유지보수 담당자를 확인하세요. 게시 계정이 실제 프로젝트의 알려진 관리자와 일치하는지 확인하십시오. 완전히 새로운 계정, 생소한 관리자, 또는 의미 있는 게시 이력이 없는 패키지는 다시 한 번 살펴볼 필요가 있습니다.
package.json 파일을 주의 깊게 읽어보세요. 저장소 URL, 라이선스, 설명을 확인하세요. 타이포스쿼트는 종종 이러한 필드를 정식 패키지에서 그대로 복사해 오지만, 때로는 저장소 링크가 예상치 못한 곳으로 연결되거나 아예 누락되어 있는 경우도 있습니다.
설치 스크립트가 실행되기 전에 검사하십시오. 설치 스크립트가 실행되기 전에 검사하십시오. preinstall, install, 그리고 postinstall 훅은 설치 과정에서 코드를 실행할 수 있으므로 각별한 주의가 필요합니다. 단순한 유틸리티 라이브러리가 원격 URL에 접속하는 난독화된 postinstall 스크립트를 포함하고 있다면, 이를 심각한 경고 신호로 간주해야 합니다. 바로 이것이 Mastra 공격에서 easy-day-js 의존성이 작동한 방식이며, 이를 통해 2단계 페이로드를 가져와 실행했습니다.
추측에 의존하지 말고 공식 레지스트리 검색 기능을 이용하세요. 검색 npmjs.com에서 검색하거나, 프로젝트의 공식 문서에서 설치 명령어를 복사하거나, 기억에만 의존해 익숙하지 않은 패키지 이름을 직접 입력하기보다는 IDE의 자동 완성 기능을 활용하세요. 근육 기억은 잘못된 패키지를 설치하기 전까지만 유용합니다.
막판에 발생하는 종속성 변경 사항을 주의 깊게 살펴보세요. 만약 package-lock.json 또는 yarn.lock , 또는 pnpm-lock.yaml diff 결과에서 예상치 못한 종속성 이름 변경이 발견되면, 병합하기 전에 작업을 중단하고 원인을 조사하십시오. 팀원 중 누구도 의도적으로 도입하지 않은 새로 추가된 패키지의 경우에도 마찬가지입니다.
프로젝트를 위한 실용적인 방어 전략
수동 검토도 도움이 되지만, 그것만으로는 확장성이 떨어집니다. 더 효과적인 접근 방식은 팀이 의존성을 설치, 검토 및 승격하는 과정에 구조적인 안전 장치를 마련하는 것입니다:
의존성을 고정하세요. 다음 파일을 커밋하세요: package-lock.json 또는 yarn.lock 파일을 커밋하고 npm ci 명령어를 사용해야 합니다. npm install을 사용하는 대신 CI/CD 파이프라인에서npm ci를 사용하십시오. 이렇게 하면 빌드 시 설치 시점에 해결되는 임의의 버전이 아닌, 여러분이 검증한 정확한 버전이 사용되도록 보장됩니다.
자동화된 스캔 도구를 사용하세요. npm audit, Snyk, Socket, GitHub Dependabot 등은 통칭하여 소프트웨어 구성 분석(SCA) 도구라고 불리며, 알려진 취약점, 악성 패키지, 예상치 못한 설치 스크립트를 식별해 줍니다. 특히 Socket은 타이포스쿼팅(typosquatting)이나 악성 설치 후 훅(postinstall hook)과 같은 공급망 위험을 탐지하기 위해 특별히 개발되었습니다. 이 도구들 중 어느 하나도 모든 문제를 완벽하게 잡아내지는 못하지만, 함께 사용하면 팀이 위험이 프로덕션 환경에 도달하기 전에 이를 발견할 가능성을 훨씬 높여줍니다.
중요한 종속성에 대해서는 정확한 버전을 고정하십시오. 민감한 작업을 수행하는 패키지의 경우, 세미버(Semver) 범위(^ 또는 ~)를 지나치게 넓게 설정하지 마십시오. 그래야 자동 업데이트 시 보안 취약점이 있는 버전이 무심코 도입되는 것을 방지할 수 있습니다. 또한 버전을 고정하면 팀, 환경 및 CI/CD 파이프라인 전반에 걸쳐 패키지 동작의 일관성을 유지할 수 있습니다.
적절한 경우에는 기본적으로 스크립트를 비활성화하십시오.
적절한 경우 기본적으로 스크립트를 비활성화하십시오. 설치 시 다음 옵션을 사용하여 실행하십시오. –ignore-scripts 옵션을 사용하여 설치 작업을 실행한 다음, 실제로 빌드 단계가 필요한 패키지를 명시적으로 허용 목록에 추가하면 악의적인 postinstall 훅으로 인한 피해 범위를 줄일 수 있습니다. pnpm도 여기서 고려해 볼 만합니다. v10부터는 기본적으로 종속성 라이프사이클 스크립트를 차단하며, 허용 목록을 통해 이를 승인해야만 실행할 수 있도록 합니다.
실행 CI에서 명령어를 실행한 후, 설치 스크립트가 실제로 필요한 패키지를 명시적으로 허용 목록에 추가하면 악의적인 postinstall 훅의 영향 범위를 줄일 수 있습니다. 이는 pnpm을 활용하기에 아주 좋은 사례입니다. pnpm은 v10부터 기본적으로 설치 스크립트를 무시합니다. –ignore-scripts 플래그를 사용할 수 있으며, .npmrc 수준, 그리고 전역 수준에서 사용할 수 있습니다.
사설 레지스트리나 프록시를 도입하세요. Verdaccio, Sonatype Nexus, JFrog Artifactory 또는 npm의 자체 비공개 레지스트리 기능과 같은 도구를 사용하면, 개발자가 패키지를 사용하기 전에 팀이 패키지를 미러링하고, 승인하며, 관리할 수 있습니다. 이를 통해 의심스러운 유사 패키지가 개발자의 컴퓨터에 도달하기 전에 차단할 수 있는 점검 지점을 확보할 수 있습니다.
코드 검토의 일환으로 새로운 종속성을 검토하십시오. 새로운 패키지를 추가하는 것은 코드베이스에 대한 접근 권한을 가진 새로운 기여자를 추가하는 것과 마찬가지로 취급해야 합니다. 기능적으로 보면 사실상 그와 비슷한 작업이기 때문입니다. 새로운 패키지의 승인 절차, 검토자가 확인해야 할 사항, 팀이 설치할 수 있는 소스를 정의하는 보안 정책을 수립하십시오. SecureChain과 같은 도구는 검증된 오픈소스 패키지가 선별된 소스를 팀에 제공함으로써 이를 지원할 수 있습니다.
팀원들에게 교육을 실시하세요. 타이포스쿼팅 패턴에 대해 5분간만 이야기해도 막대한 손실을 초래할 수 있는 사고를 예방할 수 있습니다. package.json과 관련된 모든 직책의 온보딩 과정에 이를 포함시키세요. 팀 정책을 수립하고 보안 점검을 최우선 과제로 명시한 뒤, 이를 철저히 이행하십시오.
간단한 정신적 체크리스트
추가하기 전에 어떤 새로운 종속성을 추가하기 전에 다음을 자문해 보십시오:
- 이게 제가 실제로 입력하려고 했던 이름인가요?
- 다운로드 횟수와 게시 내역이 정상적으로 보이나요?
- 이 소스 저장소가 제가 알고 있거나 확인할 수 있는 프로젝트와 일치하나요?
- 설치 스크립트가 있나요? 그리고 그 스크립트가 무엇을 하는지, 왜 존재하는지, 그리고 꼭 필요한 것인지 제대로 이해하고 있나요?
- 제 스캔 도구가 뭔가 이상 징후를 감지했나요?
최종 생각
타이포스쿼팅이 효과적인 이유는 개발자들이 끊임없이 의존하는 ‘신뢰’와 ‘습관’이라는 두 가지 요소를 악용하기 때문입니다. 다행인 점은 이러한 위험을 줄이기 위해 워크플로우 전체를 대대적으로 개편할 필요는 없다는 것입니다.
근육 기억에 의존하는 방식에서 검증된 패키지 이름, 고정된 종속성 트리, 자동화된 스캔, 승인된 레지스트리로 전환하고, 새로운 프로그램을 설치하기 전에 건전한 의구심을 잠시 갖는 것만으로도 이러한 공격 표면의 대부분을 차단할 수 있을 것입니다.

