Skip to content

perf(appsec): cache negative SQLi RASP evaluations#18704

Draft
florentinl wants to merge 1 commit into
mainfrom
codex/appsec-sqli-rasp-cache-poc
Draft

perf(appsec): cache negative SQLi RASP evaluations#18704
florentinl wants to merge 1 commit into
mainfrom
codex/appsec-sqli-rasp-cache-poc

Conversation

@florentinl

@florentinl florentinl commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Description

SQLi RASP checks run on every instrumented DB call. ORM-heavy endpoints trigger many identical queries per request (N+1 patterns, repeated lookups) — each previously ran a full libddwaf subcontext evaluation.

This PR adds a request-local negative-result cache for SQLi RASP. When the WAF returns a clean result for a `(sql_statement, db_system, persistent_addresses_snapshot)` triple, subsequent identical evaluations within the same request skip subcontext creation and WAF execution.

Cache safety:

  • Only caches `return_code == 0` results with no matches, actions, or timeout — any positive result is always re-evaluated.
  • The key includes a snapshot of `addresses_sent` (the set of persistent request-level addresses inherited by subcontexts), so the same SQL is re-evaluated if new request data arrives mid-request (e.g. body parsed after headers).
  • Request-scoped (`ASM_Environment`), discarded with the request — no cross-request sharing.
  • Max 128 entries per request to bound memory overhead.

SQLi-only for now; other RASP types lack the same high-frequency repeated-call pattern.

Testing

Three new unit tests in `tests/appsec/appsec/test_processor.py`:

  • A clean result is cached; the second identical call skips subcontext creation and WAF execution entirely.
  • The cache key invalidates when new persistent addresses arrive mid-request, forcing a fresh WAF evaluation.
  • Positive results are never cached; both calls run the full WAF.

Risks

Cache correctness depends on WAF determinism for a given `(sql, db_system, inherited_addresses)` triple, which holds for current SQLi rules. The cache is conservative: any non-trivially-clean result skips caching. False negatives (unnecessary re-evaluation) are possible; false positives (skipping a real attack) are not.

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 9 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-py | build linux serverless: [amd64, cp315-cp315, v113741238-d2b8243-manylinux2014_x86_64, 1]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-py | build linux serverless: [amd64, cp315-cp315, v113741491-d2b8243-musllinux_1_2_x86_64, 1]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-py | build linux serverless: [arm64, cp315-cp315, v113741357-d2b8243-manylinux2014_aarch64, 1]   View in Datadog   GitLab

View all 9 failed jobs.

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 93cfd4d | Docs | Datadog PR Page | Give us feedback!

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codeowners resolved as

ddtrace/appsec/_asm_request_context.py                                  @DataDog/asm-python
ddtrace/appsec/_processor.py                                            @DataDog/asm-python
ddtrace/appsec/_rasp_sqli_cache.py                                      @DataDog/asm-python
tests/appsec/appsec/test_processor.py                                   @DataDog/asm-python

@pr-commenter

pr-commenter Bot commented Jun 23, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-06-26 11:53:10

Comparing candidate commit 93cfd4d in PR branch codex/appsec-sqli-rasp-cache-poc with baseline commit a8afd8c in branch main.

Found 0 performance improvements and 3 performance regressions! Performance is the same for 81 metrics, 0 unstable metrics.

scenario:iast_aspects-re_expand_aspect

  • 🟥 execution_time [+252.394µs; +302.956µs] or [+7.066%; +8.482%]

scenario:iast_aspects-re_match_noaspect

  • 🟥 execution_time [+34.495µs; +40.925µs] or [+10.458%; +12.407%]

scenario:iastaspectsospath-ospathbasename_aspect

  • 🟥 execution_time [+88.437µs; +99.460µs] or [+19.748%; +22.210%]

@florentinl florentinl added the changelog/no-changelog A changelog entry is not required for this PR. label Jun 23, 2026
@florentinl florentinl force-pushed the codex/appsec-sqli-rasp-cache-poc branch 5 times, most recently from f28fea8 to 10e550f Compare June 26, 2026 11:25
Introduces RaspSqliCache, a per-request negative cache for SQLi RASP
evaluations. Repeated identical queries in one request skip WAF
subcontext allocation entirely on cache hit.

Design choices:
- Cache key is (hash(sql), db_system): uses hash() instead of the sql
  string reference to avoid holding query strings in memory for the
  request lifetime. Python's SipHash-1-3 with a 128-bit random seed
  makes adversarial collisions infeasible; accidental collision
  probability at 128 entries is ~4e-16 per request.
- Invalidation is event-driven: the cache is cleared whenever the
  processor sends a new persistent address to the WAF main context
  (rule_type is None branch in _waf_action), since the WAF subcontext
  inherits main context data and previously-cached results may not hold.
- Max 128 entries per request; only clean results (return_code==0, no
  data/actions/timeout/meta) are cached.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@florentinl florentinl force-pushed the codex/appsec-sqli-rasp-cache-poc branch from 10e550f to 93cfd4d Compare June 26, 2026 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/no-changelog A changelog entry is not required for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant