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
npm install ioredisConfigure shared stores
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:
{
"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
{
"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:
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
{
"routes": {
"/api/admin/*": {
"mode": "block",
"ipReputation": {
"enabled": true,
"minScore": 60,
"mode": "blocking"
}
}
}
}Custom provider
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.