Dhal emits security decisions while protecting sensitive request information by default. Observability must be treated as part of the security boundary because events can contain route, identity, IP, and rule metadata.
Redaction
{
"observability": {
"redaction": {
"enabled": true,
"ip": "mask",
"identity": "hash",
"userAgent": "full"
}
}
}IP and identity redaction modes are none, mask, hash, and omit. User agents can be retained with full or removed with omit.
Keep redaction enabled unless a documented incident-response requirement justifies collecting the original value.
Correlation IDs
Dhal checks configured headers in order:
{
"observability": {
"correlation": {
"headers": ["x-request-id", "x-correlation-id", "traceparent"]
}
}
}A custom integration can also set correlationId directly on DhalRequest.
Logs and events
{
"observability": {
"logs": {
"enabled": true,
"format": "json"
},
"events": {
"enabled": true
}
}
}The engine exposes an event bus through protection.events. Application listener failures are isolated from request decisions and counted in the runtime snapshot.
OpenTelemetry
Install the OpenTelemetry API in the host application:
npm install @opentelemetry/api{
"observability": {
"otel": {
"enabled": true,
"serviceName": "orders-api",
"emitAllowedRequests": false
}
}
}Dhal uses the application's OpenTelemetry provider. Configure exporters and resource metadata in the host application.
Signed webhooks
{
"observability": {
"webhooks": {
"enabled": true,
"urls": ["https://security.example.com/dhal/events"],
"timeoutMs": 750,
"emitAllowedRequests": false,
"signing": {
"enabled": true,
"secretEnv": "DHAL_WEBHOOK_SECRET",
"signatureHeader": "x-dhal-signature",
"timestampHeader": "x-dhal-timestamp",
"idHeader": "x-dhal-event-id"
}
}
}
}Webhook receivers should verify the timestamp, event ID, and HMAC signature; reject stale timestamps and replayed IDs; and return a 2xx response only after accepting the event.
Delivery is bounded to prevent unbounded memory growth. Non-2xx responses count as failed deliveries, and deliveries can be drained during shutdown.
Custom telemetry
import type { DhalTelemetry } from "@rokadhq/dhal";
const telemetry: DhalTelemetry = {
recordDecision(event) {
securityQueue.publish(event);
}
};
const protection = createDhal({ telemetry });Synchronous telemetry failures are isolated and counted. A custom adapter should avoid blocking the request path and should implement its own bounded queueing strategy.
Runtime snapshot
const snapshot = protection.getRuntimeSnapshot();
console.log({
inspected: snapshot.inspected,
allowed: snapshot.allowed,
blocked: snapshot.blocked,
wouldBlock: snapshot.wouldBlock,
internalErrors: snapshot.internalErrors,
overBudget: snapshot.overBudget,
eventListenerErrors: snapshot.eventListenerErrors,
telemetryErrors: snapshot.telemetryErrors,
pendingTelemetry: snapshot.telemetry?.pending,
droppedTelemetry: snapshot.telemetry?.dropped
});Export these values through an internal metrics system. Alert on sustained internal errors, over-budget inspections, listener errors, failed deliveries, or dropped telemetry.
Graceful shutdown
async function shutdown(signal: string) {
console.log(`Received ${signal}; draining Dhal.`);
await protection.close(5_000);
process.exit(0);
}
process.once("SIGTERM", () => void shutdown("SIGTERM"));
process.once("SIGINT", () => void shutdown("SIGINT"));flush(timeoutMs?)drains managed telemetry without closing the engine.close(timeoutMs?)stops new inspections, drains telemetry, and removes event listeners.- Calling
inspect()afterclose()throws an error.