Skip to content

Configuration Drift Detection

Monitors security-relevant configuration (env vars, DB settings, admin roles) and detects when values change from the captured baseline.

How It Works

Trigger Points

  1. Startup: Captures the initial baseline when initDriftDetection() runs
  2. Scheduled: Periodic checks via cron (default: every 5 minutes)
  3. Event-driven: Immediately after SettingsService.updateSingleton() modifies settings

Monitored Configurations

Environment Variables

KeyCategory
SSRF_PROTECTION_ENABLEDsecurity
RATE_LIMITER_ENABLEDsecurity
RATE_LIMITER_STOREsecurity
RATE_LIMITER_POINTSsecurity
RATE_LIMITER_DURATIONsecurity
RATE_LIMITER_GLOBAL_ENABLEDsecurity
AUDIT_HASH_ENABLEDsecurity
SECURITY_EVENTS_ENABLEDsecurity
CORS_ENABLEDsecurity
CORS_ORIGINsecurity
SESSION_COOKIE_ENABLEDauth
ACCESS_TOKEN_TTLauth
REFRESH_TOKEN_TTLauth
WEBSOCKETS_REST_AUTHauth
WEBSOCKETS_GRAPHQL_AUTHauth

Database Settings (odp_settings)

ColumnCategory
auth_login_attemptsauth
auth_password_policyauth
public_registrationauth
public_registration_verify_emailauth
public_registration_rolepermission
module_barsecurity

Admin Roles

All roles with admin_access = true are tracked (ID + name). Adding, removing, or renaming an admin role triggers a drift event.

Baseline & Hash

The baseline is a sorted array of { key, value, category } entries. The hash is computed as:

SHA-256( JSON.stringify( entries.map(e => [e.key, e.value]) ) )

This produces a single hash that summarizes the entire security configuration state.

Cross-Node Comparison

When Redis is available, each node publishes its config hash to odp:drift:{nodeId} with a 10-minute TTL. If two nodes have different hashes, their configurations are inconsistent — typically due to different env vars across pods.

Without Redis, only local drift detection runs (comparing current state vs the stored baseline on the same node).

Drift Report

When drift is detected, the security event includes:

json
{
  "type": "DRIFT_DETECTED",
  "severity": "warning",
  "details": {
    "nodeId": "pod-api-abc123",
    "driftCount": 2,
    "items": [
      {
        "key": "env:RATE_LIMITER_POINTS",
        "category": "security",
        "expected": "50",
        "actual": "100"
      },
      {
        "key": "db:auth_login_attempts",
        "category": "auth",
        "expected": "25",
        "actual": "10"
      }
    ]
  }
}

Node Identification

Node ID is resolved in order:

  1. HOSTNAME env var (set automatically in Kubernetes as the pod name)
  2. os.hostname()
  3. crypto.randomUUID() fallback

Configuration

VariableTypeDefaultDescription
DRIFT_DETECTION_ENABLEDbooleanfalseEnable drift detection
DRIFT_CHECK_SCHEDULEstring'*/5 * * * *'Cron schedule for periodic checks

Source Files

FilePurpose
src/security/drift/types.tsSecurityBaseline, DriftReport, DriftItem interfaces
src/security/drift/baseline.tscaptureCurrentBaseline(), compareBaselines()
src/security/drift/monitor.tscheckDrift() — compare + publish
src/security/drift/scheduler.tsinitDriftDetection() — startup + cron
src/security/drift/node-id.tsgetNodeId()
src/services/settings.tsEvent-driven drift check on settings update

ODP Internal API Documentation