Home Technical Support Running ZAP Scans and Uploading Results to DefectDojo – A Step‑by‑Step Guide

Running ZAP Scans and Uploading Results to DefectDojo – A Step‑by‑Step Guide

Last updated on Jan 06, 2026

Running ZAP Scans and Uploading Results to DefectDojo – A Step‑by‑Step Guide

In this article you’ll learn how to execute an OWASP ZAP scan from the DevSecOps Box, troubleshoot common errors, understand key Docker mount points, and automatically push the scan results to DefectDojo using the provided upload-results.py script. The guide covers four frequent questions that learners encounter while working through the Practical DevSecOps labs, and it includes ready‑to‑copy Jenkins pipeline snippets for continuous‑delivery scenarios.


Table of Contents

  1. Prerequisites
  2. Scanning the Production Machine with ZAP
  3. Understanding the /zap/wrk mount point
  4. Using the --environment flag in upload-results.py
  5. Uploading ZAP Results to DefectDojo from Jenkins
  6. Tips & Common Questions

Prerequisites

Requirement Why It Matters How to Verify
GitLab repository cloned on the DevSecOps Box ZAP writes its output into the repository’s working directory. Run ls -la in the box; you should see your project folder (e.g., django-nv).
Docker (image softwaresecurityproject/zap-stable:2.13.0) Provides a consistent ZAP environment. docker version
Python 3 with requests library installed (used by upload-results.py) Needed for the API call to DefectDojo. python3 -c "import requests"
DefectDojo credentials (DOJO_HOST & DOJO_API_TOKEN) stored in Jenkins or the box Allows authenticated uploads. echo $DOJO_HOST / echo $DOJO_API_TOKEN (mask in CI).

Scanning the Production Machine with ZAP

The lab asks you to run a ZAP baseline scan against the production URL https://prod-fmrtdibo.lab.practical-devsecops.training and store the XML report at /django-nv/zap-output.xml.

Typical command (run from the DevSecOps Box)

cd /django-nv                     # <-- ensure you are inside the cloned repo
docker run --rm -v $(pwd):/zap/wrk \
  -t softwaresecurityproject/zap-stable:2.13.0 \
  zap-baseline.py -t https://prod-fmrtdibo.lab.practical-devsecops.training \
  -r zap-output.xml
  • -v $(pwd):/zap/wrk mounts your current directory into the container at /zap/wrk.
  • zap-baseline.py performs a quick, unauthenticated scan and writes zap-output.xml inside the mounted folder.

Fixing the “expected File … zap‑output.xml to exist” error

If you see:

expected File /django-nv/zap-output.xml to exist

the most common causes are:

  1. Repository not cloned – The /django-nv path does not exist on the box.
    Solution: Clone the GitLab repo first:

    git clone https://gitlab.com/your‑group/django-nv.git /django-nv
    
  2. Wrong working directory – You ran the Docker command from a different folder, so the mount point is empty.
    Solution: cd /django-nv before executing the Docker run command.

  3. Permission issues – The container cannot write to the host directory.
    Solution: Ensure the directory is owned by your user (chown -R $(whoami) /django-nv) or run Docker with appropriate user flags.

After correcting the above, re‑run the scan. You should now see zap-output.xml inside /django-nv and be able to mark the lab task as complete.


Understanding the /zap/wrk Command

/zap/wrk is not a command; it is the target directory inside the Docker container where the host’s current working directory ($(pwd)) is mounted.

Host directory (e.g., /django-nv)  →  Docker container path /zap/wrk

Why it matters:

  • File persistence – Anything written to /zap/wrk inside the container ends up on the host, allowing you to keep the ZAP report after the container exits.
  • Consistency across tools – Many Docker‑based security tools (e.g., Trivy, Grype) follow the same pattern: you must supply a -v $(pwd):/some/path and often a -w /some/path flag so the tool knows where to read/write files.

If you omit the mount, the container writes the report to an ephemeral filesystem that disappears when the container stops, leading to the “file does not exist” error.


Using the --environment Flag in upload-results.py

The script upload-results.py pushes scan artifacts to DefectDojo via its REST API. The --environment argument tells DefectDojo which environment the scan represents (e.g., Development, Staging, Production).

python3 upload-results.py \
  --host $DOJO_HOST \
  --api-key $DOJO_API_TOKEN \
  --engagement_id 1 \
  --product_id 1 \
  --lead_id 1 \
  --environment "Production" \
  --result_file zap-output.xml \
  --scanner "ZAP Scan"

Benefits:

  • Filtering – In DefectDojo you can view findings per environment, making it easy to compare a dev build against production.
  • Reporting – Automated dashboards can highlight regressions that only appear in production.
  • Audit trail – Knowing the origin environment satisfies compliance requirements.

Uploading ZAP Results to DefectDojo from Jenkins

The lab’s Jenkins exercise asks you to create a new stage called defectdojo that uploads the ZAP XML generated in the previous zap-baseline stage. The key steps are:

  1. Copy the artifact from the earlier stage (using the Copy Artifact plugin).
  2. Install Python dependencies inside the Jenkins agent.
  3. Set the locale to UTF‑8 (prevents character‑encoding errors when the XML contains non‑ASCII symbols).
  4. Run upload-results.py with the proper credentials.

Full Jenkins stage example (Declarative Pipeline)

pipeline {
    agent any

    stages {
        stage('zap-baseline') {
            steps {
                // ... ZAP scan that archives zap-output.xml ...
                archiveArtifacts artifacts: 'zap-output.xml', fingerprint: true
            }
        }

        stage('defectdojo') {
            steps {
                // 1️⃣ Pull the ZAP report from the previous stage
                copyArtifacts filter: 'zap-output.xml',
                              fingerprintArtifacts: true,
                              projectName: 'django.nv',
                              selector: specific(env.BUILD_NUMBER)

                // 2️⃣ Ensure UTF‑8 locale (prevents XML parsing errors)
                sh 'export LC_ALL=C.UTF-8'

                // 3️⃣ Install Python request library (runs only if not cached)
                sh 'pip3 install --user requests'

                // 4️⃣ Upload to DefectDojo
                withCredentials([
                    string(credentialsId: 'dojo-host',      variable: 'DOJO_HOST'),
                    string(credentialsId: 'dojo-api-token', variable: 'DOJO_API_TOKEN')
                ]) {
                    sh '''
                        python3 upload-results.py \
                          --host $DOJO_HOST \
                          --api-key $DOJO_API_TOKEN \
                          --engagement_id 1 \
                          --product_id 1 \
                          --lead_id 1 \
                          --environment "Production" \
                          --result_file zap-output.xml \
                          --scanner "ZAP Scan"
                    '''
                }
            }
        }
    }

    post {
        always {
            cleanWs()
        }
    }
}

Key points in the script

  • LC_ALL=C.UTF-8 – forces the shell to use UTF‑8 encoding, avoiding “UnicodeDecodeError”.
  • withCredentials – securely injects the DefectDojo host and API token.
  • copyArtifacts – transfers zap-output.xml from the zap-baseline stage to the current workspace.

Tips & Common Questions

Question Quick Answer
Why does the upload fail with “UnicodeDecodeError”? Add export LC_ALL=C.UTF-8 (or LANG=C.UTF-8) before running the Python script.
Can I use a JSON report instead of XML? Yes – ZAP can output JSON (-J zap-output.json). Just change --result_file and set --scanner "ZAP JSON".
Do I need to run pip install requests on every build? Not if you cache the virtual environment or use a Docker agent that already includes the library.
What if the Jenkins stage still can’t find zap-output.xml? Verify the artifact name in the archiveArtifacts step of the previous stage and ensure the filter pattern matches exactly.
Is the --environment value case‑sensitive? DefectDojo stores it as a free‑text field, but it’s best to keep the same capitalization across builds for consistent filtering.

Final Checklist

  • [ ] Clone the GitLab repo to /django-nv on the DevSecOps Box.
  • [ ] Run the ZAP Docker command from /django-nv.
  • [ ] Confirm zap-output.xml exists after the scan.
  • [ ] Set the locale to UTF‑8 before invoking upload-results.py.
  • [ ] Use Jenkins Copy Artifact and the provided pipeline snippet to push results to DefectDojo.

By following this guide, you’ll be able to execute ZAP scans reliably, troubleshoot the most common pitfalls, and integrate the findings into DefectDojo for centralized vulnerability management. Happy hacking—and keep your pipelines secure!