내 오픈소스에 보안 취약점이 제보되면? GitHub 보안 권고문 대응 가이드

내 오픈소스에 보안 취약점이 제보되면? GitHub 보안 권고문 대응 가이드

오픈소스를 운영하다 보면 스타가 늘거나 이슈가 쌓이는 일에는 어느새 익숙해지는데요. 그러던 어느 날 저장소 Security and quality 탭에 처음 보는 빨간 알림이 뜨고 “당신의 패키지에서 보안 취약점을 발견했습니다”라는 제보가 들어오면 이야기가 달라집니다. 머릿속이 하얗게 변하면서 “이걸 이슈로 답해야 하나? 일단 코드부터 고쳐서 푸시할까?” 하는 생각이 먼저 떠오르죠. 😅

이건 결코 남의 일이 아닌데요. 2021년 전 세계 서버를 떨게 한 Log4j의 Log4Shell 같은 대형 사건도 있었지만, 멀리 갈 것도 없이 우리가 오랫동안 써 온 jQuery에도 XSS 취약점(CVE-2020-11022)으로 보안 권고문이 올라온 적이 있습니다. 규모가 크든 작든 코드를 공개한 이상 보안 제보는 언제든 찾아올 수 있고, 그때는 단순히 버그 하나 고치는 일과는 완전히 다른 절차가 필요하죠. 다행히 GitHub은 이런 상황을 위한 보안 권고문(Security Advisory) 워크플로우를 통째로 제공합니다. 제보를 비공개로 받고, 남들 몰래 패치를 만들고, CVE(Common Vulnerabilities and Exposures) 번호를 발급받고, 준비가 끝나면 한 번에 공개하는 흐름이죠.

이 글에서는 메인테이너가 취약점을 제보받은 순간부터 권고문을 공개하기까지, 무엇을 어떤 순서로 해야 하는지를 차근차근 정리해 보겠습니다.

보안 취약점은 왜 이슈로 올리면 안 될까요

가장 먼저 짚고 넘어가야 할 원칙이 하나 있습니다. 보안 취약점은 패치가 배포되기 전까지 절대 공개적으로 드러내면 안 된다는 거예요. 제보자가 무심코 공개 이슈를 열든, 메인테이너가 “XSS 취약점 수정”이라는 친절한 제목으로 공개 PR을 올리든, 패치가 나오기도 전에 취약점이 드러나는 순간 문제가 생깁니다. 공교롭게도 이 두 경로는 뒤에서 각각 따로 막게 되는데요. 제보자 쪽은 비공개 제보 창구로, 메인테이너 쪽은 임시 비공개 포크로 차단합니다.

생각해 보면 당연한데요. 내 패키지를 깔아 쓰는 수많은 애플리케이션은 아직 취약한 버전 그대로입니다. 그 상태에서 “어디에 어떤 구멍이 있고 어떻게 뚫리는지”를 공개 이슈에 적어두면, 그건 사용자에게 알리는 게 아니라 공격자에게 친절한 공략집을 쥐여주는 셈이 됩니다. 패치가 나오고 사용자들이 업데이트할 시간을 벌기도 전에 공격이 시작될 수 있죠.

그래서 보안 업계에는 책임 있는 공개(Coordinated Disclosure) 라는 관행이 있습니다. 제보자와 메인테이너가 비공개 채널에서 먼저 문제를 확인하고, 수정 버전을 준비해 배포한 다음, 그제야 “이런 취약점이 있었고 이미 고쳤으니 업데이트하세요”라고 세상에 알리는 순서예요. GitHub의 보안 권고문 기능은 이 관행을 그대로 제품으로 옮겨 놓은 것이라고 보면 됩니다. 앞으로 설명할 모든 단계가 결국 “패치 전까지는 조용히, 패치 후에는 확실하게”라는 이 한 문장을 지키기 위한 장치인 셈이죠.

한눈에 보는 전체 흐름

여기까지가 “왜 비공개여야 하는가”에 대한 이야기였는데요. 그렇다면 이 원칙이 실제 절차에서는 어떤 모습일까요? 세부 단계로 들어가기 전에, 전체 그림을 한 장으로 펼쳐 보겠습니다.

flowchart TD
    subgraph priv["🔒 비공개 — 패치 전까지 조용히"]
        direction TB
        R["<b>제보자</b>가 Report a vulnerability로 신고"] --> T["<b>① 제보 접수 (Triage)</b><br/>권고문 후보로 등록"]
        T --> J{"메인테이너 검토"}
        J -->|"Accept and<br/>open as draft"| B["<b>② 권고문 초안 (Draft)</b><br/>영향 범위·CWE·CVSS 정리"]
        J -->|"Close security advisory"| X["<b>반려 (Closed)</b>"]
        B --> C["<b>③ 임시 비공개 포크에서 패치</b><br/>제보자와 협업 · 상황에 따라"]
        C --> D["<b>④ CVE 번호 발급 요청</b><br/>GitHub = CNA · 권장"]
    end
    subgraph pub["📣 공개 — 패치 후 확실하게"]
        direction TB
        E["<b>⑤ 패치 배포 + 수정 버전 등록</b><br/>npm 등 레지스트리"] --> F["<b>⑥ 권고문 공개 (Published)</b><br/>제보자 공로 표기"]
        F --> G["<b>⑦ Advisory DB 등록 → Dependabot 알림</b><br/>사용자에게 자동 통지"]
    end
    D --> E
    classDef optional stroke-dasharray: 5 5
    class C,D optional

보다시피 워크플로우는 크게 두 덩어리입니다. 위쪽 비공개 구간은 제보자가 신고한 건을 메인테이너가 수락하면서 시작되는데(부적절한 제보는 이 길목에서 반려되죠), 그 뒤로는 아무도 모르게 패치를 완성합니다. 아래쪽 공개 구간에서는 수정 버전을 배포한 뒤 권고문을 열어 사용자에게 알리고요. 그리고 이 둘을 가르는 경계, 곧 ④에서 ⑤로 넘어가는 “공개 전환” 시점이 이 절차에서 가장 신중해야 할 대목이죠.

한 가지 미리 알아두면 좋은 건, 이 일곱 단계가 모두 똑같은 무게는 아니라는 점입니다. 반드시 지켜야 하는 건 의외로 단출해요. 영향 범위와 심각도를 파악하고(②), 수정 버전을 배포한 뒤(⑤), 사용자에게 알리는 것(⑥·⑦), 그리고 이 모두를 “패치 전까지 비공개”라는 원칙 위에서 진행하는 것이죠. 반면 위 그림에서 점선으로 표시한 ③ 임시 비공개 포크와 ④ CVE 발급은 심각도와 상황에 맞춰 더 가볍게 가거나 건너뛸 수도 있는 단계입니다. 제보를 받는 ① 비공개 창구도 반드시 켜야 하는 건 아니지만, 없으면 제보가 공개 이슈로 새기 쉬우니 강하게 권장되고요.

참고로 권고문은 진행 상태에 따라 목록에서 Triage(접수), Draft(초안), Published(공개), Closed(반려·종료) 네 개의 탭으로 구분됩니다. 위 다이어그램의 각 단계가 이 상태들과 그대로 맞물리는데요. 제보가 막 들어오면 Triage, 수락해 작업 중이면 Draft, 패치를 배포하고 열면 Published, 반려하면 Closed가 되는 식이에요. 목록에서 탭만 봐도 어떤 권고문이 어느 단계에 있는지 한눈에 파악할 수 있죠.

이제 이 흐름을 따라 각 단계를 하나씩 짚어 보겠습니다.

제보를 비공개로 받는 창구부터 열어두기

원칙을 알았으니, 이제 제보자가 공개 이슈를 만들지 않고도 나에게 조용히 연락할 수 있는 통로를 마련해야 합니다. 통로가 없으면 선의의 제보자도 결국 공개 이슈를 열 수밖에 없으니까요.

GitHub에는 비공개 취약점 제보(Private Vulnerability Reporting) 기능이 있습니다. 저장소의 Settings 탭으로 들어가 사이드바의 Security 섹션에서 Advanced Security를 누르면, Private vulnerability reporting 항목 옆에 Enable 버튼이 보입니다. 공개 저장소라면 추가 비용 없이 켤 수 있어요.

이 기능을 켜면 저장소의 Security and quality 탭 아래 Advisories 메뉴가 제보를 받는 창구가 됩니다. 외부 제보자가 이 페이지에 들어오면 Report a vulnerability 버튼이 보이고, 이 버튼으로 다른 사람에게는 보이지 않는 비공개 폼을 작성해 제출하죠.

한 가지 헷갈리기 쉬운 점은, 같은 Advisories 페이지라도 보는 사람에 따라 화면이 다르다는 겁니다. 메인테이너인 내가 이 페이지를 열면 Report a vulnerability 대신 New draft security advisory 버튼이 보이는데, 취약점을 직접 발견해 초안을 만들 때 쓰는 버튼이죠. 제보자가 보낸 건들은 그 아래 Triage 목록에 쌓여 하나씩 검토를 기다립니다. 제보하는 쪽과 받는 쪽에게 보이는 화면이 다른 셈이에요.

여기서 흔히 혼동하는 게 하나 있는데요. 저장소 루트에 두는 SECURITY.md 파일과 비공개 제보 기능은 서로 별개입니다. SECURITY.md는 “보안 문제는 이렇게 알려주세요”처럼 사람이 읽는 보안 정책 안내문이고, 비공개 제보 기능은 GitHub UI 안에서 작동하는 구조화된 제보 통로예요. 비공개 제보를 켜두면 제보자가 굳이 SECURITY.md의 안내를 따르지 않아도 Security and quality 탭에서 바로 제보할 수 있죠.

이때 꼭 점검할 게 하나 있습니다. SECURITY.md에 “취약점은 공개 이슈로 알려주세요” 같은 안내가 남아 있으면 안 된다는 건데요. 모처럼 비공개 창구를 열어 놓고도 안내판이 제보자를 공개 이슈로 떠미는 꼴이 되니까요. 의외로 비공개 제보가 켜져 있는데도 SECURITY.md는 여전히 공개 이슈로 신고하라고 안내하는 저장소가 적지 않습니다. 비공개 제보 쪽으로 유도하도록 SECURITY.md도 함께 손봐 두면, 어느 쪽으로 오는 제보든 안전하게 받아낼 수 있습니다.

내 저장소에 등록된 권고문은 GitHub CLIgh 명령으로도 확인할 수 있습니다. 내 저장소의 권고문 목록은 다음처럼 조회됩니다.

gh api repos/OWNER/REPO/security-advisories

이미 공개된 권고문이라면 GitHub Advisory Database에서 누구나 조회할 수 있는데요. 예를 들어 앞서 언급한 jQuery XSS 권고문을 조회하면, 요약과 심각도, 영향받는 버전 같은 정보가 그대로 내려옵니다.

결과
$ gh api /advisories/GHSA-gxr4-xjj5-5px2 | jq '{
    summary, severity, cve: .cve_id,
    vulnerable: (.vulnerabilities[] | select(.package.ecosystem=="npm") | .vulnerable_version_range)
  }'
{
  "summary": "Potential XSS vulnerability in jQuery",
  "severity": "medium",
  "cve": "CVE-2020-11022",
  "vulnerable": ">= 1.12.0, < 3.5.0"
}

보안 권고문 초안을 작성하기

제보가 들어왔다고 곧장 정식 권고문이 되는 건 아닙니다. 먼저 Triage 목록에 접수되고, 메인테이너가 내용을 살펴본 뒤 Accept and open as draft 버튼을 눌러야 비공개 권고문 초안(Draft Security Advisory) 으로 전환되죠. 외부 제보를 함부로 정식 권고문으로 만들지 않도록 검토·수락이라는 관문을 둔 셈인데, 스팸이나 오인 제보를 여기서 걸러낼 수 있습니다. 만약 유효하지 않은 제보라면 Accept 대신 Close security advisory 버튼으로 반려해 닫으면 되고요. 반대로 제보를 기다릴 것 없이 메인테이너가 직접 초안을 새로 만들 수도 있습니다. 초안은 말 그대로 비공개라서, 저장소 관리자 권한을 가진 사람과 초대된 협업자에게만 보입니다. 여기서 제보 내용을 검증하고, 영향 범위를 정리하고, 심각도를 매기는 작업을 하게 됩니다.

권고문에 채워 넣어야 하는 핵심 정보는 우선 영향받는 패키지와 버전 범위입니다. 앞서 본 jQuery라면 “jquery 패키지의 1.12.0 이상 3.5.0 미만 버전”처럼 적습니다. 그리고 약점 유형(CWE, Common Weakness Enumeration)과 심각도(CVSS, Common Vulnerability Scoring System)를 지정합니다. CWE는 “공통 약점 열거”라는 이름 그대로 취약점의 종류를 분류한 표준 식별자로, 예를 들어 입력값 검증 없이 스크립트가 실행되는 XSS는 CWE-79에 해당합니다. jQuery 취약점도 바로 이 CWE-79로 분류되었죠.

심각도를 나타내는 CVSS는 “공통 취약점 점수 체계”라는 이름처럼 점수로 환산되는데, GitHub은 이걸 Low / Moderate / High / Critical 네 등급으로 보여줍니다. CVSS는 벡터 문자열로 세부 항목을 기록하는데, 처음 보면 암호처럼 느껴지지만 분해해 보면 의외로 읽을 만합니다. 앞서 본 jQuery 권고문에 실제로 매겨진 벡터를 예로 들어 볼게요.

CVSS 3.1 벡터
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:N

여기서 AV:N은 공격 경로가 네트워크(Network)라는 뜻이고, AC:H는 공격 난이도가 높아(High) 특정 조건이 갖춰져야 한다는 의미입니다. PR:N은 별도 권한(Privileges)이 필요 없고, UI:R은 피해자의 상호작용(User Interaction)이 있어야 한다는 뜻이죠. S:C는 영향 범위(Scope)가 취약한 컴포넌트를 넘어 다른 영역까지 번진다(Changed)는 의미이고, 뒤쪽의 C:H/I:L/A:N은 각각 기밀성(Confidentiality)·무결성(Integrity)·가용성(Availability)에 대한 영향으로, 기밀성에 높은(High) 영향이 있다는 것을 나타냅니다. 이렇게 항목을 채우면 GitHub이 자동으로 점수와 등급을 계산해 줍니다. 직접 점수를 외울 필요 없이, 각 항목을 솔직하게 평가하는 게 핵심입니다.

초안 단계에서 욕심내지 말아야 할 것도 있습니다. 영향을 과장하거나 축소하지 말고, 재현 절차(Proof of Concept)를 가능한 한 구체적으로 적어두는 편이 좋아요. 나중에 패치가 실제로 그 시나리오를 막는지 검증할 기준이 되거든요.

임시 비공개 포크에서 조용히 패치하기

심각도까지 정리했다면 이제 실제로 코드를 고칠 차례인데요. 앞서 말한 원칙 때문에 평소처럼 브랜치를 따서 공개 PR을 올렸다간, 패치가 준비되기도 전에 수정 내용이 그대로 노출될 수 있습니다. 그래서 GitHub은 권고문 화면의 Start a temporary private fork 버튼으로 임시 비공개 포크(temporary private fork) 를 만들 수 있게 해줍니다.

이 포크는 권고문에 접근할 수 있는 사람에게만 보이는 비공개 공간이라, 여기서는 마음 놓고 브랜치를 만들고 커밋을 쌓고 PR을 열어 리뷰할 수 있습니다. 공개 타임라인에는 아무것도 노출되지 않으면서도, 평소에 쓰던 PR 리뷰 흐름을 그대로 쓸 수 있다는 게 장점이죠.

이 단계에서 제보자를 협업자로 초대하는 것도 좋은 선택입니다. 취약점을 가장 잘 이해하는 사람은 결국 그걸 찾아낸 제보자이기 때문에, 패치가 정말로 구멍을 막는지 함께 검토하면 헛다리를 짚을 위험이 줄어듭니다. 제보 폼을 통해 들어온 제보자는 자연스럽게 권고문 협업자로 추가되어 같은 화면을 볼 수 있고요.

패치를 만들 때는 “보고된 그 입력 하나만 막는” 식의 땜질을 경계해야 합니다. 예를 들어 어떤 입력값을 검증 없이 그대로 렌더링해서 생긴 문제라면, 특정 문자열 하나를 차단하기보다 안전한 값만 허용하는 공통 검증 함수를 두고 같은 패턴을 쓰는 모든 곳에 적용하는 편이 근본적입니다. 비슷한 취약점이 여기저기 흩어져 있는 경우가 많아서, 한 군데만 고치고 공개했다가 곧바로 다음 제보를 받는 일도 흔하거든요.

여기까지만 보면 임시 비공개 포크가 정답처럼 들리지만, 모든 취약점에 반드시 써야 하는 건 아닙니다. 핵심은 “패치 전 노출 금지”라는 원칙이지 포크라는 특정 기능이 아니거든요. reverse tabnabbing처럼 악용 난이도가 높고 영향이 제한적인 Low 등급이라면, 굳이 별도 포크를 파지 않고 “Link 컴포넌트의 rel 처리 개선”처럼 담백한 제목의 일반 PR로 조용히 고친 뒤 다음 릴리스에 실어도 충분할 때가 많아요.

다만 공개 저장소에서 일반 PR로 고치는 길을 택할 때는 patch gap이라는 함정을 알아둬야 합니다. 패치 커밋이 공개 저장소에 머지되는 순간, 그 diff 자체가 “여기에 무슨 문제가 있었는지”를 드러냅니다. 그런데 새 버전 배포와 사용자 업데이트는 그보다 늦게 이뤄지죠. 바로 이 머지와 배포 사이의 시간차가 patch gap이고, 공개 커밋을 지켜보던 공격자는 이 틈을 노려 아직 업데이트하지 않은 사용자를 공격할 수 있습니다. 제목을 담백하게 다는 건 자동 스캐너의 눈을 피하는 정도일 뿐, 끈질긴 공격자가 커밋 diff를 역설계하는 것까지 막아주지는 못해요.

그래서 “비공개 포크로 갈지, 공개 PR로 갈지”는 결국 메인테이너가 저울질해야 할 득실의 문제입니다. 심각도가 높고 악용이 쉬울수록, 또 사용자 기반이 넓을수록 patch gap의 위험이 커지므로 임시 비공개 포크로 노출을 0에 가깝게 줄이는 편이 안전합니다. 반대로 영향이 제한적이고 빠르게 고쳐 곧장 배포할 수 있다면, 공개 협업의 투명함을 살리면서 patch gap은 신속한 릴리스로 좁히는 선택도 합리적이죠.

CVE 번호 발급받기

패치 준비가 어느 정도 됐다면, 권고문에 CVE 번호를 붙일 차례입니다. 이건 반드시 거쳐야 하는 필수 단계는 아니지만, 사용자에게 제대로 알리고 싶다면 권장되는데요. CVE는 전 세계적으로 통용되는 취약점 식별 번호입니다. CVE-2026-12345 같은 형식의 이 번호가 있어야 보안 도구나 데이터베이스가 “이 취약점”을 명확하게 가리킬 수 있습니다.

그런데 번호 이야기를 더 하기 전에 짚어둘 게 하나 있습니다. 사실 권고문을 만든 순간부터 거기엔 이미 GHSA-로 시작하는 식별자가 하나 붙어 있는데요. 이 GHSA(GitHub Security Advisory) ID는 GitHub이 권고문마다 자동으로 매겨 주는 자체 번호라 따로 신청할 필요가 없고, 이것만으로도 Dependabot 경보나 GitHub Advisory Database 노출은 모두 정상 작동합니다. 반면 CVE는 이 GHSA ID와는 별개로, 내가 직접 따로 요청해야 부여되는 번호예요. 즉 한 권고문에 식별자가 둘인 셈인데, GHSA는 GitHub 생태계 안을 책임지고 CVE는 그 바깥의 보안 도구나 취약점 데이터베이스까지 가닿게 해주는 역할을 나눠 맡습니다.

요청은 내가 해야 하지만, 좋은 소식은 그렇다고 GitHub 바깥 어딘가에 따로 신청서를 보낼 필요는 없다는 점이에요. GitHub 자체가 공인된 CNA(CVE Numbering Authority), 즉 CVE 번호를 발급할 권한을 가진 기관입니다. 제보를 수락하고 나면 권고문 화면 아래쪽에 Request CVE 버튼이 보입니다. 이 버튼을 누르면 advisory가 GitHub 검토로 넘어가고, 검토를 거쳐 번호가 부여되는데 보통 영업일 기준 사흘 안팎이 걸립니다. 이 버튼은 드롭다운으로 되어 있어서, CVE가 필요 없다면 바로 옆 Publish advisory를 골라 검토 없이 곧장 공개로 건너뛸 수도 있어요. 만약 다른 경로로 이미 발급받은 CVE 번호가 있다면, 그 번호를 권고문에 직접 입력할 수도 있고요.

한 가지 더 알아두면 좋은 건, CVE 번호를 받는 것과 그 내용이 세상에 공개되는 게 서로 다른 일이라는 점입니다. Request CVE로 번호를 받아도 그 자체로 권고문이 공개되지는 않거든요. 번호를 배정받은 직후 공개 CVE 데이터베이스를 찾아보면, 아직 내용은 비어 있고 번호만 잡혀 있는 RESERVED(예약됨) 상태로만 보입니다. 실제 취약점 내용은 나중에 권고문을 공개하는 시점에야 GitHub이 CVE 프로그램(MITRE)으로 넘겨 채워 넣죠. 덕분에 패치를 준비하는 초안 단계에서 번호만 미리 확보해 두고, 그 번호를 릴리스 노트나 커밋 메시지에 적어둔 채로 정작 취약점 내용은 공개 전까지 가려둘 수 있습니다. “번호는 예약해 두되 내용은 비공개”인 셈이라, 앞서 본 patch gap을 다루기에 잘 맞는 구조예요.

작은 라이브러리라고 해서 CVE가 과하다고 느낄 필요는 없습니다. CVE가 붙어 있어야 기업 환경의 의존성 스캐너가 취약점을 자동으로 잡아내고, 사용자들이 자기 프로젝트가 영향권에 있는지 빠르게 판단할 수 있습니다. 공개를 앞두고 있다면 미리 요청해 두는 게 좋아요.

패치를 배포하고 권고문을 공개하기

이제 가장 중요하면서도 순서를 틀리기 쉬운 마지막 단계입니다. 핵심은 사용자가 도망갈 곳을 먼저 만들어 두고 나서 공개하는 것이에요.

순서를 정리하면 이렇습니다. 먼저 임시 포크의 패치를 메인 브랜치에 병합하고, 수정된 새 버전을 패키지 레지스트리(npm 등)에 배포합니다. 그다음 권고문 화면의 수정 버전(Patched version) 칸에 안전한 버전을 적어 둡니다. GitHub 문서도 “권고문을 공개하기 전에 수정 버전을 먼저 추가하라”고 강조하는데요. 이 정보가 있어야 사용자가 “그럼 어느 버전으로 올리면 되는데?”라는 질문에 즉시 답을 얻을 수 있기 때문입니다. 필수 정보가 다 채워지면 화면 위쪽에 “You’re ready to publish”라는 안내가 떠서 이제 공개해도 된다는 걸 알려주죠.

이 모든 준비가 끝났으면 권고문을 공개할 차례입니다. 앞서 본 Request CVE 버튼의 드롭다운에서 Publish advisory를 고르면 advisory가 그 자리에서 곧장 공개되고, Request CVE를 고르면 GitHub 검토를 거쳐 CVE와 함께 공개됩니다. 어느 쪽이든 공개되는 순간 두 가지 일이 연쇄적으로 일어나는데요. 우선 권고문이 GitHub Advisory Database에 등록되고, GitHub의 검토를 거쳐 영향받는 버전을 쓰는 사용자들에게 Dependabot이 자동으로 경고를 보냅니다. 이 검토에도 최대 72시간 정도가 걸릴 수 있습니다. 사용자 입장에서는 어느 날 자기 저장소에 “당신이 쓰는 jquery에 취약점이 있으니 이 버전으로 올리세요”라는 알림이 뜨는 셈이죠. 패치 버전을 먼저 배포해 두는 게 왜 중요한지 여기서 분명해집니다. 알림을 받은 사용자가 곧장 업데이트할 수 있어야 하니까요.

마지막으로 잊지 말아야 할 게 제보자에게 공로를 돌리는 일(credit)입니다. 권고문에는 기여자를 표기하는 기능이 있어서, 취약점을 찾아 책임 있게 알려준 제보자의 이름을 남길 수 있습니다. 금전적 보상이 없는 오픈소스에서 이런 인정은 제보자에게 큰 동기가 되고, “이 프로젝트는 제보를 진지하게 받아준다”는 평판으로 이어져 다음 제보를 부르기도 합니다.

흔히 저지르는 실수들

절차를 알아도 막상 닥치면 헷갈리기 쉬운 지점들이 있습니다. 몇 가지만 미리 짚어둘게요.

가장 치명적인 실수는 역시 공개적으로 먼저 알려버리는 것입니다. 급한 마음에 공개 이슈로 답하거나, 커밋 메시지에 “보안 취약점 수정”이라고 친절히 적어 공개 저장소에 푸시하는 경우죠. 패치 코드 자체가 곧 취약점의 위치를 알려주는 단서가 되기 때문에, 공개 전까지는 임시 비공개 포크 안에서만 작업해야 합니다.

다음으로 수정 버전을 배포하지 않은 채 권고문부터 공개하는 것입니다. 이러면 경고만 잔뜩 받은 사용자들이 정작 올라갈 버전이 없어 발만 동동 구르게 됩니다. 항상 “배포 먼저, 공개 나중”이라는 순서를 지켜야 해요.

제보를 너무 오래 방치하는 것도 문제입니다. 책임 있는 공개에는 보통 90일 정도의 암묵적인 유예 기간(embargo)이 있는데, 메인테이너가 응답조차 하지 않으면 제보자가 기간이 지난 뒤 스스로 공개해 버릴 수 있습니다. 당장 고칠 여유가 없더라도, 최소한 “확인했고 언제까지 대응하겠다”는 답신은 빠르게 보내는 게 좋습니다.

마지막으로 라이브러리 메인테이너가 흔히 빠지는 함정이 있습니다. “우리 코드는 사용자 입력을 직접 받지 않으니 안전하다”는 생각인데요. UI 컴포넌트나 유틸리티 라이브러리는 그 자체로는 입력을 받지 않더라도, 그것을 가져다 쓰는 애플리케이션이 사용자 입력을 그대로 넘길 가능성이 늘 있습니다. 내 라이브러리가 그 입력을 안전하게 처리해 주리라 믿고 말이죠. 그래서 영향 범위를 평가할 때는 내 코드만이 아니라 “소비하는 쪽에서 어떻게 쓸 수 있는가”까지 함께 봐야 합니다.

마치며

보안 취약점 제보를 처음 받으면 누구나 당황하지만, 제보자에서 출발하는 한 줄기 흐름으로 따라가 보면 의외로 단순합니다. 제보자가 Report a vulnerability로 취약점을 비공개로 알려 오고(Triage), 메인테이너가 이를 검토해 수락하면(Draft) 본격적인 대응이 시작되죠. 영향 범위와 심각도를 정리하고, 임시 비공개 포크에서 조용히 패치한 다음, 수정 버전을 배포하고 나서야 권고문을 공개합니다(Published). 이 가운데 임시 비공개 포크나 CVE 발급처럼 세부적인 선택은 취약점의 심각도에 맞춰 더하거나 덜어내도 괜찮습니다. 공개 PR로 가볍게 갈지 비공개 포크로 단단히 갈지, patch gap을 둘러싼 득실을 가늠하는 일 역시 메인테이너의 몫이고요. 다만 이 모든 과정을 관통하는 원칙, 곧 “패치 전까지는 조용히, 패치 후에는 확실하게”라는 책임 있는 공개만큼은 심각도와 무관하게 흔들리지 말아야 할 하나였습니다.

이렇게 공개된 권고문이 사용자에게 닿는 마지막 길목이 바로 Dependabot을 통한 의존성 보안 알림인데요. 메인테이너로서 권고문을 잘 공개해 두면, 반대편의 사용자는 별다른 노력 없이도 자동으로 경고를 받고 안전한 버전으로 올라갈 수 있습니다. 저장소 보안을 더 단단하게 만들고 싶다면 GitHub의 보안 기능들을 함께 살펴보시길 권합니다.

더 자세한 절차와 화면별 안내는 GitHub 보안 권고문 공식 문서를 참고하세요.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord