Overview
This report analyzes the zaproxy/zaproxy codebase — the core repository for OWASP Zed Attack Proxy (ZAP), the world’s most widely used free web application security scanner. ZAP functions as both an intercepting proxy for manual penetration testing and an automated DAST scanner. Originally forked from the Paros Proxy project in 2010, ZAP has evolved into a mature, plugin-driven platform maintained by Checkmarx and a large open-source community. The analysis covers commit a98cf3a on the main branch, which corresponds to version 2.18.0-SNAPSHOT.
The goal of this analysis is to understand ZAP’s architecture, its extension and addon system, the scan engine, scripting capabilities, API surface, automation support, and overall code quality. ZAP occupies a unique niche: it is simultaneously a desktop GUI tool, a daemon-mode headless scanner, a Docker-containerized CI/CD scanner, and an API-driven automation platform. Understanding how these modalities coexist in a single Java codebase reveals important design decisions and tradeoffs.
Key Findings
Architecture
ZAP’s architecture is deeply influenced by its Paros Proxy heritage. The codebase is organized into two major package hierarchies: org.parosproxy.paros (the legacy Paros layer) and org.zaproxy.zap (the ZAP extension layer). This split is not merely cosmetic — it reflects two decades of incremental development.
Dual-layer package structure. The Paros layer (org.parosproxy.paros) provides foundational infrastructure: the MVC framework (Control, Model, View), the HTTP networking stack (HttpSender, HttpMessage), the proxy server (ProxyServer, now deprecated in favor of the network addon), the scanner framework (AbstractPlugin, Scanner, Alert), the database layer (Database, HSQLDB-backed), and the extension lifecycle interface (Extension). The ZAP layer (org.zaproxy.zap) adds the addon loading system, the event bus, the scripting engine, the REST API, and approximately 35 built-in extension packages covering everything from authentication to session management.
MVC with singleton control. The Control class acts as the application controller, following a singleton pattern. It initializes the Model (data layer), View (Swing GUI, optional), and orchestrates the extension lifecycle. The ZAP main class acts as bootstrap, selecting between three operational modes: desktop (Swing GUI), daemon (headless background process), or cmdline (one-shot execution). A fourth mode, zaas (ZAP as a Service), is documented in the enum but remains work-in-progress. The bootstrap logic lives in GuiBootstrap, DaemonBootstrap, and CommandLineBootstrap classes.
Database layer. ZAP uses HSQLDB as its embedded database, accessed through a Database interface with a ParosDatabase implementation. The database stores sessions, HTTP history, alerts, and scan results. An interface-based design allows alternative database implementations, though HSQLDB remains the only production backend. Tables include TableHistory, TableSession, TableAlert, and TableAlertTag.
Event bus. A lightweight SimpleEventBus provides publish-subscribe semantics across components. Publishers register event types, consumers subscribe to specific publishers and event types. Core event publishers include ActiveScanEventPublisher, AlertEventPublisher, BreakEventPublisher, HistoryReferenceEventPublisher, and SiteMapEventPublisher. The event bus uses CopyOnWriteArrayList for thread safety and ReentrantLock for publisher registration.
Plugin System
ZAP’s extensibility is its defining architectural feature. The addon system allows functionality to be added, updated, or removed at runtime without modifying the core.
AddOn structure. Addons are ZIP files with a .zap extension, containing a ZapAddOn.xml manifest, compiled Java classes, resources, and optional SBOM (bom.json). The AddOn class parses manifests, tracks dependencies using JGraphT’s directed graph and cycle detection, and manages version ordering. Addons have quality statuses: alpha, beta, release, and weekly. The AddOnLoader extends URLClassLoader and manages dynamic class loading from addon JARs across multiple directories (installation plugin/, home plugin/, and user-specified extra directories).
Extension lifecycle. The Extension interface defines a clear lifecycle: init → hook (register UI components, listeners, API endpoints) → start → postInit (called once all extensions loaded) → stop → destroy. Extensions can declare dependencies on other extensions using getDependencies(), and the ExtensionFactory validates dependency chains with cycle detection before loading. The CoreFunctionality class explicitly lists all 36 built-in extensions in a static initializer — a deliberate design choice noted in its Javadoc: maintaining this as code rather than a manifest file makes it immediately apparent if someone moves or deletes a core component.
Dynamic addon management. The AddOnInstaller handles installation, uninstallation, and updates. A block list mechanism (addons.block) supports soft-removal of addons that ship with the installation and cannot be physically deleted due to filesystem permissions. The ExtensionFactory.loadAllExtension() method merges built-in extensions from CoreFunctionality.getBuiltInExtensions() with dynamically loaded addon extensions, sorts by extension order, and validates that all dependencies are met before loading each extension into the ExtensionLoader.
Binary compatibility enforcement. The build uses the japicmp Gradle plugin to check binary compatibility against the previous release version. Exclusions are maintained in gradle/japicmp.yaml. This is critical because many addons are maintained in a separate zap-extensions repository and must remain compatible with the core API.
Scan Engine
ZAP’s scan engine implements both active and passive scanning through a well-structured plugin architecture.
Active scanning. The Scanner class orchestrates active scans. It delegates per-host scanning to HostProcess, which runs individual scan rules (plugins) against discovered endpoints. Scan rules implement the Plugin interface (which extends Runnable) and typically extend AbstractPlugin, AbstractAppPlugin, AbstractHostPlugin, or AbstractAppParamPlugin. Each plugin declares an alert threshold (OFF, DEFAULT, LOW, MEDIUM, HIGH) and attack strength (DEFAULT, LOW, MEDIUM, HIGH, INSANE). The PluginFactory manages the registry of available plugins and their configurations.
Variant system. A key design pattern in the scan engine is the Variant hierarchy. Variants define injection points within HTTP messages: VariantURLQuery, VariantFormQuery, VariantJSONQuery, VariantXMLQuery, VariantCookie, VariantHeader, VariantURLPath, VariantPlainBody, VariantMultipartFormParameters, and specialized variants for OData, GWT, and Direct Web Remoting. This allows a single scan rule to automatically test all relevant injection points across diverse request formats.
Passive scanning. Passive scanners implement PluginPassiveScanner (extending Enableable) and the PassiveScanner interface. The PassiveScanController (deprecated since 2.16.0, being migrated to addon-based implementation) runs passive rules against proxied traffic in a background thread pool. Passive scanners examine HTTP messages without sending any requests, detecting information leakage, missing headers, and configuration issues.
Scan policies. ScanPolicy objects encapsulate a complete scanner configuration: default alert threshold, attack strength, and per-plugin overrides. Policies are loaded from XML configuration files via ZapXmlConfiguration. Recent additions (2.17.0) include readOnly and locked policy flags — locked policies restrict scans to only the rules defined in their configuration, preventing addon-added rules from silently expanding scan scope.
Alert model. The Alert class is the central finding representation, containing risk level (Informational through Critical), confidence, CWE ID, WASC ID, evidence, and attack details. A builder pattern (added in 2019) provides fluent alert construction. Alerts can be tagged via TableAlertTag for custom metadata.
Scripting API
ZAP’s scripting support enables users to extend functionality without writing Java addons.
Script engine integration. The ExtensionScript class (at 2,261 lines, one of the largest in the core) manages script engines through the Java ScriptEngineManager (JSR-223). The ScriptEngineWrapper interface abstracts engine-specific behavior. DefaultEngineWrapper and JavascriptEngineWrapper provide built-in support, while addons contribute engines for Python, Ruby, Groovy, and others. Scripts are stored as files on disk, loaded with configurable charset (defaulting to UTF-8), and managed through a tree-based ScriptTreeModel.
Script types. ZAP defines several script categories through ScriptType: standalone scripts, active scan rules, passive scan rules, proxy scripts (intercepting traffic), authentication scripts, session management scripts, HTTP sender scripts, sequence scripts, and targeted scripts. Each type has specific interface requirements — for example, ProxyScript implements proxy request/response interception, while TargetedScript runs against a specific HTTP message.
Scripts cache. The ScriptsCache class provides efficient script invocation with caching of compiled scripts, configurable maximum cache size, and automatic refresh. This is important for scan rules that invoke scripts on every request.
API exposure. The ScriptAPI class exposes script management through the REST API, enabling remote script loading, execution, and result retrieval — a critical capability for automation workflows.
HUD (Heads Up Display)
The HUD (Heads Up Display) is one of ZAP’s most distinctive features, though it is maintained as a separate addon rather than in the core zaproxy repository. The HUD injects a floating panel into the browser when traffic is proxied through ZAP, providing real-time security feedback, alert notifications, and attack tools directly in the browser context. It communicates with ZAP through the same REST API available to all external clients. While the HUD code lives in the zap-hud addon repository, the core provides the necessary API endpoints (WebUI, persistent connections) and callback mechanisms that make it possible. The ApiPersistentConnection request type was specifically added to support HUD’s real-time communication needs.
Automation Framework
ZAP provides multiple automation pathways, ranging from Docker containers to a full REST API.
REST API. The API class implements ZAP’s REST API, supporting JSON, XML, HTML, JSONP, and custom format responses. Request types include action (state-changing), view (read-only), other (custom responses), and pconn (persistent connections). Extensions contribute API endpoints by extending ApiImplementor, which auto-discovers API methods through reflection. The WebUI class generates a browseable HTML interface for all API endpoints. Client libraries are auto-generated for Python, Java, Node.js, Go, .NET, PHP, and Rust via dedicated APIGenerator classes. The API key mechanism (API_KEY_PARAM) and one-time nonces (API_NONCE_PARAM) provide authentication.
Docker automation. The docker/ directory contains three scan scripts: zap-baseline.py (passive scan), zap-api-scan.py (API scan), and zap-full-scan.py (comprehensive scan). These Python scripts use the zapv2 client library to drive ZAP programmatically. The zap_common.py module (690 lines) provides shared infrastructure: context management, authentication configuration, report generation, and a hooks system allowing custom Python modules (~/.zap_hooks.py) to override behavior at defined callback points.
Command-line mode. In cmdline mode, CommandLineBootstrap processes arguments, loads specified addons, runs extensions’ command-line handlers, and exits. Extensions implement CommandLineListener to register arguments and handle execution.
Daemon mode. DaemonBootstrap starts ZAP without a Swing UI, enabling remote API-driven operation. This is the primary mode for CI/CD integration and the Docker images.
Code Quality
Scale and heritage. The zap/ submodule contains 1,109 Java source files totaling approximately 258,000 lines of code, with 194 test files. This represents only the core — the full ZAP distribution includes dozens of addons from separate repositories. The codebase carries significant historical baggage: file headers document changes dating back to 2003 (Paros era), and many classes have changelog comments spanning 50+ lines.
Build system. The project uses Gradle with Kotlin DSL (build.gradle.kts). A version catalog (gradle/libs.versions.toml) manages all dependency versions. The build enforces code style via Spotless (with ktlint for Kotlin), static analysis via Error Prone (configured to error on MissingOverride and WildcardImport), and binary compatibility checks via japicmp. CycloneDX SBOM generation is integrated. The buildSrc/ directory contains custom Gradle plugins for JFlex grammar generation, distribution packaging, installers, GitHub releases, and spotless import validation.
Dependency management. The project carries a mix of modern and legacy dependencies: Log4j 2 (2.25.4) for logging, Apache Commons (Configuration 1.10, Collections 3.2.2, HttpClient 3.1, IO, Lang 2 and 3), HSQLDB for storage, JGraphT for dependency graph analysis, Jericho HTML Parser, RSyntaxTextArea for script editing, FlatLaf for modern Swing look-and-feel, and SwingX for extended Swing components. Some dependencies (Commons HttpClient 3.1, Commons Configuration 1.10) are quite dated, but the custom import validation step explicitly prevents use of org.apache.commons.codec and requires Commons Lang 3 over Lang 2.
Testing. Test coverage is modest: 194 test files against 1,109 source files. Tests use JUnit Jupiter 6, Mockito, AssertJ (including AssertJ Swing for GUI tests), and Hamcrest. A separate testGui source set exists for GUI-specific tests. JaCoCo generates coverage reports, and SonarQube analysis is configured.
Deprecation hygiene. The codebase shows systematic deprecation management. The ProxyServer class was deprecated in 2.12.0 in favor of the network addon. The PassiveScanController was deprecated in 2.16.0 for removal. Core scan rules have been moved to addons over time. This progressive migration pattern keeps the core lean while maintaining backward compatibility.
Assessment
Strengths
Mature plugin architecture. ZAP’s addon system is genuinely impressive: dynamic loading, dependency resolution with cycle detection, version-aware addon selection, runtime installation/uninstallation, and mandatory addon support. The AddOnLoader’s approach of scanning multiple directories and selecting the latest version of each addon is elegant and robust. The binary compatibility checking via japicmp is a mature practice that few open-source security tools implement.
Operational flexibility. Supporting four execution modes (desktop GUI, daemon, command-line, future ZaaS) from a single codebase is architecturally significant. The bootstrap pattern cleanly separates mode-specific initialization from shared logic, and the Control singleton’s ability to operate with or without a View enables the full spectrum from interactive pentesting to CI/CD automation.
Comprehensive API surface. The auto-generating API client libraries for eight languages, combined with the browseable WebUI, makes ZAP uniquely accessible for automation. The Docker scan scripts provide ready-made CI/CD integration with sensible defaults and a hooks system for customization.
Variant-based scan engine. The separation of scan rules from injection point discovery (via Variants) is a clean design that allows rules to focus on vulnerability detection logic while the framework handles the mechanics of parameter fuzzing across diverse formats (JSON, XML, multipart, cookies, headers, URL paths).
Concerns
Legacy code accumulation. The Paros heritage is both a strength (proven, stable foundation) and a liability. The org.parosproxy.paros package contains code with 20+ years of incremental changes. Classes like ExtensionLoader (1,704 lines) and AbstractPlugin (1,638 lines) have grown far beyond single-responsibility. Changelog comments in file headers (some exceeding 90 lines) suggest insufficient refactoring over the years.
Modest test coverage. With 194 test files for 1,109 source files, test coverage is low for a security-critical application. While the core scan rules have been migrated to addon repositories with their own tests, the core extension loading, addon management, and scan orchestration logic deserve more thorough testing.
Aged dependencies. Several dependencies are significantly outdated: Commons HttpClient 3.1 (2007), Commons Configuration 1.10 (2013), JGraphT 0.9.2 (very old). While these are wrapped and isolated, they represent potential maintenance and security risks. The custom ZapNTLMEngineImpl and ZapCookieSpec suggest workarounds for limitations in aging HTTP client libraries.
GUI coupling. Despite supporting headless modes, many core classes still reference Swing types. The View.getSingleton() pattern and scattered hasView() checks indicate incomplete separation of GUI from business logic.
Recommendations
-
Continue addon migration. The ongoing effort to move functionality from core to addons is the right approach. Each migration reduces the core’s maintenance burden and allows independent updates. Priority targets include remaining scanner framework code and the proxy infrastructure.
-
Increase test coverage. Focus on the addon loading lifecycle, dependency resolution, and scan orchestration paths. Integration tests covering the full scan pipeline (from addon loading through rule execution to alert generation) would catch regressions that unit tests miss.
-
Modernize HTTP stack. The reliance on Commons HttpClient 3.1 (with custom patches in
org.apache.commons.httpclient.protocol) should be addressed. Migration to Apache HttpClient 5.x or Java’s built-inHttpClientwould reduce maintenance burden and improve HTTP/2 and TLS support. -
Formalize the automation API. The REST API has grown organically. A formal OpenAPI specification, generated from the
ApiImplementormetadata, would improve documentation and enable better client library generation. The persistent connection mechanism should be documented as part of a stable automation contract.
Related
- analyzing-nuclei — ProjectDiscovery’s template-based vulnerability scanner, a modern alternative to ZAP for automated scanning
- analyzing-spiderfoot — OSINT automation tool that complements ZAP’s active scanning with passive reconnaissance
- analyzing-prowler — Cloud security assessment tool; Prowler covers cloud misconfigurations while ZAP covers web application vulnerabilities
- analyzing-ghidra-mcp — Reverse engineering platform; both ZAP and Ghidra are extensible security tools with plugin architectures
- analyzing-bloodhound — Active Directory attack path analysis; Bloodhound focuses on post-exploitation AD enumeration while ZAP targets pre-exploitation web application testing