Tools Used in DevSecOps · DefectDojo

DefectDojo

DefectDojo is an open-source, self-hosted vulnerability-management and aggregation platform — an OWASP Flagship Project — built to solve exactly one problem: a pipeline running more than one scanner produces more findings than any human can triage as a flat list, and a meaningful share of them are the same underlying bug reported two or three different ways. DefectDojo ingests scan output from well over a hundred supported tools, in whatever native format each one emits, and normalizes all of it into one hierarchy of Products, Engagements, Tests, and Findings — deduplicating across tools by rule ID or a computed hash, tracking each finding's status through a defined lifecycle, and attaching a severity-driven SLA clock that turns "we have 4,000 open findings" into a scheduled, auditable backlog. It's the tool this course's own CDP blueprint closes on, and the reason is structural: every scanner covered earlier in this course produces a report, and DefectDojo is what happens to that report next. By the end of this page you should know its architecture, its data model, exactly how its deduplication logic works, the commands and API calls you'll actually run against it, and where a narrower tool like Dependency-Track or a commercial ASPM platform earns its keep instead.

☺ Explain it like I'm 10

Imagine a school where four different teachers each grade the same stack of essays for a different thing — spelling, grammar, plagiarism, and factual accuracy — and each one hands back a separate pile of sticky notes stuck to the same essays. If you just dump all four piles on one desk, you get sticky notes that say the same thing twice, notes with no due date, and no way to tell which essay needs the most urgent rewrite. DefectDojo is the one office assistant who takes all four piles, notices when two notes are actually about the same sentence, throws away the duplicate, stamps a deadline on what's left based on how serious it is, and hands you back one single list you can actually work through.

🐿️Your host for this topic: Nutty the Squirrel — Nutty buries every scan report and compliance note this course produces, then digs up exactly the right one the moment someone asks "didn't we already know about this?" DefectDojo is Nutty's own filing system, built by other people and run at pipeline scale.

What DefectDojo is, and the problem it names directly

☺ Like you're 10: It started as one company's internal spreadsheet-killer and grew into the open-source tool most pipelines reach for once "just read every scan report by hand" stops being possible.

DefectDojo began inside Rackspace's security engineering team — credited to Greg Anderson and Jay Paz — as an internal tool for tracking findings across the company's own application-security testing, before being open-sourced and, over time, donated into the OWASP ecosystem, where it's maintained today as a community project distributed under the BSD 3-Clause license and carries OWASP's Flagship designation, the tier reserved for projects that have demonstrated both strategic value and active, ongoing maintenance. That lineage matters for a practical reason: DefectDojo wasn't designed around one scanner's output format and generalized later. It was designed from day one to be format-agnostic, because Rackspace's own security team was already running more than one tool and drowning in more than one report shape.

The problem it names directly is the one this course's closing CDP blueprint chapter spends an entire chapter on: Semgrep and gitleaks at commit, OWASP Dependency-Check or Trivy at build, Checkov against the same Terraform a container scan already covers, OWASP ZAP against staging — run daily across even a modest number of services and the raw finding count stops being something a person reads end to end. DefectDojo's answer isn't a smarter scanner. It's a normalization and bookkeeping layer that sits downstream of every scanner this course teaches, which is exactly why it's the one tool page in this course with no scan target of its own — its input is other tools' output.

◆ Key idea

DefectDojo detects nothing. It has no rule engine, no vulnerability feed of its own, no crawler. Its entire value is in what happens after a scanner already produced a finding: getting it into one place, recognizing when two tools reported the same bug, and keeping a durable, auditable status on it across every rescan that follows. That's a different kind of tool than everything else in this course's tool landscape, and it's worth holding that distinction clearly before the rest of this page.

Architecture: Django, PostgreSQL, and where the import pipeline actually runs

☺ Like you're 10: It's a website with a filing cabinet behind it, plus a second, slower worker in the back room who does the actual sorting so the website up front never has to freeze while a big import happens.

DefectDojo is a Django application — Python, a relational schema, a standard web-app shape — backed by PostgreSQL as its system of record. That alone would be enough for a small deployment, but a scan import can be a multi-thousand-line JSON or XML file, and parsing, deduplicating, and writing that many rows synchronously inside an HTTP request is exactly the kind of thing that times out a request or blocks the UI for every other user. DefectDojo solves that with an async task queue: Celery workers, coordinated through Redis as the broker, pick up import and reimport jobs off a queue and process them in the background, so the API call that submits a scan returns quickly while the actual parsing, matching, and deduplication happen on a worker process that can be scaled independently of the web tier. Celery beat handles scheduled work — periodic reimports, SLA-breach notification sweeps, scheduled reports — on its own clock, separate from anything a user triggered.

Scan file REST API or web UI upload Django app nginx + uwsgi queues the job — doesn't parse inline 201 returned immediately Redis Celery broker/queue Celery worker(s) parse · dedup · scale independently of the web tier Celery beat scheduled reimports, SLA-breach sweeps PostgreSQL Product · Engagement Test · Finding rows the system of record Outbound integrations Jira · Slack/Teams · email The import API returns fast; the queue is where parsing and dedup actually happen.

Two deployment paths cover most real installs. The official Docker Compose quick-start (docker-compose up against the project's own compose file, seeded with a generated admin password on first boot) is the fastest way to a working instance and what most teams evaluate the tool with first. For production, the project publishes a Helm chart for a Kubernetes deployment — separate Deployments for the Django app, the Celery workers, and Celery beat, backed by an external or in-cluster PostgreSQL and Redis, which is what lets the worker tier scale independently under real import volume rather than fighting the web tier for the same pod's resources. A bare-metal or manually-provisioned install is documented too, but nearly every team either runs the Docker Compose stack for evaluation or the Helm chart for anything that has to survive real traffic.

The data model: Product Type, Product, Engagement, Test, Finding

☺ Like you're 10: Before anything can be filed neatly, you need labeled drawers — a broad one, a specific one, a time window, one scanner's output, and finally the single issue itself.

Every finding DefectDojo holds lives inside a strict hierarchy, and knowing where a finding sits in it is most of what "using DefectDojo correctly" actually means. This course's blueprint chapter covers the full reasoning behind each level and how the triage workflow built on top of it operates day to day; this page's version is the tool-level summary worth having cold.

LevelRepresentsWorked example
Product TypeA broad grouping — a business unit, a portfolio, a compliance boundary"Payments"
ProductOne deployable thing — usually a 1:1 mapping to a repository or a service"checkout-service"
EngagementA bounded testing window against that product — a CI run, a sprint, a pen-test"checkout-service · main · build #4821"
TestOne scanner's output within that engagement"Trivy Scan," "ZAP Scan"
FindingOne specific issue inside a Test"CVE-2024-XXXXX in follow-redirects@1.14.9"

A sixth object sits alongside that chain rather than inside it: an Endpoint — a URL, a host, or a host-plus-port pair — is what a DAST or API-security finding attaches to in addition to its Test, which is what lets DefectDojo answer "which of our forty staging endpoints still has this reflected-XSS finding open" rather than just "this Engagement has an open XSS finding somewhere." A finding from OWASP ZAP or Burp Suite's Dastardly scanner typically carries one or more Endpoints; a finding from a source-code scanner like Semgrep typically doesn't, since a file path inside the Finding record already answers the equivalent question.

Getting a Product Type and Product created is a one-time setup step, done once per repository or service rather than per scan — everything after that is Engagements and Tests arriving continuously as the pipeline runs.

Ingesting scanner output: the import/reimport API and the parser library

☺ Like you're 10: One doorway takes in a brand-new report; a second doorway updates a report you've already filed, so the same folder gets thicker over time instead of a new folder appearing every single day.

DefectDojo ships parsers for well over a hundred scanners — covering essentially every tool named across this course's SAST, DAST, SCA, secrets, container, IaC, and compliance pages — reachable through a REST API v2 exposed at /api/v2/, documented interactively at /api/v2/doc/ via its own Swagger/OpenAPI schema. Two endpoints do nearly all the work, and which one a pipeline calls matters more than almost anything else on this page:

# first run of a given Test in a given Engagement — creates it fresh
curl -s -X POST "https://defectdojo.internal.example.com/api/v2/import-scan/" \
  -H "Authorization: Token $DD_API_TOKEN" \
  -F "product_name=checkout-service" \
  -F "engagement_name=checkout-service · main · build #4821" \
  -F "scan_type=Trivy Scan" \
  -F "file=@trivy-report.json" \
  -F "minimum_severity=Low" \
  -F "active=true"

# every run after that — updates the SAME Test instead of creating a new one
curl -s -X POST "https://defectdojo.internal.example.com/api/v2/reimport-scan/" \
  -H "Authorization: Token $DD_API_TOKEN" \
  -F "product_name=checkout-service" \
  -F "engagement_name=checkout-service · main · build #4821" \
  -F "test_id=$DD_TRIVY_TEST_ID" \
  -F "scan_type=Trivy Scan" \
  -F "file=@trivy-report.json"

import-scan creates a new Test and its findings from a blank slate; reimport-scan updates an existing Test, which is the mechanism behind auto-closing a finding that a later scan no longer reproduces, and behind a triager's False Positive marking surviving into next week's scan instead of resetting every single run. Pointing a nightly pipeline at import-scan on every run is one of the single most common DefectDojo misconfigurations — it silently produces a fresh, disconnected Test every night instead of one Test with real history, which quietly breaks reimport-driven auto-close and SLA aging without ever throwing an error.

For a tool with no dedicated parser — an internal script, a niche scanner nobody's written support for yet — DefectDojo ships a Generic Findings Import format: a defined JSON (or CSV) schema with fields like title, severity, description, cwe, and file_path, which any script can emit and hand to the same import endpoint using scan_type=Generic Findings Import. And any SARIF-emitting tool — CodeQL and most modern SAST tools among them — has a direct escape hatch, since scan_type=SARIF is a generic parser DefectDojo maintains against the SARIF spec itself rather than against any one vendor's output quirks. Parser names shift and multiply between DefectDojo releases as new tools are added and old ones renamed, so treat any specific scan_type string as illustrative and confirm the current name against your installed version's Import Scan form or the /api/v2/doc/ schema before wiring a pipeline to it.

Tool covered elsewhere in this courseTypical scan_type
Semgrep"Semgrep JSON Report"
SonarQube"SonarQube Scan"
gitleaks / TruffleHog"Gitleaks Scan" / "Trufflehog Scan"
OWASP Dependency-Check"Dependency Check Scan"
OWASP ZAP"ZAP Scan"
Trivy / Grype"Trivy Scan" / "Anchore Grype"
Checkov / tfsec"Checkov Scan" / "Tfsec Scan"
OpenSCAPan OpenSCAP-family parser, or generic ARF/XCCDF import
Anything SARIF-emitting"SARIF" — the generic escape hatch
⚠ The import call returning 201 is not proof the findings are visible yet

Because parsing and deduplication run asynchronously on a Celery worker, a successful 201 Created response from import-scan means the job was queued, not that the findings have finished landing in PostgreSQL — under real import volume, or with a worker pool sized too small for the queue depth, there can be a real, non-trivial gap between "the API accepted the file" and "the findings are queryable." A pipeline that immediately queries the finding count right after the import call, expecting it to already reflect the new scan, is checking before the work is actually done.

Deduplication logic across tools

☺ Like you're 10: Two different inspectors can write up the exact same crack in the wall using different words — dedup is teaching the filing system to recognize it's the same crack either way.

DefectDojo's deduplication engine decides whether a newly-imported finding is genuinely new or a repeat of something already on file, and the algorithm used is configurable per scan type — a setting a team can override in System Settings, though the shipped defaults per parser are what most installs run unmodified:

Scope matters as much as algorithm: by default, dedup compares findings within one Engagement — a Trivy Test and an OWASP Dependency-Check Test scanning the same image, in the same build, get compared against each other. A "deduplicate across engagements" option, set at the Product level, extends that comparison across time — matching today's build against a finding still open from last week's — which is what actually makes SLA aging and mean-time-to-remediate mean anything; without it, every new build would silently spawn a fresh copy of every still-unfixed finding instead of recognizing it as the same one, still open. When two findings are judged identical, the older one stays Active and the newer one is automatically filed as Duplicate under it — one ticket, not two, for one bug.

⚠ A dedup key tied to a line number breaks on the next unrelated commit

If a scan type's hash_code configuration includes something as volatile as an exact line number, an unrelated formatting change or an added import two lines above a finding shifts every line number below it — and every one of those findings reimports as "new," silently discarding every prior False Positive and Risk Accepted marking that had already been decided on it. Favor dedup keys built from stable identifiers — a CWE, a rule ID, a package coordinate, a normalized path — over anything that shifts when unrelated code moves.

Day-to-day operations

☺ Like you're 10: Spin it up, make an account, hand it a report, and check what it decided — a handful of commands cover almost all of it.

# evaluate locally — the official quick-start
$ git clone https://github.com/DefectDojo/django-DefectDojo && cd django-DefectDojo
$ docker-compose up -d
$ docker-compose logs initializer | grep "Admin password:"   # generated on first boot

# Django management commands, run inside the uwsgi container
$ docker-compose exec uwsgi ./manage.py createsuperuser
$ docker-compose exec uwsgi ./manage.py migrate               # after an upgrade
$ docker-compose exec uwsgi ./manage.py dbshell                # a raw psql prompt, for real trouble

# generate a personal API token from the UI (or the API itself) before scripting anything
# then confirm the API is reachable and see exactly what scan_type strings your version ships
$ curl -s -H "Authorization: Token $DD_API_TOKEN" \
    https://defectdojo.internal.example.com/api/v2/test_types/ | jq '.results[].name' | head

# create a Product via the API, so a pipeline's first-ever import has somewhere to land
$ curl -s -X POST "https://defectdojo.internal.example.com/api/v2/products/" \
    -H "Authorization: Token $DD_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"name":"checkout-service","description":"Checkout API","prod_type":1}'

# a Risk Acceptance always needs an expiry — an undated one is just a finding nobody revisits
$ curl -s -X POST "https://defectdojo.internal.example.com/api/v2/risk_acceptance/" \
    -H "Authorization: Token $DD_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"name":"Accepted: bastion-only exposure","expiration_date":"2026-11-01","accepted_findings":[4821]}'

Most day-to-day interaction after initial setup is the import/reimport calls shown earlier, wired into whatever CI system already runs the scanners — a step at the end of the SAST, SCA, DAST, or IaC stage that curls or POSTs the just-produced report file straight to DefectDojo before the pipeline continues. Jira, Slack, Microsoft Teams, and email integrations are configured once in System Settings and then fire automatically off severity or SLA thresholds — worth setting up early, since a finding nobody's notified about is functionally the same as a finding nobody triaged.

Gotchas and failure modes

☺ Like you're 10: Most surprises trace back to one of three things — the worker queue got backed up, the wrong endpoint got called, or a setting that looked fine for one product turned out to be wrong for a different one.

⚠ SLA day counts are calendar days by default

A "7-day" Critical SLA configured without adjusting for business-day calculation counts weekends and holidays the same as any other day — a Critical finding filed at 5pm on a Friday can breach its SLA by Monday morning purely on the calendar, before anyone with the ability to fix it was even back online. Decide deliberately whether a Product's SLA configuration should count calendar days or business days, rather than inheriting whichever default shipped with the instance.

DefectDojo vs. Dependency-Track, GitLab, and a commercial ASPM

☺ Like you're 10: Every alternative trades DefectDojo's "ingest anything, from any tool" breadth for something narrower and, in exchange, usually smoother in that one specific lane.

ToolScopeStrengthTrade-off
DefectDojoGeneral aggregation across SAST, DAST, SCA, secrets, IaC, and compliance — over a hundred parsersOne system of record for a genuinely multi-tool pipeline; free, self-hosted, no vendor lock-in on the data itselfYou run and scale it yourself — Django, Postgres, Celery, Redis, upgrades, and all
OWASP Dependency-TrackSBOM-centric — ingests CycloneDX SBOMs specifically and continuously monitors them against vulnerability feedsPurpose-built for the "what's in this build, and does that change tomorrow" question; lighter-weight than a general aggregator for SCA aloneNot a general SAST/DAST/IaC aggregation platform — it's one job done well, not DefectDojo's whole hierarchy
GitLab Vulnerability Management (Ultimate tier)Built into GitLab's own CI, for scanners GitLab runs itselfZero extra infrastructure if you're already all-in on GitLab CI; findings appear directly in the merge-request UITied to GitLab's own scanner integrations — a tool outside that ecosystem, or a pipeline split across CI systems, doesn't feed in cleanly
ThreadFix / FaradaySimilar aggregation space — ThreadFix from Denim Group, Faraday oriented toward pentest workflow aggregationEstablished alternatives with their own parser libraries and workflow assumptionsSmaller open-source communities and slower parser growth than DefectDojo's, in practice
Commercial ASPM platforms (e.g. application security posture management vendors)Aggregation plus automated risk scoring, code-to-cloud correlation, and reachability analysisCorrelation and prioritization logic DefectDojo leaves to a team's own SLA configuration and manual triageLicensing cost, and your finding data now lives in a vendor's cloud rather than infrastructure you fully control

The practical pattern most teams land on: DefectDojo (or Dependency-Track, for SCA-only shops) as the free, self-hosted backbone that every scanner in the pipeline reports into, with a commercial ASPM layered on top only once a team has decided the extra correlation and reachability analysis is worth its licensing cost — the same "breadth from a free tool, depth from a paid one where it earns its keep" trade this course keeps returning to across its tool pages. See Vulnerability Management & Triage for the full triage workflow built on top of whichever platform a team chooses, and The Tool Landscape for how DefectDojo sits among every other tool this course covers.

🎬 At the Shift-Left Squad
🐿️

Nutty: Queued the Trivy import twenty minutes ago and the finding count still hasn't moved. Something's stuck.

🤖

Recon the Robot: Check the worker pool before you assume it's broken. A 201 from the API only means the job was accepted — it doesn't mean a Celery worker has actually picked it up yet.

🐿️

Nutty: ...one worker replica, six products importing on the same schedule. That would do it.

🦫

Benny the Beaver: While you're fixing that — can I just point my nightly pipeline at import-scan every run? Simpler than tracking a test ID.

🐢

Timmy: Simpler today, and it quietly breaks every rescan after it. A fresh Test every night means no auto-close when you fix something, and no memory of what was already marked False Positive. Use reimport-scan and keep the same test ID.

🐦

Pip the Hummingbird: And while everyone's in there — does the SBOM importer even exist for us, or are we hand-writing Generic Findings Import JSON for the tools nobody's built a parser for yet?

🐿️

Nutty: Checked the parser list this morning — CycloneDX and SPDX both have real support now. Generic import is the fallback, not the default. I keep the parser catalogue current for exactly this reason.

✓ Checkpoint

1. What does DefectDojo actually detect on its own, and what does that imply about where it sits relative to every other tool in this course? 2. Name the five levels of DefectDojo's data hierarchy, and what an Endpoint is for. 3. What's the practical difference between the import-scan and reimport-scan API endpoints, and what silently breaks if a pipeline always calls the former? 4. Name the three deduplication algorithms and when each is the better default. 5. Give one concrete reason a team might run Dependency-Track alongside DefectDojo rather than relying on DefectDojo alone.

Check your answers
  1. Nothing — it has no scanner, rule engine, or vulnerability feed of its own. It's a normalization, deduplication, and triage layer that sits downstream of every scanner in the pipeline, which is why it's the one tool page in this course with no scan target of its own.
  2. Product Type, Product, Engagement, Test, and Finding, from broadest to most specific. An Endpoint is a URL, host, or host-plus-port that a DAST or API-security finding attaches to alongside its Test, so a finding can be tracked against the specific reachable location it applies to, not just the Engagement it was found in.
  3. import-scan creates a brand-new Test from scratch every time it's called. reimport-scan updates an existing Test, preserving its history — which is what lets DefectDojo auto-close a finding once a rescan stops reproducing it and lets a triage marking like False Positive survive into the next scan. Always calling import-scan creates a disconnected Test every run, silently breaking auto-close, SLA aging, and finding history.
  4. hash_code (a computed hash from fields like title, CWE, and location — the default when a tool supplies no natural external ID, typical for SAST/DAST), unique_id_from_tool (keyed off an identifier the scanner itself supplies — more precise when available, typical for SCA/container scans), and unique_id_from_tool_or_hash_code (tries the tool's ID first, falls back to the hash).
  5. Any reasonable answer citing Dependency-Track's SBOM-specific focus: for example, continuous monitoring of a CycloneDX SBOM against newly-published vulnerabilities as they're disclosed, which is a narrower and often faster-moving workflow than treating SCA as just one more scan type feeding DefectDojo's general-purpose import pipeline.