Transcript
00:00:00NPM 패키지를 설치한다면, 당신은 타깃입니다. 당장은 아니더라도,
00:00:05조만간 반드시 위험이 닥칠 겁니다. 지난 몇 달 동안에만 수백 개의 패키지가 해킹되었고,
00:00:09그 속도는 줄어들지 않고 있습니다. 그래서 이런 일이 터질 때마다 걱정하기보다는,
00:00:14직접 NPM, PNPM, BUN 환경 전반을 잠그기로 했습니다. 그런데 알고 보니
00:00:18당신을 구할 수 있는 대부분의 조치는 설정에 30초밖에 걸리지 않더군요. 그래서 이번 영상에서는
00:00:23가장 흔한 공격을 단 한 줄의 설정 변경으로 차단하는 방법부터,
00:00:27공격자들조차 인정하는, 패키지가 내 기기에 도달하기 전에 차단해 주는
00:00:30무료 도구까지, 7가지 변경 사항을 알려드리겠습니다. 시작해 보죠.
00:00:39첫 번째이자 가장 간단한 방법은 무조건적인 최신 패키지 다운로드를 멈추는 것입니다. 대부분의 공급망
00:00:44공격은 몇 시간 안에 드러납니다. 따라서 새로운 버전이 나오자마자 설치하지 않는 것만으로도,
00:00:48대부분의 공급망 공격을 완전히 피할 수 있습니다. 주요 Node.js
00:00:52패키지 매니저들은 이제 모두 릴리스 기간 제한(release age gating) 기능을 제공합니다. NPM의 경우 .npmrc
00:00:57파일에서 일(day) 단위로 설정합니다. PNPM은 전역 pnpm-config.yaml 파일에서 설정하거나,
00:01:03PNPM 워크스페이스 파일을 통해 프로젝트별로 설정할 수 있으며, 이 값은
00:01:08분(minute) 단위로 지정됩니다. PNPM 11부터는 기본값이 하루로 설정되어 있다는 점도 참고하세요.
00:01:14별도로 설정하지 않아도 어느 정도 보호가 됩니다. BUN은
00:01:17bunfig.toml 파일에서 전역 또는 프로젝트별로 설정하며, 여기는 초(second) 단위입니다.
00:01:23같은 설정인데 세 패키지 매니저의 단위가 제각각이라는 게 정말 놀랍습니다. 이것이 현재 생태계를
00:01:27아주 잘 보여주는 것 같네요. 설정을 마친 후,
00:01:32예를 들어 tanstack의 react-start를 설치하면, 최신 버전 대신
00:01:36제 설정(일주일 전)에 부합하는 최신 버전을 설치하게 됩니다.
00:01:41혹시라도 보안 이슈로 인해 즉시 최신 버전을 설치해야 한다면,
00:01:45명령줄에서 우회할 수 있습니다. 하지만 LLM 사용 시 주의하세요.
00:01:49LLM이 임의로 우회 설정을 적용해 최신 버전을 설치해 버리는 경우를 봤기 때문입니다. 또 기억해야 할 점은
00:01:54npx와 bunx는 이 설정을 무시하고 항상 최신 버전을 설치한다는 것입니다.
00:01:59이와 관련해 BUN에 수정 PR이 열려 있습니다. 이제 다른 설정으로 넘어가서,
00:02:03NPM의 설치 스크립트도 꺼버립시다. PNPM과 BUN은 기본적으로 이 기능을 차단하므로
00:02:07별도 설정이 필요 없습니다. 모르셨다면 알려드리죠. 패키지를 설치할 때,
00:02:12그 패키지는 설치 직후 자체 코드를 실행할 권한을 갖게 됩니다.
00:02:16이 기능은 네이티브 코드 컴파일이나 바이너리 다운로드 같은 정당한 용도로 만들어졌지만,
00:02:20문제는 거의 모든 공급망 공격이 이 메서드를 통해 설치 직후 악성 코드를 실행한다는 것입니다.
00:02:26기기에 악성 코드를 실행하는 것이죠.
00:02:30스크립트가 반드시 필요한 패키지가 있다면 개별적으로 허용할 수 있습니다. PNPM의 경우,
00:02:34설치 시 스크립트 실행이 필요한 패키지를 알려주며,
00:02:39esbuild처럼 말이죠. 이때 `pnpm approved-builds`를 실행해 허용 목록을 만들거나,
00:02:44설치 명령어에 `--allow-build` 플래그를 추가하면 됩니다.
00:02:48BUN 역시 기본적으로 설치 스크립트를 차단하지만, 자체적으로 관리하는
00:02:52허용 패키지 목록이 있고, 제가 설치한 esbuild도 포함되어 있습니다.
00:02:56이것은 package.json에 trustedDependencies를 추가하여 수정할 수 있으며,
00:03:00이렇게 하면 명시적으로 허용한 패키지만 post-install 스크립트를 실행할 수 있습니다.
00:03:04배열을 비우면 기본 목록이 무시된다고 되어 있지만,
00:03:09제 환경에서는 작동하지 않더군요. 깃허브에서 찾은 버그 같습니다.
00:03:12당장의 해결책은 목록에 아무 값이나 하나 넣는 것입니다. 그러면 기본 목록이 무시됩니다.
00:03:17BUN에서는 `bun pm untrusted` 명령으로
00:03:21스크립트를 실행하려는 비허용 패키지를 확인하고,
00:03:26`bun pm trust`를 통해 하나씩 허용하거나 아까 말한 목록에 추가할 수 있습니다.
00:03:30전역 BUN 설정에서 install-scripts를 false로 설정해 완전히 비활성화할 수도 있습니다.
00:03:35NPM은 조금 더 어렵습니다. 솔직히 NPM 대신 PNPM을 쓰시는 걸 권장하지만,
00:03:40어쩔 수 없이 NPM을 써야 한다면 Lavamote의 allow-scripts 패키지를 사용해
00:03:45package.json에서 허용 목록 기반의 스크립트 제어가 가능합니다.
00:03:50세 번째 팁은 git 기반 의존성을 차단하는 것입니다. NPM에서 의존성은
00:03:55레지스트리를 우회하는 git URL로 선언될 수 있고, 자체 .npmrc를 포함해
00:04:01라이프사이클 스크립트를 재활성화할 수도 있습니다. 이것이 최근 tanstack을 공격한 수법 중 하나입니다.
00:04:05NPM 설정에서 `allow-git`을 none으로 설정해 완전히 차단하거나,
00:04:10루트(root)로 설정하면, 루트 package.json에 명시된
00:04:15의존성만 git 기반으로 설치할 수 있게 제한할 수 있습니다.
00:04:19PNPM에는 `block-exotic-sub-dependencies` 옵션이 있습니다. 이를 true로 설정하면,
00:04:25루트 package.json에 직접 선언된 의존성만이
00:04:29git 저장소나 직접 URL 같은 외부 소스를 사용할 수 있습니다. 이 옵션은 아직 Bun에 없지만 PR이 열려 있습니다.
00:04:35설정 관련 마지막 팁으로, PNPM에는 trust-policy가 있는데,
00:04:40이를 `no-downgrade`로 설정하면 신뢰 수준이 이전 버전보다 낮아진
00:04:45패키지가 설치되려 할 때 PNPM이 오류를 냅니다. 만약
00:04:50신뢰받던 출처가 갑자기 증명이 불분명해지면 설치가 차단됩니다.
00:04:55출판 프로세스를 우회하려는 공격을 막는 데 도움이 됩니다.
00:05:00팁 4번은 아마 가장 강력할 겁니다. 바로 패키지를
00:05:04내 기기에 닿기 전에 미리 검사하는 도구를 사용하는 것이죠.
00:05:08완전 무료인 두 가지 강력한 도구를 소개합니다. 첫 번째는
00:05:14MPQ입니다. npm, pnpm, bun 명령에 별칭(alias)으로 지정해두면,
00:05:18설치 시마다 몇 가지를 검사합니다. Snyk 데이터베이스와 대조해
00:05:23취약점을 탐지하고, 출시 22일 미만인 패키지는 플래그를 표시합니다.
00:05:28타이포 스쿼팅도 잡아냅니다. 예를 들어 express를 expreess처럼
00:05:32오타를 노려 유도하는 경우죠. 레지스트리 서명과
00:05:37빌드 증명을 검증하고, 설치 스크립트 존재 여부도 경고합니다.
00:05:41추가로 다운로드 횟수, 리드미, 라이선스, 레포 URL,
00:05:46메인테이너의 이메일 도메인이 살아있는지도 확인합니다. 이 모든 과정을 거쳐
00:05:51설치할지 말지 결정할 수 있는 보고서를 제공합니다.
00:05:55두 번째는 Socket Firewall입니다. 제가 직접 쓰는 도구인데,
00:05:59마찬가지로 패키지 매니저에 별칭을 걸어 사용하며,
00:06:03파이썬이나 러스트 같은 JS 외 언어도 지원합니다.
00:06:08설치 시 확인된 악성 패키지는 차단하고, 사람이 아직 검토하지 않은
00:06:12AI 탐지 위협에 대해서는 경고를 줍니다. 솔직히
00:06:16가장 먼저 들어서 선택했지만, 실제 악성 패키지를 잘 잡아내고
00:06:21공격자들도 Socket이 패키지가 기기에 닿기 전에
00:06:25탐지한다고 언급할 만큼 성능이 증명되어 신뢰합니다.
00:06:30UV, pip, cargo 등 JS 이외 지원이 훌륭한 것도 장점입니다.
00:06:35설치 후에는 패키지 매니저의 캐시를 비워야
00:06:38모든 설치 패키지가 방화벽 검사를 통과하게 됩니다. 팁 5번은 락 파일입니다.
00:06:42락 파일에는 각 패키지의 실제 다운로드 URL이 들어있지만,
00:06:47아무도 PR에서 락 파일을 제대로 검토하지 않습니다. 그래서 공격자가 PR을 통해
00:06:51다운로드 URL을 자신이 조종하는 패키지로 바꾸고
00:06:56무결성 해시까지 맞춰두면 눈치채기 어렵습니다. 다음 설치 시
00:07:01공격자의 서버에서 코드를 받아오는 거죠. 다행히 PNPM은
00:07:05이런 취약점에서 자유롭습니다. 교체 가능한 소스를 유지하지 않으며,
00:07:09package.json에 없는 내용은 락 파일에 있어도 설치하지 않으니까요. Bun은
00:07:14확실치 않네요. PNPM을 안 쓴다면 `lockfile-lint`를 사용하세요.
00:07:18개발 의존성으로 설치해 락 파일을 검증하고, 모든 패키지가
00:07:23신뢰할 수 있는 레지스트리에서 왔는지 확인합니다.
00:07:28URL이 패키지 이름과 맞는지, 무결성 해시가 정상인지도 확인합니다.
00:07:32의심되면 설치를 실패시킵니다. 팁 6번은 CI/CD에서
00:07:37`npm install` 대신 clean install을 사용하는 것입니다. NPM은
00:07:42`npm ci`를 쓰면 되고, Bun과 PNPM은 `--frozen-lockfile` 플래그를 쓰거나
00:07:47CI 명령어를 사용하면 됩니다. PNPM은 CI 환경을 감지하면 기본적으로 작동합니다.
00:07:52Clean install은 락 파일에 명시된 그대로만 설치합니다.
00:07:57락 파일과 package.json이 맞지 않으면 즉시 중단하고 오류를 냅니다.
00:08:01멋대로 예측해서 설치하지 않죠. 따라서 공격자가
00:08:05바뀐 버전을 몰래 끼워 넣을 수 없습니다. 물론 락 파일을
00:08:09버전 관리 시스템에 커밋해야 효과가 있습니다. .gitignore에 넣지 마세요.
00:08:13어릴 때 코딩 처음 배울 땐 락 파일을 무시하는 건 줄 알았는데, 커밋하길 잘했죠.
00:08:17이제 마지막 팁, 습관에 대한 이야기입니다.
00:08:21무조건적인 업데이트를 멈추세요. `npm update`를 실행해서
00:08:25모든 걸 한 번에 최신으로 바꾸는 건 공격자들이 아주 좋아하는 행동입니다.
00:08:30하나씩 업그레이드하며 왜 필요한지 스스로 물어봐야 합니다.
00:08:35두 번째 습관은 점점 중요해지고 있습니다. 의존성을 줄이세요.
00:08:39의존성을 추가할 때마다 공격 표면이 늘어납니다.
00:08:43공격은 의존성의 의존성을 통해 퍼지니까요. Lodash나 Axios 대신
00:08:48작은 코드 조각이나 내장 fetch로 충분한지 고민하세요.
00:08:53요즘 같은 AI 코딩 시대엔 AI에게 간단한 기능을 짜달라고 하는 게 훨씬 쉽죠.
00:08:58세 번째는 의존성 버전을 고정하는 것입니다. 업그레이드가
00:09:03의도적인 선택이 되도록 말이죠. 하지만 이건 직접 선언한 패키지에만 적용됩니다.
00:09:08하위 의존성은 여전히 범위를 가질 수 있으므로
00:09:12아까 말한 릴리스 기간 제한이 중요합니다.
00:09:17이 모든 것을 조합하면 실수로 악성 패키지를 설치하지 않을 거라는 안심을 얻을 수 있습니다.
00:09:21절대 무적은 아니지만, 아무것도 안 하는 것보다는 훨씬 낫습니다.
00:09:25궁극적인 방법은 강화된 개발 컨테이너를 쓰는 거지만,
00:09:29좀 번거로워서 결국 로컬 개발로 돌아오게 되더군요.
00:09:34다른 좋은 방어법이 있다면 댓글로 알려주세요.
00:09:38구독해주시고, 늘 그렇듯 다음 영상에서 뵙겠습니다.
00:09:42시청해 주셔서 감사합니다.
Community Posts
No posts yet. Be the first to write about this video!
Write about this video