Integrating WACElib with Coraza WAF and ModSecurity
Learn how to embed WACElib as a Go library inside a Coraza WAF middleware, or expose it over gRPC for integration with ModSecurity and other WAF frameworks.
Use this file to discover all available pages before exploring further.
WACElib is designed to augment existing WAF frameworks with ML-assisted decision making, not replace them. It receives structured signal data from your WAF — such as OWASP Core Rule Set anomaly scores — through the wafParams argument to CheckTransaction, and passes that data to the configured decision plugin alongside ML model outputs. This means WACElib sits inside your WAF’s request pipeline, enriching its analysis rather than acting as a standalone proxy.
Embed WACElib directly as a Go package inside a Coraza WAF middleware. WACElib’s transaction functions (InitTransaction, Analyze, CheckTransaction, CloseTransaction) are called from within Coraza’s rule evaluation hooks. Best for Go-native deployments.
gRPC server for ModSecurity
Wrap WACElib in a gRPC server and call it from ModSecurity’s Lua or C++ connectors. ModSecurity invokes WACElib over the network at each phase boundary, passing CRS scores as gRPC request fields that become the wafParams map. Suitable for Nginx/Apache deployments.
The wafParams map[string]string argument to CheckTransaction carries WAF signal data into the decision plugin. These key-value pairs are available to the decision plugin’s CheckResults(DecisionInput) (bool, error) function via DecisionInput.WAFdata.
The following keys correspond to the standard OWASP CRS anomaly scoring fields. These are the keys used in the test suite and expected by the bundled simple decision plugin:
The following example shows where WACElib calls fit inside a Coraza HTTP middleware. Coraza fires callbacks at each WAF phase; WACElib is invoked at those same points to score the payload and check against ML model outputs.
When integrating with ModSecurity (Nginx/Apache), WACElib is wrapped in a gRPC server. The server exposes an RPC per phase; the caller (a ModSecurity Lua script or connector) passes the request payload and CRS scores as fields in the request proto, and the server maps them to wafParams before calling CheckTransaction.
// Conceptual gRPC handler — adapt to your proto definitionfunc (s *WACEServer) AnalyzePhase(ctx context.Context, req *pb.AnalyzeRequest) (*pb.AnalyzeResponse, error) { wace.InitTransaction(req.TransactionId) payload := pluginmanager.HTTPPayload{ URI: req.Uri, Method: req.Method, HTTPVersion: req.HttpVersion, RequestHeaders: protoHeadersToWACE(req.Headers), RequestBody: req.Body, } _ = wace.Analyze(req.PayloadType, req.TransactionId, payload, req.ModelIds) wafParams := make(map[string]string) for k, v := range req.WafParams { wafParams[k] = v } blocked, err := wace.CheckTransaction(req.TransactionId, req.DecisionPlugin, wafParams) if err != nil { return nil, err } // Note: for ModSecurity multi-phase use, only call CloseTransaction at the // end of the last phase, not after every AnalyzePhase call. if req.IsFinalPhase { wace.CloseTransaction(req.TransactionId) } return &pb.AnalyzeResponse{Block: blocked}, nil}
The decision plugin is solely responsible for interpreting wafParams. WACElib’s core passes the map through unchanged via DecisionInput.WAFdata. If your decision plugin does not use CRS scores (for example, a pure ML-based plugin), you can safely pass an empty map: make(map[string]string).
The metric.Meter passed to Init enables OpenTelemetry instrumentation. WACElib records two metrics out of the box: wace.model.duration.nanoseconds (a histogram per model execution) and wace.client.request.blocked.total (a counter per blocked request). Pass a real metric.Meter from your application’s OpenTelemetry provider to export these to your observability backend. In tests and development, use metric.NewMeterProvider().Meter("your-app") from go.opentelemetry.io/otel/sdk/metric.