TL;DR — OWASP ZAP is the world’s most downloaded free security scanner with over 11,000 GitHub stars and millions of users. This guide covers CI/CD integration, API scanning, custom policies, authentication configuration, and automated reporting for QA teams.

OWASP ZAP is the world’s most popular free security testing tool, trusted by security teams and QA engineers across thousands of organizations. According to OWASP’s project statistics, ZAP has been downloaded over 10 million times and consistently ranks as the most widely used open-source web security scanner. The global application security testing market is projected to reach $10.4 billion by 2027, according to research by MarketsandMarkets, making security testing skills increasingly valuable for QA professionals. Integrating ZAP into CI/CD pipelines lets teams catch vulnerabilities like SQL injection, XSS, and insecure headers before they reach production—when fixes cost 100x less than post-release patches. This guide covers every aspect of ZAP automation from baseline scans to full authenticated API testing.

“ZAP in the CI/CD pipeline is a non-negotiable for me on any project. The baseline scan alone catches missing security headers and cookie flags that developers consistently overlook. It takes 15 minutes to set up and prevents entire classes of vulnerabilities.” — Yuri Kan, Senior QA Lead

What is OWASP ZAP?

OWASP (as discussed in Penetration Testing Basics for QA Testers) Zed Attack Proxy (ZAP) is the world’s most popular free security testing tool, actively maintained by the OWASP community. For QA engineers, ZAP provides automated security scanning to detect vulnerabilities like SQL (as discussed in Security Testing for QA: A Practical Guide) injection, XSS, and insecure configurations before they reach production.

Why ZAP for QA?

  • Free and Open Source - No licensing costs, full feature access
  • Easy to Automate - CLI, Docker, APIs for CI/CD integration
  • Comprehensive Scanning - Active/passive scanning, spidering, fuzzing
  • API Testing - OpenAPI/Swagger support, GraphQL testing
  • Extensive Reporting - HTML, JSON, XML reports with remediation guidance
  • Active Community - Regular updates, plugins, marketplace

ZAP Scanning Modes

1. Passive Scanning

Analyzes HTTP traffic without sending additional requests. Safe to run in production.

# Start ZAP in daemon mode
docker run -u zap -p 8080:8080 owasp/zap2docker-stable zap.sh -daemon \
  -host 0.0.0.0 -port 8080 -config api.disablekey=true

# Access target through ZAP proxy
curl -x http://localhost:8080 https://example.com

# Generate passive scan report
curl "http://localhost:8080/JSON/core/action/jsonreport/"

Passive Scan Detects:

  • Missing security headers (CSP, X-Frame-Options, HSTS)
  • Insecure cookies (missing HttpOnly, Secure flags)
  • Information disclosure (stack traces, comments in code)
  • Outdated libraries and frameworks

2. Active Scanning

Sends attack payloads to find vulnerabilities. Only use on authorized targets.

# Spider target to discover URLs
docker run owasp/zap2docker-stable zap-baseline.py \
  -t https://example.com \
  -r report.html

# Full active scan
docker run owasp/zap2docker-stable (as discussed in [SQL Injection and XSS: Finding Vulnerabilities](/blog/sql-injection-xss)) zap-full-scan.py \
  -t https://example.com \
  -r full-report.html \
  -z "-config api.disablekey=true"

Active Scan Detects:

  • SQL Injection
  • Cross-Site Scripting (XSS)
  • Path Traversal
  • Remote Code Execution
  • XML External Entity (XXE)
  • Server-Side Request Forgery (SSRF)

3. API Scanning

Test REST APIs using OpenAPI/Swagger specifications.

# Import OpenAPI spec and scan
docker run -v $(pwd):/zap/wrk:rw owasp/zap2docker-stable \
  zap-api-scan.py \
  -t https://api.example.com \
  -f openapi \
  -d /zap/wrk/openapi.json \
  -r /zap/wrk/api-report.html
# Python API automation
from zapv2 import ZAPv2
import time

# Connect to ZAP
zap = ZAPv2(proxies={'http': 'http://localhost:8080', 'https': 'http://localhost:8080'})

# Import OpenAPI definition
zap.openapi.import_url('https://api.example.com/openapi.json')

# Spider the API
zap.spider.scan('https://api.example.com')
while int(zap.spider.status()) < 100:
    print(f'Spider progress: {zap.spider.status()}%')
    time.sleep(2)

# Active scan
zap.ascan.scan('https://api.example.com')
while int(zap.ascan.status()) < 100:
    print(f'Active scan progress: {zap.ascan.status()}%')
    time.sleep(5)

# Get alerts
alerts = zap.core.alerts(baseurl='https://api.example.com')
print(f'Found {len(alerts)} vulnerabilities')

# Generate report
zap.core.htmlreport()

CI/CD Integration

GitHub Actions

# .github/workflows/security-scan.yml
name: OWASP ZAP Security Scan

on:
  pull_request:
    branches: [main]
  schedule:

    - cron: '0 2 * * *'  # Daily at 2 AM

jobs:
  zap_scan:
    runs-on: ubuntu-latest
    steps:

      - uses: actions/checkout@v3

      - name: Build application
        run: docker-compose up -d

      - name: Wait for app to start
        run: sleep 30

      - name: ZAP Baseline Scan
        uses: zaproxy/action-baseline@v0.7.0
        with:
          target: 'http://localhost:3000'
          rules_file_name: '.zap/rules.tsv'
          cmd_options: '-a'

      - name: Upload ZAP Report
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: zap-report
          path: report_html.html

Jenkins Pipeline

// Jenkinsfile
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'docker-compose up -d'
                sh 'sleep 30'
            }
        }

        stage('OWASP ZAP Scan') {
            steps {
                script {
                    docker.image('owasp/zap2docker-stable').inside('--network=host') {
                        sh '''
                            zap-baseline.py \
                                -t http://localhost:3000 \
                                -r zap-report.html \
                                -J zap-report.json \
                                -w zap-report.md
                        '''
                    }
                }
            }
        }

        stage('Process Results') {
            steps {
                publishHTML([
                    reportDir: '.',
                    reportFiles: 'zap-report.html',
                    reportName: 'ZAP Security Report'
                ])

                script {
                    def report = readJSON file: 'zap-report.json'
                    def highAlerts = report.site.alerts.findAll { it.riskcode == '3' }

                    if (highAlerts.size() > 0) {
                        error("Found ${highAlerts.size()} high-severity vulnerabilities")
                    }
                }
            }
        }
    }

    post {
        always {
            sh 'docker-compose down'
        }
    }
}

Custom Scan Policies

rules.tsv Configuration

# Ignore false positives
10021	IGNORE	(CSP: Wildcard Directive)
10038	IGNORE	(Content Security Policy Header Not Set)

# Fail on specific vulnerabilities
40012	FAIL	(Cross Site Scripting - Reflected)
40014	FAIL	(Cross Site Scripting - Persistent)
90018	FAIL	(SQL Injection)
40018	FAIL	(SQL Injection - MySQL)

# Warn on medium severity
10054	WARN	(Cookie Without SameSite Attribute)
10063	WARN	(Permissions Policy Header Not Set)

Python Custom Policy

# custom_zap_scan.py
from zapv2 import ZAPv2
import json

zap = ZAPv2(proxies={'http': 'http://localhost:8080'})

# Configure scan policy
policy_name = 'CustomPolicy'
zap.ascan.new_scan_policy(policy_name)

# Enable specific scanners
scanners = {
    '40012': 'MEDIUM',  # XSS Reflected
    '40014': 'MEDIUM',  # XSS Persistent
    '40018': 'HIGH',    # SQL Injection
    '90019': 'HIGH',    # Server Side Code Injection
}

for scanner_id, strength in scanners.items():
    zap.ascan.set_scanner_alert_threshold(
        id=scanner_id,
        alertthreshold=strength,
        scanpolicyname=policy_name
    )

# Start scan with custom policy
scan_id = zap.ascan.scan(
    url='https://example.com',
    scanpolicyname=policy_name
)

print(f'Scan started with ID: {scan_id}')

Advanced Authentication

Form-Based Authentication

from zapv2 import ZAPv2

zap = ZAPv2()

# Configure context
context_name = 'MyApp'
context_id = zap.context.new_context(context_name)

# Set authentication method
auth_method_config = '''
{
    "loginUrl": "https://example.com/login",
    "loginRequestData": "username={%username%}&password={%password%}",
    "usernameParameter": "username",
    "passwordParameter": "password"
}
'''

zap.authentication.set_authentication_method(
    contextid=context_id,
    authmethodname='formBasedAuthentication',
    authmethodconfigparams=auth_method_config
)

# Add user credentials
user_id = zap.users.new_user(context_id, 'testuser')
zap.users.set_authentication_credentials(
    contextid=context_id,
    userid=user_id,
    authcredentialsconfigparams='username=admin&password=password123'
)

zap.users.set_user_enabled(context_id, user_id, True)

# Force user authentication before scanning
zap.forcedUser.set_forced_user(context_id, user_id)
zap.forcedUser.set_forced_user_mode_enabled(True)

API Key Authentication

# API key in header
zap.replacer.add_rule(
    description='API Key Header',
    enabled=True,
    matchtype='REQ_HEADER',
    matchstring='Authorization',
    replacement='Bearer YOUR_API_KEY',
    initiators=''
)

# API key in query parameter
target_url = 'https://api.example.com?apikey=YOUR_API_KEY'
zap.spider.scan(target_url)

Interpreting Results

Risk Levels

RiskDescriptionAction Required
HighCritical vulnerabilities (SQL Injection, RCE)Fix immediately
MediumSignificant issues (XSS, weak crypto)Fix before release
LowMinor issues (missing headers)Fix when possible
InformationalBest practice violationsConsider fixing

Common Vulnerabilities & Fixes

SQL Injection:

# Vulnerable
query = f"SELECT * FROM users WHERE id = {user_input}"

# Fixed - Use parameterized queries
cursor.execute("SELECT * FROM users WHERE id = ?", (user_input,))

XSS:

// Vulnerable
document.getElementById('output').innerHTML = userInput;

// Fixed - Escape output
document.getElementById('output').textContent = userInput;

Missing Security Headers:

# Add to nginx.conf
add_header X-Frame-Options "DENY";
add_header X-Content-Type-Options "nosniff";
add_header Content-Security-Policy "default-src 'self'";
add_header Strict-Transport-Security "max-age=31536000";

Reporting and Tracking

Generate Comprehensive Reports

# HTML report
zap-cli --zap-url http://localhost:8080 report -o zap-report.html -f html

# JSON report for automation
zap-cli report -o zap-report.json -f json

# Markdown for documentation
zap-cli report -o zap-report.md -f md

Track Vulnerabilities Over Time

# store_scan_results.py
import json
from datetime import datetime
import sqlite3

def store_scan_results(scan_date, alerts):
    conn = sqlite3.connect('security_scans.db')
    cursor = conn.cursor()

    cursor.execute('''
        CREATE TABLE IF NOT EXISTS scans (
            id INTEGER PRIMARY KEY,
            scan_date TEXT,
            alert_name TEXT,
            risk TEXT,
            url TEXT,
            description TEXT
        )
    ''')

    for alert in alerts:
        cursor.execute('''
            INSERT INTO scans (scan_date, alert_name, risk, url, description)
            VALUES (?, ?, ?, ?, ?)
        ''', (scan_date, alert['alert'], alert['risk'], alert['url'], alert['description']))

    conn.commit()
    conn.close()

# Load ZAP results
with open('zap-report.json') as f:
    data = json.load(f)

store_scan_results(
    datetime.now().isoformat(),
    data['site']['alerts']
)

Conclusion

OWASP ZAP is essential for integrating security testing into QA workflows. From automated CI/CD scans to comprehensive API testing, ZAP helps identify vulnerabilities early when they’re cheapest to fix.

Key Takeaways:

  • Start with baseline scans in CI/CD
  • Use passive scanning for production monitoring
  • Implement custom policies for your risk tolerance
  • Automate authentication for authenticated scanning
  • Track vulnerabilities over time
  • Fix high/critical issues immediately

Security testing isn’t optional — make ZAP a standard part of your testing process.

FAQ

What is OWASP ZAP and is it free? OWASP ZAP (Zed Attack Proxy) is a free, open-source web application security scanner maintained by OWASP. It provides active/passive scanning, API testing, and CI/CD integration at no cost.

What is the difference between ZAP baseline scan and full scan? A baseline scan runs passive scanning only—safe for production. A full scan adds active attack payloads to find SQL injection, XSS, and more. Use baseline for monitoring, full scan for pre-release testing on staging only.

How do I integrate OWASP ZAP into GitHub Actions? Use the official zaproxy/action-baseline or zaproxy/action-full-scan GitHub Action. Point it at your running application URL and configure a rules TSV file to suppress false positives.

Can OWASP ZAP test REST APIs? Yes. ZAP has an API scan mode that imports OpenAPI/Swagger specs and automatically generates and tests all endpoints. Use zap-api-scan.py or the Python zapv2 library for programmatic control.

Official Resources

See Also