SAST: CodeQL, Semgrep ve .NET Security Analyzer'ları ile Kaynak Kodda Güvenlik Açığı Aramak
SAST, kullanıcı girdisinin (source) tehlikeli bir çağrıya (sink) sanitizer'sız ulaştığı yolları arar; yalnız modellediği sink'leri görür ve iş kuralı ya da authorization eksikliği bulamaz. Bookstore'da CodeQL, Semgrep ve .NET security analyzer'larını aynı beş soruna karşı çalıştırıyor, kimin neyi bulduğunu, Bookstore'a özel test edilmiş bir Semgrep kuralını, false positive triage'ını ve yalnız yeni bulguyu kıran bir pipeline'ı gerçek çıktılarla gösteriyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 ile derlenip denenmiştir; taramalar CodeQL 2.27.4 (csharp-queries 1.11.0), Semgrep Community Edition 1.168 ve .NET 10 SDK'daki security analyzer'larla çalıştırılmıştır.
Önce Tahmin Et
Bookstore API'ye bir sprint'te beş değişiklik girdi: kitap aramasında SQL string birleştirme, kapak resmi endpoint'inde kullanıcıdan gelen dosya adıyla dosya okuma, parola sıfırlama token'ında MD5 (artık güvenli sayılmayan eski hash algoritması), sipariş detayında sahiplik kontrolü olmayan bir endpoint ve aynı kapak endpoint'inin PhysicalFile ile yazılmış ilk hali. Üç araç çalıştırıyoruz: CodeQL, Semgrep ve .NET'in kendi security analyzer'ları. Tahmin et: beş sorunun kaçını en az bir araç bulur, kaçını hiçbiri bulmaz?
Cevap üç ve iki. Bu yazıda SAST'ın (kaynak kodu çalıştırmadan tarayan güvenlik testi) nasıl düşündüğünü, bu üç aracın Bookstore kodunda neyi neden bulup neyi neden kaçırdığını, pipeline'a nasıl bağlandığını ve bulguları nasıl triage edeceğini gerçek çıktılarla görüyoruz.
SAST Nasıl Düşünür: Source, Sink ve Taint
Güvenlik odaklı SAST araçlarının çoğu taint analizi yapar. Source, saldırganın kontrol edebildiği verinin programa girdiği yerdir: bir controller action parametresi, bir header, bir request body. Sink, o verinin tehlikeli olduğu yerdir: SQL komutu, dosya yolu, process başlatma, HTML (HyperText Markup Language) çıktısı. Araç source'tan sink'e giden data flow yollarını arar; yol üzerinde veriyi güvenli hale getiren bir sanitizer (parametreleme, encoding, doğrulama) görmezse bulgu üretir. SQL injection, path traversal, command injection ve XSS (Cross-Site Scripting) bu modele çok iyi uyar. Zayıf kriptografi çağrısı gibi bulgular ise taint gerektirmez; belirli bir API'nin çağrılması yeterlidir.
Bu modelin iki doğal sınırı var. Birincisi, araç yalnızca modellediği source'u ve sink'i bilir; modelde olmayan bir sink'e akan veri görünmez kalır. İkincisi, SAST kodun ne yapması gerektiğini bilmez. "Müşteri yalnız kendi siparişini görebilir" kuralı bir iş kuralıdır; kodda eksik olması bir data flow hatası değildir ve hiçbir taint analizi onu bulamaz. Bu tür eksikleri API Güvenliği serisinin authorization testleri ve code review yakalar.
Bookstore'un Kodu
Arama endpoint'i SQL'i controller'da string birleştirerek kuruyor ve ortak bir SqlExecutor'a veriyor; executor da metni SqlCommand'a geçiriyor:
[HttpGet("search")]
public Task<List<string>> Search([FromQuery] string title) =>
executor.QueryTitlesAsync("SELECT Title FROM Books WHERE Title LIKE '%" + title + "%'");
public async Task<List<string>> QueryTitlesAsync(string sql, params SqlParameter[] parameters)
{
await using SqlConnection connection = new(connectionString);
await connection.OpenAsync();
await using SqlCommand command = new(sql, connection);
command.Parameters.AddRange(parameters);
// ...
}
Kapak endpoint'i ilk haliyle dosya adını doğrudan path'e ekliyor ve PhysicalFile döndürüyor; ikinci haliyle aynı path'i System.IO.File.ReadAllBytes ile okuyor. Parola sıfırlama token'ı e-posta ve zamandan MD5 ile türetiliyor. Sipariş endpoint'i ise [Authorize] altında ama siparişin kime ait olduğuna bakmıyor:
public IActionResult Get(string fileName)
{
string path = Path.Combine(environment.ContentRootPath, "covers", fileName);
return PhysicalFile(path, "image/jpeg");
}
public string CreateToken(string email, DateTimeOffset now)
{
byte[] hash = MD5.HashData(Encoding.UTF8.GetBytes($"{email}:{now.ToUnixTimeSeconds()}"));
return Convert.ToHexString(hash);
}
// Giriş yapmış her müşteri her siparişi okuyabilir: sahiplik kontrolü yok
[HttpGet("{id:int}")]
public Task<List<string>> Get(int id) =>
executor.QueryTitlesAsync(
"SELECT b.Title FROM OrderItems oi JOIN Books b ON b.Id = oi.BookId WHERE oi.OrderId = @id",
new SqlParameter("@id", id));
.NET Security Analyzer'ları
En ucuz katman zaten SDK'nın içinde. .NET analyzer'larının Security kategorisindeki kuralların çoğu varsayılan olarak kapalı; projeyi olduğu gibi build ettiğimde sıfır uyarı aldım. AnalysisModeSecurity ile bu kategoriyi tamamen açınca tablo değişiyor (yollar ve doküman linkleri kısaltıldı):
dotnet build Bookstore.slnx -p:AnalysisModeSecurity=All --no-incremental
src/Bookstore.Api/Data/SqlExecutor.cs(11,42): warning CA2100: Review if the query string passed to 'SqlCommand.SqlCommand(string cmdText, SqlConnection connection)' in 'QueryTitlesAsync', accepts any user input
src/Bookstore.Api/Data/SqlExecutor.cs(11,42): warning CA3001: Potential SQL injection vulnerability was found where 'SqlCommand.SqlCommand(string cmdText, SqlConnection connection)' in method 'Task<List<string>> SqlExecutor.QueryTitlesAsync(string sql, params SqlParameter[] parameters)' may be tainted by user-controlled data from 'string title' in method 'Task<List<string>> BooksController.Search(string title)'.
src/Bookstore.Api/Services/ResetTokenService.cs(10,23): warning CA5351: CreateToken uses a broken cryptographic algorithm MD5
CA3001 gerçek bir taint analizi: BooksController.Search'teki title'ın SqlExecutor'daki SqlCommand'a ulaştığını method sınırını aşarak buldu. CA2100 ise taint aramaz, sabit olmayan her komut metni için "gözden geçir" der. CA5351 MD5'i yakaladı. PhysicalFile'lı kapak endpoint'i için hiçbir uyarı yok. Bu kuralları kalıcı olarak Directory.Build.props'ta açmak, .editorconfig ile severity'lerini ayarlamak ve kalite kurallarıyla birlikte yönetmek Statik Analiz serisinin konusu; biz burada yalnız güvenlik tarafına bakıyoruz.
CodeQL: Kodu Bir Veritabanı Gibi Sorgulamak
CodeQL kodu önce bir veritabanına çıkarır, sonra o veritabanında sorgular çalıştırır. C# için build gerektirmeyen none build mode'u var; GitHub'ın default setup'ı da C#'ta bu modu kullanıyor. CLI ile yerelde:
codeql database create db --language=csharp --build-mode=none --source-root=bookstore
codeql database analyze db codeql/csharp-queries:codeql-suites/csharp-security-extended.qls --format=csv --output=results.csv
security-extended suite'i C# için 70 sorgu çalıştırdı ve PhysicalFile'lı haliyle tek bir bulgu verdi: SQL injection. Kapak endpoint'ini ReadAllBytes'lı haliyle taradığımda ikinci bulgu geldi (CSV satırları okunabilirlik için bölündü):
"SQL query built from user-controlled sources","Building a SQL query from user-controlled sources is vulnerable to insertion of malicious SQL code by the user.","error",
"This query depends on [[""this ASP.NET Core MVC action method parameter""|""relative:///src/Bookstore.Api/Controllers/BooksController.cs:11:57:11:61""]].",
"/src/Bookstore.Api/Data/SqlExecutor.cs","11","46","11","48"
"Uncontrolled data used in path expression","Accessing paths influenced by users can allow an attacker to access unexpected resources.","error",
"This path depends on a [[""user-provided value""|""relative:///src/Bookstore.Api/Controllers/CoversController.cs:10:37:10:44""]].",
"/src/Bookstore.Api/Controllers/CoversController.cs","13","54","13","57"
Buradan çıkan sonuç sink modellemesiyle ilgili. Aynı kullanıcı girdisi aynı path'e gidiyor; File.ReadAllBytes CodeQL'in ve CA3003'ün modelinde bir sink, ControllerBase.PhysicalFile ise bu iki aracın bu sürümlerinde değil. Kodun güvenli olduğunu "tarayıcı bir şey bulmadı" diye varsayamazsın. MD5 de CodeQL'in bu suite'inde bulgu üretmedi; aynı açığı farklı araçlar farklı açılardan görüyor.
CodeQL'in üç hazır suite'i var: default yüksek kesinlikli az sayıda sorgu çalıştırır; security-extended bunun üstüne biraz daha düşük kesinlikli sorgular ekler; security-and-quality bakım ve güvenilirlik sorgularını da katar ve advanced setup ister. GitHub'da iki kurulum yolu var. Default setup birkaç tıkla açılır, dili kendisi seçer, workflow dosyası gerektirmez; advanced setup ise bir workflow dosyasıyla gelir ve suite'i, path filtrelerini ve tetikleyicileri senin kontrolüne bırakır. Bookstore'da Semgrep'i de aynı pipeline'a bağlamak ve config'i code review'dan geçirmek istediğimiz için advanced setup kullanıyoruz.
Lisansa dikkat: CodeQL CLI'nin lisansı açık kaynak kodu analiz etmeye ve akademik araştırmaya izin verir; kapalı kaynak bir kod tabanında, örneğin private bir repo'da kullanmak GitHub Advanced Security lisansı gerektirir. GitHub'da private repo'larda code scanning için Code Security ürünü gerekiyor. Copilot Autofix, CodeQL alert'leri için düzeltme önerisi üretir ve ayrı bir Copilot aboneliği istemez; ama dokümantasyonu önerilerin deterministik olmadığını, yanlış ya da eksik olabileceğini ve her birinin review edilmesi gerektiğini açıkça söylüyor. Bir autofix'i diğer her PR gibi oku, testlerin geçtiğini ve alert'in kapandığını gör.
Semgrep: Kendi Kuralını Yazmak
Semgrep'in hazır C# kural setini aynı koda çalıştırdım:
semgrep scan --config p/csharp --metrics=off
Ran 27 rules on 6 files: 0 findings.
Sıfır bulgu. Birinci yazıda aynı kural seti new SqlCommand("..." + title) satırını yakalamıştı; burada birleştirme controller'da, SqlCommand ise başka bir dosyada. Semgrep Community Edition'ın analizi bir fonksiyonun içinde kalır, dosyalar ve method'lar arası taint takibi ücretli sürümünde. Ama Semgrep'in asıl faydası burada ortaya çıkıyor: Bookstore'un kendi API'sini bilen bir kuralı dakikalar içinde yazabilirsin. Kural şu: SqlExecutor'ın Query...Async method'larına giden ilk argüman bir birleştirme, interpolated string ya da string.Format olamaz.
rules:
- id: bookstore-sqlexecutor-raw-sql
languages: [csharp]
severity: ERROR
message: >-
SQL passed to SqlExecutor.$METHOD is built from strings. Keep the SQL text
constant and pass values as SqlParameter.
metadata:
cwe: "CWE-89: SQL Injection"
owner: bookstore-appsec
patterns:
- pattern-either:
- pattern: $EXEC.$METHOD($LEFT + $RIGHT, ...)
- pattern: $EXEC.$METHOD($"...", ...)
- pattern: $EXEC.$METHOD(string.Format(...), ...)
- metavariable-regex:
metavariable: $METHOD
regex: ^Query\w*Async$
Kural da kod gibi test edilir. Aynı klasörde aynı adla bir C# dosyası, hangi satırın yakalanması (ruleid) ve hangisinin yakalanmaması (ok) gerektiğini işaretler:
public class Samples
{
public async Task Run(SqlExecutor executor, string title, int id)
{
// ruleid: bookstore-sqlexecutor-raw-sql
await executor.QueryTitlesAsync("SELECT Title FROM Books WHERE Title LIKE '%" + title + "%'");
// ruleid: bookstore-sqlexecutor-raw-sql
await executor.QueryTitlesAsync($"SELECT Title FROM Books WHERE Id = {id}");
// ruleid: bookstore-sqlexecutor-raw-sql
await executor.QueryTitlesAsync(string.Format("SELECT Title FROM Books WHERE Id = {0}", id));
// ok: bookstore-sqlexecutor-raw-sql
await executor.QueryTitlesAsync("SELECT Title FROM Books WHERE Id = @id", new SqlParameter("@id", id));
}
}
semgrep --test semgrep-rules --metrics=off
1/1: ✓ All tests passed
Testin gerçekten bir şey ölçtüğünü görmek için ok satırını ruleid yaptım; çıktı "missed lines: [15]" ile 0/1'e düştü. Kuralın ilk sürümünde receiver'ı tipiyle, (SqlExecutor $EXEC) diye yazmıştım. Test dosyasında geçti, gerçek kodda hiçbir şey bulmadı: controller'daki executor bir primary constructor parametresi ve Semgrep onun tipini çözemedi. Tip kısıtını kaldırıp method adını regex ile daraltınca gerçek bulgu geldi:
src/Bookstore.Api/Controllers/BooksController.cs
❯❯❱ semgrep-rules.bookstore-sqlexecutor-raw-sql
❰❰ Blocking ❱❱
SQL passed to SqlExecutor.QueryTitlesAsync is built from strings. Keep the SQL text constant and
pass values as SqlParameter.
12┆ executor.QueryTitlesAsync("SELECT Title FROM Books WHERE Title LIKE '%" + title + "%'");
Lisans notu: Semgrep'in motoru (Community Edition) LGPL (kütüphane olarak bağlanmaya izin veren GPL türevi lisans) 2.1 ile açık kaynak; Aralık 2024'te Semgrep'in kendi kural repo'su Semgrep Rules License v1.0'a geçti ve bu lisans kuralları iç kullanımla sınırlıyor, rakip ürünlerde ve SaaS (Software as a Service) tekliflerinde kullanımı yasaklıyor. Kendi kodunu kendi CI'ında taramak bu kapsamda. Bu değişikliğe tepki olarak Ocak 2025'te motorun 1.100.0 sürümünden Opengrep adlı bir fork çıktı. Kendi yazdığın kurallar ise senin; lisans sorusu yalnız hazır kural setleri için geçerli.
Kim Neyi Buldu?
| Sorun | CodeQL security-extended | Semgrep p/csharp | Semgrep + Bookstore kuralı | .NET analyzer'ları |
|---|---|---|---|---|
SQL birleştirme, SqlExecutor üzerinden | Buldu (cs/sql-injection) | Bulamadı | Buldu | Buldu (CA3001, CA2100) |
Kapak, PhysicalFile | Bulamadı | Bulamadı | Kapsam dışı | Bulamadı |
Kapak, ReadAllBytes | Buldu (cs/path-injection) | Bulamadı | Kapsam dışı | Buldu (CA3003) |
| MD5 ile token | Bulamadı | Bulamadı | Kapsam dışı | Buldu (CA5351) |
| Sipariş sahiplik kontrolü yok | Bulamadı | Bulamadı | Bulamadı | Bulamadı |
Tablodan iki sonuç çıkıyor. Araçlar birbirini tamamlıyor: interprocedural taint için CodeQL ya da CA3001, ekibe özgü API'ler için Semgrep kuralı, belirli API çağrıları için analyzer'lar. Ve hiçbir araç iş kuralını bilmiyor: sipariş endpoint'indeki açık, bu yazıdaki en ciddi sorun olabilir ve dört sütunda da boş.
Düzeltmeler sırasıyla: arama parametreli sorguya döndü (LIKE @pattern, değer SqlParameter'da), token RandomNumberGenerator.GetHexString(64, lowercase: true) ile üretiliyor (token tasarımının ayrıntısı Geliştirici için Kriptografi serisinde), kapak endpoint'i ise dosya adı yerine kitap id'si alıyor. Kullanıcıdan gelen metin dosya sistemine hiç ulaşmıyor:
[HttpGet("{bookId:int}")]
[SuppressMessage("Security", "CA3003",
Justification = "The path is built from an int route value; no user-supplied text reaches the file system.")]
public IActionResult Get(int bookId)
{
string path = Path.Combine(environment.ContentRootPath, "covers", $"{bookId}.jpg");
if (!System.IO.File.Exists(path))
{
return NotFound();
}
byte[] content = System.IO.File.ReadAllBytes(path);
return File(content, "image/jpeg");
}
Bu kodun üzerindeki SuppressMessage bir sonraki bölümün konusu.
Triage: False Positive, Gerekçe ve Yeni Kod Gate'i
Kapak endpoint'ini düzeltmek için önce iki yol denedim. Path.GetFullPath ile tam yolu alıp kapak klasörüyle başladığını StartsWith ile kontrol eden doğru bir guard yazdım; hem CodeQL hem CA3003 bulguyu sürdürdü. Path.GetFileName ile dosya adını temizlemek de iki araca yetmedi. Son haldeki int tabanlı tasarımda CodeQL sıfır bulgu verdi, CA3003 ise File.Exists ve ReadAllBytes satırları için uyarmaya devam etti: int route değeri bile onun gözünde kullanıcı girdisi. Bu bir false positive ve iki doğru tepki var: aracın tanıdığı bir tasarıma geçmek ya da bulguyu gerekçesiyle bastırmak. Biz ikisini birden yaptık; tasarım CodeQL'i tatmin etti, CA3003 için gerekçeli bir SuppressMessage yazdık. SqlExecutor'daki CA2100 da aynı şekilde bastırıldı, gerekçesinde sözleşmeyi Semgrep kuralının uyguladığı yazıyor. Bastırmanın kendisi code review'dan geçmeli; gerekçesiz bir #pragma warning disable yerine Justification'lı attribute, review'da neyi kabul ettiğini açıkça gösterir.
GitHub'da CodeQL alert'leri "false positive", "won't fix" ve "used in tests" gerekçeleriyle dismiss edilir ve yorum eklenir; bu karar repo'nun geçmişinde kalır. Semgrep'te satır bazında // nosemgrep: rule-id yorumu var; kural id'sini mutlaka yaz, id'siz nosemgrep o satırdaki her kuralı susturur.
Yeni kod gate'i için birinci yazıdaki baseline ilkesini SAST'a uyguluyoruz. CodeQL PR'da yalnız PR'ın değiştirdiği koddaki yeni alert'leri annotation olarak gösterir; ruleset'lerde merge için code scanning sonuçlarını şart koşan bir kural tanımlanabilir. Semgrep'te --baseline-commit kullanıyoruz. Bookstore'un SAST workflow'u:
name: sast
on:
pull_request:
push:
branches: [main]
schedule:
- cron: "17 3 * * 1"
permissions:
contents: read
jobs:
codeql:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [csharp, javascript-typescript]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: github/codeql-action/init@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
with:
languages: ${{ matrix.language }}
build-mode: none
config-file: ./.github/codeql/codeql-config.yml
- uses: github/codeql-action/analyze@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
with:
category: "/language:${{ matrix.language }}"
semgrep:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- run: python -m pip install semgrep==1.168.0
# önce kural testleri: bozuk bir kural sessizce geçmemeli
- run: semgrep --test semgrep-rules --metrics=off
- name: Scan
env:
BASE_REF: ${{ github.base_ref }}
run: |
baseline=()
if [ -n "$BASE_REF" ]; then baseline=(--baseline-commit "origin/$BASE_REF"); fi
semgrep scan --config p/csharp --config semgrep-rules/bookstore-sql.yaml \
--exclude semgrep-rules --metrics=off --error \
--sarif --output semgrep.sarif "${baseline[@]}"
- if: always()
uses: github/codeql-action/upload-sarif@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
with:
sarif_file: semgrep.sarif
category: semgrep
name: bookstore-codeql
queries:
- uses: security-extended
paths-ignore:
- semgrep-rules
- "**/*.Tests"
Birkaç ayrıntı. security-events: write yalnız bu iki job'da; workflow seviyesi contents: read. Semgrep'in test dosyası kasıtlı olarak zafiyetli satırlar içerdiği için hem CodeQL'den (paths-ignore) hem Semgrep taramasından (--exclude) çıkarıldı. SARIF (statik analiz sonuçları için standart JSON formatı) yüklemesi if: always() ile, çünkü Semgrep bulgu bulunca --error yüzünden adım kırılıyor ama bulguların yine de code scanning'e gitmesi gerekiyor. Workflow'u actionlint ile doğruladım; scan adımının script'ini yerelde, main'i origin/main olarak işaretleyip yeni bir branch'te ISBN aramasına interpolated string ile SQL ekleyerek çalıştırdım:
Creating git worktree from 'origin/main' to scan baseline.
• Findings: 1 (1 blocking)
• Scan was limited to files changed since baseline commit.
Ran 28 rules on 1 file: 1 finding.
Exit code 1 oldu ve üretilen SARIF'te tek sonuç vardı: BooksController.cs satır 19, yani yeni eklenen satır. CodeQL job'ını GitHub üzerinde çalıştırmadım; CodeQL'in kendisini aynı sürümle yerelde koşturdum.
Üretimde Dikkat
Süreyi ölç. Bookstore'un küçük repo'sunda build-mode none ile veritabanı oluşturmak 32 saniye, security-extended analizi 10 saniye sürdü; gerçek bir repo'da dakikalar alır. CodeQL'i PR'da çalıştırırken paralel job'lar ve dil matrisi kullan; haftalık schedule, PR'a hiç dokunulmayan kodda yeni sorgu sürümlerinin bulduklarını yakalar.
Kural setini kademeli büyüt. default suite ile başla, false positive oranını birkaç sprint gözle, sonra security-extended'e geç. Semgrep'te hazır kural setinin tamamını kırıcı yapmak yerine önce rapor modunda çalıştır, ekipte karşılığı olan kuralları --error'a taşı. Kendi kurallarının sahibi olsun (metadata.owner) ve her kuralın testi olsun.
Araç sürümlerini sabitle ve güncellemeyi bir PR olarak al. CodeQL sorgu paketi ya da Semgrep kuralı güncellendiğinde dün olmayan bulgular bugün çıkabilir; bunun bir kod değişikliğinden değil araç değişikliğinden geldiğini bilmek triage'ı hızlandırır.
Sık Düşülen Tuzaklar
Birincisi, SAST'ı code review'un yerine koymak. Sipariş endpoint'indeki authorization açığını dört araçtan hiçbiri görmedi. SAST bilinen kalıpları ölçeklenebilir biçimde yakalar; iş kuralını, yetkiyi ve tasarımı review ve testler yakalar.
İkincisi, "bulgu yok" sonucunu "güvenli" diye okumak. PhysicalFile örneği gösterdi ki modellenmemiş bir sink tarayıcı için yoktur.
Üçüncüsü, gerekçesiz suppression. #pragma warning disable CA3001 ya da id'siz nosemgrep bir sonraki gerçek bulguyu da susturur.
Dördüncüsü, kuralı test etmeden kırıcı yapmak. Tipli metavariable örneğinde kural testte geçti, gerçek kodda hiçbir şey bulmadı. Kural testi ve gerçek kod üzerinde en az bir doğrulama birlikte gerekir.
Beşincisi, Copilot Autofix önerisini review etmeden merge etmek. Öneri bir başlangıç noktası; davranışı koruyup korumadığını sen doğrularsın.
Küçük Bir Deneme
★☆☆ Kendi projende dotnet build -p:AnalysisModeSecurity=All çalıştır, çıkan CA3xxx ve CA5xxx uyarılarını say ve her birini "gerçek", "false positive" ya da "emin değilim" diye etiketle.
★★☆ Ekibinin bir veri erişim helper'ı için Semgrep kuralı yaz, en az üç ruleid ve bir ok satırıyla test dosyası ekle, semgrep --test'in önce geçtiğini sonra bilerek bozduğun bir satırla kırıldığını gör.
★★★ CodeQL CLI ile projen için bir veritabanı oluştur, default ve security-extended suite'lerini ayrı ayrı çalıştır ve iki sonuç kümesinin farkını incele. Ek bulguların kaçı senin için gerçek?
Kaynak kodumuzu taradık; ama Bookstore production'a bir container image olarak gidiyor ve o image'ın içinde bizim yazmadığımız bir işletim sistemi var. Bir sonraki yazıda image ve IaC (Infrastructure as Code) taramasına, base image seçimine ve tarayıcının kendisinin bir supply chain riski olduğu Mart 2026 Trivy olayına bakıyoruz.