Security & Auth Configuration

Configuration for external authentication, Vertex AI, OAuth, auth profiles, tool policies, proactive memory, context engine, audit logging, health checks, plugins, prompt intelligence, and environment variables reference.


[external_auth]

Configures OAuth2/OIDC external authentication, allowing users to log in via identity providers like Google, GitHub, Okta, Auth0, or Keycloak.

[external_auth]
enabled = true
issuer_url = "https://accounts.google.com"
client_id = "your-client-id.apps.googleusercontent.com"
client_secret_env = "LIBREFANG_OAUTH_CLIENT_SECRET"
redirect_url = "http://127.0.0.1:4545/api/auth/callback"
scopes = ["openid", "profile", "email"]
allowed_domains = ["example.com"]
session_ttl_secs = 86400

# Where in the token the identity attributes both maps below are matched
# against are read from. Dotted paths; `<client>` is substituted with this
# provider's client_id. Defaults to ["roles", "groups"].
claim_paths = ["realm_access.roles", "resource_access.<client>.roles", "groups"]

# Which IdP groups may call the API, and at what privilege.
# Empty by default: without it an OIDC bearer authenticates nothing.
[external_auth.role_map]
"librefang-owners" = "owner"
"librefang-operators" = "admin"
"engineering" = "user"

# Which IdP groups confer membership in a local [[groups]] entry.
# Independent of role_map: membership carries no privilege.
[external_auth.group_map]
"platform-oncall" = "oncall"
"sox-reviewers" = "compliance"
FieldTypeDefaultDescription
enabledboolfalseEnable external authentication.
issuer_urlstring""OIDC issuer URL for provider discovery at {issuer_url}/.well-known/openid-configuration.
client_idstring""OAuth2 client ID registered with the identity provider.
client_secret_envstring"LIBREFANG_OAUTH_CLIENT_SECRET"Env var name holding the OAuth2 client secret.
redirect_urlstring"http://127.0.0.1:4545/api/auth/callback"OAuth2 authorization code flow callback URL.
scopeslist of strings["openid","profile","email"]OAuth2 scopes to request.
allowed_domainslist of strings[]Restrict login to these email domains. Empty = allow all.
audiencestring""JWT audience claim to validate. Defaults to client_id if empty.
session_ttl_secsu6486400Session token lifetime in seconds (24 hours default).
require_email_verifiedbooltrueReject logins whose ID token does not carry email_verified = true. Set to false only for a provider that does not issue the claim.
providerslist of objects[]Multiple OIDC/OAuth2 providers. When configured, takes precedence over the single-provider fields above.
role_maptable of string → string{}IdP role/group claim value → LibreFang role (owner / admin / user / viewer / guest). See below.
group_maptable of string → string{}IdP group/role/scope claim value → the name of a [[groups]] entry the caller is treated as a member of. See below.
claim_pathslist of strings["roles","groups"]Dotted paths to the claims both maps are matched against. <client> is substituted with the provider's client_id. See below.

role_map: turning an ID token into an API credential

Until an entry exists here, a validated OIDC bearer authorizes nothing — the daemon verifies the token against the provider's JWKS and the caller still gets the same 401 they would get with no token at all. Declaring a mapping is what grants privilege, so the grant is always something an operator wrote down rather than something the identity provider decided on its own.

The keys are values of whichever claims claim_paths resolves — the roles and groups claims by default — and the values are LibreFang roles in the same viewer < user < admin < owner ladder that [[users]] role and [channel_role_mapping] use. A caller holding several mapped groups gets the highest-privilege match, so claim ordering — which is the provider's business, not yours — cannot decide the effective role.

Three things deliberately grant nothing rather than falling back to a default:

  • a claim value absent from the map,
  • a claim value present but mapped to an unrecognised role string (a typo like "admn", which is reported as a WARN at boot and on config reload),
  • a token from a provider with neither audience nor client_id configured, because JWT audience validation is disabled in that case and any token signed by the issuer's keys would otherwise validate.

require_email_verified applies here too: a token whose email the provider did not verify carries no role.

The mapping is read live from the config swap, so POST /api/config/reload applies an edit on the next request with no restart. It is deliberately not writable through POST /api/config/set — a caller who could edit it could grant themselves owner by naming a group they already hold — but GET /api/config reads it back so you can confirm which groups currently carry privilege.

group_map: putting an IdP group on a LibreFang team

[[groups]] (see the identity section) names a team — an on-call rota, a project group, a department — that permissions and ownership can refer to instead of enumerating individuals. group_map is how membership in one of those teams comes from your identity provider instead of being typed in by hand and then going stale when someone changes roles.

The keys are claim values, exactly as in role_map; the values are [[groups]] names. As with roles, membership is mapped and never name-matched: an IdP group called oncall does not join the local oncall group unless you wrote that entry. The reason is worth being explicit about — in a tenant where creating a group is self-service, name-matching would mean anyone who can create a group could pick a name and acquire whatever that team owns.

What membership does and does not do:

  • It does confer the group's own name and every string in its roles list as binding-rule role strings, and it makes the caller a member of the group for ownership purposes.
  • It does not confer any RBAC privilege. A group named owner is a team named owner. If you want one IdP claim to carry both membership and privilege, write it into role_map and group_map — two entries, deliberately.

Membership is recomputed from the presented token on every request and is never written into config.toml. That is what makes deprovisioning propagate: remove someone from the group in your identity provider and the membership disappears here when their current token expires, with nothing to clean up locally. It also means the reverse does not happen — an IdP that stops asserting a group does not remove that person from a [[groups]] members list you typed. A name in members is your own grant, and only you can withdraw it. When both sources name the same person, the result is simply the union.

A group_map value naming a group that does not exist in [[groups]] confers nothing and is reported as a WARN at boot and on config reload — including when the group was deleted or renamed without the map being touched, which is the usual way this happens.

GET /api/authz/whoami reports what the calling credential actually resolved to: role, principal, effective groups, which of those came from the token, and the group-conferred role strings. It is the first thing to check when an SSO login has less access than expected.

claim_paths: where the claim values come from

Both maps are matched against the same resolved set of claim values, and claim_paths decides which parts of the token that set is built from. Entries are dotted paths, so Keycloak's realm_access.roles and resource_access.<client>.roles are addressable; <client> is substituted with the provider's own client_id, so one entry works across providers and never picks up another client's roles from the same token. A path resolving to an array of strings contributes its elements; one resolving to a single string contributes its whitespace-separated words. A path that resolves to nothing is not an error — providers differ in what they emit.

The default is ["roles", "groups"]. scope is deliberately not included: roles and groups are assertions your identity provider makes about the user, while scope describes what a client application asked for and was granted, which a token minted for a different OAuth client in the same tenant also carries. Add "scope" to the list if you want it, and only where you have set audience — without audience binding the daemon refuses to derive any grant at all.

Note that adding a path widens role_map as well as group_map, since both read the same set.

group_map and claim_paths are read-only through POST /api/config/set for the same reason as role_map, and readable through GET /api/config for the same reason too.

For multi-provider setups, use [[external_auth.providers]] with fields: id, display_name, issuer_url, auth_url, token_url, userinfo_url, jwks_uri, client_id, client_secret_env, redirect_url, scopes, allowed_domains, audience.


[terminal]

Configures access control for the interactive terminal WebSocket endpoint.

[terminal]
enabled = true
allow_remote = false
allowed_origins = ["https://dashboard.example.com"]
tmux_enabled = true
max_windows = 16
# tmux_binary_path = "/usr/local/bin/tmux"
FieldTypeDefaultDescription
enabledbooltrueMaster switch for the terminal feature. When false, the terminal WebSocket endpoint is disabled entirely.
allow_remoteboolfalseAllows access from remote or proxied connections. When no auth is configured, allow_unauthenticated_remote must also be true or the connection is refused. Default behavior is local-only access without auth.
allow_unauthenticated_remoteboolfalseHard foot-gun guard. Must be explicitly set to true to expose an unauthenticated shell over the network when allow_remote = true and no auth is configured. Otherwise such connections are refused even if allow_remote = true.
allowed_originslist of strings[]Additional browser Origin values allowed for terminal WebSocket connections beyond localhost. Use this when the dashboard is served from a custom domain. [*] allows any HTTP/HTTPS origin and should only be used intentionally.
require_proxy_headersboolfalseWhen true, loopback connections without proxy headers (X-Forwarded-For, X-Real-IP) are rejected. Enable only when running behind a reverse proxy that injects these headers. (Old name: trust_proxy_headers, still accepted as alias.)
tmux_enabledbooltrueEnable tmux-backed multi-window terminal. Only effective when the tmux binary is available on the system.
max_windowsu3216Maximum number of tmux windows that may exist simultaneously. Guards against resource exhaustion.
tmux_binary_pathstring or nullnullExplicit path to the tmux binary. If null, resolved via PATH.

Notes:

  • Missing Origin is allowed for non-browser clients.
  • allow_remote = true does not disable auth; if API keys or dashboard credentials are configured, remote clients still need valid auth.
  • Prefer explicit HTTPS origins over "*" for browser access.
  • The ws_terminal_messages_per_minute rate limit (default: 3600) in the [rate_limit] section controls per-connection WebSocket message throughput for interactive terminal sessions.

[vertex_ai]

Configures Google Cloud Vertex AI as an LLM provider.

[vertex_ai]
project_id = "my-gcp-project"
region = "us-central1"
credentials_path = "/path/to/service-account.json"

Credentials are resolved in this order:

  1. credentials_path in config (JSON string or file path)
  2. VERTEX_AI_SERVICE_ACCOUNT_JSON env var
  3. GOOGLE_APPLICATION_CREDENTIALS env var (file path)
  4. gcloud auth print-access-token CLI fallback
FieldTypeDefaultDescription
project_idstring or nullnullGCP project ID. Falls back to VERTEX_AI_PROJECT_ID, GOOGLE_CLOUD_PROJECT, or the project_id field in the service account JSON.
regionstring or nullnullGCP region for the Vertex AI endpoint. Falls back to VERTEX_AI_REGION or GOOGLE_CLOUD_REGION env var. Default: "us-central1".
credentials_pathstring or nullnullPath to a GCP service account JSON key file, or the raw JSON string.

[oauth]

Configures OAuth client IDs for PKCE (Proof Key for Code Exchange) flows used by the dashboard.

[oauth]
google_client_id = "your-google-client-id.apps.googleusercontent.com"
github_client_id = "your-github-app-client-id"
microsoft_client_id = "your-azure-app-client-id"
slack_client_id = "your-slack-app-client-id"
FieldTypeDefaultDescription
google_client_idstring or nullnullGoogle OAuth2 client ID for PKCE flow.
github_client_idstring or nullnullGitHub OAuth app client ID for PKCE flow.
microsoft_client_idstring or nullnullMicrosoft (Entra ID / Azure AD) OAuth application client ID.
slack_client_idstring or nullnullSlack OAuth app client ID.

[auth_profiles]

Configures multiple API key profiles per provider to enable key rotation when one key is rate-limited or exhausted.

[auth_profiles]
anthropic = [
  {name = "primary", api_key_env = "ANTHROPIC_API_KEY_1", priority = 0},
  {name = "secondary", api_key_env = "ANTHROPIC_API_KEY_2", priority = 1},
]
openai = [
  {name = "main", api_key_env = "OPENAI_API_KEY", priority = 0},
]

The value is a map from provider name to a list of AuthProfile objects:

FieldTypeDefaultDescription
namestringrequiredProfile name (e.g., "primary", "secondary").
api_key_envstringrequiredEnv var name holding the API key for this profile.
priorityu320Priority for key selection. Lower value = preferred.

[tool_policy]

Configures global tool access rules, groups, and recursion depth limits. References the ToolPolicy type.

[tool_policy]
subagent_max_depth = 10
subagent_max_concurrent = 5

[[tool_policy.global_rules]]
pattern = "shell_*"
effect = "deny"

[[tool_policy.groups]]
name = "web_tools"
tools = ["web_search", "web_fetch"]
FieldTypeDefaultDescription
agent_ruleslist of ToolPolicyRule[]Per-agent tool rules (highest priority, checked first).
global_ruleslist of ToolPolicyRule[]Global tool rules applied to all agents (checked after agent rules).
groupslist of ToolGroup[]Named tool groups for reuse in rules.
subagent_max_depthu3210Maximum subagent spawning depth.
subagent_max_concurrentu325Maximum concurrent subagents.

ToolPolicyRule fields:

FieldTypeDescription
patternstringGlob pattern to match tool names (e.g., "shell_*", "web_*", "mcp_github_*").
effectstring"allow" or "deny". Deny-wins: if any deny rule matches, the tool is blocked regardless of allow rules.

ToolGroup fields:

FieldTypeDescription
namestringGroup name (e.g., "web_tools", "code_tools").
toolslist of stringsTool name patterns included in this group.

[proactive_memory]

Configures proactive memory extraction (mem0-style automatic memory management). References the ProactiveMemoryConfig type.

[proactive_memory]
enabled = true
auto_memorize = true
auto_retrieve = true
max_retrieve = 10
extraction_threshold = 0.7
# extraction_model = "gpt-4o-mini"             # uses default provider
# extraction_model = "anthropic/claude-haiku-4" # targets a specific provider
# extraction_model = "anthropic:claude-haiku-4" # colon form also works
extract_categories = ["user_preference", "important_fact", "task_context", "relationship"]
session_ttl_hours = 24
duplicate_threshold = 0.5
confidence_decay_rate = 0.01
max_memories_per_agent = 1000
FieldTypeDefaultDescription
enabledbooltrueMaster toggle — when false, the entire proactive memory subsystem is disabled.
auto_memorizebooltrueAutomatically extract and store memories after each agent execution.
auto_retrievebooltrueAutomatically retrieve relevant memories before each agent execution.
max_retrieveusize10Maximum number of memories to retrieve per query.
extraction_thresholdf320.7Confidence threshold for near-duplicate detection (0.0–1.0).
extraction_modelstring or nullnullLLM model for extraction. Supports provider/model (e.g. "anthropic/claude-haiku-4"), provider:model, or bare model name (uses default provider). If null, uses rule-based extraction. There is no separate extraction_provider field.
extract_categorieslist of strings["user_preference", "important_fact", "task_context", "relationship"]Categories to extract from conversations.
session_ttl_hoursu3224Session memory TTL in hours. Memories older than this are cleaned up before each agent execution.
duplicate_thresholdf320.5Similarity threshold for duplicate detection (0.0–1.0). Uses vector cosine similarity when embeddings are available, otherwise falls back to Jaccard word overlap.
confidence_decay_ratef640.01Confidence decay rate per day. Follows exponential decay: conf × e^(−rate × days). Default of 0.01 takes ~70 days to halve.
max_memories_per_agentusize1000Maximum memories per agent. When exceeded, raw dialogue rows are evicted before extracted facts, lowest-confidence first within each class. 0 = no cap.

[context_engine]

Configures the pluggable context assembly engine that controls how agent memory is recalled and assembled into prompts.

[context_engine]
engine = "default"
# plugin = "qdrant-recall"    # resolves to ~/.librefang/plugins/qdrant-recall/

[context_engine.hooks]
# ingest = "~/.librefang/scripts/my_recall.py"
# after_turn = "~/.librefang/scripts/my_indexer.py"
# runtime = "python"   # python (default) | v | node | deno | go | native

[[context_engine.plugin_registries]]
name = "Official"
github_repo = "librefang/librefang-registry"
FieldTypeDefaultDescription
enginestring"default"Built-in engine name. Currently only "default" is supported.
pluginstring or nullnullPlugin name. Resolves to ~/.librefang/plugins/<name>/plugin.toml. Takes precedence over manual hooks if set.
hooks.ingeststring or nullnullScript path for the ingest hook (called on new user message).
hooks.after_turnstring or nullnullScript path for the after_turn hook (called after each completed turn).
hooks.runtimestring or null"python"Which launcher runs the hook scripts. Supported: python, v, node, deno, go, native (exec a pre-compiled binary).
plugin_registrieslist of objectsOfficial registryPlugin registries (GitHub owner/repo) to browse for installable plugins.

[audit]

Configures audit log retention.

[audit]
retention_days = 90
FieldTypeDefaultDescription
retention_daysu3290Number of days to retain audit log entries. 0 = unlimited retention.

[health_check]

Configures periodic health checks for LLM providers.

[health_check]
health_check_interval_secs = 60
FieldTypeDefaultDescription
health_check_interval_secsu6460Interval in seconds between provider health checks.

[plugins]

Configures additional plugin registries to search for installable context engine plugins.

[plugins]
plugin_registries = ["acme-corp/librefang-plugins"]
FieldTypeDefaultDescription
plugin_registrieslist of strings[]Additional GitHub owner/repo plugin registries. Merged with context_engine.plugin_registries.

[prompt_intelligence]

Configures prompt versioning and A/B experiment support. When enabled, LibreFang automatically tracks prompt version history and supports running A/B experiments to compare prompt variants. See the Prompt Intelligence guide for full documentation.

[prompt_intelligence]
enabled = false
hash_prompts = true
max_versions_per_agent = 50
FieldTypeDefaultDescription
enabledboolfalseMaster toggle. When false, no prompt versions are tracked and experiments are skipped.
hash_promptsbooltrueCompute content hashes for prompt versions.
max_versions_per_agentu3250Maximum prompt versions per agent. Oldest inactive versions are pruned when exceeded.

Environment Variables

Complete table of all environment variables referenced by the configuration. None of these are read by the config file itself -- they are read at runtime by the kernel and channel adapters.

LLM Provider Keys

VariableUsed ByDescription
ANTHROPIC_API_KEY[default_model]Anthropic API key (Claude models).
GEMINI_API_KEYGemini driverGoogle Gemini API key. Alias: GOOGLE_API_KEY.
OPENAI_API_KEYOpenAI-compat driverOpenAI API key.
GROQ_API_KEYGroq providerGroq API key (fast Llama inference).
DEEPSEEK_API_KEYDeepSeek providerDeepSeek API key.
PERPLEXITY_API_KEYPerplexity provider / web searchPerplexity API key.
OPENROUTER_API_KEYOpenRouter providerOpenRouter API key.
TOGETHER_API_KEYTogether AI providerTogether AI API key.
MISTRAL_API_KEYMistral providerMistral AI API key.
FIREWORKS_API_KEYFireworks providerFireworks AI API key.
COHERE_API_KEYCohere providerCohere API key.
AI21_API_KEYAI21 providerAI21 Labs API key.
CEREBRAS_API_KEYCerebras providerCerebras API key.
SAMBANOVA_API_KEYSambaNova providerSambaNova API key.
HUGGINGFACE_API_KEYHugging Face providerHugging Face Inference API key.
XAI_API_KEYxAI providerxAI (Grok) API key.
REPLICATE_API_KEYReplicate providerReplicate API key.

Web Search Keys

VariableUsed ByDescription
BRAVE_API_KEY[web.brave]Brave Search API key.
TAVILY_API_KEY[web.tavily]Tavily Search API key.
PERPLEXITY_API_KEY[web.perplexity]Perplexity Search API key (shared with LLM provider).

Channel Tokens

VariableChannelDescription
TELEGRAM_BOT_TOKENTelegramBot API token from @BotFather.
DISCORD_BOT_TOKENDiscordDiscord bot token.
SLACK_APP_TOKENSlackSlack app-level token (xapp-) for Socket Mode.
SLACK_BOT_TOKENSlackSlack bot token (xoxb-) for REST API.
WHATSAPP_ACCESS_TOKENWhatsAppWhatsApp Cloud API access token.
WHATSAPP_VERIFY_TOKENWhatsAppWebhook verification token.
MATRIX_ACCESS_TOKENMatrixMatrix homeserver access token.
EMAIL_PASSWORDEmailEmail account password or app password.
TEAMS_APP_PASSWORDTeamsAzure Bot Framework app password.
TEAMS_SECURITY_TOKENTeamsOutgoing webhook security token (base64) — HMAC-SHA256 key used to verify the Authorization: HMAC <…> header on every inbound activity. Production deployments should always set this.
MATTERMOST_TOKENMattermost (sidecar)Mattermost bot token (sidecar adapter — set in ~/.librefang/secrets.env).
TWITCH_OAUTH_TOKENTwitchTwitch OAuth token.
ROCKETCHAT_TOKENRocket.Chat (sidecar)Rocket.Chat personal access token (sidecar adapter — set in ~/.librefang/secrets.env).
ZULIP_API_KEYZulipZulip bot API key.
XMPP_PASSWORDXMPPXMPP account password.
GOOGLE_CHAT_SERVICE_ACCOUNTGoogle ChatService account JSON key.
LINE_CHANNEL_SECRETLINE (sidecar)LINE channel secret (librefang.sidecar.adapters.line — set in ~/.librefang/secrets.env).
LINE_CHANNEL_ACCESS_TOKENLINE (sidecar)LINE channel access token (sidecar adapter).
REDDIT_CLIENT_SECRETRedditReddit app client secret.
REDDIT_PASSWORDRedditReddit bot account password.
MASTODON_ACCESS_TOKENMastodonMastodon access token.
BLUESKY_APP_PASSWORDBlueskyBluesky app password.
FEISHU_APP_SECRETFeishuFeishu/Lark app secret.
REVOLT_BOT_TOKENRevoltRevolt bot token.
NEXTCLOUD_TOKENNextcloud (sidecar)Nextcloud Talk app password / OAuth bearer (librefang.sidecar.adapters.nextcloud).
GUILDED_BOT_TOKENGuildedGuilded bot token.
KEYBASE_PAPERKEYKeybaseKeybase paper key.
THREEMA_SECRETThreemaThreema Gateway API secret.
WEBEX_BOT_TOKENWebex (sidecar)Webex bot Bearer token (librefang.sidecar.adapters.webex).
PUMBLE_BOT_TOKENPumblePumble bot token.
FLOCK_BOT_TOKENFlockFlock bot token.
TWIST_TOKENTwistTwist API token.
MUMBLE_PASSWORDMumbleMumble server password.
DINGTALK_APP_KEYDingTalkDingTalk App Key / Client ID (sidecar — librefang.sidecar.adapters.dingtalk, stream mode only).
DINGTALK_APP_SECRETDingTalkDingTalk App Secret / Client Secret (sidecar — librefang.sidecar.adapters.dingtalk, stream mode only).
GITTER_TOKENGitterGitter auth token.
NTFY_TOKENntfyntfy auth token (optional for public topics).
GOTIFY_APP_TOKENGotifyGotify app token (sending).
GOTIFY_CLIENT_TOKENGotifyGotify client token (receiving).
WEBHOOK_SECRETWebhookHMAC signing secret for webhook verification.