Skip to content
Menu

Security & governance

Explore · Security & governance

Every request passes the same layers.

People, programs, AI assistants and scheduled work all reach your data through one identity layer, one project access evaluator and one SQL execution gate. Results are masked on the way out, and every layer leaves an audit trail.

  • 1access evaluator
  • 8gate stages
  • 6functional permissions
01Project access

Projects are the access boundary.

One evaluator decides every access, for the UI, the REST API, MCP and scheduled work alike. A restricted project does not exist for anyone who is neither a member nor an Admin: it answers 404. Pick a caller and a project to see which actions light up.

Global role
Membership in this project
Project
PROJECT · SHOP ANALYTICSEditor
  • ReadResources, SQL, metadata, stored results, documentationgranted
  • RunExecute saved SQL, evaluate data contractsgranted
  • QueryAd-hoc and draft SQL, LLM work (playground, AI actors)granted
  • WriteCreate and change resourcesgranted
  • ManageDelete, archive, take over a lock, force a metadata refreshas recorded owner
  • ManageMembersManage members, link data sourcesnot granted
  • CurateReview learned patterns, documentation and relationshipsnot granted

A restricted project: the Editor member acts with their membership role. Manage applies to what they own (a lock they hold, a subscription or contract they own).

ManageMembers and Curate come only from a ProjectAdmin membership or the Admin role. They are never granted by an open project, to an API key, to an MCP caller or to the owner of scheduled work. An API key reaches only the projects it names, capped by the lower of its scope and its owner's global role, and never gets the Admin bypass.

02Functional permissions

Administrative duties without administrative data.

The Admin role's duties are split into six permissions. Each is held through a functional role (a level-0 system role an Admin assigns) or an identity-provider app role mapped at sign-in. None of them grants project data, and API keys never hold one.

Approver

Approves and rejects query changes and governed changes (migration jobs, Custom SQL rules, recipient destinations), never their own. Reads every project's approval requests.

Conflicts withSecurityAdminUserAdmin

Auditor

Reads and verifies the security audit trail; reads the MCP audit, the MCP and project settings, the admin settings (secrets masked), every user's API keys and what a user owns, created or changed last.

Conflicts withSecurityAdminUserAdmin

SecurityAdmin

Changes the MCP settings and the admin settings except the LLM connection and base URL, manages recipients, publishes saved queries as MCP tools, sets repository tokens.

Conflicts withApproverAuditor

UserAdmin

Manages external users, their roles and sessions, sees what a user owns, created or changed last, revokes API keys, changes the owner of scheduled work, manages project members, restricts projects.

Conflicts withAuditorApproverMigrationOperator

DataSourceAdmin

Creates, tests and deletes data sources, links a source no open project holds into a project it administers, changes shared data-source state.

No conflicting pair

MigrationOperator

Runs data migration for the jobs whose data sources it may query and write. Mapped only while migrations are enabled.

Conflicts withUserAdmin

Separation of duties

With SeparationOfDuties:Enabled (off by default), giving one user both permissions of a pair is refused with 400, also when an Admin assigns them. A user who already holds both holds neither.

SecurityAdminApproverUserAdminMigrationOperatorDataSourceAdminAuditor
03The SQL execution gate

One gate in front of every database.

Every statement Beacon runs on a data source for a user or an AI feature passes the same eight stages: MCP tools, previews, scheduled checks, saved-query tools, data-quality rules, AI features and migration reads. Choose a statement to see where it stops.

PostgreSQL · database shop
SELECT order_id, total
FROM shop.orders
WHERE status = 'paid'
LIMIT 5000000;

Passes every stage. The row cap replaces the statement's own LIMIT, then the statement runs in a READ ONLY transaction.

  1. 1Deployment lockBeacon:Mcp:ForceReadOnly (on by default) forces read-only whatever the caller asked.
  2. 2LengthSQL longer than the ceiling (100,000 characters by default) is refused.
  3. 3CharactersControl characters other than tab, LF and CR, and Unicode non-characters, are refused.
  4. 4GuardrailWrite and DDL patterns are refused, judged after the dialect's tokenizer strips comments.
  5. 5AST validatorParsed in the data source's dialect; anything but a read is refused. An unknown dialect is refused.
  6. 6Cross-database ruleOther databases, remote servers, files, USE, SET ROLE, EXECUTE AS and SQL built from strings are refused.
  7. 7Host policyFor a host-exposed DbContext: only allow-listed tables and columns.
  8. 8Row capThe cap is written into the SQL, overriding any bound the statement sets itself.

Then the engine's own read-only path

PostgreSQLREAD ONLY transaction, exactly one statement per batchDatabase refuses writes
MySQLSTART TRANSACTION READ ONLY, statement batches offDatabase refuses writes
SQL ServerA transaction that is always rolled back; a statement that ends it fails the run (7116)
BigQueryThe connector's parser check and a dry run whose statement type must be SELECT
DatabricksThe connector's parser check and the configured catalog
Synapse, SnowflakeThe gate, and the data source's read-only login

The gate is the primary control and database-level read-only is defence in depth, so every data source needs a read-only login, on every engine. Every statement has a timeout: 120 seconds by default for scheduled runs, previews and Beacon's own reads, and a fixed 30 seconds for the MCP query and ask tools.

04Worked example

Three calls, traced to the refusal.

An API key never does more than its owner may do now. Each trace below follows one request through the layers to the point where Beacon refuses it, and the answer the caller receives.

Input An MCP client is configured with a key that carries only the Read scope and names the project Shop analytics.

  1. IdentityThe header starts with Bearer sk-sem_. The key is hashed with SHA-256 and found: not revoked, not expired, owner enabled.
  2. ClaimsScopes: Read. Projects: Shop analytics (12). Owner: Ana Horvat, Editor.
  3. Execute-scope policyMapBeaconMcp() mounts /beacon/mcp behind the Execute scope, because its tools run SQL. No session opens.
What the caller receives
POST /beacon/mcp
HTTP/1.1 403 Forbidden

# On a host that maps the route with a weaker policy,
# the MCP layer still refuses, and audits the tool call:
Permission denied: this credential does not have
the Execute scope
05Four-eyes approvals

SQL goes live only after a second person reviews it.

With the approval workflow enabled (it is opt-in), a change to what SQL runs or where data goes takes effect only when someone other than the requester approves exactly the content they reviewed.

  1. Save a changeFiles a PendingApproval version with its exact content and SHA-256 hash. The live query is untouched.
  2. Review the diffAn Approver other than the requester sees each step's SQL, data source and parameters side by side.
  3. Approve the hashThe decision carries expectedContentHash: the hash of the content the reviewer was shown.
  4. Re-checkStill pending, not stale, content unchanged, and every step passes the save checks again.
  5. Go liveDecision and version move commit in one transaction. Scheduled runs execute only reviewed content.
APPROVAL REQUEST · #42Pending
Query
Paid orders without shipment
Submitted by
Ana Horvat
Proposed change
v4 → v5
Step 1 · Shop (PostgreSQL)Modified
  SELECT order_id, customer_id, total, paid_at
  FROM shop.orders
  WHERE status = 'paid'
-   AND paid_at < now() - interval '24 hours';
+   AND paid_at < now() - interval '12 hours';
Content hash9c41d27a…4f0de07b
Marko Kovač may approve: Approver, not the requester.RejectApprove
POST /beacon/api/approvals/42/approve
{
"comment": "Window checked against the fulfilment SLA.",
"expectedContentHash": "9c41d27a0be35f86c1e2d4a7b09f3e6d5a28c71f40be96d3a1572e8c4f0de07b"
}

The approval applies only if

  • the request is still pending; a second decision answers 409 and never overwrites the first;
  • no other version went live since the change was made; otherwise it is stale and must be submitted again;
  • the proposal still holds the submitted content, and that content is what the reviewer was shown;
  • every step passes the save checks again: the execution gate and the data source's metadata.
WHAT ELSE WAITS FOR A SECOND PERSONdefaults
KindReviewedUntil approved
Migration jobNew job, or a changed source, steps, destination, mode or transformationEvery run is refused and recorded as failed.
Custom SQL ruleNew rule, or changed SQL or data sourceThe rule is left out of every evaluation.
Recipient destinationNew recipient, or changed type, destination or headersDeliveries skip the recipient; each skip is logged.
Scheduled runThe query's active versionRecorded as Skipped and shown as Not approved.
An approval request in Beacon: request details, the current and proposed final query side by side, and the decision form
The review page: the request, the proposed change against the active version, and the decision. The requester cannot approve it, and neither can an API key.
06Deployment locks

Guarantees that do not depend on an Admin.

Some settings are pinned by host configuration and bind Admins too. While a lock is on, the settings page shows the setting disabled and the API answers 409 (/errors/setting-locked). Only host configuration lifts it.

Read-only enforcementBeacon:Mcp:ForceReadOnly = trueRead-only SQL everywhere the gate runs.Locked on
PII detectionBeacon:Mcp:ForcePiiDetection = truePII detection and masking on.Locked on
Data migrationBeacon:Migrations:Enabled = falseThe only write path. Off: no migration endpoints, no jobs, for anyone.Locked off
No content retentionBeacon:Mcp:ForceNoContentRetention = falseSet to true: no question, SQL or error text stored with MCP audit rows.Opt-in lock
No row data to the LLMBeacon:Mcp:ForbidRowDataToLlm = falseSet to true: no row values (samples, value grounding, eval judging) reach the LLM provider.Opt-in lock
Audit content keptBeacon:Mcp:Audit:RequireContent = falseSet to true: MCP audit rows keep the question and the SQL of every statement a call ran; nobody can switch retention off.Opt-in lock
MCP row limit ceilingBeacon:Mcp:Ceilings:MaxRowLimit = noneSet a number: the upper bound for every project's MCP row limit.Opt-in lock

Values shown are the defaults. The first three are pinned out of the box; the others pin their setting once the host sets them. Stored values a lock overrides are kept, have no effect, and are listed in one startup warning. Every MCP setting change is recorded in the settings history with who changed it.

07Audit and security events

Every layer leaves a record.

Beacon keeps three audit records in its own database: an append-only, signed security audit trail, the MCP audit log and the settings history. It also writes a copy of every committed trail event, and security-relevant log events with identifiers only, to the log stream: ship them to your SIEM and alert on them.

Security audit trail

One append-only event per security-relevant action: sign-ins, users and roles, API keys, project access, data sources, delivery, settings, approvals, ownership, curation, data quality, migrations and every read of audit data. Each event is signed and verified on a schedule and before a purge. Auditors read and verify it on the Security Audit page.

MCP audit log

One row per tool call, refusals and failures included: tool, caller, project, data source, rows, timing, trace id, MCP session id, and every statement the call ran (its SQL where content is retained). Optional fail-closed mode withholds a result whose row could not be written. Auditors read it.

Settings history

Admin settings, MCP settings and migration write-target changes, with the old and new value (secrets as ***) and who made the change.

Log stream

Security log events 7101 to 7123 and the Beacon.Audit events, ids only, no personal data. Alert at least on 7119, 7113, 7114, 7115 and 7116, and on audit write or verification failures (9107, 9108).

log stream · Beacon.Audit and security events
  1. 08:59:419100McpToolAuditedTool=query ProjectId=12 Rows=48 DurationMs=212
  2. 09:02:137108AccessRefuseda 403: ids, reason, principal kind
  3. 09:05:207101MemberAddedproject and user ids only
  4. 09:05:209104SecurityAuditRecordedAction=access.member_added, committed and signed
  5. 09:11:027113UserRolesChangedsessions of the user endalert
  6. 09:14:377115MigrationWritesChangedwrite-target flag changedalert
  7. 09:20:057111ScheduledWorkSkippedowner not authorized
  8. 09:31:447119BreakGlassSignInlogged as a warningalert
  9. 09:40:107123RowDataSentToLlmCategories=sample-values
  10. 09:46:589103McpAuditReadrecorded first as audit.mcp_audit_read
  11. 10:15:029109SecurityAuditCheckpointhourly verification: Checked=212 Failed=0