Java Architecture and Response-Aware Enforcement
The native Java engine consumes an AiwafRequest and returns an AiwafDecision. Spring and Servlet integrations map HTTP traffic into that contract; storage contexts and model providers are owned by the engine.
Source and package versions
One AIWAF repository.
Python: py/aiwaf/ · Node.js: js/ · Rust/WASM: rust/ · Java: java/.
Source and canonical README.
Versions shown describe the source checkout; package releases are independent.
Current Python/Node source targets are aiwaf 1.1.8 (Python) and
aiwaf 1.1.5 (npm), with Rust/Python 0.2.7 and
WASM 0.2.5 and Java 1.3.5. Sandbox guides retain their separately pinned builds.
How AIWAF works: system architecture
The engine contracts
| Contract | Purpose |
|---|---|
AiwafRequest | Normalized method, path, client, country, headers, parameters, timing context, and disabled middleware metadata. |
AiwafConfig | Typed limits, exemptions, path rules, storage, model, and observability settings. |
AiwafDecision | Allow or deny outcome with a response status and reason. |
AiwafEngine | Orchestrates checks using its storage context, model provider, UUID lookup, and telemetry. |
The engine is independent of a controller or servlet response object. Adapters decide how to map an engine decision into HTTP behavior. Java runs natively and does not load the Rust extension.
Two-stage Spring execution
- Map and inspectDiscover route metadata, buffer permitted request data, and construct AiwafRequest.
- Before responseevaluateBeforeResponse runs deterministic controls and can prevent controller execution.
- ControllerThe allowed request traverses the normal filter chain.
- After responseUse actual status and duration for UUID behavior and anomaly evaluation when the response is still replaceable.
AiwafFilter sends an early error for a pre-response denial. After a synchronous handler, it checks whether the response is committed. An uncommitted response can be reset and replaced if the final decision denies it.
A committed response is recorded through recordCommittedResponse. Asynchronous completion uses a listener and records once. These paths update relevant history and logs without trying to replace output that has already been sent.
The request-only evaluate(request) API remains available and uses provisional status and timing for its AI features. The generic Servlet filter uses that request-time path; Spring's filter has the richer response-aware boundary.
Inside the pre-response engine
- Validate request shape, compressed-body policy, and prohibited Java-serialization payloads.
- Resolve the path rule, rate overrides, and path/IP exemption state.
- Reject active blocklisted clients unless the client exemption applies.
- Enforce the configured method policy.
- Run applicable keyword, UUID, honeypot, header, geo, and rate/flood checks.
- Return an allow decision, deferring anomaly scoring for the two-stage API.
Each failing stage finalizes a decision and returns early. Path exemptions do not bypass the initial request-shape and method gates, and an exempt path does not automatically bypass an active client blocklist. Geo-only exemptions disable geo enforcement without implying a complete bypass.
Discovery and configuration before requests arrive
Spring Boot servlet auto-configuration creates the configuration, engine, and ordered filter unless the application supplies replacements. The live MVC mappings expose registered routes and handler annotations. This lets the filter honor route metadata without maintaining a separate controller list.
Manifest generation writes discovered routes and policy metadata; PathManifestCore loads and compiles it for the engine. Generated rules are appended after explicit path rules, preserving explicit equal-prefix precedence. Optional JPA metamodel discovery supplies UUID-valued primary-key and unique-property lookup.
JSON configuration uses the shared nested setting vocabulary. Environment variables and Spring properties overlay file values. Configuration loading decides the engine's policy and dependencies before request evaluation begins.
Per-engine state and atomic Redis operations
Each engine holds a RuntimeStorage.Context. The context combines typed operational stores with RuntimeState for rate counters, form timing, UUID scores, and recent behavior. Creating another engine does not replace the first engine's context.
| Backend | Ownership | Behavior across application instances |
|---|---|---|
| Memory | Local engine state. | No shared enforcement across instances. |
| File, CSV, SQLite | Local persistent store with per-engine request state. | Persistence alone does not distribute every request counter. |
| Standalone Redis | Shared namespace with versioned keys. | Atomic server-side operations coordinate rate, form, UUID, and anomaly state as well as operational stores. |
The static storage and blocklist helpers remain compatibility facades for CLI and administrative usage; the engine evaluates requests against its own context.
Redis failure and upgrade boundaries
Java 1.3 supports standalone Redis and validates unsupported Sentinel or Cluster configurations rather than pretending they provide equivalent semantics. Connection, socket, pool-size, and pool-wait limits bound the request's wait for state.
fail_closed prevents startup or raises a request-path storage failure when Redis is unavailable. fail_open uses nonblocking enforcement fallbacks and reports failed mutations at the storage boundary. These policies change availability behavior, not model semantics.
Versioned keys and a namespace compatibility marker prevent incompatible state schemas from sharing one prefix during an upgrade. A schema mismatch refuses the namespace even under fail_open. Use separate prefixes for separate applications, environments, or intentionally incompatible schemas.
Response-derived UUID and anomaly evidence
Malformed UUID input contributes an immediate weighted signal. A valid UUID request that returns 404 adds a different probing signal; successful requests can decay the score. The post-response stage supplies the real outcome instead of treating every request as an assumed success.
The lazy model provider loads the configured model on demand or through preloading. Scoring builds the Python-aligned six-feature vector, evaluates the forest, and applies threshold and behavior-confirmation rules. Forest availability and sufficient behavior evidence determine whether the AI path can deny a request.
| Feature | What it describes | Why it helps |
|---|---|---|
path_len | Length of the request path. | Unusually long probing paths. |
kw_hits | Matches against malicious keywords. | Known probing patterns within a path. |
resp_time | Response duration in seconds. | Behavior that differs from the normal timing distribution. |
status_idx | An encoded response-status category. | Response outcomes without treating status codes as continuous measurements. |
burst_count | Requests associated with the client in the burst window. | Rapid repeated activity. |
total_404 | Not-found responses in the relevant per-client history. | Repeated discovery of nonexistent routes. |
The feature order is part of the model contract. A trainer and scorer must agree on the order, status mapping, time units, and window semantics. Summary fields ending in _ms use milliseconds; the feature vector uses seconds. Sharing feature names alone does not make every model artifact interchangeable.
New Java models persist safe JSON containing the supported Python Rust-model forest state. Restricted signed legacy Java models remain compatibility inputs, including older feature schemas. A schema match must be checked before exchanging an artifact.
Worked example: a missing UUID resource
A valid UUID request passes deterministic checks and reaches a Spring controller. The controller returns 404. If the response is uncommitted, the filter supplies that status and duration to the post-response engine. The engine updates weighted UUID behavior, evaluates eligible anomaly evidence, records recent history, and returns the final decision.
If the controller already streamed its response, the committed path records the result without replacing it. The observed 404 still matters for subsequent behavior. System overview explains the broader feedback loop; operations covers Redis and model administration.
Trace the implementation
Follow these files in the monorepo to connect the architecture to the code:
