You Have 5 Scanners and Zero Visibility: DefectDojo as Your Security Single Pane of Glass

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 LevelMaps ToExample
Product TypeTeam or service areaDevSecOps Pipeline, AWS Infrastructure
ProductRepository or applicationapi-service, frontend-app
EngagementPipeline type (CI/CD = long-running)CI-Pipeline (one per product, reused)
TestIndividual tool executionSemgrep SAST, Trivy SCA, Gitleaks Secrets
FindingIndividual vulnerabilityCWE-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

  1. Create a Product Type: Settings → Product Types → Add Product Type → name it DevSecOps Pipeline
  2. Create a Product: Products → Add Product → name it after your repo (e.g., devsecops-pipeline), assign to the product type
  3. Create an Engagement: Inside the product, Engagements → Add New CI/CD Engagement → name it CI-Pipeline, set status to In Progress
  4. 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

ParameterValueWhy
scan_typeTool-specific string (exact match required)DefectDojo uses this to select the right parser
reimport-scan (not import-scan)Endpoint choiceUpdates existing Test, auto-closes old findings
auto_create_context=TrueAuto-creates Product + EngagementNo manual setup needed — first run creates everything
close_old_findings=TrueCloses findings not in current scanA fixed vulnerability disappears from the report → DefectDojo closes it
branch_tag + commit_hashGit contextLinks findings to specific commits for traceability
environment=DevelopmentEnvironment tagLets 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.

Toolscan_type StringReport Format
GitleaksGitleaks ScanJSON
SemgrepSemgrep JSON ReportJSON
Trivy (filesystem)Trivy ScanJSON
Trivy (container)Trivy ScanJSON
OWASP ZAPZAP ScanXML
Prowler v3+Prowler ScanJSON
CodeQLSARIFSARIF
Any SARIF outputSARIFSARIF

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: 1

Integrating 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.

For the ecosystem we've built, here's what works:

ToolDedup AlgorithmWhy
Gitleaksunique_id_from_toolUses Gitleaks' native fingerprint (commit + secret hash)
Semgrepunique_id_from_toolUses Semgrep's rule ID + location fingerprint
Trivyhash_codeUses CWE + package name + version hash
ZAPhash_codeUses alert ID + URL + parameter hash
Prowlerunique_id_from_toolUses Prowler's check ID + resource ARN
CodeQL (SARIF)hash_codeUses 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.

SeverityDays to FixRationale
Critical7 daysActively exploitable — immediate action required
High30 daysExploitable under certain conditions — fix within a sprint
Medium90 daysRequires specific preconditions — track in backlog
Low180 daysInformational — 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:

  1. DefectDojo → JIRA: A new critical finding auto-creates a JIRA ticket with severity, description, affected file, and remediation guidance
  2. JIRA → DefectDojo: Developer resolves the ticket in JIRA → DefectDojo marks the finding as mitigated
  3. 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?

FeatureOpen 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) supportCommunity onlyFull support
SSO / SAMLManual configBuilt-in
GitHub native connector
SaaS (hosted)
Support and SLAsCommunityCommercial

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:


Originally published on Chaos to Control — DevSecOps blueprints for teams that ship fast and sleep well.

Read more