# Sessiz kalan hata en tehlikelisi: CommitBrief'te fail-closed

> Kısmi bir config'te tek bir CommitBrief komutu secret taramasını sessizce kapatıyordu. Temmuz–Ekim arasında kapattığım sessiz hatalar ve ilke: fail closed.

- Yayın: 2026-11-13
- Kategori: Günlük
- Etiketler: Güvenlik, CI/CD, Git, Go, Yapay Zekâ
- Okuma süresi: 6 dk okuma
- Kaynak: https://muhammetsafak.com/tr/blog/commitbrief-sessiz-hata-fail-closed/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
v1.16.0 ve öncesindeki her CommitBrief sürümünde, alanların hepsini yazmayan kısmi bir config dosyasında tek bir `config set provider openai` komutu secret taramasını kapatıyordu. Ne bir uyarı vardı ne de bir şeyin değiştiğine dair herhangi bir işaret.

Bunu v1.17.0'da düzelttim, ama asıl aldığım ders düzeltmenin kendisi değil. Temmuz ile Ekim arasında kapattığım sessiz hataların çoğu aynı biçimdeydi: araç yanlış bir şey yaptı ve bunu söylemedi. Bu yazı o hataları ve hepsinden çıkardığım tek ilkeyi anlatıyor: bir güvenlik kapısı belirsizlik karşısında kapalı başarısız olmalı (fail closed); yalnız bilgi veren komut yumuşak bozulabilir. Ayrıntıların kaynağı projenin [CommitBrief: CHANGELOG.md, v1.21.1](https://github.com/CommitBrief/commitbrief/blob/v1.21.1/CHANGELOG.md) dosyasıdır.

Bu bir suçlama yazısı değil, bir postmortem. CommitBrief benim aracım; hataların hepsi onda çıktı ve etkiyi CHANGELOG'da yazdığı kadar anlatacağım, ne eksik ne fazla.

## Tek komut, üç şey birden kapanıyordu

`config set` ve `providers use`, config dosyasını tam bir `Config` struct'ına decode ediyor, sonra bütün struct'ı yeniden marshal ediyordu. Dosyada geçmeyen her alan belgelenmiş varsayılanıyla değil, Go'nun zero value'suyla geri yazılıyordu: boolean `false`, sayı `0`, string `""`. `setup` yalnız dokunduğu alanları yazdığı için kısmi config dosyaları sık rastlanan bir şekildi.

Tek satırlık bir `provider: openai` config'iyle, eski bir sürümün binary'sinde yeniden ürettim. Tek bir `commitbrief config set provider openai` sonrasında `guard.secret_scan`, `review.baseline` ve `review.architecture` `false` olmuştu: secret tarayıcı, gürültüyü kontrol eden baseline ve mimariyi gözeten review birlikte kapanmıştı. Aynı komut `cache.enabled`, `cache.ttl_days` ve `output.lang` gibi alanları da sıfırladı.

Bu kayıt bir sızıntı raporu değil, bir korumanın haber vermeden kalkmasının raporu. Kaç kişinin bu komutu etkilenen sürümlerde çalıştırdığını bilmiyorum, o yüzden etkilenenlerin sayısı hakkında bir şey söylemiyorum.

Yukarıdaki liste, tek bir başlangıç dosyasının ürettiği sonuçtur; her config şekli için eksiksiz bir envanter değil. CHANGELOG'a da böyle yazdım. Etkilenmiş olabileceğinizi düşünüyorsanız yalnız anılan anahtarlara değil, `commitbrief config show` çıktısındaki her değere bakın ya da `commitbrief setup`'ı yeniden çalıştırıp her alanı açıkça yazılmış bir config alın.

Düzeltme, YAML'ı decode edip yeniden yazmak yerine yerinde patch'lemek: bir yazma yalnız istenen anahtara dokunuyor, yorumlar, anahtar sırası, anchor ve alias'lar ve şemanın tanımadığı anahtarlar olduğu gibi kalıyor.

Aynı sürümde buna komşu ikinci bir sessizlik de kapandı. Config'te `regex:` yerine `regexp:` yazarsanız dosya sorunsuz okunuyor ve yazdığınız şey hiçbir şey yapmıyordu. Bilinmeyen config anahtarı artık hard error: dosyayı, tam anahtarı, o seviyede izin verilen anahtarları ve yakın bir eşleşme varsa "did you mean" önerisini söylüyor. Bir koşu için uyarıya indirmek isterseniz `--ignore-unknown-config` var; o da her anahtarı yine adıyla sayıyor.

## Yanlış şeye bakan araç

Aynı sınıftan üç hata daha var. Üçünde de araç çalışıyordu; yalnız yanlış şeye bakıyor ya da yanlış şeyi gönderiyordu.

**Susturma dosyalar arası sızıyordu (v1.19.0).** Bir dosyanın N. satırındaki `commitbrief-ignore` marker'ı, aynı diff'teki başka bir dosyanın N. satırındaki bulguyu da susturuyordu. Marker'lar artık dosya ve satırla birlikte anahtarlanıyor. CHANGELOG'da yazdığı gibi: daha önce yanlışlıkla susturulan bulgular yükseltmeden sonra yeniden görünecek. Susturulmuş bir bulguyu kimse aramadığı için bu, listedeki en sinsi hata. Baseline'ın ve PR thread'lerinin bir bulguyu nasıl tanıdığını [bir önceki yazıda](https://muhammetsafak.com/tr/blog/commitbrief-bir-bulgunun-kimligi/) anlattım.

**Pre-push hook push edilen commit'e bakmıyordu (v1.18.1).** Yeni bir branch ya da tag push'unda hook `commitbrief diff <sha>` çalıştırıyordu, yani `git diff <sha>`: working tree'yi o commit'le karşılaştırıyor, push edilen commit'i incelemiyordu. Artık `<sha>^..<sha>` aralığını inceliyor. Parent'ı yerelde olmayan bir shallow clone'da bütün repoyu göndermek yerine ref'i atlayıp stderr'e uyarı yazıyor. Eski kurulumlar script'i yeniden üretene kadar eski script'i çalıştırır:

```bash
commitbrief install-hook --hook=pre-push --yes
```

**Yanlış header'lar (v1.19.0).** deepseek, mistral ve cohere endpoint'lerine OpenAI SDK'nın ortam varsayılanları yüzünden `OPENAI_ORG_ID` ve `OPENAI_PROJECT_ID` değerleri `OpenAI-Organization` / `OpenAI-Project` header'ı olarak gidiyordu. API anahtarı hiçbir zaman etkilenmedi; header'lar artık gönderilmiyor.

Aynı kefeye kendi testlerimi de koyuyorum (v1.17.0): CLI entegrasyon testleri geliştiricinin gerçek `GEMINI_API_KEY` ve `OPENAI_API_KEY` değerlerini okuyabiliyordu. Geçici bir `HOME` process ortamını izole etmiyor. Testler hangi makinede koştuklarına göre fazladan provider yapılandırıyor ya da yanlış dilde render ediyordu; yani CI'da geçip yerelde sessizce başka şey ölçüyordu.

## Boş sonuç, başarı demek değildir

En çok düşündüren ayrım SARIF çıktısında çıktı. `--sarif`, bulguları GitHub Code Scanning'e yüklenecek bir SARIF 2.1.0 log'u olarak yazıyor (v1.19.0). Model yanıtı hiç yapılandırılmış bulguya ayrışmadıysa, yani review degraded ise, dosya yazılmıyor ve hata veriliyor. Gerekçesi CHANGELOG'da: boş bir `results[]` Code Scanning'e açık uyarıların hepsinin düzeldiğini söyler.

Filtrelerden sonra diff'in boş kalması ise başarısızlık değil. Bakılacak bir şey yok; bu durumda geçerli bir boş log (`results: []`) yazılıyor ki `upload-sarif` adımı dosyasını bulsun. İkisi de "sıfır bulgu" gibi görünür ama biri "baktım, bulmadım", öteki "bakamadım". Aynı çıktıya sığdırdığınız anda kapı, bakmadığı bir şeyi temiz ilan eder.

v1.21.0'da eklenen `guard --sarif` ve `leaks --sarif` aynı ilkeyle geldi. `leaks --sarif`, log'u eksik bırakacak her durumda kapalı başarısız oluyor: taramayı daraltan bayraklar (`--no-history`, `--file`, `--dir` ve benzerleri) usage error, `--max-commits` ya da 10000 commit'lik walk ceiling ile kesilmiş bir history taraması ise dosya yazmadan exit 1. Truncation uyarıları `--quiet` altında da basılıyor; eskiden orada susuyorlardı.

Aynı sürümün bir yan hatası ters yönden bu ilkeyi sınadı: tam 10000 commit'lik bir history, `walkLimit` sınırındaki bir off-by-one yüzünden kesilmiş sayılıyor, `leaks` sahte bir truncation uyarısı veriyordu. Sahte alarm can sıkıcıdır ama düzeltmesi kolaydır: yanlış alarm görünür, eksik alarm görünmez.

**Asıl boşluk `guard`'daydı (v1.21.1).** `--sarif` yolu degraded review'ı reddediyordu, ama `--sarif` olmadan `guard` aynı review'ı "sıfır bulgu" okuyup geçiriyordu. Aynı kapının iki yolu, iki farklı cevap. Artık `guard`, degraded bir review'da `guard.degraded` ile exit 1 veriyor; mesajı nedenini de söylüyor: politika doğrulanamadığı için kapı engelliyor. Yanıt taze olsun, cache'ten ya da `--from-json` belgesinden gelsin, sonuç aynı.

`review` ve `diff --fail-on` ise graceful degrade davranışını koruyor. Bu bilinçli bir asimetri. Bilgi veren bir komutta model yanıtı bozulduysa, elimdekini bozulduğunu söyleyerek göstermek yararlı. Kapı ise karar veren komut; karar verilemiyorsa "geç" demek bir karar vermek demektir.

## Kendinden emin yanlış, hiç cevap vermemekten kötü

Bu ilke yalnız CI kapılarına ait değil. v1.14.0'da, [flaky-test dedektörünün](https://muhammetsafak.com/tr/blog/commitbrief-flaky-test-dedektoru/) statik bulgusunu bir komutla yeniden koşturup doğrulayan `review.sandbox_command` eklerken, komutun hangi testi çalıştıracağını söylemek için test adını çözmem gerekti. Önce elle yazılmış çok dilli bir tarayıcı denedim. Üç düzeltme turunun üçünde de kendinden emin ama yanlış isimler üretti.

Sonunda çözümlemeyi yalnız Go'ya indirdim (`go/parser` ile `*_test.go` dosyalarını okuyarak). Adı çözülemeyen bulgu yeniden koşturmayı atlıyor, stderr'e uyarı yazıyor ve statik bulgusunu koruyor. Python, JS, PHP ve Java testleri tam statik tespitten yararlanmaya devam ediyor; yalnız yeniden koşturma ile doğrulanamıyorlar. CHANGELOG'daki gerekçe şu: kendinden emin yanlış isimler, hiç onaylamamaktan daha kötü bir sonuç.

## Çıkardığım kurallar

- **"Boş" ile "bakamadım" aynı çıktı olmaz.** Karar veren bir kapıda sıfır bulgu, boş `results[]` ve exit 0 yalnız gerçekten bakıldığında yazılır.
- **Karar veren kapı belirsizlikte kapanır; bilgi veren komut bozulur ama bozulduğunu söyler.**
- **Sessizce yok sayılan her girdi, adıyla bir hata olmalı.** Bilinmeyen config anahtarı eskiden sessizce yok sayılıyordu, artık adıyla hata veriyor.

## Sınırlar — yine dürüst liste

- **Bu liste CHANGELOG'daki sessiz hataların bir seçkisi.** Henüz bulmadığım sessiz hataların olmadığını söylemiyorum.
- **Düzeltme, bundan sonraki yazmaları koruyor.** v1.16.0 ve öncesinde `config set` ile zaten sıfırlanmış bir config'i kendiliğinden geri getirmiyor; onu `config show` ile kontrol etmek sizde.
- **Her yer kapalı başarısız değil.** Bilinmeyen bir `commitbrief-ignore[token]` marker'ı uyarıyla birlikte kapsamsız bir marker'a düşüyor; yani daha çok bulguyu susturuyor, daha azını değil.
- **`diff --fail-on` degraded durumda yumuşak bozulmayı koruyor.** Kapı olarak kullanacaksanız `guard` tercih edin.
- **Kapı, tespit edebildiği kadar kapalı.** Yanıtın ayrışıp ayrışmadığını görür; düzgün biçimli ama yanlış bir yanıtı yakalayamaz.

## Denemek için

- [github.com/CommitBrief/commitbrief](https://github.com/CommitBrief/commitbrief)
- [commitbrief.com](https://commitbrief.com)

Bir config'inizde `commitbrief config show` çalıştırıp her değere bir göz atın. Sonra kendi CI kapınıza şunu sorun: araç bakamadığında ne yapıyor? "Geçti" diyorsa, o yeşil işaretin neyi kanıtladığını yeniden düşünmeye değer.
