Skip to main content

Environment Variables

This page is the source of truth for every environment variable AiSOC reads at runtime. Each section maps to a single service, mirroring the layout of services/ in the repo.

If you spot drift between this page and the code, please open a PR — the matching config files are linked at the top of every section.


API service (services/api)

Source: services/api/app/core/config.py

The API uses bare environment variable names (no prefix). Booleans accept true / false; lists accept comma-separated strings.

Application

VariableDefaultDescription
APP_NAMEAiSOC APIDisplay name in OpenAPI docs
APP_VERSION0.1.0Reported in /healthz
ENV / ENVIRONMENTdevelopmentOne of development, staging, production
DEBUGfalseEnables verbose error responses — never enable in production
API_PREFIX/api/v1Mount point for the versioned API
LOG_LEVELINFODEBUG, INFO, WARNING, ERROR

Security and tokens

VariableDefaultDescription
SECRET_KEYchange-me-in-production-at-least-32-charsRequired in production. Signs primary access/refresh JWTs. Generate with openssl rand -hex 32.
ACCESS_TOKEN_EXPIRE_MINUTES30Lifetime of an access token
REFRESH_TOKEN_EXPIRE_DAYS7Lifetime of a refresh token
ALGORITHMHS256JWT signing algorithm

Audit log

Source: services/api/app/services/audit.py, services/api/app/core/trusted_proxy.py, services/api/app/services/audit_redaction.py. Background: Security operations → Audit logging.

VariableDefaultDescription
AISOC_TRUSTED_PROXIESemptyComma-separated list of CIDRs (e.g. 10.0.0.0/8,192.168.0.0/16) for trusted ingress / load balancer hops. When empty, X-Forwarded-For is ignored and actor_ip is the direct TCP peer — set this in production so the audit log records the real client IP without being spoofable from the public side.
AISOC_AUDIT_MAX_CHANGES_BYTES65536Hard cap on the serialized changes payload stored per audit row. Over-sized values are replaced with a { "_truncated": true, "_size": <bytes> } marker. Set higher only if you genuinely need richer diffs and have provisioned the storage.

Passkeys (WebAuthn)

VariableDefaultDescription
PASSKEY_RP_IDlocalhostRelying-party ID — must match the eTLD+1 of the PWA origin (no scheme, no port)
PASSKEY_RP_NAMEAiSOCDisplay name shown in the OS passkey prompt
PASSKEY_RP_ORIGINShttp://localhost:3000,http://localhost:3001Comma-separated list of allowed origins
PASSKEY_CHALLENGE_TTL_SECONDS300Lifetime of a single ceremony challenge

SSO — OIDC

Source: services/api/app/auth/oidc.py

VariableDefaultDescription
OIDC_ISSUEROIDC issuer URL (used for .well-known/openid-configuration discovery)
OIDC_CLIENT_IDOAuth client ID
OIDC_CLIENT_SECRETOAuth client secret (omit if PKCE-only)
OIDC_REDIRECT_URIRedirect URI registered with the IdP
OIDC_SCOPESopenid profile emailSpace-separated scope list
OIDC_PKCEtrueEnable PKCE for the authorization code flow
JWT_SECRETchangeme-insecure-defaultRequired in production. Signs SSO-issued session JWTs.
JWT_ALGORITHMHS256SSO JWT signing algorithm
JWT_EXPIRE_MINUTES480SSO JWT lifetime

SSO — SAML 2.0

Source: services/api/app/auth/saml.py

VariableDefaultDescription
SAML_SP_ACS_URLhttp://localhost:8000/auth/saml/acsSP Assertion Consumer Service URL
SAML_SP_ENTITY_IDService provider entity ID
SAML_SP_PRIVATE_KEYPEM-encoded SP signing key
SAML_SP_CERTPEM-encoded SP certificate
SAML_IDP_ENTITY_IDIdP entity ID
SAML_IDP_SSO_URLIdP single sign-on URL
SAML_IDP_SLO_URLIdP single logout URL
SAML_IDP_CERTPEM-encoded IdP certificate
SAML_DEBUGfalseVerbose SAML logging — disable in production

Database, cache, queue

VariableDefaultDescription
DATABASE_URLpostgresql+asyncpg://aisoc:aisoc@localhost:5432/aisocAsync Postgres DSN
DATABASE_POOL_SIZE20SQLAlchemy pool size
DATABASE_MAX_OVERFLOW10SQLAlchemy max overflow
REDIS_URLredis://localhost:6379/0Redis DSN
REDIS_POOL_SIZE20Redis connection pool size
KAFKA_BOOTSTRAP_SERVERSlocalhost:9092Comma-separated broker list (canonical name; KAFKA_BROKERS is honored as a back-compat alias)
KAFKA_TOPIC_EVENTSaisoc.normalized_eventsTopic for normalized events
KAFKA_TOPIC_ALERTSaisoc.alertsTopic for emitted alerts

Search and graph

VariableDefaultDescription
OPENSEARCH_URLhttp://localhost:9200OpenSearch base URL
NEO4J_URIbolt://localhost:7687Neo4j Bolt URI
NEO4J_USERneo4jNeo4j user
NEO4J_PASSWORDNeo4j password
QDRANT_URLhttp://localhost:6333Qdrant base URL
CLICKHOUSE_HOST / CLICKHOUSE_PORT / CLICKHOUSE_DATABASE / CLICKHOUSE_USER / CLICKHOUSE_PASSWORDlocalhost / 9000 / aisoc / default / —ClickHouse connection details

Realtime, demo, and feature toggles

VariableDefaultDescription
REALTIME_BASE_URLhttp://realtime:8086Internal URL the API uses to fan out push events
REALTIME_INTERNAL_TOKENShared secret with the realtime service for internal RPC
AISOC_CORS_ORIGINS(uses service default)Canonical, repo-wide, comma-separated CORS allow-list. Applies to every API, agent, ingest, enrichment, realtime, UEBA, honeytoken, purple-team, and connectors process. See CORS configuration below for the full rules.
CORS_ORIGINShttp://localhost:3000,http://localhost:3001Legacy alias kept for backwards compatibility. AISOC_CORS_ORIGINS takes precedence when both are set.
OTEL_ENDPOINTOTLP collector endpoint
MAX_TENANTS1000Hard cap for multi-tenant deployments
DEFAULT_TENANT_PLANstarterDefault plan for newly provisioned tenants
AISOC_PLUGINS_DIR/opt/aisoc/pluginsFilesystem path the plugin loader scans
PLUGIN_TRUST_MODEwarndisabled skips signature checks, warn records signature_status but loads anyway, strict rejects unsigned/invalid plugins (including OCI installs). See Operations → Security → OCI install hardening.
PLUGIN_TRUSTED_KEYS_DIR/etc/aisoc/plugin-keysDirectory of Ed25519 public keys (*.pem/*.pub) that are allowed to sign plugins.
AISOC_DEMO_MODEfalseWhen true, mutating requests outside the demo tenant return 403
AISOC_DEMO_TENANTdemoTenant slug allowed to write in demo mode
AISOC_DEMO_BANNERDemo data resets daily at 00:00 UTC. All write actions are disabled.Banner text rendered by the web app
AISOC_DISABLE_KAFKA / AISOC_DISABLE_CLICKHOUSE / AISOC_DISABLE_OPENSEARCH / AISOC_DISABLE_NEO4J / AISOC_DISABLE_QDRANTfalseSkip the corresponding subsystem at boot — endpoints that need it return 503

Agents service (services/agents)

Source: services/agents/app/

VariableDefaultDescription
OPENAI_API_KEYRequired. OpenAI key used by the investigator and copilot agents
OPENAI_MODELgpt-4oLLM identifier — set per agent if you need different models
DATABASE_URLpostgresql+asyncpg://aisoc:aisoc@localhost:5432/aisocPostgres DSN for the Investigation Ledger
QDRANT_URLhttp://localhost:6333Vector store for case memory and RAG
ENRICHMENT_SERVICE_URLhttp://enrichment:8011URL of the enrichment service (Go)
API_SERVICE_URL / API_URLhttp://api:8000URL of the FastAPI service
REALTIME_URLhttp://realtime:8086Realtime service URL for streaming agent traces
REALTIME_INTERNAL_TOKEN / INTERNAL_TOKENShared secret matching the API/realtime services
PLAYBOOK_STORE_DIR/data/playbooksFilesystem path for executable playbook DSL files
PLAYBOOK_PACK_ROOT/data/playbook-packsRoot directory for community playbook packs
ATTCK_DATA_PATHPath to a local MITRE ATT&CK STIX bundle (falls back to the bundled snapshot)
OTEL_SERVICE_NAMEaisoc-agentsService name in OTel traces
AISOC_VERSION0.1.0Reported in OTel resource attributes
JAEGER_HOST / JAEGER_PORTlocalhost / 6831Jaeger agent endpoint (used when OTEL_EXPORTER=jaeger)
OTEL_EXPORTER_OTLP_ENDPOINTOTLP collector endpoint
OTEL_EXPORTERotlpOne of otlp, jaeger, console
AISOC_SSRF_ALLOWED_SCHEMEShttp,httpsComma-separated list of URL schemes allowed for outbound http_request and notify playbook steps. Anything else is rejected.
AISOC_SSRF_ALLOW_PRIVATEfalseWhen true, lets playbook steps reach loopback / RFC1918 / link-local destinations. Leave off in production; enable only for self-hosted webhooks on a private network.
AISOC_SSRF_EXTRA_BLOCKED_HOSTSComma-separated extra hosts or IPs to deny in addition to the built-in cloud-metadata block list (169.254.169.254, metadata.google.internal, …).

Realtime service (services/realtime)

Source: services/realtime/src/index.ts

VariableDefaultDescription
PORT8086TCP port for the WebSocket and push endpoints
LOG_LEVELinfoPino log level (trace, debug, info, warn, error)
REDIS_URLredis://localhost:6379/4Redis connection used for the push subscription store
KAFKA_BOOTSTRAP_SERVERSlocalhost:9092Kafka brokers — the realtime service consumes the fused-alerts topic to push to clients
KAFKA_TOPIC_FUSEDaisoc.alerts.fusedTopic with fused alerts that should reach connected SOC clients
VAPID_PUBLIC_KEYVAPID public key for Web Push — generate with npx web-push generate-vapid-keys
VAPID_PRIVATE_KEYVAPID private key — keep in a secret manager
VAPID_SUBJECTmailto:soc@example.comContact email or URL surfaced to push services
INTERNAL_TOKENMust match the API's REALTIME_INTERNAL_TOKEN (used by the API to fan out push events)

MCP server (services/mcp)

Source: services/mcp/src/config.ts

The MCP server runs as a sidecar that exposes Investigation Ledger tools to LLM clients (Claude Desktop, Cursor, Copilot, …) via the Model Context Protocol.

VariableDefaultDescription
AISOC_URLhttp://localhost:8081Base URL of the AiSOC API
AISOC_API_URLfalls back to AISOC_URLOverride the API URL independently if the API and web app live on separate hosts
AISOC_API_KEYLong-lived API key (preferred for server-to-server use)
AISOC_TOKENShort-lived JWT (alternative to AISOC_API_KEY)
AISOC_TIMEOUT_MS20000HTTP timeout for outbound calls to the AiSOC API
AISOC_MCP_VERBOSE0Set to 1 to log every tool invocation to stderr

Ingest service (services/ingest)

Source: services/ingest/internal/config/config.go

VariableDefaultDescription
HTTP_PORT8080HTTP listener port
KAFKA_BOOTSTRAP_SERVERSlocalhost:9092Comma-separated broker list (canonical; KAFKA_BROKERS accepted as alias)
KAFKA_TOPICsecurity.eventsTopic for normalized events
REDIS_ADDRlocalhost:6379Redis address (for dedup and rate limiting)
DATABASE_DSNpostgres://aisoc:aisoc@localhost:5432/aisoc?sslmode=disablePostgres DSN
ATTCK_DATA_PATHPath to MITRE ATT&CK STIX bundle for technique tagging
NORMALIZER_MODEautoauto, strict, or passthrough
MAX_BATCH_SIZE1000Max events flushed per batch
WORKER_COUNT4Number of normalization workers
TENANT_HEADER_KEYX-AiSOC-TenantHTTP header that carries the tenant slug
JWT_SECRETRequired outside ENV=development. HMAC secret used to verify ingest tokens.
METRICS_PORT9090Prometheus exporter port
SHODAN_API_KEYOptional — enables Shodan enrichment when paired with SHODAN_ENRICH_ENABLED=true
SHODAN_ENRICH_ENABLEDfalseToggle Shodan IP enrichment
SHODAN_CACHE_EXPIRY_SECS86400Cache TTL for Shodan lookups
VULN_CORREL_ENABLEDfalseToggle CVE correlation against ingested events
VULN_KAFKA_TOPICsecurity.vulnerabilitiesTopic for emitted vulnerability findings
NVD_API_KEYNVD API key (raises NVD rate limits when set)

UEBA service (services/ueba)

Source: services/ueba/app/core/config.py

Every variable in this section accepts both an unprefixed name (e.g. DATABASE_URL, KAFKA_BOOTSTRAP_SERVERS) and the legacy UEBA_-prefixed name (e.g. UEBA_DATABASE_URL, UEBA_KAFKA_BOOTSTRAP_SERVERS). When both are set, the unprefixed form wins — this matches the convention used by every other Python service in the repo and the services/ueba section of docker-compose.yml, which exports unprefixed names.

The table below shows the canonical (unprefixed) name first and the legacy alias second. New deployments should prefer the unprefixed form. (See PR #135 for the implementation and Issue #134 for the original report.)

VariableLegacy aliasDefaultDescription
DATABASE_URLUEBA_DATABASE_URLpostgresql+asyncpg://aisoc:aisoc@localhost:5432/aisocPostgres DSN for baselines and anomalies
KAFKA_BOOTSTRAP_SERVERSUEBA_KAFKA_BOOTSTRAP_SERVERSlocalhost:9092Kafka brokers
KAFKA_INPUT_TOPICUEBA_KAFKA_INPUT_TOPICsecurity.eventsTopic the scorer consumes
KAFKA_OUTPUT_TOPICUEBA_KAFKA_OUTPUT_TOPICueba.anomaliesTopic the scorer emits to
KAFKA_CONSUMER_GROUPUEBA_KAFKA_CONSUMER_GROUPueba-serviceKafka consumer group ID
BASELINE_WINDOW_DAYSUEBA_BASELINE_WINDOW_DAYS30History window used to compute behavioural baselines
ANOMALY_THRESHOLDUEBA_ANOMALY_THRESHOLD3.0Z-score threshold for flagging an event as anomalous
PEER_GROUP_MIN_SIZEUEBA_PEER_GROUP_MIN_SIZE3Minimum peers required for peer-group analysis
SCORING_BATCH_SIZEUEBA_SCORING_BATCH_SIZE100Events processed per scoring batch
OTEL_ENDPOINTUEBA_OTEL_ENDPOINThttp://localhost:4317OTLP gRPC endpoint
SERVICE_NAMEUEBA_SERVICE_NAMEaisoc-uebaOTel service name
HOSTUEBA_HOST0.0.0.0HTTP listener interface
PORTUEBA_PORT8004HTTP listener port

services/ueba/alembic/env.py follows the same rule: it reads DATABASE_URL first and falls back to UEBA_DATABASE_URL, so alembic upgrade head and the running service always see the same DSN.


Honeytokens service (services/honeytokens)

Source: services/honeytokens/app/core/config.py

All variables use the HONEYTOKEN_ prefix.

VariableDefaultDescription
HONEYTOKEN_DATABASE_URLpostgresql+asyncpg://aisoc:aisoc@localhost:5432/aisocPostgres DSN for token metadata and triggers
HONEYTOKEN_ALERT_WEBHOOK_URLWebhook called when a token is triggered
HONEYTOKEN_ALERT_WEBHOOK_SECRETchangemeRotate before going live. HMAC-SHA256 secret for webhook signing
HONEYTOKEN_TOKEN_TTL_DAYS365Default token expiry
HONEYTOKEN_OTEL_ENDPOINThttp://localhost:4317OTLP collector endpoint
HONEYTOKEN_SERVICE_NAMEaisoc-honeytokensOTel service name
HONEYTOKEN_HOST0.0.0.0HTTP listener interface
HONEYTOKEN_PORT8005HTTP listener port

Purple Team service (services/purple-team)

Source: services/purple-team/app/core/config.py

All variables use the PURPLE_TEAM_ prefix.

VariableDefaultDescription
PURPLE_TEAM_DATABASE_URLpostgresql+asyncpg://aisoc:aisoc@localhost:5432/aisocPostgres DSN for emulation runs and findings
PURPLE_TEAM_CALDERA_URLhttp://localhost:8888Caldera server URL
PURPLE_TEAM_CALDERA_API_KEYADMIN123Rotate before going live. Caldera REST API key
PURPLE_TEAM_ART_REPO_PATH/opt/atomic-red-teamFilesystem path to the Atomic Red Team checkout
PURPLE_TEAM_ART_ATOMICS_PATH/opt/atomic-red-team/atomicsPath to the atomics/ directory inside ART
PURPLE_TEAM_ATTACK_STIX_URLhttps://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.jsonURL to the MITRE ATT&CK STIX bundle
PURPLE_TEAM_OTEL_ENDPOINThttp://localhost:4317OTLP collector endpoint
PURPLE_TEAM_SERVICE_NAMEaisoc-purple-teamOTel service name
PURPLE_TEAM_HOST0.0.0.0HTTP listener interface
PURPLE_TEAM_PORT8006HTTP listener port

Web app (apps/web)

The Next.js frontend reads only public, build-time variables. Anything sensitive belongs in the API layer.

VariableDefaultDescription
NEXT_PUBLIC_API_URLhttp://localhost:8000Base URL the browser uses to reach the API
NEXT_PUBLIC_REALTIME_URLhttp://localhost:8086HTTP base of the realtime service (used for VAPID subscription registration)
NEXT_PUBLIC_WS_URLws://localhost:8086WebSocket URL for the realtime feed
NEXT_PUBLIC_VAPID_PUBLIC_KEYMust match the realtime service's VAPID_PUBLIC_KEY

CORS configuration

CORS is configured the same way across every AiSOC service — Python (FastAPI), Go (ingest, enrichment), and TypeScript (realtime) — by reading a single environment variable. This is the variable to set when you put AiSOC behind a custom domain or want to restrict cross-origin access in production.

Variables

VariablePriorityDescription
AISOC_CORS_ORIGINS1 (canonical)Comma-separated allow-list. Set this in every environment.
CORS_ORIGINS2 (legacy alias)Honoured when AISOC_CORS_ORIGINS is unset. Existing Helm charts and dev scripts that already use this keep working.
(none set)3 (default)Each service falls back to http://localhost:3000, http://localhost:3001, http://127.0.0.1:3000, http://127.0.0.1:3001, https://tryaisoc.com, https://www.tryaisoc.com.

Examples:

# Production, single console domain
AISOC_CORS_ORIGINS=https://soc.example.com

# Multiple consoles (analyst app + responder PWA on a subdomain)
AISOC_CORS_ORIGINS=https://soc.example.com,https://responder.example.com

# Local dev across the standard ports (matches the default)
AISOC_CORS_ORIGINS=http://localhost:3000,http://localhost:3001

Production safety guard

The Python helper at services/api/app/core/cors.py (vendored byte-identical into every Python service) and the TypeScript guard in services/realtime/src/index.ts both refuse to start if the allow-list contains * while allow_credentials is true and any of AISOC_ENV, ENVIRONMENT, or APP_ENV equals production or prod. This catches the canonical CORS misconfiguration (wildcard + cookies / Authorization headers) before the deploy goes live.

Outside production the same combination logs a warning and silently disables credentials — local dev stays usable when someone exports CORS_ORIGINS=*.

The honeytokens, purple-team, and ueba services run with allow_credentials=false by design (no session cookies cross-origin), so wildcard origins are allowed there even in production — useful when honeytoken trip pixels are fetched from arbitrary origins.

Go services (ingest, enrichment) and the realtime service (realtime)

These services read the same AISOC_CORS_ORIGINS / CORS_ORIGINS pair and fall back to the same default allow-list. ingest and enrichment keep AllowCredentials: false (they are token-authenticated per request, not cookie-authenticated). The realtime service runs with credentials enabled because the SSE / WebSocket streams carry the console's session cookie; it enforces the same production wildcard guard as the Python helper.


Example .env (full stack)

# --- API ---
SECRET_KEY=$(openssl rand -hex 32)
ACCESS_TOKEN_EXPIRE_MINUTES=30
DATABASE_URL=postgresql+asyncpg://aisoc:changeme@localhost:5432/aisoc
REDIS_URL=redis://localhost:6379/0
KAFKA_BOOTSTRAP_SERVERS=localhost:9092
OPENSEARCH_URL=http://localhost:9200
QDRANT_URL=http://localhost:6333
# Canonical, applies to every service. Legacy CORS_ORIGINS still works.
AISOC_CORS_ORIGINS=http://localhost:3000

# --- API: SSO (set both blocks if you actually use SSO) ---
JWT_SECRET=$(openssl rand -hex 32)

# --- Agents ---
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o
ENRICHMENT_SERVICE_URL=http://enrichment:8011

# --- Realtime ---
VAPID_PUBLIC_KEY=...
VAPID_PRIVATE_KEY=...
VAPID_SUBJECT=mailto:soc@example.com
INTERNAL_TOKEN=$(openssl rand -hex 32)
REALTIME_INTERNAL_TOKEN=${INTERNAL_TOKEN}

# --- MCP ---
AISOC_URL=http://localhost:8000
AISOC_API_KEY=...

# --- Ingest ---
HTTP_PORT=8080
KAFKA_TOPIC=security.events
DATABASE_DSN=postgres://aisoc:changeme@localhost:5432/aisoc?sslmode=disable
JWT_SECRET=${JWT_SECRET}

# --- UEBA / Honeytokens / Purple Team ---
UEBA_DATABASE_URL=${DATABASE_URL}
HONEYTOKEN_DATABASE_URL=${DATABASE_URL}
HONEYTOKEN_ALERT_WEBHOOK_SECRET=$(openssl rand -hex 32)
PURPLE_TEAM_DATABASE_URL=${DATABASE_URL}
PURPLE_TEAM_CALDERA_API_KEY=...

# --- Web ---
NEXT_PUBLIC_API_URL=http://localhost:8000
NEXT_PUBLIC_REALTIME_URL=ws://localhost:8086
NEXT_PUBLIC_VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY}

Before going to production, run through the Hardening Runbook to make sure every secret has been rotated away from the example values above.