As Software Development Engineers in Test (SDETs), we’re no strangers to automation. We build frameworks to validate functionality, performance, and usability. But in today’s threat landscape, security testing can no longer be an afterthought – and that’s where OWASP ZAP (Zed Attack Proxy) shines. Let’s explore why integrating ZAP into your automated tests matters and how it transforms your role in securing applications.
Why Security Testing Matters for SDETs
Shift-Left Security
Security vulnerabilities caught late in the SDLC cost 6x more to fix than those identified early. By integrating security testing into automation, SDETs enable proactive risk mitigation.Beyond Functional Testing
Modern SDETs are guardians of quality and security. A feature might work perfectly but leak sensitive data or expose SQL injection points.Compliance & Reputation
GDPR, PCI-DSS, and other regulations demand security rigor. Automated security tests provide auditable proof of due diligence.
OWASP ZAP: The SDET’s Security Swiss Army Knife
ZAP isn’t just another scanner – it’s a developer-centric toolkit designed for automation:
- Passive Scanning: Analyze traffic during functional tests
- Active Scanning: Simulate attacks (XSS, SQLi, etc.)
- API-first Design: Perfect for CI/CD pipelines
- Customizable: Addons for OAuth, GraphQL, and more
Integrating ZAP into Your Framework: Key Benefits
✅ Seamless Automation
Trigger scans programmatically via ZAP’s REST API during Selenium test suites.
✅ Context-Aware Testing
Use authenticated sessions from your functional tests to scan post-login workflows.
✅ CI/CD Friendly
Fail builds automatically when ZAP detects high-risk vulnerabilities.
✅ Allure-Ready Reports
Combine ZAP’s HTML/JSON reports with Allure’s trend analysis for security metrics.
How to Integrate ZAP with Selenium + TestNG
Architecture Overview
- ZAP Proxy Setup: Route Selenium traffic through ZAP
- Passive Scan During Tests: Let ZAP analyze HTTP/S traffic
- Active Scan Post-Test: Trigger attacks after critical workflows
- Report Generation: Export results to Allure
Note: Headless mode requires additional steps and for normal test ZAP GUI should be opened before test starts.
Example Code Snippets (Dependencies)
<!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.30.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.21.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.zaproxy/zap-clientapi -->
<dependency>
<groupId>org.zaproxy</groupId>
<artifactId>zap-clientapi</artifactId>
<version>1.16.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.2</version>
<scope>test</scope>
</dependency>
<!-- Allure -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-cucumber7-jvm</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.qameta.allure/allure-testng -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>2.29.1</version>
</dependency>
Feature File
Feature: ZAP Security Check
@sec
Scenario: I need to run security check
When i started security test
Step Definition
package com.velespit.step_definitions;
import com.velespit.utilities.BrowserUtils;
import com.velespit.utilities.ConfigurationReader;
import io.qameta.allure.Allure;
import io.qameta.allure.Description;
import io.qameta.allure.testng.Tag;
import org.testng.annotations.Test;
import org.zaproxy.clientapi.core.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zaproxy.clientapi.core.ClientApi;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
public class ZapSecurityTest {
private static final Logger logger = LoggerFactory.getLogger(ZapSecurityTest.class);
private static final String ZAP_PROXY_HOST = "localhost";
private static final int ZAP_PROXY_PORT = 8080;
private static final String ZAP_API_KEY = ConfigurationReader.getProperty("zap_api_key"); // Replace with your ZAP API key
private static ClientApi zapClient;
public ZapSecurityTest() {
zapClient = new ClientApi(ZAP_PROXY_HOST, ZAP_PROXY_PORT, ZAP_API_KEY);
}
public static void startZAPHeadless() {
try {
ProcessBuilder processBuilder = new ProcessBuilder(
"cmd.exe", "/c", ConfigurationReader.getProperty("zap_path"), "-daemon", "-port", "8080", "-host", "localhost"
);
processBuilder.directory(new File(ConfigurationReader.getProperty("zap_path")));
processBuilder.redirectErrorStream(true);
Process zapProcess = processBuilder.start();
logger.info("ZAP started in headless mode.");
// Wait until ZAP is fully initialized before continuing
waitForZapStartup();
} catch (Exception e) {
logger.error("Failed to start ZAP in headless mode.", e);
}
}
private static void waitForZapStartup() throws InterruptedException {
logger.info("Waiting for ZAP to initialize...");
int retries = 10;
while (retries > 0) {
if (isZapRunning()) {
logger.info("ZAP is ready!");
return;
}
BrowserUtils.sleep(5); // Wait 5 seconds
retries--;
}
throw new RuntimeException("ZAP did not start within the expected time!");
}
private static boolean isZapRunning() {
try {
ProcessBuilder checkProcess = new ProcessBuilder("curl", "-s", "http://localhost:8080/");
Process process = checkProcess.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String response;
while ((response = reader.readLine()) != null) {
if (response.contains("ZAP")) {
return true;
}
}
} catch (Exception e) {
return false;
}
return false;
}
public void startZapSecurityTest(String targetUrl) {
try {
// Start a new session
zapClient.core.newSession("ZAP Security Test", "true");
// Spider the target URL to discover links
logger.info("Starting ZAP Spider for: {}", targetUrl);
ApiResponse spiderResponse = zapClient.spider.scan(targetUrl, null, null, null, null);
String scanId = ((ApiResponseElement) spiderResponse).getValue();
waitForSpiderToComplete(scanId);
// Active scan for vulnerabilities
logger.info("Starting ZAP Active Scan for: {}", targetUrl);
ApiResponse activeScanResponse = zapClient.ascan.scan(targetUrl, "true", "false", null, null, null);
String activeScanId = ((ApiResponseElement) activeScanResponse).getValue();
waitForActiveScanToComplete(activeScanId);
// Generate and report security findings
reportSecurityFindings(targetUrl);
} catch (ClientApiException | InterruptedException e) {
logger.error("An error occurred during ZAP security testing: ", e);
Allure.addAttachment("Error", "text/plain", "An error occurred: " + e.getMessage());
}
}
private void waitForSpiderToComplete(String scanId) throws ClientApiException, InterruptedException {
while (true) {
BrowserUtils.sleep(2); // Poll every 2 seconds
ApiResponse spiderStatus = zapClient.spider.status(scanId);
int progress = Integer.parseInt(((ApiResponseElement) spiderStatus).getValue());
logger.info("ZAP Spider progress: {}%", progress);
if (progress >= 100) {
break;
}
}
logger.info("ZAP Spider completed.");
}
private void waitForActiveScanToComplete(String scanId) throws ClientApiException, InterruptedException {
while (true) {
BrowserUtils.sleep(2); // Poll every 2 seconds
ApiResponse activeScanStatus = zapClient.ascan.status(scanId);
int progress = Integer.parseInt(((ApiResponseElement) activeScanStatus).getValue());
logger.info("ZAP Active Scan progress: {}%", progress);
if (progress >= 100) {
break;
}
}
logger.info("ZAP Active Scan completed.");
}
private void reportSecurityFindings(String targetUrl) throws ClientApiException {
// Get alerts (vulnerabilities) using the new API method
ApiResponse alertsResponse = zapClient.alert.alerts(targetUrl, null, null, null);
List<Map<String, String>> alerts = parseAlerts((ApiResponseList) alertsResponse);
if (alerts.isEmpty()) {
Allure.addAttachment("ZAP Security Result", "text/plain", "No security issues found for: " + targetUrl);
} else {
Allure.addAttachment("ZAP Security Result", "text/plain", "Security Issues Found for: " + targetUrl);
for (Map<String, String> alert : alerts) {
String alertDetails = formatAlertDetails(alert);
if (alertDetails != null) {
Allure.addAttachment("Alert: " + alert.get("alert"), "text/plain", alertDetails);
}
}
}
}
private List<Map<String, String>> parseAlerts(ApiResponseList alertsResponse) {
List<Map<String, String>> alerts = new ArrayList<>();
for (ApiResponse response : alertsResponse.getItems()) {
if (response instanceof ApiResponseSet alertSet) {
if (alertSet.getValuesMap() != null && !alertSet.getValuesMap().isEmpty()) {
Map<String, String> alertDetails = new HashMap<>();
for (Map.Entry<String, ApiResponse> entry : alertSet.getValuesMap().entrySet()) {
if (entry.getValue() != null) {
alertDetails.put(entry.getKey(), entry.getValue().toString());
} else {
alertDetails.put(entry.getKey(), "null"); // null kontrolü
}
}
alerts.add(alertDetails);
}
}
}
return alerts;
}
private String formatAlertDetails(Map<String, String> alert) {
if (alert == null || alert.isEmpty()) {
return null; // Return null if map is empty or null
}
String risk = alert.get("risk");
if (risk == null || risk.equals("Low") || risk.equals("Informational")) {
return null; // Return null if low or informational risk
}
// Check if necessary alert keys are available
if (!alert.containsKey("alert") || !alert.containsKey("confidence") ||
!alert.containsKey("description") || !alert.containsKey("solution") ||
!alert.containsKey("reference") || !alert.containsKey("url")) {
return "Missing required alert details."; // Return error message if missing information
}
return "Alert: " + alert.get("alert") + "\n" +
"Risk: " + risk + "\n" +
"Confidence: " + alert.get("confidence") + "\n" +
"Description: " + alert.get("description") + "\n" +
"Solution: " + alert.get("solution") + "\n" +
"Reference: " + alert.get("reference") + "\n" +
"URL: " + alert.get("url") + "\n" +
"--------------------";
}
public static void shutDownZAP() {
// Shutdown ZAP after testing
try {
zapClient.core.shutdown();
logger.info("ZAP has been shut down.");
} catch (ClientApiException e) {
logger.error("Failed to shutdown ZAP.", e);
}
}
@Tag("Security")
@Description("Security Test with ZAP API using TestNG")
@Test
public void secTest() {
startZAPHeadless();
startZapSecurityTest("https://yusufasik.com");
shutDownZAP();
}
Challenges & Best Practices
⚠️ Common Pitfalls
False Positives: Always triage results
Performance: Schedule aggressive scans off-peak
Session Handling: Reuse authenticated contexts
🔑 Pro Tips
Start with passive scans to avoid test flakiness
Use ZAP’s @Tags to filter irrelevant vulnerabilities (Or my if statement if you dont want to deal with it)
Update ZAP weekly – new vulnerabilities emerge constantly
Conclusion: Security is Your Responsibility Now
SDETs are uniquely positioned to democratize security testing. By embedding ZAP into your automation framework, you:
Catch vulnerabilities before they reach production
Build security into the team’s daily workflow
Elevate your role from test author to quality architect
Ready to try it? Drop your ZAP questions below or share how you’ve integrated security testing! 👇
Top comments (0)