An HTTP filter for Envoy Proxy that validates requests against OpenAPI specifications, built as a dynamic module using Rust.
# Install mise (if not already installed)
# See https://mise.jdx.dev/getting-started.html
# Enter development environment
mise install
# Build the filter
mise run build
# Run tests
mise run test
# The compiled filter is at:
# target/release/libapi_fence.so- OpenAPI Validation: Validates HTTP requests and responses against OpenAPI 3.x specifications
- Path parameter validation with JSON Schema
- Query parameter validation with JSON Schema
- Request header validation (required, pattern, format, etc.)
- Request body validation (JSON with schema)
- Response header validation (required, pattern, format, etc.)
- Response body validation (JSON with schema)
- ModSecurity WAF Integration: Web Application Firewall protection using libmodsecurity3
- Request and response body scanning
- Bundled OWASP CoreRuleSet (CRS) v4.0.0 with zero-configuration setup
- Protection against SQLi, XSS, RCE, LFI/RFI, and protocol attacks
- Multiple ruleset profiles: full, request-only, or minimal (fastest)
- Dual ruleset support for safe migration between CRS versions
- Async scanning with thread pool for non-blocking operation
- Configurable timeout and fail-open/fail-closed behavior
- JSON string extraction optimization to reduce false positives
- Base64 detection to skip encoded data scanning
- Block or alert modes for matched rules
- Custom rules via inline, file path, or remote URL
- Mock Response Generation: Generate mock responses for API testing without a backend
- Example-based: Use examples from OpenAPI response definitions
- Schema-based: Generate realistic fake data matching response schemas
- Random selection: Automatically chooses from multiple possible response codes (200, 201, 206, etc.)
- Validation-first: Requests are validated before mocking (useful for contract testing)
- Works with all request types (GET, POST, PUT, etc.)
- Dynamic Module: Loads into Envoy without recompilation
- Schema Caching: Compiled JSON schemas are cached with configurable TTL for optimal performance
- Flexible Validation: Configure request/response validation independently
- Error Handling: Choose to fail requests on validation errors or pass them through with metadata
- Rich Metrics: Track cache hits/misses, schema compilation time, validation errors, and WAF detections (scoped per API)
- Dynamic Metadata: Validation and WAF results are exposed as Envoy dynamic metadata for logging
- Production Ready: Optimized builds with LTO and stripping, static linking for portability
The filter exposes the following metrics, scoped under your configured api_name:
OpenAPI Validation:
api_fence.<api_name>.cache.hits- Number of schema cache hitsapi_fence.<api_name>.cache.misses- Number of schema cache missesapi_fence.<api_name>.schema.compile_time_ms- Histogram of schema compilation timesapi_fence.<api_name>.request.validation_errors- Count of request validation errorsapi_fence.<api_name>.response.validation_errors- Count of response validation errors
ModSecurity WAF (when enabled):
api_fence.<api_name>.modsec.request.scans- Total request scans performedapi_fence.<api_name>.modsec.request.blocked- Requests blocked by WAF rulesapi_fence.<api_name>.modsec.request.alerts- Requests with alerts (matched but not blocked)api_fence.<api_name>.modsec.request.timeouts- Request scan timeoutsapi_fence.<api_name>.modsec.request.scan_time_ms- Request scan duration histogramapi_fence.<api_name>.modsec.response.scans- Total response scans performedapi_fence.<api_name>.modsec.response.blocked- Responses blocked by WAF rulesapi_fence.<api_name>.modsec.response.alerts- Responses with alertsapi_fence.<api_name>.modsec.response.timeouts- Response scan timeoutsapi_fence.<api_name>.modsec.response.scan_time_ms- Response scan duration histogramapi_fence.<api_name>.modsec.strings_extracted- Total strings extracted from JSONapi_fence.<api_name>.modsec.base64_skipped- Base64 strings skipped to reduce false positives
For example, with api_name: "users_api", metrics will appear as:
api_fence.users_api.cache.hitsapi_fence.users_api.request.validation_errorsapi_fence.users_api.modsec.request.blocked
This allows multiple filter instances to track metrics independently.
Access metrics at http://localhost:9901/stats/prometheus
Validation and WAF results are exposed as dynamic metadata in the api_fence namespace:
OpenAPI Validation:
request.verdict- "valid" or "invalid"request.error_count- Number of validation errorsrequest.errors- Pipe-separated list of error messagesresponse.verdict- "valid" or "invalid"response.error_count- Number of validation errorsresponse.errors- Pipe-separated list of error messages
ModSecurity WAF (when enabled):
modsec.request.verdict- "blocked", "allowed", or "alert"modsec.request.ruleset- Name of ruleset used for enforcementmodsec.request.matched_rules- JSON array of matched rule IDs (e.g.,[942100, 941110])modsec.request.matched_messages- Pipe-separated rule messagesmodsec.request.scan_time_ms- Scan duration in millisecondsmodsec.request.timed_out- Boolean indicating if scan timed outmodsec.response.verdict- "blocked", "allowed", or "alert"modsec.response.ruleset- Name of ruleset used for enforcementmodsec.response.matched_rules- JSON array of matched rule IDsmodsec.response.matched_messages- Pipe-separated rule messagesmodsec.response.scan_time_ms- Scan duration in millisecondsmodsec.response.timed_out- Boolean indicating if scan timed out
Use these in access logs to track validation and security issues:
%DYNAMIC_METADATA(api_fence:request.verdict)%
%DYNAMIC_METADATA(api_fence:request.errors)%
%DYNAMIC_METADATA(api_fence:modsec.request.verdict)%
%DYNAMIC_METADATA(api_fence:modsec.request.matched_messages)%
This project uses Mise for development environment management. See Agent.md for detailed documentation.
- Mise installed
- System dependencies: libmodsecurity3-dev, clang, llvm (for bindgen)
- (Optional) Envoy binary for integration tests
mise run build # or: cargo build --releaseThe compiled shared library will be at target/release/libapi_fence.so.
# Unit tests
mise run test-unit
# Integration tests with Envoy
mise run test-integration
# All tests
mise run test
# See available tasks
mise tasksSee tests/README.md for details.
The filter supports comprehensive configuration options:
http_filters:
- name: envoy.filters.http.dynamic_module
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_modules.v3.DynamicModuleFilter
dynamic_module_config:
name: api_fence
do_not_close: true
filter_name: api_fence
filter_config:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: |
{
"api_name": "my_api",
"openapi_spec_path": "/path/to/openapi.yaml",
"cache": {
"max_capacity": 1000,
"ttl_seconds": 3600
},
"validation": {
"validate_request": true,
"validate_response": false,
"fail_on_request_error": true,
"fail_on_response_error": false
}
}API Name (required):
api_name- Unique identifier for this API/filter instance. Used for metric scoping to allow multiple filter instances to have separate metrics. Example: "users_api", "orders_api"
OpenAPI Spec (required, one of):
openapi_spec_path- Path to OpenAPI spec file (YAML or JSON)openapi_spec_inline- Inline OpenAPI spec as YAML/JSON string
Cache Configuration (optional):
cache.max_capacity- Maximum cached schemas (default: 1000)cache.ttl_seconds- Cache time-to-live in seconds (default: 3600)
Validation Configuration (optional):
validation.validate_request- Enable request validation (default: true)validation.validate_response- Enable response validation (default: false)validation.fail_on_request_error- Reject invalid requests (default: true)validation.fail_on_response_error- Reject invalid responses (default: false)
When fail_on_*_error is false, validation errors are recorded in metrics and metadata but the request/response continues.
ModSecurity WAF Configuration (optional):
modsecurity.scan_request- Enable request body scanning (default: false)modsecurity.scan_response- Enable response body scanning (default: false)modsecurity.scan_response_as_request- Use request scanning API for responses (default: false)modsecurity.request_action- Action on match: "block" or "alert" (default: "block")modsecurity.response_action- Action on match: "block" or "alert" (default: "alert")modsecurity.pool.timeout_ms- Maximum scan timeout in milliseconds (default: 100)modsecurity.pool.timeout_action- Action on timeout: "allow" or "block" (default: "allow")modsecurity.pool.queue_capacity- Maximum queue depth for pending scans (default: 1000)
ModSecurity Ruleset Configuration:
modsecurity.primary_ruleset.name- Ruleset name for metrics/metadata (required)modsecurity.primary_ruleset.use_bundled_crs- Use bundled OWASP CRS v4.0.0 (default: false)modsecurity.primary_ruleset.bundled_crs_profile- CRS profile: "full", "request", or "minimal" (default: "full")modsecurity.primary_ruleset.rules_path- Paths to rule files (supports glob patterns like/path/*.conf)modsecurity.primary_ruleset.rules_inline- Inline rules as a stringmodsecurity.primary_ruleset.rules_remote.url- Remote URL to fetch rules frommodsecurity.primary_ruleset.rules_remote.key- Optional API key for authenticationmodsecurity.secondary_ruleset.*- Optional secondary ruleset for safe migration testing
The bundled CRS profiles provide zero-configuration WAF protection:
- full: All essential CRS rules (request + response scanning) - comprehensive protection
- request: Request-only rules (no response scanning) - faster, good for most APIs
- minimal: SQLi, XSS, RCE only - fastest, protects against most critical attacks
Mocking Configuration (optional):
mocking.enabled- Enable mock response generation (default: false)mocking.prefer_examples- Use OpenAPI examples before schema-based generation (default: true)mocking.default_status_code- Override default status code for mocking (default: first 2xx response)mocking.add_mock_header- Includex-mock-response: trueheader in mock responses (default: true)
When mocking is enabled, requests are still validated (if configured), but instead of forwarding to the backend, the filter generates and returns a mock response. This is useful for:
- Contract testing: Validate requests without needing a backend
- Frontend development: Test UI against API contracts
- Integration testing: Test API consumers with realistic responses
- Load testing: Test proxy/middleware performance without backend load
See examples/ directory:
envoy-config.yaml- Basic configuration with request validationenvoy-config-advanced.yaml- Advanced config with response validation, pass-through mode, and full access loggingenvoy-config-mock.yaml- Mock response generation for API testing (no backend required)envoy-modsec-config.yaml- ModSecurity WAF with bundled CRS rulessample-openapi.yaml- Example OpenAPI spec with header validationheader-validation-example.yaml- Comprehensive header validation examples (required/optional, patterns, formats, enums)mock-example-openapi.yaml- OpenAPI spec with response examples for mocking
Enable WAF scanning with bundled OWASP CoreRuleSet:
filter_config:
value: |
{
"api_name": "protected_api",
"openapi_spec_path": "./examples/sample-openapi.yaml",
"validation": {
"validate_request": true,
"fail_on_request_error": true
},
"modsecurity": {
"scan_request": true,
"scan_response": false,
"request_action": "block",
"response_action": "alert",
"pool": {
"timeout_ms": 100,
"timeout_action": "allow"
},
"primary_ruleset": {
"name": "crs_v4",
"use_bundled_crs": true,
"bundled_crs_profile": "request"
}
}
}This configuration provides:
- OpenAPI validation for request structure
- ModSecurity WAF scanning for attack patterns (SQLi, XSS, RCE, etc.)
- Fast request-only CRS profile
- 100ms scan timeout with fail-open behavior
- Block on WAF matches, continue on timeouts
For custom rules or external CRS installations:
"modsecurity": {
"scan_request": true,
"primary_ruleset": {
"name": "custom_rules",
"rules_path": [
"/etc/modsecurity/modsecurity.conf",
"/etc/modsecurity/crs/*.conf"
]
}
}For safe migration testing with dual rulesets:
"modsecurity": {
"scan_request": true,
"primary_ruleset": {
"name": "crs_v3",
"rules_path": ["/etc/modsecurity/crs-v3/*.conf"]
},
"secondary_ruleset": {
"name": "crs_v4",
"use_bundled_crs": true
}
}Both rulesets are evaluated, but if both match, the secondary (NEW) result is used for enforcement.
Enable mocking to test API contracts without a backend:
filter_config:
value: |
{
"api_name": "mock_api",
"openapi_spec_path": "./examples/mock-example-openapi.yaml",
"validation": {
"validate_request": true,
"fail_on_request_error": false
},
"mocking": {
"enabled": true,
"prefer_examples": true,
"add_mock_header": true
}
}Test with curl:
# GET request - returns mock data from OpenAPI examples
curl -v http://localhost:10000/users
# POST request - validates body, then returns mock response
curl -v http://localhost:10000/users/1 \
-X POST \
-H "Content-Type: application/json" \
-d '{"name":"John","email":"john@example.com"}'
# Mock response includes indicator header
# x-mock-response: trueMock responses are generated using:
- Examples first (if
prefer_examples: true): Usesexamplefields from OpenAPI responses - Schema-based fallback: Generates realistic fake data matching the schema (emails, UUIDs, dates, etc.)
- Random selection: For operations with multiple 2xx responses (200, 201, 206), randomly chooses one
Supported schema types for generation:
- Strings: Supports formats (email, uri, uuid, date, date-time) and enums
- Numbers/Integers: Respects min/max constraints
- Booleans: Random true/false
- Arrays: Generates 1-5 items
- Objects: Generates all properties
- Limitations: OneOf/AllOf/AnyOf schemas not yet supported
🚧 Early Development - This project is in active development.
Mozilla Public License 2.0 (MPL-2.0)
All source files include SPDX license identifiers:
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2026 ProxyConf AuthorsWhen contributing code:
- Every Rust file (
.rs) must include the SPDX header shown above - Follow the project rules in
.opencode/rules/rust.md - Use templates from
.opencode/templates/for new files - Run tests and linters before submitting