Security configuration
Français : ../configuration.md
Source: services/control-plane/src/eai/config.py (EAI_ prefix, lists comma separated or JSON). General configuration is in operations/en/configuration.md.
EAI_* settings
| Setting | Default | Production recommendation |
|---|---|---|
EAI_ENVIRONMENT | dev | prod (enables the startup guard) |
EAI_AUTH_DEV | false | false (mandatory) |
EAI_AUTH_SPIFFE | false | true behind a trusted proxy |
EAI_CORS_ORIGINS | empty | exact portal origins, never * |
EAI_ALLOWED_HOSTS | * | public API host names |
EAI_TRUSTED_PROXIES | 127.0.0.1/32,::1/128 | gateway pod CIDR (e.g. 10.42.0.0/16), never 0.0.0.0/0 |
EAI_DOCS_ENABLED | true | false |
EAI_SECURITY_HEADERS_ENABLED | true | true |
EAI_HSTS_ENABLED | false | true |
EAI_HSTS_MAX_AGE_SECONDS | 31536000 | 31536000 |
EAI_MAX_BODY_BYTES | 10485760 (10 MiB) | 10485760 |
EAI_MAX_UPLOAD_BYTES | 104857600 (100 MiB) | depending on usage |
EAI_MAX_JSON_DEPTH | 64 | 64 |
EAI_RATE_LIMIT_ENABLED | true | true |
EAI_RATE_LIMIT_PER_MINUTE | 1200 | tune, see runbooks.md |
EAI_RATE_LIMIT_GATEWAY_PER_MINUTE | 600 | tune |
EAI_RATE_LIMIT_AUTH_FAILURES_PER_MINUTE | 20 | 20 or less |
EAI_REDIS_URL | empty (in memory counters) | rediss:// as soon as there are several replicas, stored in OpenBao |
EAI_JWT_ALGORITHMS | RS256,RS384,RS512,PS256,PS384,ES256,ES384 | RS256,PS256,ES256; never HS* or none |
EAI_JWT_LEEWAY_SECONDS | 30 | 30 |
EAI_EGRESS_PRIVATE_ALLOWLIST | empty | exact internal services host:port |
EAI_EGRESS_ALLOWED_PORTS | 80,443,8443 | 443 |
EAI_EGRESS_ALLOW_HTTP | false | false (mandatory) |
EAI_EGRESS_DNS_TIMEOUT_SECONDS | 3.0 | 3.0 |
EAI_PROMPT_INJECTION_DEFAULT_MODE | detect | block |
EAI_MASTER_KEY / EAI_OPENBAO_URL | empty | one of them is mandatory; OpenBao recommended |
EAI_DATABASE_URL | local | sslmode=verify-full with CA |
Production startup guard
eai.security.startup.enforce calls problems(settings) and refuses to start when EAI_ENVIRONMENT=prod, listing every problem (in staging it warns). It refuses:
EAI_AUTH_DEVon;*inEAI_CORS_ORIGINSorEAI_ALLOWED_HOSTS;EAI_EGRESS_ALLOW_HTTPon;- a too wide network in
EAI_TRUSTED_PROXIES; - a symmetric algorithm or
noneinEAI_JWT_ALGORITHMS(symmetric_algorithms); - neither
EAI_MASTER_KEYnorEAI_OPENBAO_URL; EAI_DOCS_ENABLEDon,EAI_HSTS_ENABLEDoff;EAI_DATABASE_URLwithoutsslmode=require,verify-caorverify-full(database_tls);EAI_LIVEKIT_URLset whileEAI_LIVEKIT_API_KEY/EAI_LIVEKIT_API_SECRETare missing, equal to the LiveKit dev server values (devkey/secret) or with a secret shorter than 32 bytes. Outsidedevandtest, creating a realtime session is then refused (503livekit_not_configuredorlivekit_secret_weak).
Tests: test_security_http.py::test_startup_guard_refuses_each_unsafe_prod_setting, test_security_http.py::test_startup_guard_lists_every_problem_and_warns_in_staging.
Tenant security settings
eai.security.tenant_settings (defaults, merge, load, validate):
| Key | Default | Values |
|---|---|---|
prompt_injection_mode | EAI_PROMPT_INJECTION_DEFAULT_MODE | off, detect, block |
untrusted_content_wrapping | true | boolean |
output_links | strip_untrusted | allow, strip_untrusted |
output_link_allowlist | [] | public host names (*.suffix allowed), at most 200 |
egress_allowed_hosts | [] | public host names; when not empty, tenant egress is restricted to this list |
API: GET /v1/security/settings and PUT /v1/security/settings (roles tenant_admin or security_officer, change audited). Tests: test_security_api.py::test_settings_get_put_persist_and_audit, test_security_api.py::test_settings_validation, test_security_api.py::test_settings_roles, test_security_tenant_settings.py::test_validate_rejects_bad_values.
Posture API
GET /v1/security/posture (roles security_officer or tenant_admin) returns active protections (eai.domains.security.posture.protections), checks (checks) and a summary (summary). Check ids: auth_dev_disabled, cors_restricted, allowed_hosts_restricted, hsts_enabled, docs_disabled, secrets_backend, oidc_configured, egress_http_disabled, egress_private_allowlist_narrow, rate_limit_enabled, trusted_proxies_restricted, spiffe_behind_proxy, database_tls, jwt_algorithms_asymmetric, prompt_injection_enabled. Tests: test_security_api.py::test_posture_roles_and_shape, test_security_api.py::test_posture_check_logic. Contract: control-plane-v1.md.
Helm keys (deploy/helm/enterprise-ai/values-prod.yaml)
global.environment: prod,global.networkPolicy: cilium,global.secretsMode: external-secrets.controlPlane.config:EAI_ENVIRONMENT,EAI_AUTH_DEV,EAI_AUTH_SPIFFE,EAI_ALLOWED_HOSTS,EAI_TRUSTED_PROXIES,EAI_DOCS_ENABLED,EAI_HSTS_ENABLED,EAI_MAX_BODY_BYTES,EAI_MAX_UPLOAD_BYTES,EAI_RATE_LIMIT_ENABLED,EAI_JWT_ALGORITHMS,EAI_EGRESS_PRIVATE_ALLOWLIST,EAI_EGRESS_ALLOWED_PORTS,EAI_EGRESS_ALLOW_HTTP,EAI_PROMPT_INJECTION_DEFAULT_MODE, TLS URLs of data services.controlPlane.secrets.keys: sensitive values read from OpenBao (EAI_DATABASE_URL,EAI_REDIS_URL,EAI_MASTER_KEY, ...).controlPlane.secretFormats: required formats (EAI_REDIS_URLas^rediss://,EAI_DATABASE_URLwithsslmode=verify-full,EAI_NATS_URLas^tls://).
Tests: test_platform_security_manifests.py::test_prod_security_settings, test_platform_security_manifests.py::test_prod_configmap_renders_security_settings.
Identity, policy and key settings
Defaults read in services/control-plane/src/eai/config.py.
| Setting | Default | Production recommendation |
|---|---|---|
EAI_OIDC_ISSUER / EAI_OIDC_AUDIENCE | empty | issuer of the Keycloak realm; client audience (makes aud mandatory) |
EAI_OIDC_JWKS_URL | empty (discovered from the issuer) | empty unless the JWKS is served elsewhere |
EAI_OIDC_GROUP_SYNC | true | true (group mappings applied at each sign in) |
EAI_OIDC_PLATFORM_ADMIN_GROUPS | navin-platform-admins | restricted directory group |
EAI_OIDC_TENANT_ADMIN_GROUP_PREFIX | navin-admins- | prefix reserved to administrators |
EAI_LOCAL_AUTH_ENABLED | true | false when everything goes through Keycloak |
EAI_LOCAL_AUTH_MAX_FAILURES / EAI_LOCAL_AUTH_LOCKOUT_SECONDS | 5 / 900 | 5 / 900 or stricter |
EAI_LOCAL_PASSWORD_MIN_LENGTH | 12 | 12 or more |
EAI_LOCAL_SESSION_TTL_SECONDS | 28800 | per the session policy |
EAI_OPA_URL | empty (builtin engine only) | TLS URL of OPA |
EAI_OPA_DELIVERY | push | bundle with EAI_POLICY_BUNDLE_SIGNING_KEY_FILE |
EAI_POLICY_DEFAULT | allow | deny to refuse what no rule covers |
EAI_OPENFGA_URL / EAI_OPENFGA_STORE_ID / EAI_OPENFGA_MODEL_ID | empty (builtin tuples) | dedicated OpenFGA store, with EAI_OPENFGA_API_TOKEN |
EAI_KMS_PROVIDER | none | openbao or pkcs11 |
EAI_CONTEXT_REQUIRED | false | true (mandatory) |
EAI_CONTEXT_SIGNING_KEY_REF | empty | OpenBao reference (mandatory) |
EAI_BREAK_GLASS_MAX_SECONDS | 3600 | 3600 or less |
EAI_APPROVAL_TTL_SECONDS | 86400 (read by eai.domains.approvals.service) | per decision delays |
EAI_FILE_SCAN_MODE | dev | enforce (mandatory) |
EAI_METRICS_TOKEN | empty | 32 characters minimum (mandatory) |
Other startup guard refusals
On top of the list above, eai.security.startup.problems refuses in production:
EAI_METRICS_TOKENmissing or shorter than 32 characters;EAI_RUNTIME_DATABASE_URLwithout TLS,EAI_REDIS_URLwithoutrediss://, Redis withoutEAI_REDIS_PASSWORD_FILE;EAI_OPENBAO_URLandEAI_OPENBAO_TOKENmissing orEAI_AUDIT_SIGNING=hmac;EAI_CONTEXT_REQUIREDoff, a clearEAI_CONTEXT_SIGNING_KEYwithoutEAI_CONTEXT_SIGNING_KEY_REF, or no key at all;EAI_AUDIT_SEAL_SIGNING_KEY_REFmissing,EAI_AUDIT_PAYLOAD_KEKother thanopenbao-transit,EAI_AUDIT_CLOCK_SKEW_SECONDSoutside 0 to 3600;EAI_OPA_DELIVERYother thanpushorbundle, orbundlewithoutEAI_POLICY_BUNDLE_SIGNING_KEY_FILE;- an unknown
EAI_KMS_PROVIDER, orpkcs11withoutEAI_KMS_PKCS11_MODULE,EAI_KMS_PKCS11_TOKEN_LABELandEAI_KMS_PKCS11_PIN_FILE; EAI_FILE_SCAN_MODEother thanenforce, a ClamAV or YARA-X engine outsidehttps://or without a Bearer key;EAI_REALTIME_CONSENT_REQUIRED=false;EAI_SOC_WEBHOOK_SECRETshorter than 32 characters,EAI_SOC_CORRELATION_WINDOW_SECONDSoutside 60 to 86400;EAI_STORAGE_PRESIGN_MAX_SECONDSoutside 1 to 86400,EAI_STORAGE_CAPABILITY_MAX_AGE_SECONDSoutside 60 to 604800, an unreadableEAI_STORAGE_CA_BUNDLE;- a weak
EAI_BOOTSTRAP_ADMIN_PASSWORDwhile local accounts are enabled.
The full hardening procedure is in hardening.md.