Rokad

Reputation and distributed deployments

Use Redis or Valkey state and configure IP reputation without silent security downgrades.

View repository
dhal documentation
Page 6 of 9

Multiple application instances must share security state when they protect the same users and routes. Dhal supports Redis or Valkey for distributed rate-limit and credential-failure counters.

Install the Redis client

bash
npm install ioredis

Configure shared stores

ts
import Redis from "ioredis";
import {
  RedisRateLimitStore,
  RedisSignalStore,
  createDhal
} from "@rokadhq/dhal";

const redis = new Redis(process.env.REDIS_URL!);

const protection = createDhal({
  configPath: "dhal.json",
  rateLimitStore: new RedisRateLimitStore(redis, {
    prefix: "production:dhal:rate-limit"
  }),
  signalStore: new RedisSignalStore(redis, {
    prefix: "production:dhal:signals"
  })
});

Set the declared store in configuration:

json
{
  "rateLimit": {
    "enabled": true,
    "store": "redis"
  }
}

Use separate prefixes for each application and environment. Protect the datastore with authentication, network isolation, TLS where supported, and an eviction policy suitable for short-lived security counters.

No silent fallback in enforcement

Stable v1 refuses to start an enforcing deployment that declares rateLimit.store: "redis" without receiving a distributed rateLimitStore. Monitor-only operation logs a warning and can use the memory store.

This behaviour prevents an intended global limit from silently becoming a weaker per-instance limit.

Credential-stuffing signals use signalStore. Supply RedisSignalStore whenever authentication failures must be counted across instances.

IP reputation

json
{
  "ip": {
    "reputation": {
      "enabled": true,
      "provider": "abuseipdb",
      "apiKeyEnv": "ABUSEIPDB_API_KEY",
      "minScore": 75,
      "cacheTtlSeconds": 86400,
      "maxAgeInDays": 30,
      "mode": "async",
      "timeoutMs": 750
    }
  }
}

Set the API key in the environment, never in dhal.json:

bash
export ABUSEIPDB_API_KEY="..."

Use async mode for general traffic when reputation should enrich subsequent decisions without adding provider latency to every request. Use blocking only when the route can tolerate provider latency and dependency failure.

An enforcing deployment with blocking reputation refuses to start if no provider is available.

Route-level reputation

json
{
  "routes": {
    "/api/admin/*": {
      "mode": "block",
      "ipReputation": {
        "enabled": true,
        "minScore": 60,
        "mode": "blocking"
      }
    }
  }
}

Custom provider

ts
import type { IpReputationProvider } from "@rokadhq/dhal";

const provider: IpReputationProvider = {
  name: "internal-reputation",
  async check(ip) {
    return {
      ip,
      provider: "internal-reputation",
      score: 0,
      checkedAt: Date.now(),
      expiresAt: Date.now() + 60_000
    };
  }
};

const protection = createDhal({
  configPath: "dhal.json",
  ipReputationProvider: provider
});

Provider scores use a 0 to 100 scale, with larger values representing greater risk.

Production checklist

  • Share rate-limit state across all instances serving the same routes.
  • Share credential-failure state for authentication controls.
  • Separate key prefixes by application and environment.
  • Monitor datastore latency and errors.
  • Test datastore failure behaviour before enforcement.
  • Use blocking reputation only on routes with an explicit latency and availability decision.
  • Keep provider and Redis credentials outside the configuration file.