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
| Risk | Description | Action Required |
|---|---|---|
| High | Critical vulnerabilities (SQL Injection, RCE) | Fix immediately |
| Medium | Significant issues (XSS, weak crypto) | Fix before release |
| Low | Minor issues (missing headers) | Fix when possible |
| Informational | Best practice violations | Consider 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
- OWASP ZAP Documentation — Official setup guides, scan types, and API reference
- OWASP Project — OWASP Top 10, security standards, and community resources
- GraphQL Documentation
- OpenAPI Specification
- Swagger Documentation
See Also
- Burp Suite for QA Engineers: Complete Security Testing Guide - Security testing with Burp: proxy setup, scanner, intruder,…
- Security Testing for QA: OWASP Top 10 - OWASP Top 10 vulnerabilities for QA: injection, broken auth, XSS,…
- API Security Testing: Complete Guide to OAuth, JWT, and API Keys - Secure APIs testing: OAuth flows, JWT validation, API key…
- Grafana & Prometheus: Complete Performance Monitoring Stack - Performance monitoring stack: metrics collection with Prometheus,…
