Releases: DataDog/libddwaf
v2.0.0-alpha0
libddwaf v2.0.0 represents a significant redesign of the C API, focusing on explicit memory ownership, reduced memory footprint, and a more consistent interface. Beyond the public API, the internals have also been refactored to use safer and more C++-native abstractions. This is an alpha release intended for early adopters and binding library maintainers to begin integration work.
As expected, this release is not backwards compatible; the upgrading guide at docs/upgrading/UPGRADING-v2.0.md provides detailed migration examples for each of the breaking changes.
Memory Ownership and Allocators
The most fundamental change in v2 is the introduction of allocators throughout the API. In v1, memory ownership across the API boundary was often ambiguous, leading to potential issues in complex integration scenarios. The new allocator system makes memory ownership explicit: callers provide an allocator when creating objects and use the same allocator when destroying them. A default allocator is available via ddwaf_get_default_allocator() for simple use cases, while custom allocators enable advanced memory management strategies.
Object Model and Creation API
The ddwaf_object structure has been completely redesigned to reduce memory overhead. A single object now requires only 16 bytes, down from a considerably larger footprint in v1. This was achieved through carefully redesigning each of the possible type variants and, more specifically, through the removal of map keys from the main ddwaf_object in favour of representing maps through a separate ddwaf_object_kv structure. Indirectly, this new version enables further reduction in the memory and allocations through the introduction of small strings, where strings of 14 bytes or fewer are stored directly within the object without additional heap allocation, and literal strings, which reference read-only memory that is never freed by the library.
Object creation functions have been renamed to follow a consistent ddwaf_object_set_* pattern and now require an allocator parameter where allocation may occur. Container insertion returns a pointer to the newly inserted element, eliminating the need for intermediate objects. For example, where v1 required creating a value object separately and then adding it to a map, v2 allows direct initialisation: ddwaf_object_set_string(ddwaf_object_insert_key(&map, "key", 3, alloc), "value", 5, alloc). This pattern reduces boilerplate and makes the ownership model clearer.
Context and Subcontext Lifecycle
The function ddwaf_run has been renamed to ddwaf_context_eval for consistency with the rest of the API. More significantly, ephemeral data semantics have been replaced with a new subcontext mechanism. In v1, ephemeral data was passed as a separate parameter to each ddwaf_run call and was not retained. In v2, a subcontext is explicitly created from a parent context via ddwaf_subcontext_init, evaluated with ddwaf_subcontext_eval, and destroyed with ddwaf_subcontext_destroy. Subcontexts inherit all data from their parent context but maintain their own isolated evaluation state. Multiple concurrent subcontexts can be created from the same parent context, each defining its own data scope and lifetime.
Release Changelog
Changes
- Object model overhaul: immutable
object_view, owned/borrowed writable objects, raw configuration onobject_view, container view types, and updated unit tests (#341, #378, #382, #387, #390, #391, #394, #408, #413, #476). - Allocator API: propagate allocators through contexts/subcontexts, expose allocator-aware interfaces, and stop generating zero-terminated strings (#418, #420, #427, #452).
- Refactored evaluation lifecycle: separated data insertion from evaluation and lifted evaluation stages out of the context (#407, #442).
- Subcontexts with user-defined lifetimes and validator support for subcontexts/attributes (#443, #451, #468).
- Array indexing on key paths (#458).
- Instantiate obfuscator through configuration and remove
ddwaf_config(#464). - Return
DDWAF_MATCHwhen events, attributes, or actions are present (#455). - Remove legacy configuration schema (v1) support (#482).
Fixes
- Report input attribute addresses on
ddwaf_known_addresses(#485). - Remove default allocator arguments (#486).
Miscellaneous
- Upgrade fmt dependency and use as header-only (#478).
- Increase macOS target to 14.2.1 (#477).
- Add option to build on valgrind to avoid RE2 UB noise (#480).
- Lint all headers and add tests (#474).
- Intertwine context and subcontext calls within benchmark (#469).
- Disable unreliable GCC / Clang benchmarks (#470).
- Upload coverage reports to Datadog (#471).
- Remove mingw builds (#381).
- Cleanup exclusion namespace and redundant references (#456).
- Expanded fingerprint and object-view tests (#414).
- Add fuzzer v2 (#465).
- Update logger to avoid dependencies on
ddwaf.h(#453). - Achieve 100% test coverage for src/utils.cpp (#481).
- Documentation (#487).
v1.30.1 (unstable)
Note
The minimum MacOS target has been increased to 14.2.1 due to the deprecation of the macos-13 github runner.
Fixes
- Report input attribute addresses on
ddwaf_known_addresses(#484)
v1.30.0 (unstable)
This release introduces no new features, however the recently introduced block_id action parameter has been renamed to security_response_id for consistency.
Release changelog
Changes
- Rename block_id to security_response_id (#473)
v1.29.0 (unstable)
New Features
This release introduces a focused set of features addressing the most pressing use cases. Barring any necessary patch releases, this will constitute the final v1.x release.
Block ID
Block and redirect actions now include a block_id in their action parameters. This same ID is also included in the generated event, allowing the WAF caller to reference it in the blocking response or add it as a query parameter in a redirect. This ensures that each blocking action can be reliably correlated with its corresponding event.
The following is an example of a block request action, including the new ID:
actions:
block_request:
status_code: 403
grpc_status_code: 10
type: auto
block_id: "55af6314-9e02-11f0-b0a3-23dee2d4f390"Identifier validation through checksums
A new operator, match_regex_with_checksum, has been introduced to improve the accuracy of detections performed using a regular expression, when the identifier itself has a built-in validation mechanism, by performing a secondary validation through a checksum. The checksum may be a purpose-built or generic algorithm, however at this time only the Luhn algorithm is supported.
The configuration of the operator is equivalent to the match_regex operator, except for the addition of the checksum parameter:
operator: match_regex_with_checksum
parameters:
inputs: [{ address: server.request.body }]
regex: "\\b4\\d{3}(?:(?:,\\d{4}){3}|(?:\\s\\d{4}){3}|(?:\\.\\d{4}){3}|(?:-\\d{4}){3})\\b"
options: { min_length: 16 }
checksum: luhnIncremental processor overrides
Processor overrides have been updated to allow for incremental additions or removals of scanners to a given processor. Before this release, processor overrides fully replaced the list of scanners of a processor, however now each override contributes to the set by either adding or removing scanners.
While this is technically a breaking change, processor overrides are not currently in use. An example of a processor override including and excluding scanners can be seen below:
{
"processor_overrides": [
{
"target": [{ "id": "extract-content" }],
"scanners": {
"include": [{ "id": "scanner-001" }],
"exclude": [{ "tags": { "type": "email" } }]
}
}
]
}Release changelog
Fixes
- Fix single-char matches on
phrase_match(#462)
Changes
match_regex_with_checksumoperator: match and verify regexes with a checksum (#450)- Add block ID to block & redirect action parameters & event (#459)
- Incremental processor overrides (#461)
Miscellaneous
- Replace number with integer for integer-only attrs (#457)
v1.28.1 (unstable)
Fixes
- Support float status codes in configuration (#448)
v1.28.0 (unstable)
New Features
This release introduces a new URI-decomposition preprocessor along with enhancements to action parameter types, initialization performance, and platform support.
Since this release introduces breaking changes, a new section has been added to the upgrading guide.
URI parsing preprocessor
A new uri_parse preprocessor has been introduced to convert a single URI into a structured map. URI decomposition follows RFC 3986, with some additions from WHATWG for compatibility. This preprocessor improves the ability of the rule writer to target specific URI components without the need for crafting complex and innacurate regular expressions.
An example definition of this preprocessor can be seen below:
id: decompose-uri
generator: uri_parse
conditions: []
parameters:
mappings:
- inputs:
- address: server.request.uri.raw
output: server.request.uri
evaluate: true
output: falseAfter the evaluation of this example preprocessor server.request.uri will be available as a map containing the following fields:
{
"scheme": <string>,
"userinfo": <string>,
"host": <string>,
"port": <unsigned>,
"path": <string>,
"query": {},
"fragment": <string>
}Action parameters: broader scalar support
Action parameters can now include any of the available scalar types in addition to strings. This enables more natural configurations and prevents the need for ad‑hoc conversions by the WAF caller. This is a small, incremental improvement ahead of v2’s planned complex‑type support.
In addition to this change, the status_code field of the block_request and redirect_request actions, as well as the grpc_status_code field of the block_request action, are now being stored and interpreted as an unsigned integer, rather than a string.
Performance & Initialisation
Due to the static initialisation cost of tokenizer regular expressions, this is now deferred to the first ruleset instantiation. This avoids startup overhead and protects request latency from any one‑time initialization costs. No behavior changes are expected for existing rules.
Platforms & CI
Continuous integration now builds and tests libddwaf on Windows ARM64, leveraging the windows-11-arm runners, this is an external contribution from @Greenie0701.
Release changelog
Changes
- URI parsing preprocessor (#439)
- Support other scalar types on action parameters (#441)
- Load tokenizer regexes on first ruleset instantiation (#446)
Miscellaneous
- Add support for building and testing windows arm64 (#440)
v1.27.0 (unstable)
New Features
This release of libddwaf includes several new features designed to enhance usability and configurability for both users and rule writers. The following sections provide detailed descriptions of each significant addition.
Note: This release contains no breaking changes.
Improved WAF Builder
Although not a direct feature, the WAF builder has been improved to support empty configurations or configurations without side-effects, such as configurations lacking compatible items.
SSRF Operator Configuration
New configuration settings have been introduced to provide enhanced control over the SSRF heuristic's sensitivity.
Heuristic Options:
-
authority-inspection(default: true):- When set to
true, scans the authority component (RFC-3986::Authority) for injections. - If
false, the authority is ignored unlessenforce-policy-without-injectionis enabled, in which case the authority is checked against denylists regardless of injection.
- When set to
-
path-inspection(default: false):- When set to
true, inspects the path (RFC-3986::Path) for injections. - If
false, path injections are ignored.
- When set to
-
query-inspection(default: false):- When set to
true, inspects the query (RFC-3986::Query) for injections. - If
false, query injections are ignored.
- When set to
-
forbid-full-url-injection(default: false):- When set to
true, injections involving a full URL are flagged as vulnerabilities. - If
false, these injections are ignored.
- When set to
-
enforce-policy-without-injection(default: false):- When enabled, policies are enforced irrespective of detected injections, ensuring schemes and hosts are validated against allowlists and denylists.
- If
false, the policy applies only upon detecting relevant injections.
Policy Options:
allowed-schemes: Array of allowed schemes (RFC-3986::Scheme), validated upon injection detection or when policy enforcement is active.forbidden-domains: Array of forbidden domains (RFC-3986::Host), validated upon injection detection or when policy enforcement is active.forbidden-ips: Array of forbidden IPv4/IPv6 addresses, evaluated similarly toforbidden-domains.
Example Configuration:
id: rasp-934-100
name: Server-side request forgery exploit
tags:
type: ssrf
module: rasp
conditions:
- parameters:
resource:
- address: server.io.net.url
params:
- address: server.request.query
# Additional parameters...
options:
authority-inspection: true
path-inspection: false
query-inspection: false
forbid-full-url-injection: true
enforce-policy-without-injection: false
policy:
allowed-schemes: []
forbidden-domains: []
forbidden-ips: []
operator: ssrf_detectorNegated Operator Improvements
Negated operators (e.g. !match_regex) have been enhanced for greater clarity and functionality:
- Negated operators now explicitly require the presence of the defined key path.
- Evaluations must involve at least one compatible object type; for example,
!match_regexmatches only if evaluated data contains strings. - Non-matching values are now clearly reported when evaluating scalar or single-value arrays.
These improvements ensure more precise and predictable rule behavior.
JSON to Object Helper
A new helper function simplifies object creation from JSON strings:
bool ddwaf_object_from_json(ddwaf_object *output, const char *json_str, uint32_t length);output: Pointer to the object populated with JSON content.json_str: JSON data as a string.length: Length of the JSON string.
The function returns a boolean indicating success (true) or failure (false). Both the input string and resulting object remain owned by the caller.
Release changelog
Changes
- Add helper for object creation from JSON string (#430)
- SSRF Operator Configuration (#434)
- Negated operator fixes & improvements (#435)
- Accept empty and inconsequential configurations (#437)
Miscellaneous
v1.26.0 (unstable)
New features
This release introduces a new operator, hidden_ascii_match, designed to detect hidden ASCII characters within arbitrary text inputs. Hidden ASCII refers specifically to characters found within the Unicode range [U+E0000, U+E007F]. This Unicode block was initially defined to provide non-printable mappings of standard ASCII characters, effectively allowing ASCII data to be embedded invisibly within text.
Hidden ASCII characters have been increasingly leveraged to inject concealed instructions into prompts provided to LLMs, manipulating their behavior without explicit visibility to users or systems. The introduction of the hidden_ascii_match operator represents the first step toward a deterministic AI security strategy, proactively identifying and flagging these character sequences to support the effective monitoring and mitigation of potential Unicode-based prompt injection exploits.
Release changelog
Changes
- Hidden ASCII Matcher (#411)
Fixes
- Add missing
ddwaf_builder_get_config_pathsexport to windows shared libraries (#421)
Miscellaneous
v1.25.1 (unstable)
Fixes
- Support backwards-incompatible rules through the
rules_compatkey (#409)
v1.25.0 (unstable)
New features
This new version of libddwaf introduces a plethora of new features in order to support new use cases and expand or improve existing ones.
Since this release introduces breaking changes, a new section has been added to the upgrading guide.
Rule Output Configuration & Attributes
This version expands the mechanisms that rules can use to provide information to the user. Previous versions relied on the generation of events to ensure that the caller had a full picture of the rules, conditions and relevant input data which caused a match. However, this mechanism isn't always suitable as it provides too much information in cases where only a small amount is needed, such as when identifying or extracting request-adjacent metadata. For this reason, rules now have ability to produce attributes in addition to, or instead of, an event as a result of a match. As a consequence of the introduction of attributes, the configuration of rules has been extended with an output object, as can be seen below:
{
"output": {
"keep": (true | false),
"event": (true | false),
"attributes": { ... }
}
}
Where:
keep: indicates whether the outcome of the rule must be prioritised, overriding any potential transport sampling (such as trace sampling). This new flag allows the rule writer to ensure that high-frequency / low-value information is only sent opportunistically rather than on every match.event: enables (true) or disables (false) event generation, however a rule must always generate either an event or one or more attributes, or both.attributes: specifies the list of attributes which must be generated and included in the result upon matching the given rule.
The attributes object can follow two possible schemas, the first one defines an attribute with a literal scalar value, as follows:
{
ATTRIBUTE : {
"value": LITERAL_VALUE
}
}
While the second one defines an attribute containing a scalar value extracted from the data provided within the given context:
{
ATTRIBUTE : {
"address": ADDRESS,
"key_path": [ PATH, ... ],
"transformers": [ TRANSFORMER_ID, ... ]
}
}
Note that transformers are not supported in this iteration.
JWT Decoding Processor
A new processor has been developed to decode and parse targeted JWT tokens. The main purpose of this processor is to generate a new address which can then be analysed by rules in order to identify malicious, invalid, expired or unsafe tokens. As an example, the following preprocessor decodes and parses the authorization header into the server.request.jwt address:
{
"id": "processor-001",
"generator": "jwt_decode",
"conditions": [],
"parameters": {
"mappings": [
{
"inputs": [
{
"address": "server.request.headers.no_cookies",
"key_path": [
"authorization"
]
}
],
"output": "server.request.jwt"
}
]
},
"evaluate": true,
"output": false
}Partial Event Obfuscation
To prevent accidental sensitive data leaks, generated events are obfuscated through the use of regular expressions. Until this version, sensitive values were completely replaced with <Redacted>, however this new version can perform partial obfuscation of only the relevant values of an unstructured payload. For example, a payload containing "?token=sensitive-token" will now be obfuscated as follows: "?token=<Redacted>", preserving more of the semantics of the payload so that the user can better understand the nature of the attack.
This feature provides significant benefit in the case of Exploit Prevention rules, as those tend to contain larger payloads of unstructured data and are often prone to being fully redacted.
Note that this feature requires the use of the default regular expression for values, overriding it disables partial event obfuscation.
Processor Overrides
Last but not least, this release also introduces a new mechanism to override the default configuration of processors, specifically aimed at adding or removing scanners to be used during the process of schema extraction. This can now be done through the processor_override top-level configuration key, which has the following schema:
{
( "processor_override": [
{
( "target": [ PROCESSOR TARGET, ... ], )
( "scanners": [ SCANNER_TARGET, ...] )
},
...
] )
}
Where each PROCESSOR_TARGET is an object which specifies the processor to which this override should apply, with the following schema:
{
"id": PROCESSOR_ID,
}
Note that in the future, PROCESSOR_TARGET, and consequently processors in general, may support tags as well.
Finally, SCANNER_TARGET is also an object which specifies the scanners which must be used by this processor, this can be done through their id or tags, as follows:
{
( "id": SCANNER_ID, )
( "tags": {
TAG: VALUE,
...
} )
}
Release changelog
Changes
- Support for basic processor overrides (#397)
- JWT Decoding Processor (#400)
- Replace
ddwaf_resultwithddwaf_object(#402) - Support for partial event obfuscation (#403)
- Support for attribute generation from rules (#404)