Skip to content

Releases: DataDog/libddwaf

v2.0.0-alpha0

21 Jan 18:24
Immutable release. Only release title and notes can be modified.
cf49c49

Choose a tag to compare

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 on object_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_MATCH when 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)

11 Dec 14:31
Immutable release. Only release title and notes can be modified.
da7fd66

Choose a tag to compare

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)

28 Oct 10:25
Immutable release. Only release title and notes can be modified.
00e895f

Choose a tag to compare

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)

01 Oct 09:54
bea83f9

Choose a tag to compare

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: luhn

Incremental 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_checksum operator: 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)

03 Sep 12:43
03f7484

Choose a tag to compare

Fixes

  • Support float status codes in configuration (#448)

v1.28.0 (unstable)

01 Sep 09:55
bbe9915

Choose a tag to compare

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: false

After 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)

01 Aug 16:21
a37a7a2

Choose a tag to compare

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 unless enforce-policy-without-injection is enabled, in which case the authority is checked against denylists regardless of injection.
  • path-inspection (default: false):

    • When set to true, inspects the path (RFC-3986::Path) for injections.
    • If false, path injections are ignored.
  • query-inspection (default: false):

    • When set to true, inspects the query (RFC-3986::Query) for injections.
    • If false, query injections are ignored.
  • 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.
  • 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 to forbidden-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_detector

Negated 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_regex matches 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

  • Fix typo in doc-string (#424)
  • Fix markdown typo in UPGRADING.md (#429)
  • Update linux builds and tests to use LLVM-19 (#431)
  • Use github-provided ubuntu arm64 runner (#433)

v1.26.0 (unstable)

02 Jul 10:43
75c1e92

Choose a tag to compare

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_paths export to windows shared libraries (#421)

Miscellaneous

  • Replace re2::StringPiece with std::string_view (#412)
  • Add artifact comparison to PR (#422)

v1.25.1 (unstable)

28 May 14:45
e8fb3cf

Choose a tag to compare

Fixes

  • Support backwards-incompatible rules through the rules_compat key (#409)

v1.25.0 (unstable)

22 May 12:05
8dbee18

Choose a tag to compare

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_result with ddwaf_object (#402)
  • Support for partial event obfuscation (#403)
  • Support for attribute generation from rules (#404)

Fixes

  • Fix ddwaf_builder_remove_config example (#398)
  • Make SQL comment injection check stricter (#399)

Miscellaneous

  • Enforce CMake 3.5 compatibility (#395)
  • Update schemas and tests to include validation (#396)