Skip to content

Configuration

View .md

This is the reference for everything you can configure in Beacon. It applies whether you run the Beacon.SampleProject host or embed the Beacon.* packages in your own ASP.NET Core application; Host wiring shows the registration calls in context.

Beacon has two kinds of settings:

  1. Configuration keys, read from IConfiguration: appsettings*.json, environment variables, user secrets or a secrets provider. Everything under Core and database through Telemetry below.
  2. Code-only options, set in the AddBeaconServices(configuration, options => …) lambda. They are not read from configuration. See Code-only options.

Rules that apply throughout:

  • Validated at startup. Most sections are validated when the host starts; a bad value stops the host with a message that names the key (never a secret’s value). Sections marked “validated at first use” are checked when the feature first reads them.
  • Secrets never go in appsettings.json. Supply Beacon:EncryptionKey, connection strings, the OIDC client secret and LLM keys through environment variables or a secrets provider. .NET configuration does not expand ${...} placeholders: the text itself would become the value (for Beacon:EncryptionKey and Beacon:Mcp:CallerHashKey startup refuses it).
  • The sample host. src/Beacon.SampleProject/appsettings.json holds only Logging; the sample’s Beacon settings are in appsettings.Development.json, which loads only with ASPNETCORE_ENVIRONMENT=Development. Outside Development, supply every value yourself.

Replace each : with a double underscore. List items take an index; dictionary keys are path segments.

Terminal window
export ConnectionStrings__BeaconContext="Host=db;Database=beacon;Username=beacon;Password=…"
export Beacon__EncryptionKey="<base64 of 32 random bytes>"
export Beacon__PreviousEncryptionKeys__0="<the key used until now>"
export Beacon__UserManagement__LocalLogin__BreakGlassUserNames__0="superadmin"
export Beacon__Query__MaxExecutionRows=50000
export Beacon__Mcp__ForceReadOnly=true

An Azure Key Vault secret named Beacon--EncryptionKey maps to Beacon:EncryptionKey the same way.

A condensed version of the sample host’s wiring. The SPA is served at /beacon, so cookie authentication, the login and setup endpoints and the middlewares take that base path.

using Beacon.AI;
using Beacon.Api;
using Beacon.Api.Authentication;
using Beacon.Api.Endpoints;
using Beacon.Core;
using Beacon.Core.PostgreSql;
using Beacon.MCP;
using Beacon.MCP.Discovery;
using Beacon.UI;
builder.Services.AddBeaconHostInfrastructure<MyClaimsTransformation>();
builder.Services.AddBeaconServices(builder.Configuration, options =>
{
options.AddBeaconScheduler<BeaconScheduler>(); // required: your IBeaconScheduler
options.BaseUrl = builder.Configuration["Beacon:BaseUrl"];
options.UseAI = true;
options.AddEmailAdapter<MyEmailAdapter>();
options.Authorization.Enabled = true;
options.Authentication.EnableLoginForm = true;
options.AddAuthenticationProvider<DatabaseAuthenticationProvider>();
options.EnableUserManagement();
})
.AddPostgreSqlConnector()
.AddSqlServerConnector()
// … other connectors …
.UsePostgreSql(); // ConnectionStrings:BeaconContext, schema "beacon"
builder.Services.AddBeaconCookieAuthentication(BeaconUiEndpointRouteBuilderExtensions.BasePath); // "/beacon"
builder.Services.AddBeaconDataProtection(builder.Configuration, builder.Environment);
builder.Services.AddBeaconOidcAuthentication(builder.Configuration); // optional SSO
builder.Services.AddBeaconAI(builder.Configuration);
builder.Services.AddBeaconMcp();
builder.Services.AddOpenApi();
builder.Services.AddAntiforgery(x =>
{
x.HeaderName = "X-XSRF-TOKEN";
x.Cookie.Name = ".Beacon.Antiforgery";
});
builder.Services.AddBeaconApiAuthorization();
builder.Services.AddBeaconApiServices();
var app = builder.Build();
var beaconConfiguration = app.Services.GetRequiredService<BeaconConfiguration>();
app.UseStaticFiles();
app.UseMcpResourceMetadataChallenge();
app.UseMiddleware<ApiKeyAuthMiddleware>();
app.UseBeaconJwtBearerAuthentication();
app.UseAuthentication();
app.UseMiddleware<BeaconCookieAuthMiddleware>();
app.UseAuthorization();
app.UseMiddleware<LoginFormAuthMiddleware>(beaconConfiguration, BeaconUiEndpointRouteBuilderExtensions.BasePath); // with the login form
app.UseMiddleware<FirstRunSetupMiddleware>(beaconConfiguration, BeaconUiEndpointRouteBuilderExtensions.BasePath); // with user management
app.UseAntiforgery();
app.MapOpenApi(); // /openapi/v1.json
app.MapBeaconApi(); // /beacon/api/* and the hub /beacon/api/hub
app.MapLoginEndpoints("/beacon", beaconConfiguration); // /beacon/api/auth/login, logout, sso
app.MapSetupEndpoints("/beacon"); // /beacon/api/setup/* (with user management)
app.MapBeaconMcp(); // /beacon/mcp, Execute-scope policy
app.MapMcpDiscovery(); // /.well-known/* (anonymous)
app.MapBeaconUi(); // the React SPA at /beacon

The middleware order is load-bearing; keep it. Beacon does not create or migrate its own schema when the host starts: apply its migrations as described in Metadata database.

PathWhat
/beaconthe React UI (/beacon/login, /beacon/setup, …)
/beacon/api/*REST API; /beacon/api/health is anonymous
/beacon/api/hubSignalR hub
/beacon/mcpMCP server
/.well-known/oauth-protected-resource, /.well-known/mcp/server-card.jsonMCP discovery (anonymous)
/openapi/v1.jsonOpenAPI document
/warpWarp dashboard (sample host, Admin only)

Set in the AddBeaconServices lambda. None of these are read from configuration, except the two noted.

OptionDefaultWhat it does
AddBeaconScheduler<T>()none, requiredYour IBeaconScheduler; startup fails without one (see Scheduling)
AddEmailAdapter<T>()noneYour IEmailAdapter; Beacon ships no email implementation (see Email adapter)
BaseUrlnullThe address put in notification links: {BaseUrl}/notifications/{id}. Include the /beacon path (https://beacon.example.com/beacon). Not read from configuration by Beacon; the sample host reads Beacon:BaseUrl into it. Admin settings can replace it at runtime (see Base URL)
UseAIfalseTurns on the AI features; with false they are registered as stand-ins that report AI as unavailable
Authorization.EnabledfalseEnforces global roles on /beacon/api (see Authorization)
AddAuthorizationProvider<T>()DefaultAuthorizationProvider (allows all); DatabaseAuthorizationProvider with user managementThe global-permission provider
Authentication.EnableLoginFormfalseThe login form and the login redirect
AddAuthenticationProvider<T>()noneHow a login-form password is checked (DatabaseAuthenticationProvider, JwtExternalApiAuthenticationProvider, HybridAuthenticationProvider or your own)
Authentication.LoginRedirectPath/beacon/homeWhere a sign-in lands (a full browser path)
Authentication.LoginPath/loginThe login page, under the base path
Authentication.CookieExpirationHours24Requested session-cookie lifetime; capped by Beacon:Authentication:Session:AbsoluteLifetimeHours (default 12)
Authentication.EnableRememberMetrueOffer “remember me” on the login form
Authentication.RememberMeExpirationDays30Requested “remember me” lifetime; capped the same way
UserManagement.Enabled (EnableUserManagement())falseBeacon’s user store, Users page and first-run setup
UserManagement.AllowInternalUserstrueAllow password users
UserManagement.MinimumPasswordLength8Shortest password
UserManagement.RequirePasswordComplexitytrueUpper case, lower case, digit and another character required
UserManagement.SetupTokenunsetAlso bound from Beacon:UserManagement:SetupToken (see User management)
UserManagement.LocalLoginonAlso bound from Beacon:UserManagement:LocalLogin
ApprovalWorkflow.* (EnableApprovalWorkflow(…))offThe four-eyes workflow (see Approval workflow)
SavedQueryTools.NamedToolLimit25Up to this many saved-query MCP tools per caller are listed as their own q_<name> tools; above it, callers get search_saved_queries and run_saved_query. Must not be negative

Bearer-token and external-login settings are code-only too, through builder.Services.AddBeaconJwtAuthentication(jwt => …) (see JWT bearer).

KeyTypeDefaultWhat it does
ConnectionStrings:BeaconContextstringrequiredBeacon’s own database (PostgreSQL or SQL Server), read by .UsePostgreSql() / .UseSqlServer() without arguments. Missing: startup fails
ConnectionStrings:WarpContextstringfalls back to BeaconContextSample host: the Moberg.Warp job store, PostgreSQL only, warp schema. When Beacon runs on SQL Server, point it at a PostgreSQL database
ConnectionStrings:{Name}stringnoneThe connection of a host-managed data source, which stores only the name and reads the string at call time
Beacon:SchemastringbeaconNot configurable. Any value other than beacon stops the host (see below). Leave it out
Beacon:BaseUrlstringnone (the sample host falls back to https://localhost:7187)Read by the sample host into options.BaseUrl
Beacon:PublicBaseUrlstring (origin)derived from the requestThe origin advertised in the MCP discovery documents and the WWW-Authenticate challenge. Unset, it follows the request’s Host header, which the client controls, and startup logs a Warning. Set it behind any proxy
Beacon:ForwardedHeaders:EnabledboolfalseSample host: apply X-Forwarded-For and X-Forwarded-Proto from known proxies
Beacon:ForwardedHeaders:KnownProxiesstring[]emptySample host: proxy IP addresses to trust; entries that are not IP addresses are skipped, and an empty list trusts none
AllowedHostsstringNoneStandard ASP.NET Core host filtering
Logging:*n/an/aStandard logging. For JSON console logs with trace ids: Logging:Console:FormatterName=json, Logging:Console:FormatterOptions:IncludeScopes=true

Beacon runs in the beacon schema and only that schema. Its migration snapshots bake the schema in, so any other schema would make Database.Migrate() fail with PendingModelChangesWarning; Beacon refuses the setting up front:

Beacon only supports the 'beacon' schema, but 'tenant_a' was configured.
Remove Beacon:Schema (or set it to 'beacon') and move any existing Beacon tables into 'beacon'.

.UsePostgreSql(connectionString, schema) and .UseSqlServer(connectionString, schema) accept a schema argument for the same reason only as beacon. On PostgreSQL the search path is beacon,public, so a pgvector extension installed in public resolves.

The forwarded-headers keys are read by the sample host only. A host embedding the packages calls UseForwardedHeaders itself. Behind a proxy this matters: the login and setup throttles key on the client address, and SameAsRequest cookie security keys on the scheme.

{
"Beacon": {
"ForwardedHeaders": {
"Enabled": true,
"KnownProxies": [ "10.0.0.5" ]
}
}
}

BaseUrl is the address of the Beacon UI in outgoing links (Teams, Slack, webhooks: {BaseUrl}/notifications/{id}). The UI lives under /beacon, so include it:

{ "Beacon": { "BaseUrl": "https://beacon.example.com/beacon" } }

At startup the base URL is what the host’s code sets (options.BaseUrl). Saving Base URL in Admin settings replaces it in the running process at once; after a restart the startup value applies again until the settings are saved. Keep the two in step.

KeyTypeDefaultWhat it does
Beacon:EncryptionKeystring (base64)requiredEncrypts data-source connection data, repository tokens, sensitive settings and recipient destinations at rest (AES-256-GCM, per-value nonce; the AES key is the SHA-256 of the key text). Checked in every environment
Beacon:PreviousEncryptionKeysstring[]emptyOlder keys, used only to decrypt
Beacon:Mcp:CallerHashKeystringderived from the encryption keySecret behind the MCP caller hashes in the audit; keeps them stable across a key change

Generate a key with:

Terminal window
openssl rand -base64 32

The key must be base64 that decodes to at least 32 random bytes: a passphrase, a short value, base64 of text (every decoded byte printable) or of a repeated pattern (fewer than 16 distinct byte values) is refused. A published sample value and a value containing ${ are refused too. The message never contains the key.

Terminal window
# Development
dotnet user-secrets --project src/Beacon.SampleProject set "Beacon:EncryptionKey" "$(openssl rand -base64 32)"
# Elsewhere
export Beacon__EncryptionKey="<the generated value>"

API keys are not encrypted: they are SHA-256-hashed, and the raw key is shown once.

Values are always written with Beacon:EncryptionKey. To replace a key, set the new one and list the old one in Beacon:PreviousEncryptionKeys (as a list: Beacon__PreviousEncryptionKeys__0; a single value is read as one key, with a startup Warning). Previous keys only decrypt and are not strength-checked, since their purpose is to retire a weak key.

Terminal window
export Beacon__EncryptionKey="<the new value>"
export Beacon__PreviousEncryptionKeys__0="<the key configured until now>"

IStoredValueReEncryptionService re-encrypts every stored value only a previous key decrypts (conditional writes, so a concurrent save wins). The sample host enqueues it as ReEncryptStoredValuesJob at every start while a previous key is listed; a host with its own jobs does the same. Startup also logs how many values remain under a previous key, how many no key decrypts, and how many recipient approval hashes cannot be recomputed. Remove the previous keys once none remain.

Before changing the key, set Beacon:Mcp:CallerHashKey to the key in use now, so MCP audit rows keep grouping together across the change. The MCP audit’s content hashes (ContentHash, SqlHash) come from the same secret and behave the same way. Set Beacon:Audit:IntegrityKey before you change the key too, so security audit events written from then on do not depend on it; events signed before keep verifying while their secret stays configured (see Security audit trail).

Section titled “Data Protection (the keys behind the session cookie)”

ASP.NET Core Data Protection signs and encrypts the Beacon.Auth cookie and the antiforgery tokens. It belongs to the host: AddBeaconCookieAuthentication configures none of it.

KeyTypeDefaultWhat it does
Beacon:DataProtection:ApplicationNamestringBeacon in Development; required elsewhereUsed by AddBeaconDataProtection(configuration, environment), which keeps the key ring in the Beacon database (data_protection_keys). Give each deployment its own name (beacon-production, beacon-uat) so a cookie from one is refused by another. Missing outside Development: startup fails
Beacon:DataProtection:AllowUnencryptedKeysboolfalseAcknowledges a key ring persisted without encryption outside Development (a startup Warning instead of a refusal)
  • Keys encrypted at rest. Outside Development the host does not start when the key ring is persisted without key encryption. Chain ProtectKeysWithAzureKeyVault(...) or ProtectKeysWithCertificate(...) with a key only that environment holds, or acknowledge another protection with AllowUnencryptedKeys.
  • Keys written before encryption are revoked. At each start outside Development with key encryption on, keys still stored unencrypted are revoked and replaced (DataProtectionKeyRing, 7121); their users sign in again once.
  • No repository configured gets a startup Warning outside Development.
  • Your own setup. Call AddDataProtection() with your own application name, PersistKeysTo* and ProtectKeysWith*, and skip AddBeaconDataProtection.
builder.Services.AddBeaconDataProtection(builder.Configuration, builder.Environment)
.ProtectKeysWithAzureKeyVault(new Uri(builder.Configuration["DataProtection:KeyUri"]!), new DefaultAzureCredential());

After restoring a Beacon database into another environment: give every user a new security stamp (sessions signed in before end), delete the copied key rows so the copy creates its own keys, and if a copy may have been exposed, revoke the source environment’s keys too.

-- PostgreSQL
UPDATE beacon.users SET security_stamp = md5(random()::text || clock_timestamp()::text || id::text);
-- SQL Server
UPDATE beacon.Users SET SecurityStamp = LOWER(REPLACE(CONVERT(nvarchar(36), NEWID()), '-', ''));

Beacon’s sessions use the Beacon.Auth cookie (HttpOnly, SameSite=Lax, SecurePolicy=SameAsRequest, sliding). There is no default account: first-run setup creates the super admin. Register cookie authentication with the UI’s base path:

builder.Services.AddBeaconCookieAuthentication("/beacon"); // login page /beacon/login

Bound from Beacon:Authentication:Session, validated at startup.

KeyTypeDefaultRangeWhat it does
RevalidateMinutesint50–1440 (0 = every request)How often, at most, a session is checked against the user store (user exists, enabled, not archived, same security stamp; roles replaced by the stored ones). Also how long a realtime connection lives before it is checked again (at least 1 minute)
AbsoluteLifetimeHoursint121–8760A session ends this long after sign-in, however often it is renewed; caps the cookie’s expiry and “remember me”

Bound from Beacon:UserManagement:LocalLogin, validated at startup.

KeyTypeDefaultWhat it does
EnabledbooltruePassword sign-in through the login form. false: POST /beacon/api/auth/login is not mapped (unless a break-glass account is listed), password providers refuse every account, and the login page shows no password form. Startup fails when nobody could sign in (no OIDC, no BeaconApiOptions.ExternalLogin (a host-owned login page), no break-glass account)
BreakGlassUserNamesstring[]emptyUp to 3 stored user names (exact, case-sensitive) that may still sign in with a password while Enabled is false, at /beacon/login?local=1. A blank entry or more than 3 fail startup; a name no enabled internal user has gets a startup Warning

See User management → Local password sign-in.

Bound by builder.Services.AddBeaconOidcAuthentication(builder.Configuration) from Beacon:Authentication:Oidc; validated at startup when Enabled.

{
"Beacon": {
"Authentication": {
"Oidc": {
"Enabled": true,
"Authority": "https://login.microsoftonline.com/{tenant-id}/v2.0",
"ClientId": "{client-id}",
"AllowedTenants": [ "{tenant-id}" ],
"RequiredRoles": [ "Beacon.User" ],
"DisplayName": "Microsoft"
}
}
}
}
KeyTypeDefaultWhat it does
EnabledboolfalseTurns SSO on
Authority, ClientId, ClientSecretstringRequiredAll three required when enabled. Authorization code flow with PKCE. Supply ClientSecret as Beacon__Authentication__Oidc__ClientSecret or from a secrets provider
CallbackPathstring/signin-oidcThe redirect callback
Scopesstring[]openid, profile, emailRequested scopes (replaces the list)
AllowedTenantsstring[]emptyEntra tenant ids (tid) that may sign in. Startup fails when empty, unless AllowAnyTenant
AllowAnyTenantboolfalseSkip the tenant check (an identity provider without tid). A Warning at startup with an Entra authority
BlockGuestsbooltrueRefuse Entra B2B guests (acct=1, or an idp naming another provider)
RequiredRoles / RequiredGroupsstring[]emptyWhen either is set, a user must hold one listed app role (roles) or group (groups). Prefer app roles: Entra drops groups when a user is in too many
DefaultRoleNamestringunset (no role)Role for a user provisioned at first sign-in. May not be Admin or a functional role (startup fails)
DisplayNamestringSSOThe SSO button’s label
EnableFrontChannelLogoutboolfalseServe /signout-oidc, an anonymous GET that ends the session
RequireMfaboolfalseRefuse an interactive SSO sign-in whose ID token shows no MFA (amr contains mfa, or acr in AcceptedAcrValues). Covers the interactive SSO sign-in only
AcceptedAcrValuesstring[]emptyacr values that count as MFA
McpJwksEndpointstring (URL)NoneSample host: with SSO enabled, turns on bearer JWT with issuer = Authority, audience = ClientId and this JWKS URL. Not read by the Beacon libraries

Admission runs before anything is provisioned: tenant, then guest, then required role or group. See User management → Single sign-on admission.

Bearer tokens on the REST API and the external login API are configured in code; there is no configuration section:

builder.Services.AddBeaconJwtAuthentication(jwt =>
{
jwt.EnableBearerAuthentication = true;
jwt.Validation.JwksEndpoint = "https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys";
jwt.Validation.ValidIssuer = "https://login.microsoftonline.com/{tenant}/v2.0";
jwt.Validation.ValidAudience = "{beacon-client-id}";
});
OptionDefaultWhat it does
ExternalLoginEndpointNoneThe login API the login form posts to (JwtExternalApiAuthenticationProvider)
EnableBearerAuthenticationfalseAccept Authorization: Bearer <jwt> on the REST API
AccessTokenClaimNoneFor a non-Entra issuer without typ: at+jwt: a claim only its access tokens carry. A claim ID tokens carry stops startup
Validation.SigningKey / JwksEndpointNoneOne is required
Validation.ValidIssuer(s), ValidAudience(s)NoneAt least one of each is required
Validation.ClockSkew5 minutesAt most 5 minutes
Validation.ValidateLifetime, ValidateIssuer, ValidateAudiencetrueMust stay true
ClaimsMapping.*sub, preferred_username, email, roles, nameWhich claims hold the user id, user name, e-mail, roles and display name

With bearer or external login on, a missing key, issuer or audience, a validation flag turned off, or a skew above 5 minutes stops the host. A token must be an access token and name an existing, enabled, external Beacon user; see User management. Entra callers on /beacon/mcp are configured separately (below).

MCP callers from Entra ID (Beacon:Mcp:Callers)

Section titled “MCP callers from Entra ID (Beacon:Mcp:Callers)”

Validated at startup. Without the section, no JWT caller is admitted on /beacon/mcp. Active callers also need bearer JWT enabled with issuer and audience validation.

KeyTypeDefaultWhat it does
AllowedTenantsstring[]emptyTenants whose tokens map to a caller; required when callers are active
Users:EnabledboolfalseAccept delegated user tokens
Users:RequiredRoles / RequiredGroupsstring[]emptyAny-of admission rules; both empty fails startup unless AllowAnyTenantUser
Users:AllowAnyTenantUserboolfalseAdmit every user of the allowed tenants
Users:ProjectIdsint[]emptyProjects for every accepted user
Users:GroupProjectsmap group or role → int[]emptyExtra projects per group or app role
Users:ScopeRead | ExecuteExecuteScope of user callers
Users:AutoProvisionbooltrueFind or create a Beacon user keyed on oid
Users:DefaultRoleNamestringViewerRole for provisioned users; not Admin or a functional role
Systems[i]:NamestringRequiredRequired, unique
Systems[i]:ClientId or ObjectIdstringRequiredExactly one: an app-only token’s client, or a service user’s oid
Systems[i]:ProjectIds, Scope, HostClaims[]ExecuteThe system caller’s projects, scope and host-endpoint claims

See Entra ID callers.

Bound from Beacon:Authorization, validated at startup. Whether authorization is on at all is code-only (options.Authorization.Enabled). See Authorization.

KeyTypeDefaultWhat it does
RequireProjectMembershipboolfalseRestrict every project to its members and Admins; data sources no project links become Admin-only. Startup fails without a membership source (Beacon user management or a host IProjectMembershipSource)

Functional permissions: role names, identity-provider app roles, separation of duties

Section titled “Functional permissions: role names, identity-provider app roles, separation of duties”
{
"Beacon": {
"Authorization": {
"Roles": {
"Auditor": "Compliance Reviewer",
"HonourHostRoleClaims": false
},
"IdpRoles": {
"Enabled": true,
"Map": { "beacon.auditor": "Auditor" },
"ClaimLifetimeMinutes": 60
},
"SeparationOfDuties": { "Enabled": true }
}
}
}
KeyTypeDefaultWhat it does
Roles:Approver, Auditor, SecurityAdmin, UserAdmin, DataSourceAdmin, MigrationOperatorstringthe permission’s nameThe level-0 system role that grants the permission. At most 50 characters, not Admin, Editor or Viewer, unique across permissions (ignoring case); blank keeps the default
Roles:HonourHostRoleClaimsboolfalseHost without Beacon user management: let session role claims named like a permission’s role grant it. No effect with user management
IdpRoles:EnabledboolfalseMap identity-provider app roles to permissions at SSO sign-in (session claims only, never stored). Needs at least one Map entry
IdpRoles:Mapmap app role → permissionemptyOnly the six permissions; never Admin, Editor or Viewer
IdpRoles:ClaimLifetimeMinutesint601–1440. How long a mapped permission counts after the sign-in
SeparationOfDuties:EnabledboolfalseRefuse (400) assignments giving one user Approver + SecurityAdmin, Auditor + SecurityAdmin, Auditor + UserAdmin, UserAdmin + Approver or UserAdmin + MigrationOperator; an IdpRoles:Map onto such a pair fails startup

User management itself is code-only (Code-only options); these keys are bound from configuration:

KeyTypeDefaultWhat it does
Beacon:UserManagement:SetupTokenstringunsetThe secret first-run setup asks for. At least 32 characters after trimming, or startup fails (checked when user management is on). Unset: Beacon generates a token per process and prints it once to the process console (standard error, never the log) while no super admin exists. Set it when several replicas serve the setup page
Beacon:UserManagement:LocalLogin:*See Local password sign-in

Login attempts are throttled at 10 per 60 seconds per client address; the first-run setup endpoints have their own budget. Neither is configurable.

Bound from Beacon:ApiKeys, validated at startup. See API keys.

KeyTypeDefaultRangeWhat it does
MaxLifetimeDaysint3651–3650Longest lifetime a new key may have; a key created without a date gets 90 days, capped at this
EnforceMaxLifetimeOnExistingKeysboolfalseA key stored without an expiry expires MaxLifetimeDays after creation. Off: such keys keep working and warn once per process
MaxOwnerInactivityDaysint0 (off)0–3650A key stops working (401) while its owner has not signed in with their own identity for this many days

Scopes are Read and Execute; a key stored with the retired Admin scope is read as Execute.

Bound from Beacon:Query, validated at startup. These apply to every statement Beacon runs for its own features: previews, subscription runs, AI actor checks, ad-hoc queries.

KeyTypeDefaultRangeWhat it does
MaxExecutionRowsint1000001–10,000,000The most rows one statement may return. The cap is written into the SQL (a larger LIMIT/TOP/FETCH is lowered to it); on PostgreSQL, MySQL, SQL Server, Azure Synapse and Snowflake the read also stops one row past it. A larger result fails the run, never truncates, with a message naming the key. Rows are held in memory
StatementTimeoutSecondsint1201–3600Cancels one statement: a query step, a final query on the in-memory join, a metadata sample. A subscription’s own timeout may only be shorter (saving a longer one is refused)

Data migration reads use Beacon:Migrations:MaxRows instead. The MCP query and ask tools use a fixed 30-second timeout.

KeyTypeDefaultRangeWhat it does
Beacon:Metadata:ForcedRefreshCooldownMinutesint150–1440 (0 = off)After a successful forced metadata refresh of a data source, the next one answers 429 until this passes. Per process; Admins and DataSourceAdmins are not held to it. Validated at startup

Bound from Beacon:Migrations, validated at startup. A deployment lock: only configuration turns it on.

KeyTypeDefaultWhat it does
EnabledboolfalseMaps the migration endpoints, shows the feature, lets jobs run and lets an Admin mark data sources as migration write targets. Off: nothing runs, for Admins too
MaxRowsint1000001–10,000,000. One run’s read and write budget; a larger source fails before anything is written

See Data migration.

The four-eyes workflow is off unless the host turns it on, in code. See Approvals for what it governs.

builder.Services.AddBeaconServices(builder.Configuration, options =>
{
// ...
options.EnableApprovalWorkflow(x =>
{
x.RequireApprovedVersionToRun = true; // strict mode
});
});
Option (options.ApprovalWorkflow.*)DefaultWhat it does
EnabledfalseEvery change to a query’s executable content waits for a second person’s approval; subscriptions run only reviewed SQL
AllowSelfApprovalfalseLets the requester approve their own change (for a host with one approver)
RequireApprovedVersionToRunfalseStrict mode: a subscription or saved-query MCP tool runs only a version approved for exactly its content
CoversMigrationJobstrueWhile the workflow is on, a new migration job runs only after approval
CoversCustomSqlRulestrueWhile the workflow is on, a new or changed Custom SQL rule is evaluated only after approval
CoversRecipientDestinationstrueWhile the workflow is on, a new recipient or changed destination receives nothing until approved

The Covers… switches apply only while Enabled is on. Turning a switch, or the workflow, off releases what it held; startup warns with the counts. Startup also logs how changes are governed (a Warning outside Development while the workflow is off).

Beacon records sign-ins and changes to identities, keys, access, data sources, delivery, settings, approvals, ownership, tasks, curation, data quality and migrations in an append-only, signed trail an Auditor reads (see Security audit trail). It needs no configuration; these settings tune it:

{
"Beacon": {
"Audit": {
"IntegrityKey": "<from a secret store>",
"PreviousIntegrityKeys": [],
"RetentionDays": null
}
}
}
KeyTypeDefaultWhat it does
Beacon:Audit:IntegrityKeystringderivedThe secret events are signed with: at least 32 characters, supplied like the encryption key (a secret store, or Beacon__Audit__IntegrityKey), never committed. Unset, Beacon derives a key for this purpose from Beacon:Mcp:CallerHashKey, else from Beacon:EncryptionKey. Set it before you change the encryption key
Beacon:Audit:PreviousIntegrityKeysstring[]emptyIntegrity keys used earlier, as a list (Beacon__Audit__PreviousIntegrityKeys__0, …): accepted when verifying events, never used to sign. Move the old key here when you set a new one
Beacon:Audit:RetentionDaysintnone (keep forever)Days events are kept: between 365 and 36500. The purge must be scheduled; the sample host runs it daily as the Warp job security-audit-retention (see Retention)

An integrity key shorter than 32 characters or containing ${, or a retention outside the range, stops the host at startup with a message that never contains a key. At startup the host logs the id of the key events are sealed under and where it comes from (never the key), with a Warning while IntegrityKey is unset.

Two jobs keep the trail checked and bounded; neither runs unless the host schedules it. The sample host schedules both as Warp recurring jobs: security-audit-verify (hourly at :15), which verifies the events written since its last run and writes a checkpoint to the Beacon.Audit log stream, and security-audit-retention (daily), the purge. A host embedding Beacon schedules ISecurityAuditVerificationService.VerifyNewEventsAsync and ISecurityAuditRetentionService.PurgeExpiredAsync itself (see Scheduled verification). Alert on the counters beacon.security_audit.write_failures and beacon.security_audit.verification_failures above zero.

Bound from Beacon:LLM by builder.Services.AddBeaconAI(builder.Configuration). Optional; not validated. AI features also need options.UseAI = true.

KeyTypeDefaultWhat it does
ProviderOpenAI | Claude | AzureOpenAI | BedrockOpenAIThe provider (Claude is Anthropic)
ApiKeystring""The provider key. Bedrock: empty uses the default AWS credential chain; accessKey:secretKey gives explicit credentials
EndpointstringNoneRequired for AzureOpenAI
RegionstringNoneRequired for Bedrock
SessionTokenstringNoneBedrock temporary credentials
Modelstring""The model; for Azure the deployment name, for Bedrock the model id
FastModelstringNoneUsed for short, high-volume calls. Unset: OpenAI and Claude use a built-in fast model; Azure and Bedrock use Model
Limits:MaxConcurrentRequestsint50Concurrency of the LLM request queue, fixed when the host starts
Limits:TokensPerMinute, RequestsPerMinute, MonthlyBudgetint, int, decimal80000, 1000, 100.00Recorded and shown in settings
{
"Beacon": {
"LLM": {
"Provider": "Claude",
"Model": "<model-id>",
"FastModel": "<fast-model-id>"
}
}
}
Terminal window
export Beacon__LLM__ApiKey="<provider key>"

At startup the provider is built from Beacon:LLM. Saving the AI provider in Admin settings swaps it immediately, without a restart; after a restart Beacon:LLM applies again until the settings are saved. Keep Beacon:LLM in step with Admin settings, or save them again after a restart. See AI providers.

KeyTypeDefaultWhat it does
EnabledboolfalseRun a local ONNX embedding model in-process for semantic retrieval
ModelPathstringNoneThe ONNX model file (384 dimensions, for example bge-small-en-v1.5)
TokenizerPathstringNoneThe WordPiece vocabulary file

The service is available only when Enabled is true and both files exist; otherwise retrieval falls back to lexical matching. On PostgreSQL the vectors live in a pgvector column, so the vector extension must be available to Beacon’s migrations; on SQL Server they are compared in memory. The sample host re-indexes every 12 hours (mcp-embedding-reindex, mcp-docchunk-reindex).

Bound from Beacon:Notifications, validated at startup (each bad entry is named); the effective policy is logged once.

KeyDefaultWhat it does
AllowedHosts:Slack / Teams / JiraemptyExtra hosts on top of the built-in ones: Slack hooks.slack.com; Teams *.webhook.office.com, *.logic.azure.com, *.environment.api.powerplatform.com; Jira *.atlassian.net, api.atlassian.com. *.example.com matches subdomains only; *.com is refused
AllowedHosts:Webhookempty (any public host)When set, the only hosts a webhook may target
AllowedHosts:Emailempty (any domain)When set, the only e-mail domains
DisabledTypesemptyTypes this host does not deliver: Email, Teams, Slack, Jira, Webhook
AllowedPrivateNetworks:Webhook / Slack / Teams / JiraemptyIP addresses, CIDR ranges or host names that calls of that type may still reach though private
AllowedHeadersemptyWebhook header names allowed besides Authorization, Api-Key, X-Api-Key, Ocp-Apim-Subscription-Key. Reserved names (Host, Content-Length, X-Forwarded-*, Metadata-Flavor, …) are refused
Nat64PrefixesemptyExtra NAT64 /96 prefixes besides 64:ff9b::/96
UseSystemProxyfalseSend notification calls through the system proxy, with the destination checked before sending. Requires AllowedHosts:Webhook (or Webhook disabled)
RequireEncryptedSecretsfalseRefuse to send to a recipient whose secrets are still stored unencrypted

Slack, Teams, Jira and webhook destinations need https without user credentials. Calls never follow redirects, time out after 30 seconds (10 seconds to connect), and by default connect directly, only to public addresses Beacon resolved and checked itself.

Recipients saved by an older version keep working with unencrypted secrets until saved again or until IRecipientSecretEncryptionService.EncryptStoredSecretsAsync runs (the sample’s EncryptRecipientSecretsJob, which the host enqueues once every node runs the new version; an earlier version cannot read the encrypted secrets). Then set RequireEncryptedSecrets to true. Notification URLs are secrets: leave notification calls out of HTTP tracing (NotificationRequests.IsNotificationRequest). See Notifications.

Beacon’s libraries ship no email implementation. Register your own IEmailAdapter with options.AddEmailAdapter<T>(). The sample host’s BeaconMailSender reads these keys, at send time:

KeyDefaultWhat it does
Beacon:Smtp:HostrequiredSMTP server
Beacon:Smtp:Port465Port
Beacon:Smtp:EnableSsltrueTLS
Beacon:Smtp:UsernamerequiredLogin
Beacon:Smtp:PasswordrequiredPassword; supply as a secret
Beacon:Smtp:FromAddressrequiredSender address

A missing required key fails the send, not the startup. These keys belong to the sample’s adapter; a host with its own adapter reads whatever it needs.

Settings that bind Admins too and that nothing at runtime can change; only host configuration sets them:

KeyDefaultPins
Beacon:Mcp:ForceReadOnlytrueMCP read-only enforcement on, globally and per project; the SQL gate judges every statement read-only whatever it is asked. false logs a startup Warning
Beacon:Mcp:ForcePiiDetectiontruePII detection on. false logs a startup Warning
Beacon:Mcp:ForceNoContentRetentionfalseContent retention off: questions, SQL and free-text errors are never persisted
Beacon:Mcp:Audit:RequireContentfalseContent retention on, globally and per project, so the MCP audit keeps the question and the SQL of every statement a call ran. Cannot be combined with ForceNoContentRetention: with both on the host fails to start. See Keeping audit content
Beacon:Mcp:ForbidRowDataToLlmfalseSample-value collection, value grounding and the eval judge off: no value read from a data source goes to the LLM provider
Beacon:Mcp:Ceilings:*noneCap the MCP row limit and the SQL and question lengths; the other ceilings clamp recorded settings (see MCP)
Beacon:Mcp:Audit:RequiredfalseWithhold an MCP tool result whose audit row could not be written
Beacon:Migrations:EnabledfalseData migration, the only write path

A write that contradicts an MCP lock answers 409 (/errors/setting-locked), an Admin’s included; the MCP settings page shows the locked settings disabled, and GET /beacon/api/mcp/settings/locks lists them (lockedFields) and each active lock with the setting it pins (locks: fieldName, lockName). Stored values a lock overrides are kept and listed in one startup warning; they apply again when the lock is lifted. Settings resolve as lock → project override → global value → built-in default, ceilings last.

Other startup-validated keys (Beacon:Authorization:*, Beacon:Authentication:*, Beacon:UserManagement:*, Beacon:ApiKeys:*, Beacon:Query:*, Beacon:Notifications:*, Beacon:EncryptionKey, Beacon:DataProtection:*) have no runtime override either. Of the configuration values, only the base URL and the LLM settings can also be changed at runtime, in Admin settings.

Validated at startup. Without the section, ForceReadOnly and ForcePiiDetection are on, nothing else is locked, and only the built-in length caps apply.

{
"Beacon": {
"Mcp": {
"ForceReadOnly": true,
"ForcePiiDetection": true,
"ForceNoContentRetention": false,
"ForbidRowDataToLlm": false,
"Ceilings": { "MaxRowLimit": 1000, "MaxSqlChars": 100000, "MaxQuestionChars": 4000 },
"Audit": { "Required": true, "RetentionDays": 400, "RequestIdHeader": "X-Request-Id", "RequireContent": false }
}
}
}
KeyTypeDefaultWhat it does
ForceReadOnly, ForcePiiDetection, ForceNoContentRetention, ForbidRowDataToLlmbooltrue, true, false, falseSee Deployment locks
Ceilings:MaxRowLimitintnoneCaps the row limit the MCP tools apply; must be greater than 0 when set
Ceilings:StatementTimeoutSeconds, MaxResultBytes, MaxConcurrentQueriesPerKeyintnoneClamp the recorded setting, shown on the MCP settings page; must be greater than 0 when set. The MCP query and ask tools use a fixed 30-second timeout
Ceilings:MaxExplainCostdecimalnoneClamps the recorded setting, shown on the MCP settings page; greater than 0 when set
Ceilings:MaxSqlCharsint100000 (always applies)Longest SQL text the gate evaluates
Ceilings:MaxQuestionCharsint4000 (always applies)Longest question ask accepts
Audit:RequiredboolfalseFail closed: withhold a tool result whose audit row could not be written
Audit:RetentionDaysintnone (keep forever)Days audit rows are kept; greater than 0 when set. The sample host purges daily (PurgeExpiredMcpAuditLogsJob); other hosts schedule it themselves
Audit:RequestIdHeaderstringX-Request-IdUpstream correlation header stored with each audit row; empty reads none
Audit:RequireContentboolfalseSee Deployment locks
CallerHashKeystringderived from Beacon:EncryptionKeySee Encryption key

The MCP settings page’s own built-in defaults (row limit 1000, PII detection on, content retention on, sample values and value grounding off) are runtime settings, not configuration. See MCP server and MCP observability and audit.

MCP discovery (Beacon:Mcp:ProtectedResource)

Section titled “MCP discovery (Beacon:Mcp:ProtectedResource)”

Read at request time.

KeyTypeDefaultWhat it does
AuthorizationServersstring[]emptyIssuers published in /.well-known/oauth-protected-resource; each must equal its server metadata’s issuer
DeriveAuthorizationServerFromJwtIssuerboolfalseWhen AuthorizationServers is empty, publish the bearer JWT issuer
ScopesSupportedstring[]emptyScopes published for an external identity provider (for example api://{id}/mcp.access)

Without the section, the authority published is Beacon:Authentication:Oidc:Authority when SSO is enabled. Startup warns about a derived issuer that is not absolute, and about an external server published without ScopesSupported.

KeyTypeDefaultWhat it does
Beacon:Telemetry:CaptureContentboolfalsePut an MCP tool’s input on the current trace span (beacon.tool.input), only where the project’s content retention allows. Logs and metrics never carry content
OTEL_EXPORTER_OTLP_ENDPOINTstringunsetSample host: when set, registers OpenTelemetry tracing and metrics with the OTLP exporter (/beacon/api/health and notification calls are not traced)
OTEL_SERVICE_NAMEstringbeaconSample host: the service name

Other standard OTEL_* variables are read by the OpenTelemetry SDK itself. The Beacon libraries choose no exporter.

Read by IEmbedTokenService, a host-facing service that mints and validates HS256 tokens for embeddable integrations; no Beacon route uses it. Validated at first use.

KeyTypeDefaultWhat it does
SigningKeystringRequiredRequired; at least 32 bytes (UTF-8). Supply as a secret
IssuerstringbeaconMust not be blank
DefaultTtlSecondsint300Greater than 0, at most MaxTtlSeconds
MaxTtlSecondsint900Greater than 0

Beacon does not bundle a job runner; recurring and background work goes through IBeaconScheduler, registered with options.AddBeaconScheduler<T>() (required). The sample host implements it on Moberg.Warp.

namespace Beacon.Core.Worker;
public interface IBeaconScheduler
{
Task AddOrUpdate(int subscriptionId, string subscriptionName, string cron);
Task Remove(int subscriptionId, string subscriptionName);
// Optional: default implementations throw NotImplementedException.
Task AddOrUpdateDataQualityJob(int contractId, string contractName, string cron);
Task RemoveDataQualityJob(int contractId, string contractName);
Task<string> EnqueueProjectDocumentation(int projectId, int userId, string? notifyUserId);
Task<string> EnqueueAiActorThinkCycle(int actorId, int subscriptionId);
Task<string> EnqueueMcpEval(int runId);
}

Beacon calls AddOrUpdate when a subscription is created or its cron changes and Remove when it is deleted or disabled; your implementation maps them onto recurring jobs that call IJobService.ExecuteQuery(subscriptionId). The Enqueue… methods return the job id the UI correlates JobStatusChanged events by. A host that runs Beacon’s jobs on its own job host must mark each job’s DI scope as system work (ISystemWorkScope.MarkAsSystemWork()). See Background jobs.

Register a connector per engine on the builder: .AddPostgreSqlConnector(), .AddSqlServerConnector(), .AddMySqlConnector(), .AddBigQueryConnector(), .AddSnowflakeConnector(), .AddDatabricksConnector(), .AddAzureSynapseConnector(), .AddCloudWatchConnector(), .AddApiConnector(). Data-source connections are entered in the UI and encrypted at rest. Give every data source Beacon reads a read-only login. On PostgreSQL and MySQL, Beacon also runs each read in a read-only transaction. See Data-source connectors and Data sources.

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
}
}

Beacon logs identifiers and counts, never query text, connection strings, row data or tokens. Security events are structured log events 7101–7123; route them to your log pipeline.

{
"ConnectionStrings": {
"BeaconContext": "Host=db;Database=beacon;Username=beacon;Pooling=true;MaxPoolSize=50"
},
"Beacon": {
"BaseUrl": "https://beacon.example.com/beacon",
"PublicBaseUrl": "https://beacon.example.com",
"DataProtection": { "ApplicationName": "beacon-production" },
"Authentication": {
"Session": { "RevalidateMinutes": 5, "AbsoluteLifetimeHours": 12 },
"Oidc": {
"Enabled": true,
"Authority": "https://login.microsoftonline.com/{tenant-id}/v2.0",
"ClientId": "{client-id}",
"AllowedTenants": [ "{tenant-id}" ]
}
},
"UserManagement": {
"LocalLogin": { "Enabled": false, "BreakGlassUserNames": [ "superadmin" ] }
},
"Authorization": {
"RequireProjectMembership": true,
"SeparationOfDuties": { "Enabled": true }
},
"ApiKeys": { "MaxLifetimeDays": 180 },
"Mcp": { "Audit": { "Required": true, "RetentionDays": 400 } },
"ForwardedHeaders": { "Enabled": true, "KnownProxies": [ "10.0.0.5" ] },
"LLM": { "Provider": "OpenAI", "Model": "<model-id>" }
},
"Logging": { "LogLevel": { "Default": "Information" } }
}

Supply Beacon__EncryptionKey, Beacon__Authentication__Oidc__ClientSecret, Beacon__UserManagement__SetupToken, Beacon__LLM__ApiKey and the database password as environment variables or from a secrets provider.