Imported from DataDog/libddwaf-java (
AGENTS.md). Install upstream withnpx skills add DataDog/libddwaf-java. Copyright stays with the author.
AGENTS.md
Guidance for coding agents working in this repository.
Project shape
libddwaf/is a git submodule pointing at DataDog/libddwaf (the C++ WAF engine). It has no.gitmodulesbranch/tag pin: the version in use is whatever commit the submodule gitlink currently points to.src/main/c/is the JNI bridge in C (waf_jni.cis the core). Headers undersrc/main/c/jni/*.hare generated but checked in.src/main/java/com/datadog/ddwaf/is the public Java API.src/test/groovy/com/datadog/ddwaf/holds the JUnit4/Groovy test suite..github/workflows/actions.ymlis the single CI workflow. It builds and tests native binaries for Linux (glibc/musl, x86_64/aarch64), macOS (x86_64/aarch64), and Windows (x86_64).
Build, format, test
Requirements: JDK 8+, CMake 3.15+.
./gradlew check # standard suite: compiles libddwaf from the submodule + JNI + runs tests
./gradlew check -PwithASAN # optional, mirrors the CI ASan job
./gradlew check -PuseZGC # required to also run the GC race regression, see below
./gradlew spotlessCheck # Java/Groovy formatting check
./gradlew format # auto-fix Java/Groovy formatting
clang-format-18 -n -Werror $(find src/main/c -type f) # C formatting check
Plain ./gradlew check does not run the full suite: ReachabilityFenceTest
is excluded from the standard test task and only runs via the dedicated
testGCRace task, which is wired into check exclusively when -PuseZGC is
passed (see build.gradle, testGCRace/useZGC handling). This test
reproduces a GC-race SIGSEGV regression (APPSEC-62784) that the CI Alpine
JDK 21/25 matrix entries exercise. If your change touches WafContext or
arena/native-memory lifetime, run ./gradlew check -PuseZGC — plain check
passing is not sufficient evidence of correctness for that kind of change.
To build the native JNI lib against a separately-built libddwaf without
touching the submodule checkout, use
./gradlew buildNativeLibDebug -PlibddwafConfig=/path/to/libddwaf/out/share/cmake/libddwaf
— this must be the directory containing libddwaf-config-debug.cmake
(the default is $libddwafInstallPrefix/share/cmake/libddwaf, see the
cmakeNativeLibDebug task in build.gradle), not the .cmake file itself.
(the README.md currently says -PlibddwafDir; that property name is
stale — -PlibddwafConfig is what build.gradle actually implements. If
you fix one, fix the other.).
CI (.github/workflows/actions.yml, "Build Native Libraries") is the source
of truth for what must pass. The TestsPass job (the branch-protection gate)
fans in Test, Dev_Tests, Jmh_Build, Spotless, and
Jar_File_Stage_build_jar (which itself needs every Native_binaries_Stage_*
job, including ASan and the static analyzer). Coverage and ClangFormat
run as independent checks and are not part of that fan-in — a green
TestsPass does not imply they passed. Reproduce the relevant job locally
before opening a PR rather than guessing from the workflow file alone.
Conventions
- Branch naming:
<you>/<short-description>, or<you>/<TICKET>-<short-description>when there's a tracking ticket. - For feature work (not a mechanical version bump), open an issue to
discuss the approach before submitting a PR (see
CONTRIBUTING.md). - When a doc (this file,
README.md, Confluence) and the actual code disagree, the code wins — but fix the stale doc in the same PR instead of just working around it silently.
Task: bump the libddwaf version
There is a detailed internal guide with full context, historical PR references and known pitfalls at: https://datadoghq.atlassian.net/wiki/spaces/SAAL/pages/5678497795/Upgrade+libddwaf+version+in+libddwaf-java (Datadog-internal Confluence; not reachable outside the corporate network). If you can reach it, read it before starting. If you cannot, the checklist below covers the same ground. Note the guide can drift from the code over time — if you find a discrepancy, trust the code and consider fixing the guide.
Before touching any file
Read the new libddwaf release's UPGRADING.md and CHANGELOG.md
(https://github.com/DataDog/libddwaf/blob/master/UPGRADING.md and
.../CHANGELOG.md on the target tag). Specifically look for:
- Renamed or removed keys in the
ddwaf_runresult or in the diagnostics output (e.g. a past release renamedblock_idtosecurity_response_id). These are not caught by the compiler: this binding reads result/diagnostics fields by hardcoded string key insrc/main/c/waf_jni.candsrc/main/c/output.c(e.g."timeout","duration","actions","events","attributes","keep","ruleset_version","rules", ...). A rename compiles and links fine and silently returns null/0/default at runtime. - Changed types in the WAF output (e.g. a past release changed status codes
from string to long, breaking Groovy assertions like
is('403')until changed tois(403L)). - New or removed public API on
ddwaf.h(e.g. theddwaf_builderAPI replacing the olderddwaf_init/RuleSetInfoflow in a past major bump). A change of this size is not a simple version bump: expect to touchWafBuilder/WafDiagnostics-equivalent Java classes and the JNI glue.
If in doubt about the scope of the change, do NOT assume it's a trivial
version bump just because it looks like one in the diff — verify against
UPGRADING.md first.
Steps
- Create a branch named
<you>/update-to-<version>(or<you>/<TICKET>-libddwaf-update-<version>if there's a tracking ticket). - Move the submodule to the exact release tag (libddwaf tags have no
vprefix, e.g.1.30.0notv1.30.0). Iflibddwaf/was never initialized (e.g. a fresh clone without--recurse-submodules), it's an empty directory, not a git repo — runningcd libddwafand thengitcommands there would silently operate on the outer superproject instead. Initialize it first:
Verifygit submodule update --init libddwaf cd libddwaf git fetch --tags git checkout <new-version> cd .. git add libddwafgit -C libddwaf rev-parse HEADmatches the tag commit exactly — never leave it pointing at an arbitrarymastercommit. - Update the
libddwafVersionenv var, near the top of.github/workflows/actions.yml(grep -n "libddwafVersion:" .github/workflows/actions.ymlto find its current line). This is a second, independent source of truth from the submodule: it controls which precompiled release tarballs CI downloads for the native-binary jobs. It must always match the submodule version — nothing enforces this automatically. - Update the
LIB_VERSIONconstant insrc/main/java/com/datadog/ddwaf/Waf.java(grep -n "LIB_VERSION" src/main/java/com/datadog/ddwaf/Waf.java). - Bump the artifact
versionat the top ofbuild.gradle(grep -n "^version" build.gradle). A libddwaf minor bump conventionally maps to a Java artifact minor bump. Do not bump the Java artifact's major version just because libddwaf did — the two version schemes are unrelated; only bump major on an actual Java-facing API break in this project. - Verify the new libddwaf release actually publishes all 4 tarballs the
workflow expects (each with a
.sha256):libddwaf-<v>-darwin-x86_64.tar.gz,-darwin-arm64.tar.gz,-windows-x64.tar.gz, and-{x86_64,aarch64}-linux-musl.tar.gz. Note that all Linux jobs, including glibc ones, consume the-linux-muslasset (a static build). - Build and test locally (see "Build, format, test" above): at minimum
./gradlew check, plus-PwithASANif you touched the C bridge. - Only if you changed a
nativemethod signature in Java (not needed for a plain version bump): regenerate JNI headers with./gradlew generateJniHeaders, copy the result frombuild/generated/jniintosrc/main/c/jni/*.h, and fix inner-class naming by hand (see JDK-8145897). - Fix any failing Groovy tests under
src/test/groovy/com/datadog/ddwaf/caused by behavior/type/schema changes in the new libddwaf version — this is expected and normal, not a sign something is broken in the binding. - Open the PR. If the WAF's behavior changed in a way that could affect
dd-trace-java(the consumer of this library), say so explicitly in the PR description — this update is rarely self-contained, and a matching change is often needed downstream.
Sanity checks before considering the work done
libddwafsubmodule commit and thelibddwafVersionenv var in.github/workflows/actions.ymlrefer to the exact same version.Waf.LIB_VERSIONmatches both of the above../gradlew checkpasses locally, includingBasicTests.groovy'sassert Waf.version =~ Waf.LIB_VERSION(this is a regex match, not equality — it catches a forgotten bump but is not a strict guarantee).- You checked whether a newer libddwaf patch release already exists before opening the PR (patch releases often land within hours to days of a release).