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"
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable external authentication. |
issuer_url | string | "" | OIDC issuer URL for provider discovery at {issuer_url}/.well-known/openid-configuration. |
client_id | string | "" | OAuth2 client ID registered with the identity provider. |
client_secret_env | string | "LIBREFANG_OAUTH_CLIENT_SECRET" | Env var name holding the OAuth2 client secret. |
redirect_url | string | "http://127.0.0.1:4545/api/auth/callback" | OAuth2 authorization code flow callback URL. |
scopes | list of strings | ["openid","profile","email"] | OAuth2 scopes to request. |
allowed_domains | list of strings | [] | Restrict login to these email domains. Empty = allow all. |
audience | string | "" | JWT audience claim to validate. Defaults to client_id if empty. |
session_ttl_secs | u64 | 86400 | Session token lifetime in seconds (24 hours default). |
require_email_verified | bool | true | Reject logins whose ID token does not carry email_verified = true. Set to false only for a provider that does not issue the claim. |
providers | list of objects | [] | Multiple OIDC/OAuth2 providers. When configured, takes precedence over the single-provider fields above. |
role_map | table of string → string | {} | IdP role/group claim value → LibreFang role (owner / admin / user / viewer / guest). See below. |
group_map | table 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_paths | list 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 aWARNat boot and on config reload), - a token from a provider with neither
audiencenorclient_idconfigured, 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
roleslist 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
owneris a team namedowner. If you want one IdP claim to carry both membership and privilege, write it intorole_mapandgroup_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"
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Master switch for the terminal feature. When false, the terminal WebSocket endpoint is disabled entirely. |
allow_remote | bool | false | Allows 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_remote | bool | false | Hard 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_origins | list 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_headers | bool | false | When 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_enabled | bool | true | Enable tmux-backed multi-window terminal. Only effective when the tmux binary is available on the system. |
max_windows | u32 | 16 | Maximum number of tmux windows that may exist simultaneously. Guards against resource exhaustion. |
tmux_binary_path | string or null | null | Explicit path to the tmux binary. If null, resolved via PATH. |
Notes:
- Missing
Originis allowed for non-browser clients. allow_remote = truedoes 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_minuterate 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:
credentials_pathin config (JSON string or file path)VERTEX_AI_SERVICE_ACCOUNT_JSONenv varGOOGLE_APPLICATION_CREDENTIALSenv var (file path)gcloud auth print-access-tokenCLI fallback
| Field | Type | Default | Description |
|---|---|---|---|
project_id | string or null | null | GCP project ID. Falls back to VERTEX_AI_PROJECT_ID, GOOGLE_CLOUD_PROJECT, or the project_id field in the service account JSON. |
region | string or null | null | GCP region for the Vertex AI endpoint. Falls back to VERTEX_AI_REGION or GOOGLE_CLOUD_REGION env var. Default: "us-central1". |
credentials_path | string or null | null | Path 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"
| Field | Type | Default | Description |
|---|---|---|---|
google_client_id | string or null | null | Google OAuth2 client ID for PKCE flow. |
github_client_id | string or null | null | GitHub OAuth app client ID for PKCE flow. |
microsoft_client_id | string or null | null | Microsoft (Entra ID / Azure AD) OAuth application client ID. |
slack_client_id | string or null | null | Slack 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:
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Profile name (e.g., "primary", "secondary"). |
api_key_env | string | required | Env var name holding the API key for this profile. |
priority | u32 | 0 | Priority 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"]
| Field | Type | Default | Description |
|---|---|---|---|
agent_rules | list of ToolPolicyRule | [] | Per-agent tool rules (highest priority, checked first). |
global_rules | list of ToolPolicyRule | [] | Global tool rules applied to all agents (checked after agent rules). |
groups | list of ToolGroup | [] | Named tool groups for reuse in rules. |
subagent_max_depth | u32 | 10 | Maximum subagent spawning depth. |
subagent_max_concurrent | u32 | 5 | Maximum concurrent subagents. |
ToolPolicyRule fields:
| Field | Type | Description |
|---|---|---|
pattern | string | Glob pattern to match tool names (e.g., "shell_*", "web_*", "mcp_github_*"). |
effect | string | "allow" or "deny". Deny-wins: if any deny rule matches, the tool is blocked regardless of allow rules. |
ToolGroup fields:
| Field | Type | Description |
|---|---|---|
name | string | Group name (e.g., "web_tools", "code_tools"). |
tools | list of strings | Tool 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
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Master toggle — when false, the entire proactive memory subsystem is disabled. |
auto_memorize | bool | true | Automatically extract and store memories after each agent execution. |
auto_retrieve | bool | true | Automatically retrieve relevant memories before each agent execution. |
max_retrieve | usize | 10 | Maximum number of memories to retrieve per query. |
extraction_threshold | f32 | 0.7 | Confidence threshold for near-duplicate detection (0.0–1.0). |
extraction_model | string or null | null | LLM 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_categories | list of strings | ["user_preference", "important_fact", "task_context", "relationship"] | Categories to extract from conversations. |
session_ttl_hours | u32 | 24 | Session memory TTL in hours. Memories older than this are cleaned up before each agent execution. |
duplicate_threshold | f32 | 0.5 | Similarity 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_rate | f64 | 0.01 | Confidence decay rate per day. Follows exponential decay: conf × e^(−rate × days). Default of 0.01 takes ~70 days to halve. |
max_memories_per_agent | usize | 1000 | Maximum 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"
| Field | Type | Default | Description |
|---|---|---|---|
engine | string | "default" | Built-in engine name. Currently only "default" is supported. |
plugin | string or null | null | Plugin name. Resolves to ~/.librefang/plugins/<name>/plugin.toml. Takes precedence over manual hooks if set. |
hooks.ingest | string or null | null | Script path for the ingest hook (called on new user message). |
hooks.after_turn | string or null | null | Script path for the after_turn hook (called after each completed turn). |
hooks.runtime | string or null | "python" | Which launcher runs the hook scripts. Supported: python, v, node, deno, go, native (exec a pre-compiled binary). |
plugin_registries | list of objects | Official registry | Plugin registries (GitHub owner/repo) to browse for installable plugins. |
[audit]
Configures audit log retention.
[audit]
retention_days = 90
| Field | Type | Default | Description |
|---|---|---|---|
retention_days | u32 | 90 | Number 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
| Field | Type | Default | Description |
|---|---|---|---|
health_check_interval_secs | u64 | 60 | Interval 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"]
| Field | Type | Default | Description |
|---|---|---|---|
plugin_registries | list 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
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Master toggle. When false, no prompt versions are tracked and experiments are skipped. |
hash_prompts | bool | true | Compute content hashes for prompt versions. |
max_versions_per_agent | u32 | 50 | Maximum 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
| Variable | Used By | Description |
|---|---|---|
ANTHROPIC_API_KEY | [default_model] | Anthropic API key (Claude models). |
GEMINI_API_KEY | Gemini driver | Google Gemini API key. Alias: GOOGLE_API_KEY. |
OPENAI_API_KEY | OpenAI-compat driver | OpenAI API key. |
GROQ_API_KEY | Groq provider | Groq API key (fast Llama inference). |
DEEPSEEK_API_KEY | DeepSeek provider | DeepSeek API key. |
PERPLEXITY_API_KEY | Perplexity provider / web search | Perplexity API key. |
OPENROUTER_API_KEY | OpenRouter provider | OpenRouter API key. |
TOGETHER_API_KEY | Together AI provider | Together AI API key. |
MISTRAL_API_KEY | Mistral provider | Mistral AI API key. |
FIREWORKS_API_KEY | Fireworks provider | Fireworks AI API key. |
COHERE_API_KEY | Cohere provider | Cohere API key. |
AI21_API_KEY | AI21 provider | AI21 Labs API key. |
CEREBRAS_API_KEY | Cerebras provider | Cerebras API key. |
SAMBANOVA_API_KEY | SambaNova provider | SambaNova API key. |
HUGGINGFACE_API_KEY | Hugging Face provider | Hugging Face Inference API key. |
XAI_API_KEY | xAI provider | xAI (Grok) API key. |
REPLICATE_API_KEY | Replicate provider | Replicate API key. |
Web Search Keys
| Variable | Used By | Description |
|---|---|---|
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
| Variable | Channel | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN | Telegram | Bot API token from @BotFather. |
DISCORD_BOT_TOKEN | Discord | Discord bot token. |
SLACK_APP_TOKEN | Slack | Slack app-level token (xapp-) for Socket Mode. |
SLACK_BOT_TOKEN | Slack | Slack bot token (xoxb-) for REST API. |
WHATSAPP_ACCESS_TOKEN | WhatsApp Cloud API access token. | |
WHATSAPP_VERIFY_TOKEN | Webhook verification token. | |
MATRIX_ACCESS_TOKEN | Matrix | Matrix homeserver access token. |
EMAIL_PASSWORD | Email account password or app password. | |
TEAMS_APP_PASSWORD | Teams | Azure Bot Framework app password. |
TEAMS_SECURITY_TOKEN | Teams | Outgoing 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_TOKEN | Mattermost (sidecar) | Mattermost bot token (sidecar adapter — set in ~/.librefang/secrets.env). |
TWITCH_OAUTH_TOKEN | Twitch | Twitch OAuth token. |
ROCKETCHAT_TOKEN | Rocket.Chat (sidecar) | Rocket.Chat personal access token (sidecar adapter — set in ~/.librefang/secrets.env). |
ZULIP_API_KEY | Zulip | Zulip bot API key. |
XMPP_PASSWORD | XMPP | XMPP account password. |
GOOGLE_CHAT_SERVICE_ACCOUNT | Google Chat | Service account JSON key. |
LINE_CHANNEL_SECRET | LINE (sidecar) | LINE channel secret (librefang.sidecar.adapters.line — set in ~/.librefang/secrets.env). |
LINE_CHANNEL_ACCESS_TOKEN | LINE (sidecar) | LINE channel access token (sidecar adapter). |
REDDIT_CLIENT_SECRET | Reddit app client secret. | |
REDDIT_PASSWORD | Reddit bot account password. | |
MASTODON_ACCESS_TOKEN | Mastodon | Mastodon access token. |
BLUESKY_APP_PASSWORD | Bluesky | Bluesky app password. |
FEISHU_APP_SECRET | Feishu | Feishu/Lark app secret. |
REVOLT_BOT_TOKEN | Revolt | Revolt bot token. |
NEXTCLOUD_TOKEN | Nextcloud (sidecar) | Nextcloud Talk app password / OAuth bearer (librefang.sidecar.adapters.nextcloud). |
GUILDED_BOT_TOKEN | Guilded | Guilded bot token. |
KEYBASE_PAPERKEY | Keybase | Keybase paper key. |
THREEMA_SECRET | Threema | Threema Gateway API secret. |
WEBEX_BOT_TOKEN | Webex (sidecar) | Webex bot Bearer token (librefang.sidecar.adapters.webex). |
PUMBLE_BOT_TOKEN | Pumble | Pumble bot token. |
FLOCK_BOT_TOKEN | Flock | Flock bot token. |
TWIST_TOKEN | Twist | Twist API token. |
MUMBLE_PASSWORD | Mumble | Mumble server password. |
DINGTALK_APP_KEY | DingTalk | DingTalk App Key / Client ID (sidecar — librefang.sidecar.adapters.dingtalk, stream mode only). |
DINGTALK_APP_SECRET | DingTalk | DingTalk App Secret / Client Secret (sidecar — librefang.sidecar.adapters.dingtalk, stream mode only). |
GITTER_TOKEN | Gitter | Gitter auth token. |
NTFY_TOKEN | ntfy | ntfy auth token (optional for public topics). |
GOTIFY_APP_TOKEN | Gotify | Gotify app token (sending). |
GOTIFY_CLIENT_TOKEN | Gotify | Gotify client token (receiving). |
WEBHOOK_SECRET | Webhook | HMAC signing secret for webhook verification. |