대상: 작은 팀과 함께 여러 웹 앱(여러 프레임워크 혼재)을 운영하며, 의존성 취약점(CVE)을 제대로, 한 번에 고치고 싶은 사람. 공격 절차는 없다 — 오직 고치고, 수정이 유지되게 하고, 계속 감시하는 실천이다. 이런 일을 자주 촉발하는 사고 유형은 방치된 공개 RCE가 부정 과금으로 청구된 사례를 보라.
본 사이트의 견해: 소규모 팀은 두 규율로 안전하다
소규모 팀에서 통하는 것은 화려한 도구가 아니라 — 두 규율이다: 1) 자동 변화 탐지(새 취약점에만 경보)와 2) 로컬→push→배포(프로덕션은 받기만, 절대 편집 안 함). 본 사이트도 같은 방식으로 돈다: 모든 배포 전 의존성 감사(pnpm audit) 더하기 매일 cron, 그리고 push-to-deploy로 프로덕션에 전달. 보안을 "한 번의 수정"이 아니라 "계속 발화하는 시스템"으로 다루는 것이 가장 값싸게 오래가는 길이다.
먼저 완료의 4부 "정의"를 고정하라
성급하게 "끝났다"고 부르는 것을 막는 가드레일. 미리 정하라: 넷이 모두 맞아떨어지기 전까지는 미완이다.
1. 스캔
상태를 숫자로 만든다
2. 수정
dev 아닌 크리티컬/높음을 근본 수정
3. 격리/인계
고칠 수 없는 것을 명시화
4. 모니터링
매일 변화 탐지를 넣는다
1. 스캔: 상태를 숫자로 만들어라
먼저, 무엇이 어디 있는지를 기계로 보라. 수동 임의 점검은 늘 빠진다.
락파일을 자동 발견하고 스캔
composer.lock / package-lock.json / pnpm-lock.yaml 을 찾아, 매일 오픈소스 스캐너(osv-scanner 또는 pnpm audit)로 돌린다. 락파일만 읽으므로 부하는 미미하다.심각도가 하나의 숫자가 아님을 알라
무시가 아니라 기록된 이유와 함께 노이즈 제외
2. 수정: 증상 가리기가 아니라 근본 수정
응급 처치(증상 가리기)와 근본 수정은 다른 작업이다. 둘 다 해야 끝난다.
증상 가리기만(봉쇄)
- 리버스 프록시에서 "수상해 보이는" 요청만 차단
- 증상이 멈췄으니 "처리됨"으로 친다
- 취약한 의존성은 그대로 — RCE는 살아 있다
근본 수정(올바름)
- 취약한 의존성을 패치 버전으로 업그레이드
- 업그레이드 후 로그에서 시그니처가 사라졌는지 확인
- 응급 처치와 근본 수정을 별개 작업으로 보고, 둘 다 한다
메이저 업그레이드는 프로덕션 전에 빌드 검증
패치와 달리, 메이저 버전 점프(프레임워크 14→15, UI 키트 4→6 등)는 호환성 깨짐을 동반한다. 무턱대고 올려 프로덕션 빌드를 깨는 것이 최악이다. 수정한 소스를 프로덕션과 동일한 런타임(같은 Node/PHP 버전 컨테이너)에 넣고, push 전에 빌드/타입 체크를 완전히 통과시켜라. 오류를 하나씩 해소하라(sync→async 코드모드, 여러 줄 CSS 클래스 문자열, 폐기된 전역 타입 네임스페이스 등). 기존 컨테이너가 계속 돌고 있다면, 실패는 무중단으로 롤백된다.
완료에는 수정의 지속성이 포함된다
완벽한 수정도 다음 배포가 지우면 가치가 0이다. 가장 흔한 함정이다.
프로덕션 작업 트리에서 커밋하지 마라
한 방향으로 표준화: 로컬→push→배포
HTTP 200을 '정상'으로 믿지 마라
curl | grep), 로그에 새 오류가 있는가, 그리고 홈페이지뿐 아니라 동적 경로(DB 기반, 매개변수화)를 걸어보라. 캐시가 변경을 지연시킬 수 있으니 기다리거나 먼저 비워라.3. 격리/인계: 고칠 수 없는 것을 명시화하라
지금 당장 모든 것을 고칠 수는 없다. 요령은 "고칠 수 없음 / 내 영역 아님 / 미사용"을 결코 모호하게 두지 않는 것이다.
방치/EOL 코드: 삭제 전에 격리
먼저 정말 참조되지 않는지 확인
고칠 수 없는 것은 명시적으로 인계
4. 모니터링: 매일 변화 탐지가 있어야 비로소 완료
이제 마지막으로 발화 장치를 넣는다. 그것이 없으면, 한 수정이 재발할 때 알려주지 않는다.
매일 모든 것이 아니라 새로운 것에만 경보
스캔 결과를 이전 실행(상태 파일)과 비교(diff)해 새 크리티컬/높음이 나타날 때만 알려라. 매일 같은 내용은 무시된다(경보 피로). 매일 cron으로 하나의 이메일(신규 / 해소 / 현재)로 요약하라. 공유 호스팅도 스캐너 바이너리 + cron 하나는 문제없이 처리한다(부하는 밀리초 단위; 안심을 위해 nice를 더하라).
인벤토리가 함께 드러내는 "시크릿"과 "키"
CVE를 제대로 고치다 보면 의존성 외의 구멍도 함께 드러나는 경향이 있다. 두 고전 — 공개 디렉터리에 남은 시크릿 파일(웹루트에 놓인 옛 토큰; 공유 템플릿에서 왔다면 모든 사이트가 같은 구멍을 가짐)과 침해될 수 있는 환경에 넘겨진 루트 키. 둘 다 "한 번 유출되면 전부"라는 패턴을 만드니, 같은 점검에서 함께 확인하라(각각 별도의 깊은 글이 마땅하다). 시크릿 기본은 시크릿을 안전하게 저장하기와 기준선 체크리스트를 보라.
다음으로 읽기
- 의존성 모니터링: osv-scanner 설치와 사용 · CVE에 뒤처지지 않기
- 사고: 방치된 공개 RCE가 부정 과금으로 청구된 사례
- 운영: 프로덕션은 받기만 / 자체 호스팅 Git vs GitHub
- 토대: 보안 기준선 체크리스트
FAQ
Q취약점 작업은 언제 실제로 '완료'되나요?
네 가지가 맞아떨어질 때뿐입니다: 1) 스캔해 상태를 숫자로 만들고, 2) dev가 아닌 크리티컬/높음을 수정하고, 3) 고칠 수 없는 것/방치 코드를 명시적으로 격리하거나 인계하고, 4) 매일 변화 탐지(신규·재발을 잡는 모니터링)를 갖춥니다. 4가 갖춰지기 전까지는 완료가 아닙니다 — 의존성은 내일 다시 취약해집니다.
Q매일 스캔하면 경보 피로가 생기지 않나요?
매일 모든 것을 경보하면 금세 무시됩니다. 이전 결과(상태 파일)와 비교(diff)해 새로운 크리티컬/높음이 나타날 때만 알리세요 — 변화 탐지입니다. 매일 같은 것을 보내지 않는 것이 모니터링을 실제로 오래가게 합니다.
Q수정이 왜 가끔 다시 취약 상태로 되돌아가나요?
프로덕션 작업 트리에서 직접 커밋하면, 다음 배포(pull이나 checkout -f)가 덮어써 수정을 지웁니다. 한 방향 — 로컬→push→배포 — 으로 표준화하고 프로덕션을 '받기만' 하게 하세요. 수정을 지우는 작업 흐름 위의 완벽한 수정은 가치가 0입니다.