ㅂㄹㄱ

← 목록으로

GitHub 감사 로그 점검기 — 90일을 보라는데 5일치만 남아 있었다

it-trend/security 5분


이 글은 깃허브 PAT 유출과 경찰 권고문 분석의 후속이다.
그 글 마지막에 쓴 말을 내 계정에 그대로 적용해봤다.

들어가며

PAT 유출 사건을 정리하면서 이렇게 끝맺었다. "조직 계정과 감사 로그 스트리밍이 없으면 사고가 나도 사후 확인이 되지 않는다."

남 얘기처럼 썼는데, 정작 내 계정이 그 상태인지는 확인하지 않았다. 그래서 감사 로그를 붙이는 작업을 해보기로 했다. 결론부터 말하면 붙이지 못했다. 붙일 수 없는 구조였기 때문이다.

그 과정에서 확인한 숫자들이 권고문을 다시 읽게 만들었다.


내 구조부터 확인했다

저장소 117개가 어디에 어떻게 놓여 있는지 먼저 셌다.

위치유형저장소비공개
mack-erel개인 User 계정64개45개
tiny-universeOrganization (Free)53개52개

첫 줄에서 이미 문제가 보인다. 나는 이걸 "내 org 두 개"라고 부르고 있었는데, 하나는 org가 아니라 2014년에 만든 개인 계정이었다. 그 안에 비공개 저장소가 45개 있다.

gh api users/mack-erel --jq .type
# "User"

조직 쪽 설정도 확인했다.

gh api orgs/tiny-universe --jq '{
  two_factor: .two_factor_requirement_enabled,
  default_perm: .default_repository_permission
}'
# {"two_factor": false, "default_perm": "read"}

기본 권한은 read로 잘 잡혀 있는데 2단계 인증 요구가 꺼져 있다. 멤버는 두 명이다.


첫 번째 벽 — 감사 로그 API가 404다

조직 감사 로그부터 받아보려 했다.

gh api "orgs/tiny-universe/audit-log?per_page=1"
# {"message":"Not Found","status":"404"}

403이 아니라 404다. 처음엔 토큰 스코프 문제인 줄 알고 admin:org를 붙여봤지만 똑같았다.

원인은 플랜이다. 감사 로그 API는 GitHub Enterprise Cloud 전용이다. Free나 Team 조직에서는 엔드포인트가 아예 없는 것으로 취급된다. 권한이 부족한 게 아니라 기능이 없는 것이다.

404와 403의 차이가 여기서 실질적인 의미를 갖는다. 403이었다면 권한을 고쳐서 해결하지만 404는 돈을 내야 해결된다.

개인 계정 쪽은 더 간단하다. GET /orgs/{org}/audit-log에 대응하는 개인 계정 엔드포인트가 존재하지 않는다. 웹 UI의 Security log에서 눈으로 보는 것이 전부다.


두 번째 벽 — 발급한 토큰 목록도 못 본다

권고문의 첫 항목은 "비정상적인 접근 기록 확인"이었다. 그 전에 내가 어떤 토큰을 발급해뒀는지부터 알아야 한다.

gh api user/tokens
# {"message":"Not Found","status":"404"}

gh api /user/personal-access-tokens
# {"message":"Not Found","status":"404"}

둘 다 없다. 본인이 발급한 PAT 목록조차 API로 조회할 수 없다.

조직은 fine-grained PAT에 한해 승인·조회 API가 있지만 classic PAT은 거기에도 잡히지 않는다. 개인 계정이라면 그마저도 없다. 설정 화면에 들어가 눈으로 세는 수밖에 없다.

토큰이 몇 개인지 프로그래매틱하게 알 수 없으면, 토큰 회전을 자동화하거나 만료를 모니터링하는 것도 불가능하다.


그럼 뭐가 남나 — events API 실측

마지막 대안은 events API다. 여기서 예상 밖의 결과가 나왔다.

gh api "users/mack-erel/events?per_page=100"

인증된 상태로 자기 계정을 조회하면 비공개 저장소 활동도 나온다. 300건 중 279건이 public: false였다. 공개 활동만 보인다고 생각했는데 아니었다.

여기까지는 좋은 소식이다. 문제는 그다음이다.

항목실측값
최대 조회 건수300건 (4페이지에서 하드 컷)
300건이 덮는 기간5일 (2026-07-30 ~ 08-05)
비공개 저장소 활동279 / 300

4페이지를 요청하면 API가 거부한다. "In order to keep the API fast for everyone, pagination is limited"라는 응답이 온다. 300건이 상한이다.

그리고 그 300건이 5일치밖에 안 된다. 활동량이 많으면 창이 그만큼 좁아진다.

경찰 권고문은 "최근 1~3개월간 비정상적인 접속 기록이 있는지" 확인하라고 했다. 실제로 확인 가능한 것은 5일이다.


결정적 공백 — clone은 아예 안 남는다

건수보다 더 큰 문제가 있다. 남는 것이 무엇인지 보면 드러난다.

이벤트 타입건수
PushEvent280
PullRequestEvent12
CreateEvent4
IssueCommentEvent3
DeleteEvent1

전부 쓰기 활동이다. clone이나 fetch 같은 읽기 접근은 한 건도 없다. 애초에 이 API가 다루는 대상이 아니다.

권고문의 두 번째 항목은 "업무 외 시간대에 소스코드가 다운로드(Clone)됐는지" 확인하라는 것이었다.

토큰으로 저장소 45개를 통째로 내려받아도 계정 쪽에는 아무것도 남지 않는다. 앞 글에서 "인증된 접근이 개인 계정에 아무 흔적도 남기지 않는다"고 썼던 것이 그대로 재현됐다.

한 가지 더 눈에 띈 것은 이벤트에 섞여 있던 소유자다. 내 개인 계정, 내 조직, 그리고 회사 조직의 저장소 활동이 한 계정에 함께 잡힌다. 권고문이 "개인 계정으로 회사 비공개 저장소에 접근하는 구조를 다시 보라"고 한 것이 바로 이 그림이다.


그래서 무엇을 하기로 했나

감사 로그를 붙이는 것은 실패했다. 대신 할 수 있는 것을 순서대로 정리했다.

즉시 — Free 플랜에서 가능한 사전 통제

감사 로그가 없으면 사후 확인 대신 사전 차단에 무게를 옮기는 수밖에 없다.

  • 조직 2단계 인증 요구 활성화 (현재 꺼져 있었다) — 켜는 순간 2FA 미설정 멤버가 조직에서 자동 제거되므로 먼저 공지해야 한다
  • 기본 저장소 권한 read 유지
  • 멤버의 비공개 저장소 생성 권한 제한
  • classic PAT 대신 fine-grained PAT으로 전환하고 만료일을 짧게

단기 — events 스냅샷 적재

300건 창이 5일이면, 5일보다 짧은 주기로 떠서 쌓으면 된다. 완전한 감사 로그는 아니지만 없는 것보다 낫다.

# 하루 한 번 스냅샷을 떠서 날짜별로 보관한다
gh api "users/mack-erel/events?per_page=100" \
  --paginate --slurp \
  > "events-$(date +%Y%m%d).json"

여기서 유의할 점은 이 방법이 clone을 잡아주지 않는다는 것이다. 어디까지나 쓰기 활동 이력이다.

중기 — 개인 계정의 비공개 저장소를 조직으로

이게 근본 조치다. 개인 계정에 둔 채로는 플랜을 아무리 올려도 감사 로그를 붙일 수 없다. 조직으로 옮겨야 나중에 Enterprise로 갈 때 선택지가 생긴다.

비공개 45개를 한 번에 옮기는 건 무리다. 협업이 있거나 배포에 연결된 것부터 단계적으로 옮기는 쪽으로 잡았다.


정리

권고문의 각 항목을 실제로 수행할 수 있는지 대조하면 이렇다.

권고 항목개인 계정에서 가능한가
최근 1~3개월 비정상 접속 기록 확인부분적 — events API로 5일치, 웹 Security log는 로그인 위주
업무 외 시간대 소스코드 clone 확인불가능 — 읽기 접근은 기록되지 않는다
발급된 토큰 점검수동만 — 목록 API가 없다
외부 SIEM 연동불가능 — 감사 로그 API 자체가 없다

점검하면서 제일 뼈아팠던 건 "흔적이 남는다"는 가정을 한 번도 확인하지 않았다는 점이다. 최소한 90일치는 볼 수 있다고 막연히 믿고 있었는데 실제로는 5일이었고, 정작 확인하려던 clone은 아예 대상이 아니었다.

기술적으로는 404와 403을 구분해서 읽는 습관이 필요하다. 403이면 권한을 고쳐 해결하지만 404는 기능이 없다는 뜻이다. 감사 로그 API에서 404를 받고 스코프를 만지느라 시간을 썼다.

그래서 방향을 사후 추적에서 사전 통제로 옮겼다. 2단계 인증 강제와 토큰 만료 단축은 화려하지 않지만 지금 켤 수 있는 것들이다.


참고자료


태그 audit-log credential-leak devsecops github github-api github-organization incident-response personal-access-token security security-log two-factor-auth

공유 X 링크드인

FAQ