Forensic-grade Application Security testing framework with multi-layer SAST/DAST/SCA coverage and the CFQI scoring algorithm.
Author: Elyer Maldonado — AI Quality & Risk Architect Sibling project: INGRID — AI Security Testing Framework
TL;DR. Boot 3 Docker containers (~700 MB RAM), seed a synthetic banking-like portfolio, open the live Grafana dashboards on
localhost:3003, hit the FastAPI normalizer onlocalhost:8001/docs, and emit two A4 forensic PDFs (one corporate-style for CISOs, one dictamen pericial for auditors). The whole pipeline runs on a laptop and is built on the proprietary CFQI algorithm (Code Forensic Quality Index, sibling of AFQI from INGRID).
- The QASL Ecosystem
- What makes this different
- Architecture
- Pre-requisites
- Installation — commands in order
- First-run verification
- Daily operations
- The CFQI algorithm
- Standards & compliance
- Repository structure
- Troubleshooting
- Roadmap
- Author, license & disclaimer
This module is part of the QASL (Quality Assurance Security Lab) Ecosystem:
QASL Ecosystem
├── MATHCORE ← mathematical & statistical engine
├── INGRID ← AI/LLM Security Testing (AFQI algorithm)
└── QASL Test Security ← Application Security Testing (CFQI algorithm) ← you are here
INGRID audits AI systems forensically using the AFQI algorithm. QASL Test Security does the same for traditional applications using the CFQI algorithm — the natural sibling, applied to code.
| Module | Domain | Algorithm | Status |
|---|---|---|---|
| MATHCORE | Mathematical / statistical engine | — | Shared core |
| INGRID | AI / LLM Security Testing | AFQI v1.0 | Production |
| QASL Test Security ← here | Application Security Testing | CFQI v1.0 | Production |
Most security frameworks aggregate findings from scanners. QASL Test Security goes further with three differentiators:
A proprietary scoring algorithm modeled after AFQI that produces a single 0-100 score with grade A/B/C/D/F, computed from three forensic dimensions:
CFQI = 100 · [α·S(c) + β·D(d) + γ·R(r)] − Λ(C) · μ(R)
S(c) = Static layer (SAST + Secrets + IaC) α = 0.35
D(d) = Dependencies layer (SCA + SBOM + Container) β = 0.30
R(r) = Runtime layer (DAST + API + Fuzzing) γ = 0.35
Λ(C) = Compliance penalty (uncovered OWASP Top 10 categories)
μ(R) = VCR risk multiplier (Value · Cost · Risk profile)
See docs/cfqi-algorithm.md for the full mathematical justification.
The framework deliberately produces two PDFs, not one — because mixing visual languages confuses readers:
| Pages | Style | Audience | |
|---|---|---|---|
QASL-Executive-Report |
3 | INGRID corporate (dark blue + gold) | CISOs, management, stakeholders |
QASL-CFQI-Dictamen-Forense |
1 | AFQI pericial (beige + serif) | Auditors, peritos, legal, compliance |
One framework, all the major automatable security layers:
| Layer | Tool | Standard |
|---|---|---|
| SAST | Semgrep | CWE, OWASP Top 10 |
| SCA | Trivy | NVD CVE, CycloneDX SBOM |
| Secrets | gitleaks | Pattern matching |
| IaC | Checkov / Trivy | CIS Benchmarks |
| Container | Trivy | NVD CVE |
| DAST | OWASP ZAP | OWASP Top 10, CWE |
| API | OWASP ZAP / Nuclei | OWASP API Security |
Four independent layers, decoupled by a unified schema:
┌── 1) Security Tools (CI/CD or local) ─────────────────┐
│ Semgrep · Trivy · OWASP-ZAP · gitleaks · Checkov │
└──────────────────────────┬───────────────────────────┘
│ SARIF / JSON
▼
┌── 2) Normalizer (FastAPI) ──┐
│ Parse · Dedup · Enrich │
└────────────┬────────────────┘
│
▼
┌── 3) PostgreSQL (single source of truth) ──┐
│ projects · scans · findings · cfqi_scores │
│ views: v_active_findings · v_latest_cfqi │
└──┬──────────────────────────────────┬──────┘
│ │
▼ ▼
┌── 4a) Grafana ──┐ ┌── 4b) PDF Generator ──┐
│ 3 dashboards │ │ CFQI calculator │
│ Live queries │ │ Executive Report │
│ Time series │ │ Dictamen Forense │
└─────────────────┘ └───────────────────────┘
Live observability Forensic point-in-time
| Component | Stack | RAM (idle / load) | Host port | Container port |
|---|---|---|---|---|
| PostgreSQL 16-alpine | SQL + JSONB | 80 MB / 200 MB | 5433 | 5432 |
| Normalizer | FastAPI + uvicorn | 60 MB / 150 MB | 8001 | 8000 |
| Grafana 10.4.2 | Observability platform | 150 MB / 250 MB | 3003 | 3000 |
| PDF Generator | Node + PDFKit | — / 150 MB transient | — | — |
ℹ️ Why non-standard host ports? This module is designed to coexist with sibling QASL stacks (e.g. QASL Framework) on the same host. To avoid collisions, host ports are non-standard (
3003 · 8001 · 5433), container names are prefixedqasl-test-*and Docker volumes are prefixedqasl_test_*. Internally, containers still use canonical ports (5432/8000/3000).
Detailed architecture: docs/architecture.md
| Requirement | Minimum version | Verify with |
|---|---|---|
| OS | Windows 11 / macOS 12+ / Linux (any modern) | — |
| Docker Desktop | 29.x | docker --version |
| Docker Compose v2 | v2.x | docker compose version |
| Node.js | 18+ | node --version |
| Python | 3.10+ | python --version |
| Free RAM | ~2 GB | Task Manager / free -h |
| Free disk | ~3 GB (first image pull) | — |
| Free host ports | 3003, 8001, 5433 |
Get-NetTCPConnection -LocalPort 3003,8001,5433 (Windows)lsof -i :3003,:8001,:5433 (macOS/Linux) |
Run in this exact order from the project root. Windows users: PowerShell. macOS/Linux: bash.
git clone https://github.com/E-Gregorio/qasl-test-security.git
cd qasl-test-security# Windows (PowerShell)
Copy-Item .env.example .env# macOS / Linux
cp .env.example .envDefault credentials in
.env.exampleare for development only (qasl_admin / changeme_dev_only). Rotate before any non-local deployment.
docker compose up -dThis will:
- Pull
postgres:16-alpine,grafana/grafana:10.4.2(~200 MB total, first time only) - Build the local
normalizerimage (Python 3.12-slim, ~150 MB) - Start all 3 services with health checks
- Provision Grafana datasource + 3 dashboards automatically
Wait 60–90 seconds the first time, then verify:
docker compose psExpected: 3 containers qasl-test-postgres, qasl-test-normalizer, qasl-test-grafana — all running, postgres + normalizer healthy.
# Windows
pip install psycopg2-binary# macOS / Linux
pip3 install psycopg2-binary# Windows — PYTHONIOENCODING required because Python 3.13 stdout uses cp1252
$env:PYTHONIOENCODING = "utf-8"
python seed\seed_demo.py# macOS / Linux
python3 seed/seed_demo.pyExpected output (last lines):
✅ Seed completed: 4 projects, 96 findings
📊 Open Grafana at http://localhost:3003
cd pdf-generator
npm install
cd ..# Windows
$env:DATABASE_URL = "postgresql://qasl_admin:changeme_dev_only@localhost:5433/qasl_security"
cd pdf-generator
node generate-cfqi-report.mjs --project=PaymentService-API
cd ..# macOS / Linux
export DATABASE_URL="postgresql://qasl_admin:changeme_dev_only@localhost:5433/qasl_security"
cd pdf-generator
node generate-cfqi-report.mjs --project=PaymentService-API
cd ..Output in pdf-generator/reports/:
QASL-Executive-Report-PaymentService-API-{date}.pdf(3 pages)QASL-CFQI-Dictamen-Forense-PaymentService-API-{date}.pdf(1 page)
| URL | Credentials |
|---|---|
| http://localhost:3003 | admin / qasl_admin |
| http://localhost:8001/docs | — (open) |
| http://localhost:8001/redoc | — (open) |
Skip steps 5.3–5.8 with the bundled launcher:
.\launch-demo.ps1 -PdfPerforms all the boot/seed/verify/PDF/browser steps in one shot. See launch-demo.ps1.
| Check | URL / command | Expected |
|---|---|---|
| Containers healthy | docker compose ps |
3/3 running, postgres + normalizer healthy |
| Normalizer health | curl http://localhost:8001/health |
{"status":"healthy","service":"qasl-normalizer","version":"0.1.0"} |
| Swagger UI | http://localhost:8001/docs | 6 endpoints under 3 tags (meta · ingestion · query) |
| ReDoc | http://localhost:8001/redoc | Clean markdown-rendered API docs |
| OpenAPI JSON | http://localhost:8001/openapi.json | Valid OpenAPI 3.1 with 2 servers, 3 tags, MIT license |
| Grafana login | http://localhost:3003 | Login form, admin / qasl_admin works |
| Dashboards loaded | Grafana → Dashboards | Folder QASL Test Security with 3 items |
| API: list projects | curl http://localhost:8001/projects |
4 projects with their CFQI score |
| API: project CFQI | curl http://localhost:8001/cfqi/PaymentService-API |
Full breakdown (D1/D2/D3, weights, λ, μ, grade) |
| PDFs generated | pdf-generator/reports/ |
2 A4 PDFs, valid %PDF-1.3 magic |
Three dashboards auto-provisioned at boot. Each targets a different reader profile.
| Dashboard | URL | Audience |
|---|---|---|
| Executive Overview | http://localhost:3003/d/qasl-executive-overview | CISO, management |
| CFQI Forensic Analysis | http://localhost:3003/d/qasl-cfqi-forensic | Security engineer |
| OWASP Top 10 & Compliance | http://localhost:3003/d/qasl-owasp-compliance | Auditor, compliance |
4 severity KPIs, donut chart by scan type, CFQI table per project, 30-day vulnerability trend.
4 gauges — overall CFQI plus per-dimension scores (D1 Static / D2 Dependencies / D3 Runtime) — and a forensic audit table per project.
OWASP category distribution, top critical/high findings, MTTR per severity for SLA tracking.
Customizing dashboards: edit the JSON files under
grafana/dashboards/and restart Grafana. UI edits don't persist — provisioning overrides them at restart.
The Normalizer ingests raw scanner output, normalizes findings into a unified schema, deduplicates by SHA256 fingerprint and persists into PostgreSQL.
| Method | Path | Tag | Purpose |
|---|---|---|---|
GET |
/health |
meta | Liveness + DB connectivity check |
GET |
/ |
meta | Service identity + endpoint catalog |
POST |
/ingest |
ingestion | Submit scanner output (SARIF/JSON) |
GET |
/findings/{project_name} |
query | Active findings, severity-sorted |
GET |
/cfqi/{project_name} |
query | Latest CFQI breakdown |
GET |
/projects |
query | Portfolio view with CFQI per project |
Three documentation endpoints:
- Swagger UI — http://localhost:8001/docs (interactive try-it-out)
- ReDoc — http://localhost:8001/redoc (clean reading layout)
- OpenAPI JSON — http://localhost:8001/openapi.json (machine-readable)
./scripts/upload_to_normalizer.sh PaymentService-API semgrep SAST semgrep-output.sarifcurl -X POST http://localhost:8001/ingest \
-H "Content-Type: application/json" \
-d @payload.jsonTwo PDFs are generated per run, intentionally separated.
![]() |
![]() |
| QASL-Executive-Report (3 pp) INGRID corporate · CISO/management |
QASL-CFQI-Dictamen-Forense (1 p) AFQI pericial · auditor/compliance |
cd pdf-generator
node generate-cfqi-report.mjs --demo# Windows
$env:DATABASE_URL = "postgresql://qasl_admin:changeme_dev_only@localhost:5433/qasl_security"
node generate-cfqi-report.mjs --project=PaymentService-API# macOS / Linux
DATABASE_URL="postgresql://qasl_admin:changeme_dev_only@localhost:5433/qasl_security" \
node generate-cfqi-report.mjs --project=PaymentService-APIBoth PDFs are deterministic: same input findings → same score, same narrative, same grade. That reproducibility is what makes CFQI defendable to a regulator.
CFQI = 100 · [α·S(c) + β·D(d) + γ·R(r)] − Λ(C) · μ(R)
Three weighted dimensions, minus a compliance penalty, divided by a risk multiplier.
| Symbol | Meaning | Default |
|---|---|---|
S(c) |
D1 Static layer (SAST + Secrets + IaC) | weight α = 0.35 |
D(d) |
D2 Dependencies layer (SCA + Container) | weight β = 0.30 |
R(r) |
D3 Runtime layer (DAST + API + Fuzzing) | weight γ = 0.35 |
Λ(C) |
Compliance penalty (uncovered OWASP categories) | 0–10 pts |
μ(R) |
VCR risk multiplier (Value · Cost · Risk) | 1.0 std · 1.5 high · 2.0 critical |
| Severity | Penalty |
|---|---|
critical |
25 |
high |
10 |
medium |
3 |
low |
1 |
info |
0 |
| Score | Grade | Verdict | Action |
|---|---|---|---|
| ≥ 90 | A | SECURE | Maintain controls. Quarterly review. |
| 75–89 | B | OPERABLE | Plan to remediate highs next sprint. |
| 60–74 | C | CON RESERVAS | Re-evaluate weakest dimension. |
| 40–59 | D | RIESGO ALTO | Suspend deployment. Threat-model. |
| < 40 | F | INACEPTABLE | Do not deploy. External audit. |
Reference implementation: pdf-generator/lib/cfqi-calculator.mjs (~150 lines, intentionally readable).
Mathematical justification: docs/cfqi-algorithm.md.
Project-wide VCR/μ policy (v2.0 guide): docs/vcr-project-profile.md.
The framework does not invent its own categories — every finding is mapped to industry-recognized standards so any auditor or regulator can cross-validate.
| Standard | Coverage |
|---|---|
| OWASP Top 10 2021 | Per-finding mapping (owasp_top10 field) + dashboard 03 |
| OWASP ASVS 4.0 | Optional owasp_asvs field; matrix in docs/owasp-asvs-mapping.md |
| OWASP Testing Guide v4.2 | Manual checklist in docs/manual-testing-checklist.md |
| NIST SP 800-218 SSDF | Practices referenced in program-level docs |
| ISO/IEC 27001:2022 | Compatible with controls A.5–A.18 on security testing |
| SARIF v2.1.0 | Native parser (Semgrep · Snyk · SonarQube) |
| CycloneDX SBOM | Compatible with Trivy SCA output |
| CWE / CVE / CVSS / EPSS | Cross-referenced per finding when applicable |
qasl-test-security/
├── README.md # ← this file
├── MANUAL.html # Operations & user manual (HTML, print-ready)
├── QUICKSTART.md # 6-step rapid start
├── INTERVIEW-NOTES.md # Talking points for technical interviews
├── docker-compose.yml # Stack orchestration (3 services)
├── launch-demo.ps1 # One-command launcher (Windows)
├── .env.example # Environment template
├── img/ # Screenshots used in this README
├── docs/
│ ├── architecture.md # Detailed architecture + threat model
│ ├── architecture-diagram.svg # Visual diagram
│ ├── cfqi-algorithm.md # CFQI math & justification
│ ├── threat-modeling.md # STRIDE template
│ ├── manual-testing-checklist.md # OWASP Testing Guide checklist
│ ├── owasp-asvs-mapping.md # ASVS coverage matrix
│ ├── security-program.md # Program-level context
│ └── vcr-project-profile.md # VCR project profile → μ policy (v2.0 guide)
├── grafana/
│ ├── provisioning/ # Auto-provisioned datasource + dashboard providers
│ └── dashboards/
│ ├── 01-executive-overview.json
│ ├── 02-cfqi-forensic-analysis.json
│ └── 03-owasp-top10-coverage.json
├── normalizer/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── main.py # FastAPI app
│ └── parsers/ # Tool-specific parsers
│ ├── sarif.py # Semgrep · Snyk · SonarQube
│ ├── trivy.py # SCA · Container · IaC
│ ├── zap.py # OWASP ZAP DAST
│ └── gitleaks.py # Secret scanners
├── pdf-generator/
│ ├── package.json
│ ├── generate-cfqi-report.mjs # Produces both PDFs (Executive + Dictamen)
│ ├── lib/
│ │ └── cfqi-calculator.mjs # CFQI algorithm reference impl
│ └── reports/ # Generated PDFs land here
├── scripts/
│ └── upload_to_normalizer.sh # Helper to ingest scanner output
├── seed/
│ └── seed_demo.py # Synthetic demo data generator
└── sql/
└── schema.sql # PostgreSQL schema + views
Another service is bound to 5433, 8001, or 3003. Identify it:
# Windows
Get-NetTCPConnection -LocalPort 3003,8001,5433 | Format-Table# macOS / Linux
lsof -i :3003 -i :8001 -i :5433Free the port or remap in docker-compose.yml (e.g. "3004:3000").
Three usual causes:
-
Seed didn't run. Verify:
docker exec qasl-test-postgres psql -U qasl_admin -d qasl_security -c "SELECT COUNT(*) FROM findings;"
If
0, re-run step 5.5. -
Datasource lost. Grafana → Configuration → Data Sources → QASL-PostgreSQL → Test. Should return "Database Connection OK".
-
Time range too narrow. Top-right time picker → Last 30 days.
Python 3.13 on Windows defaults stdout to cp1252. Fix:
$env:PYTHONIOENCODING = "utf-8"
python seed\seed_demo.pydocker compose logs normalizer --tail=100Most common: import error in main.py (new module not in requirements.txt) or postgres still booting. Wait ~60 s on first boot; if it persists, rebuild:
docker compose up -d --build normalizerPre-existing query bug fixed in v1.1. The view v_active_findings already exposes project_name and business_criticality — don't add JOIN projects p. The current query is SELECT * FROM v_active_findings WHERE project_name = $1.
docker compose down -v
rm -rf pdf-generator/reports/*.pdfThen start over from step 5.3.
| Sprint | Scope |
|---|---|
| Sprint 1 ✅ | Stack, normalizer, CFQI, Grafana, PDF generator (this release) |
| Sprint 2 | GitHub Actions integration; live tool runs against vulnerable demo apps (Juice Shop, DVWA) |
| Sprint 3 | Slack notifications, Jira integration, SLA enforcement |
| Sprint 4 | OWASP Dependency-Track integration as parallel SBOM authority |
| Sprint 5 | Risk acceptance workflow, exception management, IAST integration (Contrast Community) |
| Sprint 6 | Public demo VM with HTTPS + Caddy reverse proxy + URL sharing |
Elyer Maldonado — AI Quality & Risk Architect
- GitHub: @E-Gregorio
- Sibling project: INGRID — AI Security Testing Framework
- Companion docs:
MANUAL.html·QUICKSTART.md·INTERVIEW-NOTES.md
MIT — see LICENSE.
This framework is for defensive security purposes: identifying vulnerabilities in your own systems before attackers do. Do not use against systems you don't own or have explicit authorization to test. Authors are not responsible for misuse.
QASL ECOSYSTEM · MATHCORE + INGRID + QASL TEST SECURITY OWASP Top 10 2021 · OWASP ASVS 4.0 · NIST SP 800-218 (SSDF) · ISO/IEC 27001







