GitHub Enterprise 자격증명 인벤토리 CSV 내보내기 — 토큰 종류 정리와 10월 2일 API 변경
GitHub Enterprise Cloud에 자격증명 인벤토리 내보내기(credential inventory export) 기능이 9월 21일 추가됐다. 엔터프라이즈에 접근할 수 있는 SSH 키, 개인 액세스 토큰(PAT), OAuth·GitHub 앱 토큰을 한 번에 CSV 파일로 받아 보는 기능이다. 공지 문구는 이렇다. "Enterprise owners can now export a complete inventory of every credential that can access their enterprise."(엔터프라이즈 소유자는 이제 엔터프라이즈에 접근할 수 있는 모든 자격증명의 전체 목록을 내보낼 수 있다.) 설치형 GitHub Enterprise Server는 "upcoming releases"(다음 버전들)에서 지원할 예정이다.
무엇이 담기고, 무엇이 빠지나
토큰 종류가 많아서 먼저 정리가 필요하다. GitHub 문서 기준으로 보면 이렇다.
| 유형 | 접두사 | 수명 | 특징 |
|---|---|---|---|
| classic PAT | ghp_ | 만료 강제 없음 | 권한을 scope 단위로 넓게 준다 |
| fine-grained PAT | github_pat_ | 최대 1년 또는 무기한(엔터프라이즈 기본 한도 366일) | 저장소·권한을 세밀하게 지정 |
| OAuth 앱 토큰 | gho_ | 장기 | 앱이 사용자를 대신해 동작 |
| GitHub 앱 사용자 토큰 | ghu_ | 8시간 | 앱이 사용자를 대신하되, 활동은 그 사용자 이름으로 남음 |
| GitHub 앱 설치 토큰 | ghs_ | 1시간 | 앱 자체로 동작 |
인벤토리에는 이 다섯 가지와 SSH 키, 그리고 엔터프라이즈에 접근할 수 있는 연합 자격증명(federated credential)이 들어간다. 설치 토큰은 1시간짜리라 토큰 하나하나가 아니라 "토큰을 발급할 수 있는 앱 설치" 단위로 잡힌다. 반대로 배포 키(deploy key)와 Actions의 GITHUB_TOKEN은 목록에 없다.
GitHub 문서는 방향을 분명히 한다. "GitHub recommends that you use fine-grained personal access tokens instead of personal access tokens (classic) whenever possible."(가능하면 classic 대신 fine-grained PAT를 쓰라.) 인벤토리를 받아 보면 이 권고를 조직이 얼마나 따르고 있는지가 숫자로 드러난다.
내 토큰이 어떤 종류인지는 터미널에서 바로 확인할 수 있다. 이 글을 쓰며 GitHub CLI로 gh auth status를 실행하니(9월 30일, gh 2.92.0) 토큰 앞부분이 gho_, 권한은 'gist', 'read:org', 'repo'로 나왔다. GitHub CLI는 그 자체가 OAuth 앱이어서, 로그인 토큰이 OAuth 앱 토큰으로 잡힌다. 인벤토리에서도 이런 토큰이 사용자 수만큼 줄지어 나올 것이다.
중요한 점 하나. 문서에 "The export never includes the token value."(내보내기에는 토큰 값이 절대 포함되지 않는다)라고 못 박혀 있다. 대신 hashed_token 열에 SHA-256 해시를 Base64로 인코딩한 값이 들어간다. 단 fine-grained PAT는 이 열이 비어 있어서 credential_id 같은 다른 열로 맞춰야 한다. 감사 로그의 같은 필드와 대조하면, 원래 토큰 값 없이도 어느 토큰이 어디서 쓰였는지 추적할 수 있다. SSH 키는 fingerprint 열로 맞춘다.
받는 법과 필터에 대한 오해
경로는 Settings → Authentication security에서 "Overview" 옆의 Export CSV다. 엔터프라이즈 소유자, 또는 "View enterprise credentials" 세분화 권한이 있는 사람이 받을 수 있다. 내보내기가 끝나면 파일이 자동으로 내려받아지고, 다운로드 링크가 메일로도 온다.
공지에는 "filter by user, app, credential type, or organization"(사용자·앱·유형·조직별로 필터)이라는 문장이 있다. 그런데 화면에서 걸러서 받는다는 뜻이 아니다. 문서는 "You cannot filter the inventory before exporting it."(내보내기 전에는 필터할 수 없다)라고 적었다. 전체를 받은 뒤 CSV 열로 거르거나, REST API의 필터 파라미터를 써야 한다. 화면의 Overview에는 유형별 개수만 나오고, 1만 개가 넘으면 10k+로 표시된다.
CSV 한 행은 "자격증명 × 그 자격증명을 승인한 조직" 조합이다. 열은 26개인데, 먼저 볼 열은 몇 개로 줄일 수 있다.
expiry_status가never인 것 — 만료 없는 토큰past_expiration_policy가true인 것 — 강제 수명 한도나 권고 기준을 넘긴 토큰(어느 쪽인지는past_expiration_policy_basis열이enforced_limit·proposed_baseline으로 알려 준다)last_used_at이 비었거나 오래된 것 — 안 쓰는데 살아 있는 토큰repository_selection이all이면서scopes에repo나admin:org가 있는 것 — 영향 범위가 가장 넓은 토큰
이 넷에 동시에 걸리는 토큰부터 정리하면 된다. 참고로 1년 동안 쓰지 않은 PAT와 OAuth 토큰은 GitHub이 자동으로 폐기한다. "오래 안 쓴 토큰"은 대개 1년 안쪽에서 방치된 것들이다.
API로 받는다면: 동기 목록 API는 10월 2일로 종료
REST API는 두 갈래다. 목록을 바로 돌려주는 동기 방식(GET /enterprises/{enterprise}/credentials)은 10월 2일 제거로 예고됐고, 그 날짜가 지났다. 이제는 비동기 내보내기를 써야 한다.
# 1) 내보내기 시작 → 202 응답에 export_id
gh api -X POST /enterprises/ENTERPRISE/credentials/exports \
-H "X-GitHub-Api-Version: 2026-03-10"
# 2) 상태 확인 → 완료되면 302로 다운로드 URL 안내
gh api /enterprises/ENTERPRISE/credentials/exports/EXPORT_ID \
-H "X-GitHub-Api-Version: 2026-03-10"
제거 일정이 실제 응답에도 박혀 있는지 확인해 봤다. 9월 30일 존재하지 않는 엔터프라이즈 이름으로 목록 API를 호출하니 본문은 404였지만, 응답 헤더에 Deprecation: Tue, 29 Sep 2026과 Sunset: Fri, 02 Oct 2026이 찍혀 나왔다. 필요한 권한도 헤더에 드러났다 — X-Accepted-OAuth-Scopes: read:enterprise. 우리 로그인 토큰엔 이 scope가 없어서 실제 목록 조회까지는 해 보지 못했다. 아직 동기 목록 API로 인벤토리를 뽑는 스크립트가 있다면 비동기 방식으로 바꿔야 한다. 내보내기는 하루 횟수 제한이 있어서, 너무 자주 부르면 429 오류가 난다.
왜 지금 이 기능이 나왔나
올해 GitHub은 자격증명 사고 대응 기능을 연달아 냈다. 2월엔 엔터프라이즈 전체의 SSO 승인을 한 번에 취소하는 사고 대응 도구, 3월엔 토큰 값만 알면 누구나 폐기할 수 있는 폐기 API 확장, 6월엔 사용자 한 명의 자격증명을 통째로 폐기하는 기능이 나왔다. 이번 인벤토리는 그 앞 단계인 "무엇이 있는지 파악"을 채운다. 공지 표현으로는 "During a security incident, this enables their security teams to quickly assess the risk surface area"(사고가 났을 때 보안팀이 위험 범위를 빠르게 파악하게 해 준다)이다. 문서가 덧붙이듯 인벤토리 자체는 읽기 전용이고, 실제 폐기는 유형에 따라 엔터프라이즈·조직·앱·사용자 단계에서 따로 한다.
사고 사례를 보면 이 순서가 이해된다. 2025년 3월 tj-actions/changed-files 침해(CVE-2025-30066) 때 미국 CISA는 GitHub PAT와 npm 토큰 등이 노출됐다고 경보를 냈다. 같은 해 9월과 11월 npm 웜 Shai-Hulud는 훔친 GitHub 토큰으로 피해자 계정에 저장소를 만들어 탈취한 시크릿을 올렸다. 올해 5월 Microsoft가 분석한 후속 공격도 ghp_, gho_, ghs_ 토큰을 골라 수집했다. 그리고 5월엔 GitHub 자신도 직원 기기에 설치된 오염된 VS Code 확장 때문에 내부 저장소 접근을 당했다고 공개했다(GitHub은 고객 저장소 영향 증거는 없다고 밝혔다).
숫자도 같은 얘기를 한다. Verizon 「2026 데이터 유출 조사 보고서(DBIR)」에서 최초 침투 경로로서의 자격증명 악용은 13%로 줄었다. 다만 보고서는 올해 분류 방식을 바꾼 영향이라고 설명하면서 이렇게 덧붙였다. 침해 과정 어디서든 자격증명 악용이 나온 경우를 세면 "it still sits on top at 39%"(여전히 39%로 1위)다. GitGuardian 「State of Secrets Sprawl 2026」은 2022년에 유효했던 유출 자격증명 가운데 64% 이상이 올해 1월 재검사에서도 여전히 살아 있었다고 밝혔다. 한 번 새어 나간 토큰은 누가 치우기 전까지 계속 유효하다는 얘기다.
개인적으로는 사고 대응보다 평소 점검에 더 쓸모 있다고 본다. 분기에 한 번 CSV를 받아 위 네 조건으로 거르는 루틴만 만들어 둬도, 사고가 났을 때 뒤질 목록 자체가 짧아진다.
인용한 GitHub 문서(docs.github.com) 문장은 CC BY 4.0 라이선스이며, 체인지로그 문장과 Verizon DBIR 문장은 원문을 짧게 인용했다.