- GitHub API kullanılırken PR yorum bağlantısı oluşturma özelliğinde kimlik uyuşmazlığı nedeniyle bağlantıların çalışmadığı bir sorun ortaya çıktı
- Araştırma sonucunda GitHub’ın GraphQL node ID ile REST API database ID olmak üzere iki ayrı ID sistemi kullandığı görüldü
- node ID base64 ile decode edildiğinde, alt 32 bitte database ID’nin yer aldığı doğrulandı ve bunun basit bir bit maskesi işlemiyle dönüştürülebildiği anlaşıldı
- Ek analizler, GitHub’ın MessagePack tabanlı yeni bir ID biçimi ile string tabanlı legacy biçimi birlikte kullandığını ortaya koydu
- Bu yapı, GitHub’ın dahili nesne kimliklendirme sistemindeki ikiliği gösteriyor ve API entegrasyonu yapan geliştiricilerin dikkatli olmasını gerektiriyor
GitHub’ın çift ID sisteminin keşfi
- Greptile’ın yapay zeka kod inceleme aracı özelliği geliştirilirken, GitHub PR yorum bağlantılarının çalışmaması sorunu yaşandı
- Kaydedilen yorum ID’si URL’ye eklendi, ancak tıklandığında GitHub sayfasına gidilmedi
- GitHub belgeleri incelendiğinde, GraphQL API’nin node ID’si ile REST API’nin database ID’sinin birbirinden farklı iki sistem olduğu görüldü
- node ID örneği:
PRRC_kwDOL4aMSs6Tkzl8
- database ID örneği:
2475899260
- node ID, GitHub genelinde nesneleri küresel olarak tanımlamak için kullanılan base64 encode edilmiş bir string, database ID ise URL’de kullanılan tamsayı türünde bir tanımlayıcı
node ID ile database ID arasındaki ilişkinin analizi
- Birden fazla PR yorumunun node ID ve database ID değerleri karşılaştırıldığında, iki değerin de düzenli aralıklarla arttığı görüldü
- node ID’nin base64 kısmı decode edildiğinde 96 bitlik bir tamsayı elde edildi ve bu değerin alt 32 bitinin database ID ile eşleştiği doğrulandı
- Örnek:
PRRC_kwDOL4aMSs6Tkzl8 → alt 32 bit = 2475899260
- Basit bir bit maskesi işlemiyle database ID çıkarılabiliyor
- Dönüşüm,
decoded & ((1 << 32) - 1) biçimindeki işlemle yapılıyor
GitHub’ın legacy ID biçimi
- Eski bir depodaki (
torvalds/linux) node ID decode edildiğinde farklı biçimde bir string ortaya çıktı
- Örnek:
MDEwOlJlcG9zaXRvcnkyMzI1Mjk4 → 010:Repository2325298
- Bu biçim,
[nesne türü numarası]:[nesne adı][Database ID] yapısında, açık string tabanlı bir tanımlayıcı
- Ağaç nesnelerinde
04:Tree2325298:7201bfb9... biçimi görülüyor ve burada depo ID’si ile SHA değeri birlikte yer alıyor
- GitHub, legacy biçim ile yeni biçimi birlikte kullanıyor; biçim, nesne türüne ve oluşturulma zamanına göre değişiyor
Yeni node ID biçiminin yapısı
- GitHub’ın GraphQL migration guide dokümanı, node ID’nin opak bir string olarak ele alınmasını söylese de, içinde bir yapı bulunuyor
- base64 decode işleminden sonra MessagePack ile unpack edildiğinde dizi biçiminde veri elde ediliyor
- Örnek:
[0, 47954445, 2475899260]
- Dizinin yapısı
- İlk öğe (0): sürüm tanımlayıcısı olduğu tahmin ediliyor
- İkinci öğe (47954445): deponun database ID’si
- Üçüncü öğe (2475899260): nesnenin database ID’si
- Nesne türüne göre dizinin uzunluğu değişiyor; commit nesneleri SHA içeriyor, repository nesneleri ise yalnızca iki öğe içeriyor
Pratik kullanım ve sonuç
1 yorum
Hacker News yorumları
En yeni GitHub global node ID'si
'X-Github-Next-Global-ID'başlığı üzerinden zorla kullanılabiliyorID, nesnenin tip öneki ve base64 ile kodlanmış bir msgpack payload'undan oluşuyor
Örneğin benim kullanıcı ID'm
"U_kgDOAAhEkg",[0, 541842]olarak decode ediliyor; bu da REST API'dekidatabaseIdile eşleşiyorAncak böyle iç uygulama detaylarına dayanmak yerine, GraphQL API'deki
databaseIdalanını doğrudan sorgulamak daha iyiİlgili belgeler: GraphQL global node ID migration guide, GitHub kullanıcı bilgilerim, CyberChef decode örneği, GitHub ETag implementasyonu
Bu şekilde decode etmenin kırılgan olduğunu düşünüyorum
GraphQL'in global node ID'leri aslında opaque olmalı
GitHub'daki birçok tip (
PullRequestvb.)databaseIdalanı sağlıyor; doğru olan onu kullanmakÇoğu GraphQL API'si tip adını ve DB ID'sini base64 ile kodlar, ama bu kuralın hep korunacağı garanti edilemez
Referans: PullRequest nesnesi dokümantasyonu, GraphQL global ID spec
permalink,urlgibi alanlar veUniformResourceLocatablearayüzü var; bu yüzden URL'yi elle kurmak gerekmiyorAPI'nin permalink vermesinin nedeni de bu. ID ya da link desenleri her an değişebilir
Bu yaklaşım pagination token'larında da sık görülür
010:Repository2325298gibi ID'lerin belirgin bir yapısı var010tip enum'u,Repositoryadı,2325298ise DB ID'siYani bu bir length prefix biçimi. Repository 10 karakter, Tree ise 4 karakter
Opus 4.5 bu GitHub ID decode numarasını biliyor ve otomatik olarak decode kodu yazıyor
Yazarın bulduğu şey teknik olarak doğru ama dokümante edilmemiş ve desteklenmiyor
GitHub geçmişte de node ID'nin iç yapısını sessizce değiştirmişti
MessagePack dizisine alan eklenirse, encoding değişirse, şifrelenirse ya da UUID tabanlı hale gelirse
bu iç yapıya dayanan sistemler anında bozulur
Benim açıkça sakladığım GitHub tanımlayıcıları en fazla değişmez URL anahtarları (
issue/prnumaraları veya commit hash'leri)Yorum ID'lerini de JSON blob'un içine olduğu gibi koyuyorum
Her şeyi normalize etmeye çalışmak gerekmiyor. JSON yeterince hızlı
Yorum düzeyinde çapraz sorgular yapmadığınız sürece, bunun bir performans sorunu olarak ortaya çıkması pek olası değil
Depo ad değiştirirse ya da başka bir organizasyona taşınırsa URL değişebilir
Eski v3 API'de ID yoktu; bu yüzden biri kullanıcı adını ya da depo adını değiştirince kimin kim olduğunu takip etmek zordu
Bu yüzden takım bazlı bir sahiplik yönetim sistemini kendim yazmıştım
Terraform provider pek iyi değildi; bu nedenle offboarding sırasında “tek admin olan kişi ayrıldı” gibi sorunlar sık yaşanıyordu
Tüm depoların sahibi bir takım olur ve erişim izinleri de yalnızca takım bazında verilir
Bu tür takım tabanlı erişim kontrolü sadece GitHub için değil, başka sistemlerde de faydalı
Bu, Hyrum’s Law için klasik bir örnek — insanlar dokümante edilmemiş davranışlara bağımlı olmaya başlayınca eninde sonunda her şey bozulur
Veritabanı tasarımında genelde dışarıya opaque doğal anahtarlar verilir, içeride ise artan tamsayı ID'ler kullanılır
Ama bileşik ID kullanıldığında bu sorunlar azalır.
Örneğin depo ID'sinin içinde nesne ID'si de varsa, ID artırılarak ancak aynı depo içindeki nesneler taranabilir
Buna entropi veya timestamp da eklenirse, kötüye kullanım neredeyse imkansız hale gelir
Bu yüzden anlamsız bir surrogate key'i dışarıya açmak daha güvenlidir
Örneğin YouTube içeride indeks numarası kullanıyor olsa bile, dışarıya anlamsız kod biçiminde ID'ler verir
GitHub ekibinin son birkaç yılda Rails'e sharded/multi-database desteğini neden ciddi biçimde genişlettiği şimdi daha anlaşılır geliyor