You Have 5 Scanners and Zero Visibility: DefectDojo as Your Security Single Pane of Glass
Your pipeline runs Gitleaks, Semgrep, Trivy, ZAP, and Prowler. Five tools generating findings across five dashboards that nobody cross-references. A SQL injection gets flagged by both Semgrep and CodeQL — congratulations, you now have two alerts for one vulnerability and no way to know they're the same thing. Here's how to fix that with DefectDojo — open-source, self-hosted, and $0 in licensing.
I've spent the last few posts building a DevSecOps pipeline from scratch, hardening it with configurable gates. The pipeline prevents. Prowler detects. Notifications alert. The IAM toolkit remediates.
But here's the question nobody asked until now: where do you actually go to see everything?
The answer, for most teams, is "it depends." Semgrep findings are in the GitHub Security tab. Trivy results are in a different SARIF upload. ZAP opens an issue. Prowler dumps a JSON report. Gitleaks blocks the push but the finding lives in an Actions log. The security "posture" is scattered across five tools, three dashboards, and a Slack channel that everyone's muted.
That's not a security program. That's five security tools pretending to be a program.
What you actually need is a vulnerability management platform — a single place where every finding from every tool lands, gets deduplicated, gets assigned, gets tracked, and gets closed. In the enterprise world, they call this Application Security Posture Management (ASPM). In the open-source world, it's called DefectDojo.
What DefectDojo Actually Does
DefectDojo is an open-source vulnerability management platform maintained by OWASP. It's not a scanner — it doesn't find vulnerabilities. It's where vulnerability findings go after they're found.
Think of it as the difference between a security camera and a security operations center. The camera captures footage. The SOC watches all the cameras, correlates events, dispatches responses, and tracks resolution. Your scanners are cameras. DefectDojo is the SOC.
Here's what it does:
Aggregates findings from 200+ tools. Every scanner you're running — Gitleaks, Semgrep, Trivy, ZAP, Prowler, CodeQL — has a native importer. Upload the report, DefectDojo parses it and normalizes the findings into a consistent format.
Deduplicates across tools. Semgrep found a SQL injection in app.py:42. CodeQL found the same SQL injection in app.py:42. Without DefectDojo, that's two alerts. With DefectDojo, it's one finding with two sources. Your vulnerability count reflects reality, not tool count.
Tracks remediation. Each finding has a lifecycle: active → verified → mitigated → closed. Assign it to a developer, set an SLA, track progress. When the fix is deployed and the next scan comes back clean, DefectDojo auto-closes the finding.
Enforces SLAs. Critical findings must be fixed in 7 days. High in 30. Medium in 90. DefectDojo tracks the clock and sends notifications (or JIRA comments) when SLAs are breached.
Pushes to JIRA. Bi-directional sync. A critical finding auto-creates a JIRA ticket. Developer closes the ticket, DefectDojo marks the finding resolved. No manual status updates across systems.
Provides the dashboard. One URL. All findings. Filterable by product, tool, severity, age, SLA status, assignee. The "single pane of glass" that every security audit asks for and nobody actually has.
How It Fits the Ecosystem
Here's the architecture with DefectDojo as the central hub:
┌──────────────────────────────────────────────────────────────┐
│ CI/CD Pipeline (GitHub Actions) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Gitleaks │ │ Semgrep │ │ Trivy │ │ ZAP │ │
│ │ (JSON) │ │ (JSON) │ │ (JSON) │ │ (XML) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └──────────────┴────────────┴──────────────┘ │
│ │ │
│ POST /api/v2/reimport-scan/ │
└──────────────────────────┬───────────────────────────────────┘
│
▼
┌───────────────────────┐
│ DefectDojo │ ← Single pane of glass
│ ┌─────────────────┐ │
│ │ Deduplication │ │ Same vuln from 2 tools = 1 finding
│ │ SLA Tracking │ │ CRITICAL: 7 days. HIGH: 30 days.
│ │ Risk Scoring │ │ Severity + age + exploitability
│ │ Dashboards │ │ One URL for everything
│ └─────────────────┘ │
└──┬──────┬──────┬──────┘
│ │ │
┌───────┘ │ └────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ JIRA │ │ Slack │ │ Prowler │
│ Tickets │ │ Alerts │ │ Cloud Audit │
└──────────┘ └──────────┘ └──────┬───────┘
│ Prowler JSON
┌────────────────────────┘
▼
POST /api/v2/reimport-scan/
│
▼
DefectDojo (same instance)Pipeline → DefectDojo: Each stage of the DevSecOps pipeline pushes its report to DefectDojo after the scan completes. One API call per tool.
GHAS → DefectDojo: If you're running CodeQL and dependency review, export the SARIF and push it. DefectDojo's SARIF parser handles CodeQL natively.
Prowler → DefectDojo: Your scheduled Prowler audit outputs JSON. A post-scan step pushes it to DefectDojo under a separate product (e.g., "AWS Infrastructure").
DefectDojo → JIRA: Critical and high findings auto-create tickets. The developer works in JIRA. DefectDojo stays in sync.
DefectDojo → Slack: Webhook notifications for new criticals, SLA breaches, and finding state changes. Smarter than raw tool output — "3 new CRITICAL findings from today's pipeline run" instead of a wall of scanner output.
The Data Model: How to Organize Your Findings
Before you start importing, understand how DefectDojo organizes things:
Product Type → "Backend Team" or "Platform Security"
└── Product → "API Service" or "Frontend App" (maps to a repo)
└── Engagement → "CI/CD Pipeline" (long-running, continuous)
└── Test → "Semgrep Scan - 2026-02-12" (one per tool per run)
└── Finding → "SQL Injection in app.py:42"For a CI/CD pipeline, here's the mapping that works:
| DefectDojo Level | Maps To | Example |
|---|---|---|
| Product Type | Team or service area | DevSecOps Pipeline, AWS Infrastructure |
| Product | Repository or application | api-service, frontend-app |
| Engagement | Pipeline type (CI/CD = long-running) | CI-Pipeline (one per product, reused) |
| Test | Individual tool execution | Semgrep SAST, Trivy SCA, Gitleaks Secrets |
| Finding | Individual vulnerability | CWE-89: SQL Injection in app.py |
The key insight: use reimport-scan (not import-scan) for CI/CD. The reimport endpoint updates the existing Test, auto-closes findings that disappeared, and avoids creating duplicate Tests on every pipeline run. The import endpoint creates a new Test every time — use it only for one-off pen test imports.
Setting Up DefectDojo (Docker Compose)
The fastest way to get running:
bash
# Clone the repo
git clone https://github.com/DefectDojo/django-DefectDojo.git
cd django-DefectDojo
# Start with PostgreSQL + RabbitMQ (recommended)
docker compose --profile postgres-rabbitmq \
--env-file ./docker/environments/postgres-rabbitmq.env \
up -d
# Get the auto-generated admin password
docker compose logs initializer 2>&1 | grep "Admin password:"DefectDojo runs on port 8080 (HTTP) and 8443 (HTTPS). Open http://localhost:8080, log in with admin and the generated password.
First-Time Setup
- Create a Product Type:
Settings → Product Types → Add Product Type→ name itDevSecOps Pipeline - Create a Product:
Products → Add Product→ name it after your repo (e.g.,devsecops-pipeline), assign to the product type - Create an Engagement: Inside the product,
Engagements → Add New CI/CD Engagement→ name itCI-Pipeline, set status toIn Progress - Generate an API token:
Settings → API v2 Key→ copy the token. You'll need this for the pipeline integration.
Or skip all of that and let the API auto-create everything (covered in the next section).
Integrating with the DevSecOps Pipeline
Here's the integration — a new job in your devsecops-pipeline.yml that pushes every tool's report to DefectDojo after the scans complete.
GitHub Actions Workflow Step
Add this after each scan stage, or as a final aggregation job:
yaml
# ─── Push Results to DefectDojo ──────────────
defectdojo-import:
name: "Push to DefectDojo"
runs-on: ubuntu-latest
needs: [ gitleaks, semgrep, trivy-sca ]
if: always() # Run even if gates blocked — we want all findings tracked
steps:
- name: Download scan artifacts
uses: actions/download-artifact@v4
with:
path: reports/
- name: Push Gitleaks results
if: hashFiles('reports/gitleaks/results.json') != ''
run: |
curl -X POST "${{ secrets.DEFECTDOJO_URL }}/api/v2/reimport-scan/" \
-H "Authorization: Token ${{ secrets.DEFECTDOJO_TOKEN }}" \
-F "scan_type=Gitleaks Scan" \
-F "file=@reports/gitleaks/results.json" \
-F "product_name=${{ github.event.repository.name }}" \
-F "engagement_name=CI-Pipeline" \
-F "auto_create_context=True" \
-F "close_old_findings=True" \
-F "environment=Development" \
-F "branch_tag=${{ github.ref_name }}" \
-F "commit_hash=${{ github.sha }}"
- name: Push Semgrep results
if: hashFiles('reports/semgrep/semgrep.json') != ''
run: |
curl -X POST "${{ secrets.DEFECTDOJO_URL }}/api/v2/reimport-scan/" \
-H "Authorization: Token ${{ secrets.DEFECTDOJO_TOKEN }}" \
-F "scan_type=Semgrep JSON Report" \
-F "file=@reports/semgrep/semgrep.json" \
-F "product_name=${{ github.event.repository.name }}" \
-F "engagement_name=CI-Pipeline" \
-F "auto_create_context=True" \
-F "close_old_findings=True" \
-F "environment=Development" \
-F "branch_tag=${{ github.ref_name }}" \
-F "commit_hash=${{ github.sha }}"
- name: Push Trivy SCA results
if: hashFiles('reports/trivy/trivy-sca.json') != ''
run: |
curl -X POST "${{ secrets.DEFECTDOJO_URL }}/api/v2/reimport-scan/" \
-H "Authorization: Token ${{ secrets.DEFECTDOJO_TOKEN }}" \
-F "scan_type=Trivy Scan" \
-F "file=@reports/trivy/trivy-sca.json" \
-F "product_name=${{ github.event.repository.name }}" \
-F "engagement_name=CI-Pipeline" \
-F "auto_create_context=True" \
-F "close_old_findings=True" \
-F "environment=Development" \
-F "branch_tag=${{ github.ref_name }}" \
-F "commit_hash=${{ github.sha }}"The Key Parameters
| Parameter | Value | Why |
|---|---|---|
scan_type | Tool-specific string (exact match required) | DefectDojo uses this to select the right parser |
reimport-scan (not import-scan) | Endpoint choice | Updates existing Test, auto-closes old findings |
auto_create_context=True | Auto-creates Product + Engagement | No manual setup needed — first run creates everything |
close_old_findings=True | Closes findings not in current scan | A fixed vulnerability disappears from the report → DefectDojo closes it |
branch_tag + commit_hash | Git context | Links findings to specific commits for traceability |
environment=Development | Environment tag | Lets you filter findings by environment in dashboards |
Scan Type Strings (Must Match Exactly)
These are case-sensitive. Get them wrong and DefectDojo silently drops findings.
| Tool | scan_type String | Report Format |
|---|---|---|
| Gitleaks | Gitleaks Scan | JSON |
| Semgrep | Semgrep JSON Report | JSON |
| Trivy (filesystem) | Trivy Scan | JSON |
| Trivy (container) | Trivy Scan | JSON |
| OWASP ZAP | ZAP Scan | XML |
| Prowler v3+ | Prowler Scan | JSON |
| CodeQL | SARIF | SARIF |
| Any SARIF output | SARIF | SARIF |
For the pipeline, you'll need to modify each scan stage to also save the report as an artifact (so the DefectDojo job can download it). Add actions/upload-artifact@v4 after each scan:
yaml
# Example: Add to the Semgrep stage
- name: Upload Semgrep report for DefectDojo
uses: actions/upload-artifact@v4
if: always()
with:
name: semgrep
path: semgrep.json
retention-days: 1Integrating Prowler (Cloud Security)
Prowler findings are a different beast — they're not from your CI/CD pipeline, they're from your AWS account audit. Create a separate product for them.
bash
# After Prowler scan completes, push results
curl -X POST "$DEFECTDOJO_URL/api/v2/reimport-scan/" \
-H "Authorization: Token $DEFECTDOJO_TOKEN" \
-F "scan_type=Prowler Scan" \
-F "file=@prowler-output.json" \
-F "product_type_name=AWS Infrastructure" \
-F "product_name=AWS Account - Production" \
-F "engagement_name=Prowler-Weekly-Audit" \
-F "auto_create_context=True" \
-F "close_old_findings=True" \
-F "environment=Production"This creates a separate product hierarchy:
Product Type: AWS Infrastructure
└── Product: AWS Account - Production
└── Engagement: Prowler-Weekly-Audit
└── Test: Prowler Scan - 2026-02-12
└── 584 findings (S3 public, IAM overpermissioned, etc.)Now when you open DefectDojo, you see both your code vulnerabilities AND your cloud misconfigurations in one place. Filter by product type to see just code issues or just infrastructure issues.
Integrating GHAS (CodeQL + Dependency Review)
If you're running CodeQL via GHAS, you have two options for getting findings into DefectDojo:
Option 1: Export SARIF and push via API. After the CodeQL analysis step, download the SARIF artifact and push it:
yaml
- name: Push CodeQL results to DefectDojo
run: |
curl -X POST "${{ secrets.DEFECTDOJO_URL }}/api/v2/reimport-scan/" \
-H "Authorization: Token ${{ secrets.DEFECTDOJO_TOKEN }}" \
-F "scan_type=SARIF" \
-F "file=@codeql-results.sarif" \
-F "product_name=${{ github.event.repository.name }}" \
-F "engagement_name=GHAS-Scans" \
-F "auto_create_context=True" \
-F "close_old_findings=True"Option 2: Use DefectDojo's GitHub connector. DefectDojo Pro (the paid version) has a native GitHub integration that pulls findings from the Security tab. If you're on the open-source edition, Option 1 is the way.
For dependency review, the dependency-review-action doesn't produce a downloadable report by default — it's a pass/fail check. To get findings into DefectDojo, use Trivy SCA as the source of truth for dependency vulnerabilities (it produces the same data in a parseable format).
Deduplication: The Real Value
Here's where DefectDojo earns its spot in the stack. You're running Semgrep (SAST) and CodeQL (also SAST). Both find a SQL injection in api/routes.py:87. Without deduplication, your dashboard shows 2 critical findings. With deduplication, it shows 1 finding with 2 sources.
How It Works
DefectDojo deduplicates in two modes:
Same-tool deduplication (enabled by default): When you reimport a Semgrep scan, DefectDojo matches new findings against previous Semgrep findings using the tool's native fingerprint. Fixed findings are auto-closed. Unchanged findings are left alone. New findings are created.
Cross-tool deduplication (optional): When enabled, DefectDojo compares findings across tools using standardized fields — file path, line number, CWE, vulnerability type. This is how a Semgrep SQL injection and a CodeQL SQL injection get matched to the same finding.
Recommended Configuration
For the ecosystem we've built, here's what works:
| Tool | Dedup Algorithm | Why |
|---|---|---|
| Gitleaks | unique_id_from_tool | Uses Gitleaks' native fingerprint (commit + secret hash) |
| Semgrep | unique_id_from_tool | Uses Semgrep's rule ID + location fingerprint |
| Trivy | hash_code | Uses CWE + package name + version hash |
| ZAP | hash_code | Uses alert ID + URL + parameter hash |
| Prowler | unique_id_from_tool | Uses Prowler's check ID + resource ARN |
| CodeQL (SARIF) | hash_code | Uses CWE + file + line number hash |
For cross-tool dedup between Semgrep and CodeQL, enable it based on cwe + file_path + line fields. This catches the 80% case (same vulnerability, same location, detected by both tools) without false dedup.
SLA Tracking: Making Security Measurable
Without SLAs, findings pile up forever. Someone opens the dashboard, sees 300 findings, and closes the tab. SLAs force prioritization.
Recommended SLA Configuration
| Severity | Days to Fix | Rationale |
|---|---|---|
| Critical | 7 days | Actively exploitable — immediate action required |
| High | 30 days | Exploitable under certain conditions — fix within a sprint |
| Medium | 90 days | Requires specific preconditions — track in backlog |
| Low | 180 days | Informational — fix when convenient |
Configure in DefectDojo: System Settings → SLA Configuration → Set days per severity.
With SLAs active, the dashboard shows:
- 12 findings within SLA (green — on track)
- 3 findings approaching SLA (yellow — action needed)
- 2 findings past SLA (red — escalation required)
If JIRA integration is enabled, DefectDojo posts daily comments to JIRA tickets for findings past SLA — "This CRITICAL finding has been open for 12 days. SLA is 7 days." Until someone acts on it, the reminder keeps coming.
JIRA Integration
The JIRA integration is bidirectional:
- DefectDojo → JIRA: A new critical finding auto-creates a JIRA ticket with severity, description, affected file, and remediation guidance
- JIRA → DefectDojo: Developer resolves the ticket in JIRA → DefectDojo marks the finding as mitigated
- SLA breach: DefectDojo posts daily comments to the JIRA ticket until the finding is closed
Configure in DefectDojo: System Settings → JIRA → Add JIRA Configuration → enter your Atlassian URL, API token, project key, and field mappings.
The result: developers never have to log into DefectDojo. They get a JIRA ticket, fix the code, close the ticket, and DefectDojo tracks the resolution automatically.
What the Dashboard Looks Like
With everything connected, here's what you see at https://defectdojo.your-company.com:
┌────────────────────────────────────────────────────────┐
│ DefectDojo Dashboard │
│ │
│ Products: 3 │
│ ├── api-service │ 12 findings │ 2 past SLA │
│ ├── frontend-app │ 8 findings │ 0 past SLA │
│ └── AWS-Production │ 47 findings │ 5 past SLA │
│ │
│ By Severity: │
│ ├── Critical: 4 (3 within SLA, 1 past) │
│ ├── High: 18 (15 within SLA, 3 past) │
│ ├── Medium: 31 (28 within SLA, 3 past) │
│ └── Low: 14 (all within SLA) │
│ │
│ By Tool: │
│ ├── Semgrep: 15 findings │
│ ├── Trivy: 22 findings │
│ ├── Prowler: 19 findings │
│ ├── Gitleaks: 2 findings │
│ ├── CodeQL: 7 findings │
│ └── ZAP: 2 findings │
│ │
│ Dedup savings: 12 findings removed (cross-tool match) │
│ │
│ Trend: ↓ 15% fewer findings vs. last month │
│ │
│ JIRA tickets: 22 open, 8 resolved this week │
└────────────────────────────────────────────────────────┘One URL. Every finding. Every tool. Every product. Filterable, sortable, exportable.
An auditor asks, "What's your vulnerability posture?" You open DefectDojo. Done.
The Complete Security Lifecycle
With DefectDojo as the central hub, the lifecycle that started with the first pipeline post is now complete:
┌─────────────────────────────────────────────────────────────┐
│ PREVENT │
│ DevSecOps Pipeline + GHAS │
│ Gitleaks → Semgrep → Trivy → ZAP → CodeQL │
│ Blocks vulns before merge │
└────────────────────┬────────────────────────────────────────┘
│ findings
▼
┌─────────────────────────────────────────────────────────────┐
│ TRACK & MANAGE │
│ DefectDojo │
│ Aggregates → Deduplicates → Assigns → Tracks SLAs │
│ Single pane of glass for all security findings │
└────┬───────────────┬──────────────────┬─────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌────────────────┐
│ DETECT │ │ RESPOND │ │ REMEDIATE │
│ Prowler │ │ Slack Alerts │ │ IAM Toolkit │
│ Cloud │ │ JIRA Tickets │ │ Right-sized │
│ Audit │ │ SLA Tracking │ │ policies │
└──────────┘ └──────────────┘ └────────────────┘Pipeline prevents → DefectDojo tracks → Prowler detects → Notifications respond → Toolkit remediates.
That's the full cycle. Every stage is automated, every finding is tracked, every tool feeds into one place.
Open-Source vs Pro: Do You Need to Pay?
| Feature | Open Source (Free) | Pro ($1,250+/month) |
|---|---|---|
| 200+ tool importers | ✅ | ✅ |
| REST API for CI/CD | ✅ | ✅ |
| Deduplication | ✅ | ✅ |
| JIRA integration | ✅ | ✅ |
| SLA tracking | ✅ | ✅ |
| Dashboards and metrics | ✅ | ✅ |
| Docker Compose deployment | ✅ | ✅ |
| Kubernetes (Helm) support | Community only | Full support |
| SSO / SAML | Manual config | Built-in |
| GitHub native connector | — | ✅ |
| SaaS (hosted) | — | ✅ |
| Support and SLAs | Community | Commercial |
For the setup we're running — small team, self-hosted, Docker Compose — the open-source edition has everything you need. Spin it up on a $20/month EC2 instance or a spare machine. The paid version makes sense when you hit enterprise scale (50+ products, SSO requirements, managed Kubernetes, SLA on the platform itself).
Rolling This Out
Day 1: Deploy DefectDojo. Clone the repo, docker compose up, create your API token. Takes 30 minutes.
Day 2: Connect the pipeline. Add the DefectDojo import job to your workflow. Start with one tool (Semgrep is a good first choice — high-quality findings, native fingerprints for dedup). Push results and verify they appear in the dashboard.
Day 3: Add remaining tools. Connect Gitleaks, Trivy, ZAP. Each one is a copy-paste of the curl command with a different scan_type and report file. Add Prowler as a separate product.
Day 4: Enable dedup and SLAs. Configure dedup algorithms per parser. Set SLA thresholds. Start getting a real count of unique vulnerabilities.
Day 5: Connect JIRA. Map severity → priority. Critical findings auto-create P1 tickets. Let the integration run for a week before trusting it in production workflows.
Ongoing: Tune and expand. Add CodeQL SARIF imports if you're running GHAS. Adjust SLA thresholds based on your team's velocity. Add custom dashboards for different stakeholders — CTO sees trends, developers see their assignments, auditors see SLA compliance.
What's Next
This post added the management layer. The ecosystem now has prevention (pipeline), detection (Prowler), notification (Slack), remediation (IAM toolkit), and management (DefectDojo).
The next posts in the series will cover:
- Secrets Detection Deep Dive: Gitleaks vs TruffleHog vs GitSecrets — which tool catches what, and when you need more than one
- Generating SBOMs with Syft — software bill of materials for supply chain compliance. DefectDojo ingests SBOMs too, by the way.
- Custom Semgrep Rules for Your Stack — advanced pattern matching for the vulnerabilities only your codebase has
The pipeline is your prevention layer. DefectDojo is your management layer. Together, they turn scattered scan results into an actual security program.
Resources:
- DefectDojo — GitHub Repository — Clone and deploy
- DefectDojo Documentation — API reference, importers, configuration
- DefectDojo Supported Tools — Full list of 200+ parsers
- DefectDojo API Import Guide — CI/CD integration patterns
- DevSecOps Pipeline — The pipeline that feeds DefectDojo
- Prowler — Cloud security auditing
- AWS Security Notification System — Real-time Slack alerts
- IAM Least Privilege Toolkit — Right-size IAM permissions
- GitHub Rulesets — Enforce merge gates
Originally published on Chaos to Control — DevSecOps blueprints for teams that ship fast and sleep well.