"Pipeline'da güvenlik taraması var" dendiğinde çoğu zaman kastedilen, bir kere eklenip unutulmuş tek bir araç. Neyi kaçırdığına kimse bakmıyor. Kimi ekip kodu tarayan bir araç seçiyor, kimi web uygulamasını tarayan; ikisi de korunduğunu düşünüyor. Ben bunu sayıyla görmek istedim. Küçük bir Flask uygulaması yazıp içine kendi elimle 10 güvenlik açığı koydum. Aynı commit üzerinde iki statik tarayıcıyı ve bir dinamik tarayıcıyı iki farklı modda çalıştırdım, hangi açığı hangisinin bulduğunu tek tek saydım. SAST 8'ini, DAST 5'ini buldu; ikisi birlikte 9'unu. İçinde veritabanı şifresi olan unutulmuş bir backup dosyasını ise hiçbir tarama bulamadı. Bu yazıda SAST ve DAST'ın ne olduğunu, her birinin neyi görüp neyi göremediğini, ikisini GitHub Actions ya da GitLab CI pipeline'ına nasıl koyacağınızı ve lab'ın ne gösterdiğini anlatıyorum.
SAST nedir? DAST nedir?
SAST (Static Application Security Testing) kaynak kodu çalıştırmadan okur. Veriyi girişten (request.args) tehlikeli çağrılara (cursor.execute, subprocess.run, pickle.loads) kadar takip eder, dosya ve satır numarası verir. Pull request'te, daha hiçbir şey build edilmeden, saniyeler içinde çalışır.
DAST (Dynamic Application Security Testing) kodu hiç görmez. Çalışan uygulamayla HTTP üzerinden bir saldırgan gibi konuşur: sayfaları gezer, header'lara ve cookie'lere bakar, bulduğu her parametreye ', <script>, ; cat /etc/passwd gibi payload'lar gönderir. URL ve parametre verir. Bunun için deploy edilmiş bir uygulama gerekiyor, genelde staging ortamı.
Lab'daki araçlar:
- Semgrep: birçok dil için pattern ve taint kuralları; neye bakacağını
p/python,p/secrets,p/security-auditgibi kural paketleri belirliyor. - Bandit: PyCQA'nın sadece Python için statik tarayıcısı, sabit bir kontrol listesi var (B101...B703).
- OWASP ZAP: açık kaynak web tarayıcı.
zap-baseline.pysadece gezer ve izler (pasif);zap-full-scan.pybunlara ek olarak saldırı da yapar (aktif).
Bunların yanında bilmekte fayda olan iki yöntem daha var: SCA (software composition analysis, örneğin Trivy ya da Dependabot) bağımlılıklarda bilinen CVE'lere bakar; IAST çalışan uygulamanın içine bir ajan koyup hem isteği hem kod yolunu görür. İkisi de bu lab'ın konusu değil.
SAST ve DAST hangi sorunu çözüyor?
Prod'a çıkan açık pahalıya patlıyor ve review'da kolayca gözden kaçıyor. f-string ile kurulmuş bir SQL sorgusu diff'te masum görünüyor. Eksik bir HttpOnly flag'i ise diff'te hiç görünmüyor, çünkü kimsenin yazmadığı bir varsayılan değer. Güvenlik taraması bunları bir kullanıcıdan ya da saldırgandan önce yakalamak için var.
Her tarama türü başka bir yerde kör. SAST sunucunun gerçekte ne cevap verdiğini göremez: response header'ları, framework'ün koyduğu cookie flag'leri, birinin static/ altına kopyaladığı bir dosya. DAST da ulaşamadığı kod yolunu göremez, sadece gönderdiği payload'ların tetiklediği açıkları bulur. Bunlardan sadece birini koşan ekip, öbürünün bulacağı açığı prod'a taşır.
Ne kadar erken, o kadar ucuz. Pull request'te çalışan SAST, değişiklik henüz yazarın aklındayken sorunlu satırı gösterir ("shift left"). DAST ise build edilip ayarlanmış ve deploy edilmiş uygulamanın gerçekte nasıl davrandığını gösterir; bu, saldırganın gördüğüne daha yakın. İkisine de ihtiyaç var, ama farklı aşamalarda.
SAST ve DAST: avantajlar ve dezavantajlar
| SAST | DAST | |
|---|---|---|
| İhtiyacı | kaynak kod | çalışan uygulama (URL) |
| Pipeline'da yeri | her pull request, build'den önce | staging'e deploy'dan sonra |
| Bu lab'da süre | 6–12 sn | pasif 60 sn, aktif 731 sn |
| Raporu | dosya ve satır | URL, parametre, payload |
| İyi olduğu yer | injection noktaları, koddaki secret'lar, zayıf kripto, debug=True, güvensiz deserialization |
header'lar, cookie'ler, sunucu ayarı, gerçekten enjekte edilebilen parametreler, hata sayfaları |
| Kör olduğu yer | runtime ayarı, header'lar, kimsenin referans vermediği dosyalar | ulaşamadığı kod, denemediği payload türleri |
| False positive | daha çok (veri akışı hakkında tahmin yürütüyor) | injection'da daha az (cevabı gördü), header'larda gürültülü |
| Dil | dile özel | dilden bağımsız |
| Düzeltme maliyeti | düşük, yazar hâlâ değişikliğin başında | daha yüksek, değişiklik merge olmuş |
SAST ve DAST bir CI/CD pipeline'ına nasıl yerleşir?

Kullandığım kural: SAST, ERROR seviyesinde merge'ü durdurur; DAST, High risk alarmda release'i durdurur. Bunun altındaki her şey rapor, gate değil. Eşiğin neden önemli olduğunu aşağıda, Nerede patladı #5'te anlattım.
GitHub Actions örneği
# .github/workflows/security.yml
name: security
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
sast:
runs-on: ubuntu-24.04
container: semgrep/semgrep:1.182.1
steps:
- uses: actions/checkout@v5
- name: Semgrep
run: >
semgrep scan --metrics=off
--config p/python --config p/secrets --config p/security-audit
--severity ERROR --error
--sarif -o semgrep.sarif .
- uses: actions/upload-artifact@v4
if: always()
with:
name: semgrep
path: semgrep.sarif
dast:
needs: sast
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- name: Build and start the app
run: |
docker build -t shop:${{ github.sha }} app
docker run -d --name shop -p 8080:8080 shop:${{ github.sha }}
for i in $(seq 30); do curl -sf -o /dev/null http://localhost:8080/ && break; sleep 1; done
- name: ZAP full scan
run: |
mkdir -p zap && chmod 777 zap
docker run --rm --network host -v "$PWD/zap:/zap/wrk:rw" zaproxy/zap-stable:2.17.0 \
zap-full-scan.py -t http://localhost:8080 -m 2 -J report.json -r report.html -I
- name: Fail on High risk alerts
env:
ZAP_IGNORE: "43" # reviewed false positives, reasons in .zap/ignore.tsv
run: |
high=$(jq '[.site[].alerts[] | select(.riskcode == "3")
| select(.pluginid as $p | env.ZAP_IGNORE | split(",") | index($p) | not)] | length' zap/report.json)
echo "High risk alerts: $high"
test "$high" -eq 0
- uses: actions/upload-artifact@v4
if: always()
with:
name: zap
path: zap/
-I parametresi ZAP'ın uyarılar yüzünden kendi kendine fail etmesini engelliyor; kararı jq adımı veriyor. ZAP_IGNORE içinde, biri tarafından incelenip false positive olarak işaretlenmiş alarmların plugin id'leri duruyor; her birinin gerekçesi workflow'un yanındaki bir dosyada. Bu listeye neden ihtiyaç duyduğumu Nerede patladı #3'te anlattım. Bir rules dosyası yazmazsanız ZAP'ın kendi exit kodları (FAIL için 1, WARN için 2), eksik bir Permissions-Policy header'ına SQL injection ile aynı muameleyi yapıyor. Ben gate'in okunabilir tek bir satır olmasını istedim. Gerçek bir staging URL'i için uygulamayı başlatan docker run yerine ortamınızın URL'ini yazın.
GitLab CI örneği
# .gitlab-ci.yml
stages: [sast, build, dast]
variables:
IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
semgrep:
stage: sast
image: semgrep/semgrep:1.182.1
script:
- >
semgrep scan --metrics=off
--config p/python --config p/secrets --config p/security-audit
--severity ERROR --error
--gitlab-sast -o gl-sast-report.json .
artifacts:
when: always
reports:
sast: gl-sast-report.json
build:
stage: build
image: docker:28.5
services: [docker:28.5-dind]
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build -t "$IMAGE" app
- docker push "$IMAGE"
zap:
stage: dast
image: zaproxy/zap-stable:2.17.0
services:
- name: $IMAGE
alias: shop
variables:
ZAP_IGNORE: "43" # reviewed false positives, reasons in .zap/ignore.tsv
script:
- mkdir -p /zap/wrk
- zap-full-scan.py -t http://shop:8080 -m 2 -J report.json -r report.html -I
- cp /zap/wrk/report.* .
- >
python3 -c "import json,os,sys; ig=os.environ['ZAP_IGNORE'].split(',');
a=[x for s in json.load(open('report.json'))['site'] for x in s['alerts'] if x['riskcode']=='3' and x['pluginid'] not in ig];
print('High risk alerts:', len(a)); sys.exit(1 if a else 0)"
artifacts:
when: always
paths: [report.html, report.json]
--gitlab-sast, raporu GitLab'ın merge request widget'ında gösterebileceği formatta yazıyor. GitLab'ın kendi SAST.gitlab-ci.yml ve DAST template'leri de var; ben aynı komutlar iki platformda da, yerelde de çalışsın diye araçları açıkça yazdım. İki dosya da bir YAML parser ile kontrol edildi; bu yazı için GitHub'da ya da GitLab'da koşmadım. Lab aynı komutları yerelde koştu.
Lab: 10 bilinen açık, 5 tarama
Neden OWASP Juice Shop değil de kendi uygulamam? Bir tarayıcının kaç açık bulduğunu ölçmek için kaç açık olduğunu bilmem gerekiyor. Juice Shop'ta yüzden fazla challenge var ve tarayıcı çıktısını onlara eşlemek başlı başına bir proje. Benim uygulamam 90 satır civarında bir Flask uygulaması: üç formlu bir ana sayfa, cookie koyan bir login, o cookie'yi okuyan bir profil sayfası. Koyduğum her açığın satırında # Vn etiketi var, böylece bir script her bulguyu dosya ve satıra (SAST) ya da ZAP kural id'sine (DAST) göre bir açığa eşliyor. Hiçbir açığa eşlenmeyen bulguları "ekstra" olarak ayırıp elle kontrol ettim.
| # | Koyduğum açık | Nerede görünür |
|---|---|---|
| V1 | SQL injection: /user'da execute() içinde f-string |
kod ve runtime |
| V2 | OS command injection: /ping'de subprocess.run(..., shell=True) |
kod ve runtime |
| V3 | Koda gömülü secret'lar: Flask SECRET_KEY ve bir AWS anahtar çifti |
kod |
| V4 | Güvensiz deserialization: session cookie'sine pickle.loads |
kod ve runtime |
| V5 | app.run(debug=True): Werkzeug debugger |
kod ve runtime |
| V6 | Şifreler için md5 | kod |
| V7 | Güvenlik header'ları yok (CSP, X-Frame-Options, ...) | runtime |
| V8 | HttpOnly, Secure, SameSite olmayan cookie |
runtime (sonradan anlaşıldı ki kodda da) |
| V9 | Reflected XSS ve template injection: /search'te render_template_string("..." + q) |
kod ve runtime |
| V10 | Hiçbir yerden link verilmeyen, içinde DB şifresi olan static/config.php.bak |
runtime |
Tüm tarayıcıları aynı Docker ağında container olarak çalıştırdım; ZAP uygulamaya http://vulnapp:8080 adresinden ulaştı.
A. SAST: yaygın kural paketleriyle Semgrep

"CI'a Semgrep ekleyin" rehberlerinin çoğu p/python artı p/secrets ile başlıyor. Bu kurulum 12 saniyede 10 açığın 6'sını buldu ve her birinde tam satırı gösterdi. Benim koymadığım iki XSS açığını da buldu: /ping komut çıktısını HTML olarak dönüyor, /profile cookie'den gelen kullanıcı adını bir f-string'e koyuyor. İkisi de gerçek. Ama uygulamanın en tehlikeli açığını kaçırdı: cookie üzerinden uzaktan kod çalıştırmaya izin veren pickle deserialization'ı.
B. SAST: p/security-audit eklenmiş Semgrep ve Bandit

Bir kural paketi daha, iki açık daha: pickle (V4) ve cookie flag'leri (V8). V8'i "sadece runtime" sütununa koymuştum, ama httponly=True olmadan yazılmış set_cookie("session_data", ...) kodda görünüyor ve audit paketinde bunun için bir kural var. Sonucu araçtan çok, seçilen kural paketi değiştirdi.

Bandit de 6 açık buldu, ama farklı 6 açık: ek paket olmadan pickle'ı yakaladı, render_template_string içindeki XSS'i kaçırdı. Koda gömülü AWS secret'ını ise LOW olarak işaretledi; sadece HIGH'ta duran bir gate bunu geçirirdi.
C. DAST: ZAP baseline (pasif)

Baseline tarama sayfaları gezip cevaplara bakar, hiç saldırı göndermez. 60 saniyede tam olarak SAST'ın iyi göremediği iki açığı buldu: eksik header'lar (V7) ve cookie flag'leri (V8). Uygulamada SQL injection ve command injection varken tek bir High alarm bile çıkmadı. Baseline'ın yeşil olması uygulamanın güvenli olduğunu değil, sadece cevapların normal göründüğünü gösteriyor.
D. DAST: ZAP full scan (aktif)


Aktif tarama 12 kat uzun sürdü ama injection'ları gerçekten kanıtladı. 127.0.0.1&cat /etc/passwd& gönderdi, cevapta root:x:0:0 satırı döndü. {{7014*3535}} gönderdi, cevapta 24794490 döndü; bu da açığın sadece XSS değil, template injection olduğunu kanıtlıyor. Review'da bu bulgular tartışma konusu olmaz, çünkü hem payload hem cevap raporda duruyor. Yine de pickle açığını (ZAP pickle payload'u üretmiyor), koda gömülü secret'ları ve md5'i (HTTP üzerinden görünmüyorlar), debug konsolunu (Werkzeug 3, Host header'ı localhost ya da 127.0.0.1 değilse /console'a 400 dönüyor, olsa bile PIN istiyor) ve backup dosyasını kaçırdı.
E. Matris

- Sadece SAST buldu: V3 secret'lar, V4 pickle, V5 debug modu, V6 md5.
- Sadece DAST buldu: V7 güvenlik header'ları.
- İkisi de buldu: V1 SQL injection, V2 command injection, V8 cookie flag'leri, V9 XSS.
- Kimse bulmadı: V10, içinde veritabanı şifresi olan backup dosyası.
F. Pipeline gate: açıklı uygulama, düzeltilmiş uygulama ve bir false positive
Sonra on açığın hepsini uygulamanın ikinci bir kopyasında kapattım: parametreli sorgu, liste ile subprocess.run ve host adı allowlist'i, secret'lar environment'tan, pickle yerine imzalı JSON cookie, debug sunucusu yerine waitress, md5 yerine scrypt, güvenlik header'larını koyan bir after_request hook'u, cookie'de httponly/secure/samesite, autoescape'li template'ler; static/ klasörünü de tamamen kaldırdım. make pipeline yukarıdaki YAML'larla aynı gate'leri çalıştırıyor: --severity ERROR --error ile Semgrep, sonra build, sonra ZAP full scan, sonra High alarm kontrolü.

- Açıklı uygulama: SAST, pipeline'ı 8. saniyede 8 engelleyici bulguyla durdurdu. Image build edilmedi, ZAP hiç koşmadı. Bir açığı durdurmanın en ucuz yeri burası.
- Düzeltilmiş uygulama, ilk koşu: SAST geçti, ZAP 541 saniye koştu ve gate 1 High alarmla kaldı. Alarm bir false positive'di (aşağıda Nerede patladı #3).
- Düzeltilmiş uygulama, incelenmiş tek bir ignore satırından sonra: 505 saniyede geçti. Kalanlar: Medium
HTTP Only Site(lab'da TLS yok; staging'de bu gerçek bir bulgu), LowCookie Slack Detectorve LowCross-Origin-Embedder-Policy Header Missing; bu da benim eklemediğim bir header. Hiçbiri pipeline'ı durdurmuyor, hepsi raporda duruyor.
Nerede patladı: 6 gerçek sürpriz
1. p/secrets ilk AWS anahtarımı görmezden geldi: sonu EXAMPLEKEY ile bitiyordu
İlk sürümde AWS dokümanındaki örnek anahtarı, wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'i kullandım. Ne p/secrets ne de p/gitleaks bunu raporladı; secret tarayıcıları dokümandaki örneği bilerek allowlist'e alıyor. Aynı formatta rastgele bir sahte anahtar koydum, iki paket de hemen yakaladı. Bir secret tarayıcıyı test ediyorsanız dokümandaki anahtarla test etmeyin.
2. p/python tek başına cookie'deki pickle.loads çağrısını raporlamadı
Uygulamanın en tehlikeli açığı olan, session cookie'si üzerinden uzaktan kod çalıştırma, en yaygın Semgrep kurulumunda hiç görünmedi. İlgili kurallar (avoid-pickle, insecure-deserialization) p/security-audit paketinde. Bandit ise bunu varsayılan kontrolleriyle buldu. Sadece aracın çalıştığına değil, kural paketinin içinde ne olduğuna bakın.
3. Bir false positive, düzeltilmiş uygulamanın release'ini durdurdu
ZAP, /ping?host=127.0.0.1 ve /search?q=shoes için High seviyesinde Source Code Disclosure - File Inclusion alarmı verdi. Parametre değeri olarak ping ve search kelimelerini gönderdi ve cevabı (ping çıktısını, arama metnini) sızmış kaynak kod sandı. Uygulamada file inclusion yok. Açıklı uygulamada bu alarm beş gerçek High alarmın arasında gözden kaçtı. Düzeltilmiş uygulamada ise tek High alarm buydu ve ortada tek bir gerçek açık yokken pipeline'ı kırdı: High=1, FAILED.
İlk akla gelen çözümler yanlış: gate'i "2 High alarmda dur" diye gevşetmek ya da DAST gate'ini tamamen kaldırmak. Plugin id'sini gerekçesi ve tarihiyle bir ignore dosyasına ekledim, gate sadece o id'yi atlıyor. O dosyadaki her satır, birinin verdiği ve sonradan hesabı sorulabilecek bir karar.
4. Backup dosyası açıkta dururken ZAP PASS: Backup File Disclosure [10095] dedi
ZAP'ın backup dosyaları için bir kuralı var ve bu kural çalıştı da. Zaten bildiği URL'lerin yanında index.bak, index.old gibi isimleri deniyor. /static/config.php.bak'e hiçbir yerden link yoktu, ZAP o klasörde böyle bir dosya olduğunu hiç öğrenmedi, kural da geçti. SAST da görmedi, çünkü bu bir kod dosyası değil. Bunu yakalamanın yolu, image'a neyin girdiğini review etmek (.dockerignore, sadece uygulamanın ihtiyacı olanı COPY etmek) ya da wordlist kullanan bir forced browsing aracı.
5. Düzeltmeden sonra audit kuralları hâlâ 9 bulgu raporladı
Düzeltilmiş uygulamada varsayılan Semgrep seti hiç bulgu vermedi. p/security-audit eklenince 5 WARNING çıktı; hepsi render-template-string kuralından, değerleri autoescape'li {{ }} ile basılan sabit template'ler için. Bandit 4 bulgu verdi: B404 (subprocess import'u), B603 ve B607 (ping'i shell olmadan, adıyla çalıştırmak) ve B104 (0.0.0.0'a bind; bir container zaten bunu yapmak zorunda).
Bunların hiçbiri açık değil. SAST gate'i WARNING'de ya da Bandit LOW'da dursaydı düzeltilmiş uygulama hiç yayına çıkamazdı, ekip de her yere # nosec yazmayı öğrenirdi. Gate'in sadece ERROR olmasının sebebi bu: açıklı uygulamayı durduran 8 bulgunun hepsi ERROR'du, düzeltilmiş uygulamada kalan 9'un hepsi WARNING, LOW ya da MEDIUM. Audit paketini yine de çalıştırmaya değer; gate olarak değil, reviewer'ın okuduğu bir rapor olarak.
6. Server Leaks Version Information debug modunun kanıtı değil
ZAP her cevaptaki Server: Werkzeug/3.1.9 Python/3.13.18 header'ını işaretledi. Önce bunu V5 (debug modu) saydım, sonra kontrol ettim: Werkzeug bu header'ı debug=False ile de gönderiyor. ZAP debugger'ın kendisini hiç bulmadı: /console, Host: vulnapp:8080'e 400 döndü, Host: 127.0.0.1 ile "console is locked" gösterip PIN istedi. Werkzeug 3 debug konsoluna kazara ulaşmayı zorlaştırmış; prod'da debug=True yine de bir açık ve onu sadece SAST raporladı. V5'i DAST'a saymadım; saysaydım matris DAST için daha iyi görünürdü.
Hangi durumda hangisi
| Durum | Kullan | Neden |
|---|---|---|
| Her pull request | SAST, ERROR'da gate | saniyeler, tam satır, yazar merge'den önce düzeltir |
| Koddaki secret'lar | her push'ta SAST secret kuralları (ya da gitleaks) | DAST bunları hiç görmez |
| Staging'e her deploy'dan sonra | DAST baseline | 60 sn, header ve cookie'leri yakalar, sık koşmak güvenli |
| Gece ya da release öncesi | DAST full scan, High'ta gate | bu lab'da 12 dakika sürdü, injection'ı gerçekten kanıtlıyor; saldırı gönderdiği için izin almadan asla prod'da çalıştırılmamalı |
| Bilinen CVE'li bağımlılıklar | SCA (Trivy, Dependabot) | ne SAST ne DAST kütüphane sürümlerine bakar |
| Image'da kimsenin referans vermediği dosyalar | image review, .dockerignore, forced browsing |
beş taramanın hepsi V10'u kaçırdı |
Özet
- SAST kodu okur ve satır verir; DAST çalışan uygulamaya saldırır ve URL verir. 10 açık koyduğum aynı uygulamada SAST 8'ini, DAST 5'ini buldu; ikisi birlikte 9'unu.
- Tek başına SAST güvenlik header'larını kaçırdı; tek başına DAST secret'ları, pickle'ı, md5'i ve debug modunu kaçırdı. Injection'ları ve cookie flag'lerini ikisi de buldu.
- Semgrep'te kural paketi seçimi, araç seçiminden daha çok fark yarattı:
p/python+p/secrets6 açık buldu,p/security-auditeklenince 8'e çıktı; pickle üzerinden uzaktan kod çalıştırma da bunlara dahil. - Uygulamada SQL ve command injection varken ZAP baseline sadece 2 açık buldu ve hiç High alarm vermedi; injection'ları ancak aktif tarama buldu (60 sn yerine 731 sn).
- Gate'i SAST'ta ERROR'a, DAST'ta High'a koydum; gerisi rapor. Açıklı uygulama SAST'ta 8 saniyede durdu; düzeltilmiş uygulama ancak incelenmiş bir false positive ignore dosyasına girince geçti. Backup dosyasını kimse bulmadı; o bir image review işi.
Kaynaklar: OWASP Static Code Analysis (Controls for Static Code Analysis); OWASP ZAP dokümanları ZAP Baseline Scan, ZAP Full Scan, Alert details; Semgrep dokümanları Rule packs, CLI reference (--severity, --error, --gitlab-sast); Bandit dokümanları Test plugins; GitHub Docs Workflow syntax for GitHub Actions; GitLab Docs Static application security testing, Dynamic application security testing, Artifacts reports.
Serinin devamı
Bu yazı Temel Konular, Ölçerek serisinin üçüncüsü. Bir önceki yazıda, Git Branching Stratejisi Nedir?, bir değişikliğin prod'a nasıl ulaştığını ölçmüştüm; bu yazıda pipeline'ın yolda neyi kontrol ettiğini ölçtüm. Sıradaki yazının konusu rootless container'lar ve image sıkılaştırma: bu sefer CVE'leri ve root çalışan bir container'ın host'a neler yazabildiğini sayacağım.
Bu lab'ın kodu (açıklı ve düzeltilmiş Flask uygulaması, SAST, DAST ve pipeline script'leri, her bulguyu bir açığa eşleyen puanlama script'i ve Makefile) Buy Me a Coffee'deki Lab Access üyeleriyle paylaşılıyor; üye olduktan sonra repo daveti e-postayla geliyor: https://buymeacoffee.com/miraccanyilmaz/membership