Güvenlik Requirement'larını Yazmak: OWASP ASVS 5.0 ile Seviye Seçimi, Acceptance Criteria ve Security Champion
Güvenlik requirement'ı, hedeflenen ASVS seviyesinin yazılı olarak seçilmesi ve seçilen her maddenin bir testle ya da kanıtla doğrulanmasıdır; 345 maddeyi doldurmak değil. Bookstore için ASVS 5.0 seviye 2 hedefi ve bilerek eklenen bir seviye 3 maddesi, requirement dosyası, güvenlik acceptance criteria'sı ve abuse story, Definition of Done, dosya ile testleri eşleyen xUnit testi, security champion ve NIST SSDF eşlemesi.
Sürüm notu: Bu yazıdaki kodlar .NET 10 ile derlenip denenmiştir; testler xUnit v3 4.0.3 ile çalıştırılmış, requirement'lar ASVS'nin (Application Security Verification Standard; uygulama güvenliği doğrulama standardı) 30 Mayıs 2025 tarihli 5.0.0 sürümüne göre yazılmıştır.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için ikinci yazıda bir dizi karar verdik: kurumsal satın alma isteğini isteyen kişi onaylayamaz, onaydan sonra değişen istek yeniden onay bekler, webhook imzası doğrulanır. Bu kararlar şu an üç yerde yaşıyor: threat model'de, birkaç testte ve Emre'nin kafasında. Üç ay sonra Ayşe kurumlar için "birim yöneticisi" diye yeni bir rol ekleyecek. Dört göz kuralını nereden öğrenecek?
Bir de şunu tahmin et: uygulama güvenliği standardı ASVS 5.0 toplam 345 requirement içeriyor. Bookstore bunların kaçını karşılamalı ve bunu nasıl göstermeli? Cevap "hepsini" değil; "hangi seviyeyi hedeflediğini yazılı olarak seçmeli ve seçtiklerini test ya da kanıtla göstermeli". Bu yazıda tehdit modelinden çıkan kararları ASVS 5.0 ile yazılı güvenlik requirement'larına çeviriyor, onları user story'lere, Definition of Done'a ve testlere bağlıyoruz.
ASVS 5.0'a Kısa Bakış
OWASP (Open Worldwide Application Security Project; açık uygulama güvenliği topluluğu) ASVS'nin 5.0.0 sürümünü 30 Mayıs 2025'te yayımladı. Web Güvenliği (OWASP Top 10) serisinin OWASP'ın diğer listelerini anlattığı yazısında ASVS'ye genel bir bakış var; burada yalnız bu yazı için gereken kısmı alıyoruz. 5.0, 17 bölüm (1. bölüm Encoding and Sanitization'dan 17. bölüm WebRTC'ye) ve toplam 345 requirement'tan oluşuyor; bunların 70'i seviye 1, 183'ü seviye 2, 92'si seviye 3'te. Her requirement'ın bir kimliği var ve başka bir dokümanda ondan bahsederken sürümü de yazman isteniyor: v5.0.0-8.2.2, 5.0.0 sürümündeki 8. bölümün (Authorization) 2. kısmının 2. maddesi demek.
4.0.3'ten gelenler için değişiklik büyük. Standardın kendi geçiş bölümüne göre 4.0.3'teki 286 requirement'tan yalnız 11'i olduğu gibi kaldı; 109'u ayrı bir requirement olmaktan çıktı (50'si silindi, 28'i tekrar olduğu için kaldırıldı, 31'i başkalarıyla birleştirildi). OAuth ve OpenID Connect, WebRTC, self-contained token'lar, web frontend güvenliği ve secure coding ile mimari için yeni bölümler eklendi; eski mimari bölümü kaldırıldı. CWE (Common Weakness Enumeration; yazılım zayıflık türleri kataloğu) ve NIST (ABD Ulusal Standartlar ve Teknoloji Enstitüsü) eşlemeleri doğrudan requirement'ların yanından çıkarıldı. İki değişiklik bu yazı için özellikle önemli. Birincisi "documented security decisions": her bölümün ilk kısmı artık uygulamanın bir kuralı yazılı olarak tanımlamasını istiyor; 4.0'daki threat modeling ve policy requirement'larının devamı. İkincisi, seviye 1 artık "dışarıdan, kaynak koda bakmadan test edilebilen" requirement'lar diye tanımlanmıyor; seviyeler risk azaltma ile uygulama maliyeti dengesine göre belirleniyor.
Seviye Seçmek: Bookstore için Neden Seviye 2?
ASVS 5.0 seviyeleri şöyle anlatıyor. Seviye 1 asgari başlangıç: requirement'ların yaklaşık beşte biri, önkoşul gerektirmeyen yaygın saldırılara karşı ilk savunma katmanı. Seviye 2 çoğu uygulamanın hedeflemesi gereken seviye; seviye 1'in üzerine requirement'ların yaklaşık yarısını ekliyor. Seviye 3 kalan kısım: derinlemesine savunma ya da uygulaması zor kontroller, en yüksek güvence gereken sistemler için.
Bookstore ödeme alıyor, müşterinin kişisel verisini tutuyor ve kurumlar adına binlerce liralık sipariş oluşturuyor. Seviye 1 bunlar için az; seviye 3'ün tamamı ise küçük bir ekip için orantısız. Karar: hedef seviye 2. Ama seviye seçmek, bir üst seviyeden hiçbir şey almamak demek değil. v5.0.0-2.3.5, yüksek değerli iş akışlarının birden fazla kişinin onayını gerektirmesini istiyor ve seviye 3'te. Kurumsal Sipariş tam olarak bu tür bir akış, ikinci yazıda bulduğumuz T09 tehdidi de bunu söylüyor. O yüzden bu tek requirement'ı bilerek listeye ekledik ve nedenini dosyaya yazdık.
Standart bir şeyi de açıkça teşvik ediyor: kurumun kendi ihtiyaçlarına göre uyarladığı bir ASVS. Kullanmadığın bölümler (Bookstore'da WebRTC ve GraphQL) listeden çıkar; kimlikler değişmeden kalır ki "8.2.2'yi karşılıyoruz" cümlesi her yerde aynı anlama gelsin.
Requirement'ları Repo'ya Yazmak
Bookstore'da seçilen requirement'lar bir JSON dosyasında duruyor. Dosya ASVS'nin metnini kopyalamıyor; yalnız kimliği, seviyeyi ve nasıl doğrulandığını tutuyor. Metin standardın kendisinde; kopyalanan metin eskir.
{
"standard": "OWASP ASVS 5.0.0",
"targetLevel": 2,
"scope": "Checkout, Payments, Shipping and Institutional purchase approval (Bookstore.Api)",
"requirements": [
{ "id": "v5.0.0-2.1.3", "level": 2, "verification": "review", "evidence": "docs/security/authorization-rules.md" },
{ "id": "v5.0.0-2.3.1", "level": 1, "verification": "test" },
{ "id": "v5.0.0-2.3.2", "level": 2, "verification": "test" },
{ "id": "v5.0.0-2.3.4", "level": 2, "verification": "test" },
{ "id": "v5.0.0-2.3.5", "level": 3, "verification": "test", "note": "L3 requirement taken on purpose: institutional orders above the unit limit" },
{ "id": "v5.0.0-2.4.1", "level": 2, "verification": "planned", "backlog": "SEC-14" },
{ "id": "v5.0.0-8.1.1", "level": 1, "verification": "review", "evidence": "docs/security/authorization-rules.md" },
{ "id": "v5.0.0-8.2.1", "level": 1, "verification": "test" },
{ "id": "v5.0.0-8.2.2", "level": 1, "verification": "test" },
{ "id": "v5.0.0-8.3.1", "level": 1, "verification": "test" },
{ "id": "v5.0.0-16.3.2", "level": 2, "verification": "planned", "backlog": "SEC-15" }
]
}
Üç doğrulama türü var. test: en az bir otomatik test bu requirement'ı doğruluyor. review: requirement bir doküman istiyor ve kanıt o doküman; örneğin 8.1.1 yetkilendirme kurallarının yazılı olmasını istiyor ve docs/security/authorization-rules.md tam olarak bunu yapıyor. planned: henüz karşılanmıyor, ama bir backlog item'ı var. Planlı olanı listeden çıkarmak yerine yazmak dürüstlüğün kendisi: kurumsal bir müşteri "seviye 2'de neler eksik?" diye sorduğunda cevap bu dosyada. Listenin dökümünü jq ile almak da kolay:
jq -r '.requirements | group_by(.verification)[] | "\(.[0].verification)\t\(length)\t\([.[].id] | join(" "))"' docs/security/asvs-requirements.json
planned 2 v5.0.0-2.4.1 v5.0.0-16.3.2
review 2 v5.0.0-2.1.3 v5.0.0-8.1.1
test 7 v5.0.0-2.3.1 v5.0.0-2.3.2 v5.0.0-2.3.4 v5.0.0-2.3.5 v5.0.0-8.2.1 v5.0.0-8.2.2 v5.0.0-8.3.1
Requirement'ı User Story'ye Bağlamak
Requirement dosyası "ne" sorusuna cevap verir; geliştirici ise story üzerinden çalışır. Bu yüzden her story kendi güvenlik acceptance criteria'sını taşır ve ilgili ASVS kimliğini yanına yazar. Acceptance criteria yazma tekniği Requirements Analysis serisinin acceptance criteria ve example mapping yazısında; burada yalnız güvenlik tarafını ekliyoruz:
# KS-12 Purchase approval above the unit limit
As an institution approver, I want requests above my unit's limit to wait for my approval,
so that the school does not spend more than its budget allows without a second person.
## Acceptance criteria
- A request whose total is above the unit's approval limit is created as `PendingApproval`.
- An approver of the same institution can approve it; the request becomes `Approved`.
- The order can be placed only for an `Approved` request.
## Security acceptance criteria
- The requester cannot approve their own request, even with both roles. (ASVS v5.0.0-2.3.5)
- A buyer without the approver role gets 403 on approve. (ASVS v5.0.0-8.2.1)
- An approver of another institution gets 404, not 403. (ASVS v5.0.0-8.2.2)
- Calling "place order" on a `PendingApproval` request returns 409. (ASVS v5.0.0-2.3.1)
- Changing the lines after approval needs a new approval. (threat T10)
## Abuse story
As a buyer who wants a large order without waiting, I split it into several requests under the limit.
Decision: accepted for release 1, see `threat-model/risk-register.md` R-02.
Abuse story, normal story'nin tersidir: sistemi kötüye kullanmak isteyen birinin bakış açısıyla yazılır. Product Owner Selin'in en kolay anladığı güvenlik dokümanı budur, çünkü teknik terim yerine iş diliyle konuşur. Her abuse story'nin sonunda bir karar olmalı: ya bir acceptance criterion'a dönüşür ya da risk register'a girer.
Definition of Done'a Güvenlik Maddeleri
Story bazında yazılan maddeler unutulabilir; Definition of Done ise her story için aynı kalan sözleşmedir. Bookstore'un Definition of Done'ına eklenen güvenlik maddeleri kısa ve kontrol edilebilir:
# Definition of Done
A backlog item is done when:
- The acceptance criteria pass as automated tests on the pull request build.
- Code review is done by someone who did not write the code.
- Threat model check in the PR template is answered; a new or changed data flow is in `threat-model/`.
- Every ASVS requirement the story names has a test (`[Trait("asvs", "v5.0.0-x.y.z")]`) or an entry with
`"verification": "review"` and its evidence in `docs/security/asvs-requirements.json`.
- No new High or Critical finding from the pipeline's security scans, or an exception with an owner and a date.
- Security-relevant events of the story are logged (see `docs/security/authorization-rules.md`).
"Güvenli kod yazıldı" gibi bir madde yok, çünkü kimse onu kontrol edemez. Her madde bir PR'da (pull request; kod değişikliği isteği) evet ya da hayır diye cevaplanabiliyor. Pipeline'daki tarama eşikleri ve istisna yönetimi DevSecOps serisinin uçtan uca pipeline yazısında anlatıldı.
Requirement ile Test Arasındaki Bağ
Dosya ile testler zamanla birbirinden uzaklaşır: biri bir testi siler, biri dosyaya yeni bir kimlik ekler ve kimse fark etmez. Bunu bir testle kapatıyoruz. Testler [Trait("asvs", "v5.0.0-8.2.2")] ile işaretleniyor; SecurityRequirementsTests test assembly'sini reflection ile tarayıp dosyayla karşılaştırıyor:
private static readonly IReadOnlyList<(string Test, string Id)> AsvsTraits =
[
.. typeof(SecurityRequirementsTests).Assembly.GetTypes()
.SelectMany(t => t.GetMethods())
.SelectMany(m => m.GetCustomAttributes<TraitAttribute>()
.Where(a => a.Name == "asvs")
.Select(a => ($"{m.DeclaringType!.Name}.{m.Name}", a.Value)))
];
[Fact]
public void Every_requirement_verified_by_test_has_a_test()
{
string[] missing =
[
.. File.Requirements
.Where(r => r.Verification == "test")
.Select(r => r.Id)
.Where(id => !AsvsTraits.Any(t => t.Id == id))
];
Assert.True(missing.Length == 0, $"No test with [Trait(\"asvs\", ...)] for: {string.Join(", ", missing)}");
}
Aynı class'ta iki test daha var: biri her asvs trait'inin dosyada bulunan bir kimliğe işaret ettiğini, diğeri review ve planned satırlarının kanıtını ya da backlog item'ını yazdığını kontrol ediyor. Dosyayı ilk yazdığımızda 8.2.1 (fonksiyon düzeyinde yetki) için test yoktu; onay endpoint'ine yalnız alıcı rolündeki birinin istek atmasını deneyen kimse yoktu:
dotnet test --filter-class "*SecurityRequirementsTests"
failed Bookstore.Api.Tests.SecurityRequirementsTests.Every_requirement_verified_by_test_has_a_test (7ms)
No test with [Trait("asvs", ...)] for: v5.0.0-8.2.1
Test run summary: Failed!
total: 3
failed: 1
succeeded: 2
Eksik test, onay rolü olmayan Ada'nın onay endpoint'ini çağırmasını deniyor:
[Fact]
[Trait("asvs", "v5.0.0-8.2.1")]
public async Task Buyer_without_the_approver_role_cannot_approve()
{
PurchaseRequestView request = await CreateAsync(factory.CreateClientAs(TestUsers.Linus), (Middlemarch, 20));
HttpResponseMessage approve = await factory.CreateClientAs(TestUsers.Ada)
.PostAsync($"/api/institutions/me/purchase-requests/{request.Id}/approve", null, ct);
Assert.Equal(HttpStatusCode.Forbidden, approve.StatusCode);
}
Test run summary: Passed!
total: 20
failed: 0
succeeded: 20
skipped: 0
duration: 987ms
Kod zaten doğruydu; eksik olan kanıttı. Bu testin değeri tam da burada: "sanırım çalışıyor" ile "çalıştığını gösteren bir test var" arasındaki farkı build'e taşıyor. Tek bir requirement'ın testlerini dotnet test --filter-trait "asvs=v5.0.0-2.3.5" ile çalıştırabiliyorsun; bir denetçi ya da kurumsal müşteri sorduğunda kanıt bir komut uzaklıkta.
Security Champion ve 15 Dakikalık Refinement Adımı
Ayrı bir güvenlik ekibi olmayan küçük ekiplerde bu işi kim yapacak? Bookstore'un cevabı security champion modeli: ekipten bir geliştirici (burada Emre) zamanının bir kısmını, Bookstore'da kabaca beşte birini, güvenliğe ayırır. Champion bir kapı bekçisi değildir; her PR'ı onaylamaz. Görevi threat modeling oturumlarını yürütmek, asvs-requirements.json dosyasının sahibi olmak, pipeline'daki bulguların sahipsiz kalmamasını sağlamak ve ekibin geri kalanının sorularını cevaplamaktır. Champion'ın tek başına her şeyi bilmesi beklenmez; ne zaman dışarıdan yardım isteyeceğini bilmesi beklenir.
Threat modeling'i her story için ayrı bir toplantıya çevirmek sürdürülemez. Bookstore'da backlog refinement'ın sonunda her story için en fazla 15 dakikalık bir adım var ve üç soru soruluyor: Bu story bir trust boundary'yi geçiyor ya da yeni bir data flow ekliyor mu? Yeni bir kişisel veri ya da para hareketi var mı? Yeni bir yetkilendirme kuralı gerekiyor mu? Üçüne de "hayır" cevabı verilen story olduğu gibi devam eder. Bir "evet" varsa story'ye güvenlik acceptance criteria'sı ve ASVS kimlikleri eklenir; cevap büyükse (yeni bir ödeme yöntemi, yeni bir entegrasyon) ilk yazıdaki gibi ayrı bir threat modeling oturumu planlanır.
NIST SSDF ile Eşleme
Kurumsal müşteriler ve denetçiler bazen "güvenli yazılım geliştirme sürecin hangi çerçeveye uyuyor?" diye sorar. En sık atıf yapılanlardan biri NIST'in SSDF'i (Secure Software Development Framework; güvenli yazılım geliştirme çerçevesi): SP 800-218, sürüm 1.1, Şubat 2022. Pratikleri dört gruba ayırır: Prepare the Organization (PO), Protect the Software (PS), Produce Well-Secured Software (PW) ve Respond to Vulnerabilities (RV). SSDF "nasıl" demez, "ne" der; o yüzden Bookstore'un yaptıklarını ona eşlemek kolay:
| SSDF görevi | Kısaca | Bookstore'da karşılığı |
|---|---|---|
PO.1.2 | Kurumun geliştirdiği yazılım için güvenlik requirement'larını tanımlamak | docs/security/asvs-requirements.json, story'lerdeki güvenlik acceptance criteria'sı |
PW.1.1 | Threat modeling gibi risk modelleme ile tasarım risklerini bulmak | threat-model/, birinci ve ikinci yazı |
RV.1.3 | Zafiyet bildirimi ve düzeltme politikası | Serinin dördüncü yazısı: security.txt ve advisory süreci |
RV.3 | Zafiyetlerin kök nedenini analiz edip süreci düzeltmek | Beşinci ve altıncı yazı: pentest bulguları ve incident sonrası |
NIST, SSDF 1.2 olarak SP 800-218 Rev. 1'in taslağını 17 Aralık 2025'te yorum için yayımladı; bu yazı yazılırken nihai sürüm çıkmamıştı. Eşleme tablosu tutarken hangi sürüme göre yazdığını da not et.
Üretimde Dikkat
Requirement dosyası bir sürüme bağlı. ASVS'nin bir sonraki sürümü çıktığında kimlikler değişebilir; dosyadaki v5.0.0- öneki bu yüzden var. Yeni sürüme geçmek ayrı ve planlı bir iş olmalı, sessiz bir bul-değiştir değil.
review satırları zamanla eskir. Yetkilendirme kuralları dokümanı yeni rol eklendiğinde güncellenmezse kanıt yanlış bir şeyi kanıtlar. Bookstore'da champion her release öncesi review satırlarının kanıtlarını açıp tarihine bakıyor; Ayşe'nin yeni rolü de bu kontrol sırasında dokümana girdi.
Kurumsal müşterilerin güvenlik anketlerinde "ASVS seviye 2 sertifikanız var mı?" sorusu gelebilir. OWASP kimseyi sertifikalandırmıyor; standardın değerlendirme bölümü de doğrulamayı yapan kişinin kapsamı ve seviyeyi açıkça yazmasını istiyor. Doğru cevap "seviye 2'yi hedefliyoruz; kapsam şu, karşılanan ve planlanan requirement'lar şu dosyada, kanıtı şu testler" olur.
Sık Düşülen Tuzaklar
En yaygın tuzak ASVS'yi baştan sona doldurulacak bir form gibi kullanmak. 345 satırlık bir tabloya "Evet" yazmak iki gün sürer ve hiçbir şeyi değiştirmez; "Evet" yazılan satırların kanıtı yoktur. Kendi kapsamını seç, ilgisiz bölümleri çıkar ve her satıra bir doğrulama yöntemi yaz.
İkinci tuzak ASVS metnini story'lere aynen kopyalamak. Standardın dili genel, story'nin dili Bookstore'a özel olmalı: "data-specific access is restricted" yerine "başka kurumun approver'ı 404 alır". Kimliği yanına yazmak bağlantıyı korur.
Üçüncüsü security champion'ı tek sorumlu yapmak. Champion bir kolaylaştırıcıdır; güvenlik kararlarını ekip verir, kodu yazan düzeltir. Champion'ın izinde olduğu hafta güvenlik durmamalı.
Küçük Bir Deneme
★☆☆ v5.0.0-2.4.1 şu an planned. Sipariş endpoint'ine bir rate limiter ekle (ASP.NET Core Güvenliği serisinin rate limiting yazısındaki gibi), testi yaz, Trait'i ekle ve satırı test yap. Traceability testi ne zaman kırmızı, ne zaman yeşil oluyor?
★★☆ ASVS 5.0'ın V8 (Authorization) bölümünü oku. Bookstore'un seviye 2 hedefi için listede olmayan ama olması gereken bir requirement bul, dosyaya ekle ve doğrulama türüne karar ver.
★★★ Traceability testine bir kural daha ekle: dosyadaki her requirement'ın seviyesi targetLevel'dan büyükse note alanı boş olamasın. Neden böyle bir kural istiyoruz?
Şimdiye kadar kendi bulduğumuz tehditlere baktık. Bir gün tehdidi biz değil, dışarıdan biri bulacak ve bize haber vermek isteyecek. Serinin dördüncü yazısında Bookstore'un bu bildirimi nasıl karşıladığını konuşuyoruz: security.txt, coordinated disclosure, GitHub Security Advisory ve CVE (yayımlanmış güvenlik açıklarına verilen standart kimlik numarası).