Skip to content

Add server-side bot protection via DataDome Protection API #317

@ChristianPavilonis

Description

@ChristianPavilonis

Summary

Trusted Server already has a first-party proxy integration for DataDome (client-side SDK + signal collection proxied through the publisher's domain). The official DataDome Fastly Compute module adds a second layer: server-side request validation that calls DataDome's Protection API on every incoming request to block/challenge bots before they reach the origin.

This proposal is to implement that server-side validation layer in Rust, making it edge-provider agnostic — not tied to Fastly's JS SDK or any other platform-specific runtime.

Background

Two Layers of DataDome Integration

Layer Status What It Does
1. First-party proxy Built (currently disabled) Proxies tags.js + signal collection through publisher domain. Rewrites HTML/DOM references to use first-party paths.
2. Server-side validation Not built On every request, calls DataDome's Protection API with request metadata. Blocks/challenges bots before they reach the origin.

Layer 1 makes client-side signals more reliable (bypasses ad blockers that block datadome.co). Layer 2 blocks bots even without client-side signals. Together they provide full coverage.

What the Official Fastly Compute Module Does

The @datadome/module-fastly-compute npm package:

  1. Intercepts every incoming request before it reaches the origin
  2. Sends request metadata to POST https://api.datadome.co/validate-request/ (the Protection API)
  3. Blocks or challenges bots (returns 403, CAPTCHA pages, etc.)
  4. Only forwards allowed requests to the origin, enriched with DataDome metadata headers
  5. Fails open on timeout (allows the request through)

Proposal

New IntegrationRequestFilter Trait

The integration registry currently has no pre-request filtering hook. A new trait would be added:

#[async_trait(?Send)]
pub trait IntegrationRequestFilter: Send + Sync {
    fn integration_id(&self) -> &'static str;
    
    /// Return Some(Response) to block/challenge, None to allow.
    async fn filter_request(
        &self,
        settings: &Settings,
        req: &Request,
    ) -> Option<Result<Response, Report<TrustedServerError>>>;
}

This is a natural extension of the existing registry pattern (IntegrationProxy, IntegrationAttributeRewriter, IntegrationScriptRewriter, etc.) — it's the one missing lifecycle hook.

Hook Point

Insert after enforce_basic_auth() in route_request(), before any route matching:

for filter in integration_registry.request_filters() {
    if let Some(response) = filter.filter_request(settings, &req).await {
        return response;
    }
}

Request Flow

Client Request
  → enforce_basic_auth()
  → IntegrationRequestFilter (NEW — DataDome Protection API call)
      → Bot? → Return block/challenge response
      → Allowed? → Continue
      → Timeout? → Fail open, continue
  → Route matching (integration proxies, publisher origin, etc.)
  → Response

DataDome Implementation

The DataDome IntegrationRequestFilter would:

  1. Check if the URL matches the exclusion pattern (skip static assets like .css, .js, .png, etc.)
  2. Extract request metadata (IP via Fastly-Client-IP / X-Forwarded-For, User-Agent, Referer, URL, method, cookies, headers)
  3. POST to DataDome's Protection API with the server-side key
  4. If DataDome says block → return the block/challenge response directly
  5. If DataDome says allow → return None, continue normal processing
  6. On timeout → fail open (allow the request)

Configuration

[integrations.datadome]
enabled = true
# Existing first-party proxy settings (unchanged)
sdk_origin = "https://js.datadome.co"
api_origin = "https://api-js.datadome.co"
cache_ttl_seconds = 3600
rewrite_sdk = true

# New server-side validation settings
server_side_key = ""
client_side_key = ""
protection_api = "https://api.datadome.co"
enable_protection = false          # Independent toggle for Layer 2
url_pattern_exclusion = "\\.(css|js|ico|jpg|png|gif|svg|woff2?)$"
timeout_ms = 1500

Why This Makes Sense

  1. Edge-agnostic — The Protection API is plain HTTP. Rust/WASM can call it from Fastly, Cloudflare, or any edge platform. Official DataDome modules are platform-specific. This would be the first Rust implementation.
  2. Complements existing integration — Layer 1 (proxy) + Layer 2 (validation) together provide full bot protection coverage.
  3. Ad protection — DataDome has an "Ad Protect" product for invalid traffic (IVT). Server-side validation could filter bots before auction/bidding runs, saving bid costs.
  4. Clean architectureIntegrationRequestFilter is a natural addition to the registry trait system and could be used by other integrations in the future.

Tradeoffs to Consider

Concern Detail
Latency Extra roundtrip to DataDome API per request (~1-5ms from edge POPs). URL exclusion pattern mitigates this for static assets.
Cost DataDome bills per validated request. Exclusion patterns for static/internal routes are important.
Fail-open On timeout/error, requests are allowed through (standard across all DataDome modules).

Files Involved

  • crates/common/src/integrations/datadome.rs — Extend with IntegrationRequestFilter impl
  • crates/common/src/integrations/registry.rs — Add IntegrationRequestFilter trait + registry support
  • crates/fastly/src/main.rs — Add filter hook in route_request()
  • trusted-server.toml — Add new config fields

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions