보안 가이드

의존성 CVE를 제대로 고치기: 스캔, 수정, 격리, 그리고 계속 감시

소규모 환경에서 의존성 CVE를 실제로 고치는 플레이북입니다. 스캔, 수정, 격리, 모니터링의 네 단계로 완료를 정의하고, 수정이 다음 배포로 사라지지 않게 하는 법을 다룹니다.

게시 2026-06-11 업데이트 2026-10-05 7분 읽기
이 페이지에서

대상: 작은 팀과 함께 여러 웹 앱(여러 프레임워크 혼재)을 운영하며, 의존성 취약점(CVE)을 제대로, 한 번에 고치고 싶은 사람. 공격 절차는 없다 — 오직 고치고, 수정이 유지되게 하고, 계속 감시하는 실천이다. 이런 일을 자주 촉발하는 사고 유형은 방치된 공개 RCE가 부정 과금으로 청구된 사례를 보라.

본 사이트의 견해: 소규모 팀은 두 규율로 안전하다

소규모 팀에서 통하는 것은 화려한 도구가 아니라 — 두 규율이다: 1) 자동 변화 탐지(새 취약점에만 경보)와 2) 로컬→push→배포(프로덕션은 받기만, 절대 편집 안 함). 본 사이트도 같은 방식으로 돈다: 모든 배포 전 의존성 감사(pnpm audit) 더하기 매일 cron, 그리고 push-to-deploy로 프로덕션에 전달. 한 번의 수정으로 끝내지 않고 계속 동작하는 경보를 갖추는 것이, 가장 값싸게 오래가는 방법이다.

먼저 "완료"의 네 가지 조건을 정하라

너무 일찍 "끝났다"고 부르는 것을 막기 위해서다. 미리 정하라: 넷이 모두 맞아떨어지기 전까지는 미완이다.

1. 스캔

상태를 숫자로 만든다

2. 수정

dev 아닌 크리티컬/높음을 원인에서 수정

3. 격리/인계

고칠 수 없는 것을 명시화

4. 모니터링

매일 변화 탐지를 넣는다

취약점 작업 완료 = 네 가지의 한 세트. 하나라도 빠지면 고쳤다고 생각해도 취약점이 남는다.

1. 스캔: 지금 있는 것을 세라

먼저, 무엇이 어디 있는지를 기계로 보라. 수동 임의 점검은 늘 빠진다.

1

락파일을 자동 발견하고 스캔

composer.lock / package-lock.json / pnpm-lock.yaml 을 찾아, 매일 오픈소스 스캐너(osv-scanner 또는 pnpm audit)로 돌린다. 락파일만 읽으므로 부하는 미미하다.
2

심각도가 하나의 숫자가 아님을 알라

같은 결함이 CVSS에서는 높음(예: 7.5)이지만 벤더의 검증된 등급에서는 보통일 수 있다. CVSS는 기계가 산정하므로 과대 계상 경향이 있다. 어느 기준이 임계값을 정하는지 결정하고, 팀 전체가 어느 것을 쓰는지 알게 하라(→ CVSS란 무엇인가). 실제 악용 여부(KEV)와 교차해 우선순위를 매겨라.
3

무시가 아니라 기록된 이유와 함께 노이즈 제외

테스트/빌드 전용 dev 의존성은 프로덕션 번들에 없는 경우가 많아 실제 위험이 없을 수 있다. 스캐너의 그룹화로 dev를 알림에서 빼되, 왜 제외했는지 기록하라. 나중에 "이거 아직 안 고친 거 아냐?"에 답할 수 있게 하라.

2. 수정: 취약한 버전 자체를 업데이트하라

증상을 가리는 임시 조치와 실제 원인의 수정은 다른 작업이다. 둘 다 해야 끝난다.

증상 가리기만(원인은 남는다)

  • 리버스 프록시에서 "수상해 보이는" 요청만 차단
  • 증상이 멈췄으니 "처리됨"으로 친다
  • 취약한 의존성은 그대로 — RCE는 여전히 악용 가능

원인 수정(올바름)

  • 취약한 의존성을 패치 버전으로 업그레이드
  • 업그레이드 후 로그에서 시그니처가 사라졌는지 확인
  • 임시 조치와 실제 수정을 별개 작업으로 보고, 둘 다 한다

메이저 업그레이드는 프로덕션 전에 빌드 검증

패치와 달리, 메이저 버전 점프(프레임워크 14→15, UI 키트 4→6 등)는 호환성 깨짐을 동반한다. 확인 없이 올려 프로덕션 빌드를 깨면 서비스가 멈춘다. 수정한 소스를 프로덕션과 동일한 런타임(같은 Node/PHP 버전 컨테이너)에 넣고, push 전에 빌드/타입 체크를 완전히 통과시켜라. 오류를 하나씩 해소하라(sync→async 코드모드, 여러 줄 CSS 클래스 문자열, 폐기된 전역 타입 네임스페이스 등). 기존 컨테이너가 계속 돌고 있다면, 실패는 무중단으로 롤백된다.

다음 배포가 수정을 되돌리지 않는지 확인하라

완벽한 수정도 다음 배포가 지우면 가치가 0이다. 가장 흔한 실수다.

1

프로덕션 작업 트리에서 커밋하지 마라

프로덕션에서의 직접 커밋은 중앙 저장소와 히스토리가 갈라지고, 다음 배포(pull / checkout -f)가 그것을 덮어써 수정이 사라진다. 프로덕션은 "배포 결과가 도착하는 곳"이지 편집하는 곳이 아니다.
2

한 방향으로 표준화: 로컬→push→배포

로컬에서 편집 → 커밋 → push → (프로덕션) pull / 자동 배포. 프로덕션에서 검증해야 했던 변경조차 로컬로 되감아 다시 push해야 한다(→ 프로덕션을 받기 전용으로).
3

HTTP 200을 '정상'으로 믿지 마라

공유 호스팅에서는 치명적 오류도 200을 반환할 수 있다. 실제 콘텐츠로 검증하라 — 기대한 제목/텍스트가 렌더링되는가(curl | grep), 로그에 새 오류가 있는가, 그리고 홈페이지뿐 아니라 동적 경로(DB 기반, 매개변수화)를 걸어보라. 캐시가 변경을 지연시킬 수 있으니 기다리거나 먼저 비워라.

3. 격리/인계: 고칠 수 없는 것을 기록하라

지금 당장 모든 것을 고칠 수는 없다. 요령은 "고칠 수 없음 / 내 영역 아님 / 미사용"을 결코 모호하게 두지 않는 것이다.

1

방치/EOL 코드: 삭제 전에 격리

더는 서비스하지 않는 옛 소스(EOL 프레임워크 등) — 곧장 삭제하지 말고 이름을 바꿔 격리하고, 언제/왜/어디서 서비스됐는지 표시를 남겨라. 스캔에서도 빼라. "삭제"는 되돌릴 수 없지만 "격리"는 되돌릴 수 있고 흔적을 남긴다.
2

먼저 정말 참조되지 않는지 확인

리버스 프록시 설정, 빌드 구성, 마운트를 추적해 아무것도 참조하지 않음을 확인한 뒤 격리하라 — 실수로 다시 서비스되는 것을 막는다.
3

고칠 수 없는 것은 명시적으로 인계

손이 닿지 않는 것은 방치하지 말고 "누가 / 무엇을"을 명시해 인계하라. 인벤토리의 가치는 미수정이 보이지 않는 방치로 변하지 않게 하는 데 있다.

4. 모니터링: 매일 변화 탐지를 설정하라

이제 마지막으로 경보 장치를 넣는다. 그것이 없으면, 한 수정이 재발할 때 알려주지 않는다.

매일 모든 것이 아니라 새로운 것에만 경보

스캔 결과를 이전 실행(상태 파일)과 비교(diff)해 새 크리티컬/높음이 나타날 때만 알려라. 매일 같은 내용은 무시된다(경보 피로). 매일 cron으로 하나의 이메일(신규 / 해소 / 현재)로 요약하라. 공유 호스팅도 스캐너 바이너리 + cron 하나는 문제없이 처리한다(부하는 밀리초 단위; 안심을 위해 nice를 더하라).

diff
새 것에만 알림 = 피로 없음
매일
cron으로 계속 돌린다
이메일 1통
신규 / 해소 / 현재 요약
넷 모두
모니터링이 켜져야 비로소 완료

인벤토리가 함께 찾아내는 API 키와 SSH 키

CVE를 제대로 고치다 보면 의존성 외의 문제도 함께 드러나는 경향이 있다. 흔한 두 가지 — 공개 디렉터리에 남은 시크릿 파일(웹루트에 놓인 옛 토큰; 공유 템플릿에서 왔다면 모든 사이트가 같은 문제를 가짐)과 침해될 수 있는 환경에 넘겨진 루트 키. 둘 다 "한 번 유출되면 전부"라는 패턴을 만드니, 같은 점검에서 함께 확인하라(각각 별도의 깊은 글이 마땅하다). 시크릿 기본은 시크릿을 안전하게 저장하기와 기준선 체크리스트를 보라.

다음으로 읽기

FAQ

Q취약점 작업은 언제 실제로 '완료'되나요?
A

네 가지가 맞아떨어질 때뿐입니다: 1) 스캔해 상태를 숫자로 만들고, 2) dev가 아닌 크리티컬/높음을 수정하고, 3) 고칠 수 없는 것/방치 코드를 명시적으로 격리하거나 인계하고, 4) 매일 변화 탐지(신규·재발을 잡는 모니터링)를 갖춥니다. 4가 갖춰지기 전까지는 완료가 아닙니다 — 의존성은 내일 다시 취약해집니다.

Q매일 스캔하면 경보 피로가 생기지 않나요?
A

매일 모든 것을 경보하면 금세 무시됩니다. 이전 결과(상태 파일)와 비교(diff)해 새로운 크리티컬/높음이 나타날 때만 알리세요 — 변화 탐지입니다. 매일 같은 것을 보내지 않는 것이 모니터링을 실제로 오래가게 합니다.

Q수정이 왜 가끔 다시 취약 상태로 되돌아가나요?
A

프로덕션 작업 트리에서 직접 커밋하면, 다음 배포(pull이나 checkout -f)가 덮어써 수정을 지웁니다. 한 방향 — 로컬→push→배포 — 으로 표준화하고 프로덕션을 '받기만' 하게 하세요. 수정을 지우는 작업 흐름 위의 완벽한 수정은 가치가 0입니다.