소중한 내 코드를 지키는 최소한의 방어선 공급망 공격 예방을 위한 패키지 매니저 보안 설정 가이드

소중한 내 코드를 지키는 최소한의 방어선 공급망 공격 예방을 위한 패키지 매니저 보안 설정 가이드

2026년 3월 한 달 동안 정말 많은 보안 사고가 연달아 발생했는데요.

공급망 공격이 더 이상 먼 나라 이야기가 아니라 우리 개발 환경 바로 옆까지 다가왔음을 실감하는 시기입니다.

지난 3월 19일에는 보안 스캐너로 유명한 'Trivy'의 GitHub Actions가 침해되어 SSH 키와 클라우드 토큰이 유출되는 사태가 벌어졌거든요.

이 사고는 여기서 멈추지 않고 3월 24일에는 파이썬 생태계의 'LiteLLM'으로 번지더니 오늘인 3월 31일에는 'axios'의 메인테이너 계정이 탈취되기에 이르렀습니다.

하나의 프로젝트가 뚫리면 그 파동이 연쇄적으로 다른 프로젝트까지 덮치는 무서운 상황인데요.

이제는 내 프로젝트는 괜찮을 거라는 막연한 낙관보다는 실질적인 방어 체계를 구축해야 할 때입니다.

다행히도 이런 공격 중 상당수는 우리가 매일 사용하는 패키지 매니저의 설정만 제대로 바꿔도 사전에 방어할 수 있거든요.

지금부터 라이브러리 사용자로서 최소한으로 갖춰야 할 방어 전략을 하나씩 짚어보겠습니다.

첫 번째 방어 전략 쿨다운 시스템의 도입

악의적인 목적으로 배포된 패키지 버전의 가장 큰 특징은 공개된 직후에 피해가 집중된다는 점인데요.

하지만 다행스럽게도 이런 오염된 버전들은 보통 배포 후 일주일 이내에 커뮤니티나 보안 도구에 의해 감지되어 삭제되곤 합니다.

이번에 발생한 axios 사태 역시 단 몇 시간 만에 적절한 대응이 이루어졌거든요.

이 시간차를 이용하는 전략이 바로 '쿨다운' 혹은 '최소 출시 기간' 설정입니다.

특정 패키지가 배포된 지 일정 기간이 지나지 않았다면 아예 설치되지 않도록 차단하는 방식인데요.

이 설정은 CI/CD 환경뿐만 아니라 로컬 개발 환경에서도 매우 유용하게 작동합니다.

주요 패키지 매니저들은 이미 이런 쿨다운 기능을 속속 도입하고 있습니다.

가장 대중적인 'npm'의 경우 11.10 버전부터 '.npmrc' 파일에 'min-release-age' 옵션을 설정할 수 있거든요.

여기서 기준을 7일 정도로 잡아두면 배포된 지 일주일이 안 된 따끈따끈한 버전은 설치 시도조차 하지 않게 됩니다.

'pnpm'을 사용하신다면 'pnpm-workspace.yaml' 파일에서 분 단위로 기간을 지정해 주시면 됩니다.

최근 인기를 끌고 있는 'Bun'은 'bunfig.toml' 파일의 설치 섹션에서 초 단위로 기간을 명시할 수 있습니다.

파이썬 생태계의 'uv' 역시 특정 기간보다 최신 버전은 제외하는 옵션을 제공하며 패키지별로 기간을 다르게 설정하는 것도 가능하거든요.

물론 이런 설정이 보안 패치를 늦게 적용하게 만든다는 단점은 분명히 존재합니다.

하지만 악성코드가 담긴 라이브러리를 생각 없이 설치해서 전체 시스템이 장악당하는 리스크에 비하면 일주일 정도의 지연은 충분히 감수할 만한 가치가 있습니다.

대부분의 공격이 7일 이내에 걸러진다는 통계적 사실을 믿고 프로젝트의 허용 범위에 맞춰 이 기간을 조정해 보세요.

두 번째 방어 전략 락파일의 엄격한 운영

우리가 'npm install'을 실행할 때 가끔 생각지 못한 버전이 설치되어 당황할 때가 있는데요.

이는 'package.json'과 락파일 사이에 불일치가 발생할 때 패키지 매니저가 임의로 최신 버전을 가져오기 때문입니다.

공급망 공격은 바로 이런 틈새를 노려 우리의 의도와 상관없이 악성 버전을 주입하거든요.

이를 방지하기 위해서는 CI/CD 환경에서 반드시 'npm ci'와 같은 명령어를 사용해야 합니다.

이 명령어는 락파일과 설정이 조금이라도 다르면 설치를 중단하고 에러를 뱉어내기 때문에 안정성을 보장해 줍니다.

'pnpm'이나 'Bun' 그리고 'uv' 사용자 역시 락파일을 강제로 고정하는 옵션을 기본적으로 사용해야 하는데요.

'frozen-lockfile'이나 'locked' 옵션을 통해 락파일에 기록된 그 버전 그대로만 설치되도록 강제하는 것이 핵심입니다.

또한 내가 직접 사용하는 라이브러리가 아니더라도 간접적으로 의존하는 패키지에서 문제가 생길 수 있거든요.

이럴 때는 'overrides'나 'resolutions' 설정을 통해 특정 패키지의 버전을 안전한 버전으로 못 박아두는 전략이 필요합니다.

이번 axios 사태처럼 특정 버전이 위험하다고 판명되면 이 설정을 통해 하위 의존성에 숨어있는 axios 버전까지 한꺼번에 통제할 수 있습니다.

세 번째 방어 전략 설치 스크립트의 원천 차단

이번 axios 공격의 핵심 메커니즘은 설치 직후 자동으로 실행되는 'postinstall' 스크립트였는데요.

패키지를 다운로드하자마자 우리 컴퓨터의 쉘 권한을 얻어 악성 행위를 수행하는 아주 고전적이면서도 치명적인 방식입니다.

사실 대부분의 라이브러리는 설치 시점에 별도의 스크립트를 실행할 이유가 전혀 없거든요.

따라서 우리는 패키지 매니저 설정에서 'ignore-scripts' 옵션을 활성화하여 이 통로를 아예 막아버려야 합니다.

'npm'의 경우 '.npmrc' 파일에 해당 옵션을 추가하는 것만으로도 모든 설치 스크립트의 실행을 중단시킬 수 있습니다.

네이티브 모듈 빌드처럼 꼭 필요한 경우에만 예외적으로 수동 빌드 명령어를 실행하는 습관을 들이는 것이 좋습니다.

최신 버전인 'pnpm v10'은 더 진보된 방식을 채택하여 기본적으로 모든 스크립트를 블록 처리해 주는데요.

신뢰할 수 있는 특정 패키지만 골라서 실행을 허용하는 'allowBuilds' 목록을 관리함으로써 보안과 편의성의 균형을 맞췄습니다.

'Bun' 역시 신뢰할 수 있는 의존성 목록을 별도로 관리하는 방식을 통해 원치 않는 스크립트가 실행되는 사고를 미연에 방지하고 있습니다.

네 번째 방어 전략 GitHub Actions의 커밋 해시 고정

우리는 보통 GitHub Actions를 사용할 때 버전 태그를 기반으로 액션을 가져다 쓰곤 하는데요.

하지만 'v4'와 같은 태그는 공격자가 태그를 덮어쓰는 방식을 통해 언제든지 악의적인 코드로 교체될 수 있다는 치명적인 약점이 있습니다.

이를 방어하기 위해서는 태그 대신 변하지 않는 고유한 값인 'SHA 커밋 해시'를 사용해야 합니다.

커밋 해시는 한 번 생성되면 절대 바꿀 수 없기 때문에 내가 검증한 그 시점의 코드임을 100퍼센트 보장해 주거든요.

매번 복잡한 해시값을 찾아서 입력하는 것이 번거롭다면 'pinact'와 같은 자동화 도구를 활용해 보세요.

이 도구는 워크플로우 파일에 적힌 태그들을 자동으로 최신 커밋 해시로 변환해 주는 아주 기특한 역할을 수행합니다.

더 나아가 앞서 설명한 쿨다운 개념을 적용하여 배포된 지 얼마 안 된 액션으로의 업데이트를 차단하는 기능도 제공하는데요.

새로운 워크플로우가 추가될 때마다 해시 고정이 누락되지 않도록 CI 단계에서 체크하는 과정을 추가하면 더욱 완벽한 방어 체계가 완성됩니다.

다섯 번째 방어 전략 의존성 리뷰 액션의 도입

공격적인 패키지가 우리 프로젝트의 메인 브랜치에 들어오는 것을 막는 마지막 관문은 바로 '풀 리퀘스트' 단계인데요.

'Dependency Review Action'을 도입하면 새로운 의존성이 추가될 때마다 자동으로 취약점 스캔을 수행합니다.

만약 보안상 위험이 있는 패키지가 감지되면 CI를 실패하게 만들어 코드 리뷰어가 이를 인지하도록 돕습니다.

이는 보안 사고를 사후에 수습하는 것이 아니라 사전에 차단하는 'Shift Left' 보안 철학의 핵심이라고 할 수 있습니다.

브랜치 보호 규칙과 연동하여 이 체크를 통과하지 못한 코드는 절대 머지되지 않도록 설정하는 것이 가장 권장되는 방식입니다.

추가 가이드 공급망 공격을 대하는 우리의 자세

지금까지 기술적인 설정법들을 살펴보았지만 사실 가장 중요한 것은 개발자의 보안 감수성인데요.

공급망 공격은 기술적인 취약점보다 인간의 '신뢰'를 역이용하는 경우가 훨씬 많기 때문입니다.

메인테이너의 계정이 탈취되거나 유명 패키지와 이름이 비슷한 가짜 패키지를 배포하는 방식이 대표적인 사례입니다.

우리는 새로운 라이브러리를 추가할 때 해당 프로젝트의 메인테이너가 누구인지 그리고 최근 커뮤니티의 평판은 어떤지 확인하는 습관을 가져야 합니다.

또한 간접 의존성이라는 보이지 않는 위험에 대해서도 깊이 고민해 볼 필요가 있는데요.

내가 직접 설치한 패키지는 10개뿐이라도 그 패키지들이 물고 들어오는 수백 개의 하위 패키지들이 우리 프로젝트의 운명을 결정합니다.

패키지 매니저의 'audit' 기능을 정기적으로 활용하여 불필요하게 비대해진 의존성을 다이어트하고 취약한 연결 고리를 끊어내야 합니다.

최소한의 라이브러리만 사용하는 '미니멀리즘'은 코드의 가독성뿐만 아니라 보안 측면에서도 매우 훌륭한 전략이 됩니다.

마지막으로 보안 설정을 단순히 개인의 선택으로 남겨두지 말고 팀 전체의 문화로 정착시켜 보세요.

조직 차원에서 'OIDC' 기반의 인증을 의무화하고 락파일 변경 사항에 대한 엄격한 리뷰 가이드라인을 수립해야 합니다.

보안은 한 번의 설정으로 끝나는 것이 아니라 끊임없이 변화하는 공격 기법에 맞춰 진화해야 하는 과정임을 명심해야 하거든요.

오늘 소개한 다섯 가지 전략이 여러분의 소중한 코드를 지켜주는 든든한 방패가 되어주길 진심으로 바랍니다.