최신 보안 경보: Node.js 취약점 공지
2025년 1월 Node.js에서는 워커 권한 우회 취약점(CVE-2025-23083)을 포함한 4개의 심각한 보안 취약점을 발표했습니다. 영향을 받는 버전은 v18.20.6, v20.18.2, v22.13.1, v23.6.1 이하입니다. 많은 회사들이 여전히 지원 종료된 버전을 사용 중이라는 점이 문제죠. 이것이 단순한 뉴스가 아닌 이유는 명확합니다. 당신의 서버가 해킹될 수도 있다는 뜻이거든요. 그런데도 많은 팀들이 버전 관리를 미루고 있습니다.
나는 왜 이 글을 쓰게 됐나
작년 말 회사 프로젝트에서 갑자기 npm install이 먹통이 됐습니다. 아무것도 설치되지 않았어요. 에러 메시지는 “Node.js 버전이 너무 낮다”는 것이었죠. 그 순간 깨달았습니다. 버전 관리를 제때 하지 않으면 나중에 개발 자체가 불가능해진다는 것을. 그 뒤로 팀의 여러 프로젝트를 보니 상황이 더 심각했어요. 어떤 프로젝트는 Node 14를 쓰고 있었고, 어떤 프로젝트는 18을 쓰고 있었습니다. 팀원들은 각자 다른 버전을 사용하고 있었죠. 결국 “내 컴퓨터에서는 되는데 왜 서버에서는 안 돼?” 같은 말이 매일 나왔습니다.
그때부터 본격적으로 버전 관리를 팀에 강제했습니다. 처음엔 저항이 있었어요. 개발자들은 “그냥 무조건 최신 버전으로 올리면 되지 않냐?”고 물었거든요. 하지만 실무는 그렇게 간단하지 않습니다. 3년 묵은 프로젝트도 있고, 급하게 배포해야 하는 서비스도 있고, 클라이언트가 특정 버전만 쓰도록 지정한 경우도 있으니까요.
개발자들이 놓치는 버전 관리의 현실
저는 지난 2년간 여러 팀의 Node.js 버전 관리를 봤습니다. 대부분의 실패 패턴은 비슷했어요. 첫 번째는 무관심입니다. 버전은 그냥 설치된 것을 쓰는 거라고 생각하는 거죠. 두 번째는 과신입니다. “어차피 자바스크립트는 하위 호환성이 좋아서 버전 차이는 문제 없겠지”라고 생각합니다. 세 번째는 두려움입니다. 업그레이드하다가 뭔가 깨질까봐 시도도 안 하는 거예요. 가장 흔한 건 무관심과 두려움의 조합입니다.
하지만 현실은 이렇습니다. Node.js의 메이저 버전이 올라가면서 많은 API가 바뀝니다. V8 엔진이 업그레이드되면서 성능은 물론 동작 방식도 달라질 수 있어요. 더 중요한 건 보안입니다. 오래된 버전을 쓰면서 취약점에 노출되는 건 선택이 아닙니다. 그건 책임 문제가 됩니다.
실제로 팀원들과 대화해보면 “버전 관리 따로, 개발 따로”라고 생각하는 사람들이 많습니다. 마치 버전 관리는 DevOps 팀의 일이고 자신의 책임은 아니라고 생각하는 거죠. 하지만 이건 큰 착각입니다. 개발자가 먼저 자신의 로컬 환경에서 버전을 제대로 관리하지 않으면 배포 환경도 망가집니다.
팀 전체를 위한 버전 관리 전략
제가 지난 1년 반 동안 시행한 전략을 공유하겠습니다. 처음에는 저항도 많았지만, 지금은 팀 문화가 바뀌었어요. 가장 중요한 건 명확한 규칙입니다. 우리는 먼저 모든 프로젝트에 .nvmrc 파일을 강제했습니다. 이 파일에는 단 한 줄, 프로젝트가 필요로 하는 Node.js 버전만 적혀있어요. 예를 들어 22.0.0이라고요.
그 다음은 온보딩입니다. 새로운 팀원이 오면 “먼저 NVM을 설치하고, nvm use라고 입력하세요”라고 알려줍니다. 이 한 줄의 명령어로 자동으로 프로젝트의 정확한 버전으로 전환됩니다. 더 이상 “어떤 버전을 써야 하나요?”라는 질문은 없어졌어요. 마치 git처럼 버전도 프로젝트에 커밋되는 거죠.
package-lock.json은 더욱 중요합니다. 저희 팀은 이 파일을 git에서 절대 무시하지 않습니다. 많은 팀들이 이 파일을 .gitignore에 추가하거나 무시하는데, 그러면 개발자마다 다른 패키지 버전이 설치됩니다. “내 컴퓨터에서는 된다”는 상황이 발생하는 거죠. 우리는 package.json과 package-lock.json을 함께 관리합니다. 둘 다 git에 커밋되고, 모든 팀원이 동일한 환경에서 일합니다.
새 버전을 도입하는 타이밍의 기술
그렇다면 언제 새 버전으로 업그레이드할까요? 이건 의외로 복잡한 문제입니다. 저는 몇 가지 기준을 만들었어요.
첫째, 보안입니다. 취약점이 발견되면 그건 더 이상 선택이 아닙니다. 즉시 업그레이드를 계획해야 합니다. 최소 보안 패치 버전이라도 올려야 해요. 둘째, 의존 라이브러리입니다. 만약 팀에서 사용 중인 webpack, React, Express 같은 핵심 라이브러리가 새로운 Node.js 버전을 요구한다면 고려해봐야 합니다. 셋째, LTS 전환 시점입니다. Node.js는 우수한 버전이 LTS로 전환되는데, 이때가 도입의 적기입니다. 마지막으로 성능입니다. 새 버전이 실제로 우리의 성능 병목을 해결할 수 있다면 도입을 서두르는 게 맞습니다.
가장 피해야 할 패턴은 “요즘 나온 거니까 써야겠다”는 생각입니다. Current 버전(홀수)은 몇 개월마다 업그레이드되고 API가 변하기도 합니다. 저는 Current 버전은 개인 프로젝트나 실험 목적으로만 추천합니다. 실무 프로젝트는 반드시 LTS 버전을 사용하세요. 이것이 안정성과 지원 기간을 보장하는 유일한 방법입니다.
마이그레이션 계획의 현실적 접근
새 버전으로 업그레이드하기로 결정했다면, 계획이 필요합니다. 저희 팀은 다음과 같은 순서로 진행합니다. 먼저 테스트 환경에서 테스트합니다. 개발 브랜치를 따서 Node.js 버전만 바꾸고 모든 테스트를 실행해요. npm test, npm run build 같은 모든 스크립트를 돌려봅니다. 이때 에러가 나면 해당 라이브러리의 버전을 조정하거나 코드를 수정합니다.
다음은 팀의 동료 검토입니다. 최소 두 명 이상이 PR을 확인하고 테스트 환경에서 직접 실행해봅니다. 왜 동료 검토가 중요할까요? 첫째, 내가 놓친 부분이 있을 수 있기 때문입니다. 둘째, 팀 전체가 변화를 인식하기 위함입니다. “앗, Node.js 22로 올렸구나”라고 인식하는 것만으로도 나중에 이슈 발생 시 원인 파악이 쉬워져요.
세 번째는 점진적 롤아웃입니다. 모든 서버를 한 번에 업그레이드하지 마세요. 트래픽이 적은 서비스부터 시작해서 주요 서비스로 확대합니다. 최소 일주일은 모니터링을 하세요. 에러율, 응답 속도, 메모리 사용량 등을 지켜봅니다. 문제가 생기면 즉시 롤백할 준비를 하고요.
레거시 프로젝트와의 싸움
가장 현실적인 문제는 오래된 프로젝트입니다. 저희 팀에도 5년 된 프로젝트가 있어요. 이 프로젝트는 Node 14를 사용 중입니다. 지원 종료된 버전입니다. 하지만 올릴 수 없었어요. 왜냐하면 그 사이 의존하는 라이브러리들이 더 이상 업그레이드되지 않았기 때문입니다. 전체 패키지를 뜯어고쳐야 했어요.
이런 상황에서 저는 세 가지 선택지가 있다고 봅니다. 첫째는 마이그레이션입니다. 오래된 코드를 현대적 코드로 리팩토링하고 새 버전으로 올립니다. 이건 가장 이상적이지만 비용이 많이 듭니다. 둘째는 컨테이너화입니다. Docker를 사용해서 그 안에서 Node 14를 돌립니다. 더 이상 개발 머신에서 실행하지 않으므로 버전 충돌이 없어요. 셋째는 운명을 받아들이기입니다. 그냥 보안 취약점이 있다는 것을 인정하고 보험처럼 모니터링을 강화합니다. 합법적이지만 권장하지는 않습니다.
저희는 결국 첫 번째와 두 번째를 조합했습니다. 마이그레이션 계획을 세우면서 동시에 Docker를 도입해서 즉시 취약점 노출을 줄였어요.
버전 전환할 때 놓치기 쉬운 것들
Node.js를 업그레이드하면서 많은 팀들이 실수하는 부분들입니다. 첫째, npm 캐시입니다. 새 버전으로 올렸는데도 이상하게 예전 패키지들이 남아있을 수 있어요. npm cache clean –force로 캐시를 비워야 합니다. 둘째, node_modules 폴더입니다. 버전을 바꿀 때마다 rm -rf node_modules && npm install을 실행하세요. 최악의 경우 구 버전의 바이너리가 섞여있을 수 있거든요.
셋째, 환경 변수입니다. PATH 설정이 제대로 되지 않으면 여전히 구 버전을 쓸 수도 있어요. which node를 입력해서 정말 올바른 경로에서 실행되고 있는지 확인하세요. 넷째, npm 버전입니다. Node.js는 올렸는데 npm은 안 올렸을 수도 있어요. npm -v로 확인하고 npm install -g npm으로 업그레이드하세요. 다섯째, peer dependency 에러입니다. npm 7부터는 peer dependency를 자동으로 설치하려고 시도하는데, 이 과정에서 충돌이 생길 수 있어요. 무작정 –legacy-peer-deps로 우회하지 말고, 어떤 라이브러리가 문제인지 파악하고 버전을 조정하는 게 맞습니다.
마이그레이션 체크리스트
실제로 우리 팀이 사용 중인 체크리스트를 공유합니다. 새 버전으로 올릴 때 이것들을 하나하나 확인합니다. 첫째, .nvmrc 파일 변경. 둘째, npm cache clean –force. 셋째, rm -rf node_modules package-lock.json. 넷째, npm install 재실행. 다섯째, 모든 npm 스크립트 실행 (npm test, npm run build, npm start). 여섯째, 개별 패키지 테스트 (특히 네이티브 모듈). 일곱째, Slack에서 팀원들에게 공지.
넓게 보면 여덟째, 도메인 환경 변수 확인. 아홉째, 배포 파이프라인 테스트. 열째, 실제 API 호출 테스트. 열한째, 메모리 사용량 모니터링. 열두째, 에러로그 확인. 마지막으로 열세째, 팀원들에게 피드백 수집.
이렇게 보니 많지만, 실제로는 대부분 자동화할 수 있습니다. CI/CD 파이프라인이 절반을 해줍니다. 중요한 건 마지막 단계입니다. “뭔가 이상하지 않나요?”라는 팀원의 피드백이 미처 발견하지 못한 버그를 찾아낼 수 있거든요.
마무리하며: 버전 관리는 기술이 아니라 문화다
지난 1년반을 돌아보면, Node.js 버전 관리는 단순한 기술 문제가 아니었습니다. 그건 팀 문화의 문제였어요. “빠르게 개발하면 되지 왜 버전을 관리해?”라는 생각에서 “안전하고 예측 가능한 환경이 더 빠른 개발을 만든다”는 인식으로 바뀌었을 때 모든 게 달라졌습니다.
지금 우리 팀의 모든 프로젝트는 명확한 Node.js 버전을 가지고 있습니다. 새로운 팀원이 와도 “nvm use”만 입력하면 정확한 환경에서 시작할 수 있습니다. 버전 충돌로 밤을 새우는 일도 없어졌어요. 가장 중요한 건 보안입니다. 우리는 취약점 뉴스가 나오면 신속하게 대응할 준비가 되어있습니다.
당신의 팀도 이런 문화를 만들 수 있습니다. 작게라도 시작하세요. 첫 번째 프로젝트에 .nvmrc를 만들어보세요. 그 다음엔 두 번째, 세 번째 프로젝트가 따라올 겁니다. Node.js 버전 관리는 선택이 아닙니다. 프로젝트를 안전하고 안정적으로 운영하기 위한 기본입니다. 그리고 그걸 처음 깨달은 게 작년 말 npm install이 먹통이 되는 경험이었다면, 이제는 그런 경험을 하지 않아도 될 겁니다. 왜냐하면 버전 관리의 중요성을 제대로 이해했으니까요.