Default-Deny Unknown Power BI Modeling Tools
Pins a per-tenant allowlist of audited Power BI tool-name suffixes and denies any call whose tool name does not end with an allowlisted entry.
- Direction
- ingress
- Rego package
power_bi.ingress.default_deny_unknown_modeling_ops- App
- power-bi
- Bundle
- soc2
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:ca1ad268d9225a10f6a87db32915623b9178fb4830f1618d4d84ab088b94f605
power-bidefault-deny-unknown-toolsallowlistmodelingingresssoc2
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny — only allowlisted Power BI tool-name suffixes pass
Package: power_bi.ingress.default_deny_unknown_modeling_ops
What it does
Pins a per-tenant allowlist of audited Power BI tool-name suffixes and denies any call whose tool name does not end with an allowlisted entry. Everything the tenant has not explicitly reviewed is blocked before it reaches the Power BI MCP server, and the deny surfaces as a gateway event — the drift signal that flags a new, renamed, or newly enabled tool the moment it first appears.
The local Power BI Modeling MCP server (microsoft/powerbi-modeling-mcp)
is explicitly public preview: its README warns tools "may significantly
change" before GA. It exposes ~21 coarse <object>_operations multiplexer
tools, each fronting many sub-operations (list/create/update/delete-style),
so new or renamed tools can appear between upgrades without review. This
default-deny gate is the outer boundary for the app: a tool that stops matching
an allowlisted suffix — or a freshly introduced one — is denied until it is
re-audited.
This is also the only enforcement point that cannot be bypassed with the
modeling server's --skipconfirmation flag or with clients that do not
implement MCP elicitation. The server's own confirmation prompts are
client-side and optional; the gateway allowlist is not.
A missing, empty, non-string, or non-ASCII tool name matches nothing and is denied (fail closed).
Pin the allowlist to YOUR tenant at import time
The shipped allowed_tool_suffixes array is a starter set of the tool
names verified from Microsoft's own sources — the modeling server's README
(the 21 *_operations names) and the remote server's wire-level names
(skills-for-fabric). It is not, and cannot be, the list of tools your
deployment has reviewed.
The allowlist is a per-tenant pin: after each Power BI MCP upgrade, re-run the gateway dump-input technique to capture the exact tool names the server now advertises, audit any new or renamed names, and add only the audited ones. Every unlisted tool is denied until you do. Do not guess — the string the gateway sends is deployment-specific (it prepends the configured server name), so verify before pinning.
Compliance alignment
- SOC 2 CC6.1 — supports logical access security over protected assets: the agent channel can only reach Power BI modeling/query capabilities that were explicitly reviewed and enumerated.
- SOC 2 CC6.6 — supports boundary protection: preview-server tools added or renamed in an upgrade do not become reachable through the gateway boundary without an explicit allowlist change.
- SOC 2 CC6.8 — supports prevention of unauthorized software: any modeling
or query tool the tenant has not audited is unauthorized-by-default on the
agent path, including tools that would otherwise run past the server's
--skipconfirmationbypass. - GDPR Art. 25 — supports data protection by design and by default on the agent channel: the default state of any new Power BI tool is "inaccessible until audited," and semantic models routinely front regulated data (finance, HR, customer PII) imported or DirectQueried from the warehouse.
Tool name matching
Power BI's tool names follow three incompatible conventions (see the landscape
note): the modeling server uses snake_case <object>_operations multiplexers
(model_operations); the remote hosted server uses PascalCase verbs
(ExecuteQuery); the community server uses prefixed snake_case (desktop_…,
cloud_…). Behind the DTwo gateway each is prefixed with the configured MCP
server name (e.g. powerbi-modeling-mcp-model_operations), and that prefix is
not standardized.
To stay portable, the policy matches case-insensitively on
lower(input.resource.name) with endswith against the bare suffix:
model_operations(bare) — matches,powerbi-modeling-mcp-model_operations(gateway-prefixed) — ends withmodel_operations, matches,ExecuteQuery/powerbi-mcp-ExecuteQuery— lowercased, ends withexecutequery, matches.
Before matching, the raw (pre-lowercase) name must consist only of the
ASCII set real tool names and gateway prefixes use — [A-Za-z0-9._-]. This is
checked before lower() runs, which closes a Unicode case-folding evasion:
lower() folds a handful of non-ASCII code points onto ASCII letters (e.g. the
Kelvin sign U+212A → k), so an allowlisted suffix could otherwise be spoofed
with a folded character. A name containing any character outside that ASCII set
is denied.
The starter allowlist covers the verified modeling multiplexers plus the verified remote read tools:
- Modeling multiplexers (21):
connection_operations,database_operations,transaction_operations,trace_operations,model_operations,table_operations,column_operations,measure_operations,relationship_operations,partition_operations,user_hierarchy_operations,calculation_group_operations,perspective_operations,named_expression_operations,function_operations,culture_operations,object_translation_operations,calendar_operations,query_group_operations,security_role_operations,dax_query_operations. - Remote read tools (4):
ExecuteQuery,ValueSearch,GetSemanticModelSchema,GetReportMetadata.
Deliberately excluded (denied until audited): the remote Copilot generator
GenerateQuery (consumes Copilot capacity), the unverified discovery helpers
DiscoverArtifacts / ResolveReportIdFromUrl (landscape-noted as unverified on
/mcp/powerbi), every community-server tool, and any Fabric-server tool
(OneLake file delete, item CRUD).
Argument shape
This policy inspects only the tool name (input.resource.name); it reads no
arguments, so it is insensitive to argument-shape differences between the three
servers. A missing resource or resource.name resolves to "" via
object.get and matches nothing (deny). A non-string name (null, number,
object, array — a malformed or hostile request) is coerced to "" rather than
passed to lower(); without that guard lower() would raise a built-in type
error that leaves allow and reason undefined — a deny with no surfaced
reason. With the guard it is a clean, reasoned deny.
Examples
Allowed — audited modeling multiplexer
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "powerbi-modeling-mcp-model_operations", "type": "tool" },
"payload": {
"name": "powerbi-modeling-mcp-model_operations",
"args": { "operation": "list" }
}
}
}
allow = true, no reason.
Allowed — audited remote read tool
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "powerbi-mcp-ExecuteQuery", "type": "tool" },
"payload": {
"name": "powerbi-mcp-ExecuteQuery",
"args": { "query": "EVALUATE TOPN(10, 'Sales')" }
}
}
}
allow = true, no reason.
Denied — unaudited tool (drift)
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "powerbi-modeling-mcp-dataflow_operations", "type": "tool" },
"payload": {
"name": "powerbi-modeling-mcp-dataflow_operations",
"args": {}
}
}
}
allow = false, reason = "This Power BI tool is not on the audited allowlist (...)".
Composition
This policy is the outer gate — it decides which Power BI tools exist for
agents. It gates only tool names; the per-call operation enum inside each
*_operations multiplexer is undocumented and unverified, so an allowlisted
multiplexer may still perform a write or a delete. Pair it with the policies
that constrain what a permitted tool may actually do:
apps/power-bi/block-rls-bypass-service-principal— denies the RLS- sensitive read/query tools under service-principal identity.freeze-rls-role-edits(RLS-tampering guard onsecurity_role_operations) — stops row-security filter definitions from being rewritten.guard-warehouse-sql-dax— inspects the DAX text on the query tools (ExecuteQuery,dax_query_operations) for whole-table dumps.- An egress PII redaction policy on query/
ValueSearch/GetReportMetadataresponses so returned model data is masked before it reaches the model.
Known limitations
- The starter allowlist is not your tool list. Pinning it to the suffixes your tenant has actually reviewed — and re-running dump-input after every Power BI MCP upgrade, since the server is preview and may rename tools — is a required deployment step, not a tuning step.
- Name-based trust only; the operation enum is not gated. The
*_operationstools multiplex reads and writes (list/create/update/delete) selected by an operation argument whose enum is undocumented and unverified. Allowlistingmeasure_operationsorsecurity_role_operationsby name permits every sub-operation it fronts. This policy gates names, not actions — it must be paired with the RLS-tampering, service-principal, and DAX-guard companions above to constrain behavior. - M / Power Query injection via allowlisted multiplexers has no listed
companion.
partition_operationsandnamed_expression_operationsare verified modeling tools and are therefore allowlisted by name, but they edit Power Query (M) expressions, and M can callWeb.Contents(...). A rewritten partition source is both a data-poisoning and an exfiltration channel that fires later, at refresh time, outside the gateway's view — the deny-event the gateway would otherwise surface never appears because the malicious fetch happens off the MCP path. None of the four companions listed under Composition constrains this (the DAX guard inspects DAX on the query tools, not M on the partition/expression tools). If agents do not need to edit partitions or named expressions, droppartition_operationsandnamed_expression_operationsfrom your pinned allowlist, or add a dedicated M-injection guard that inspects the operation and the M source string. endswithmatching trusts the suffix, not a separator. To cover the bare name and any gateway server-name prefix with one entry, matching does not require a separator before the suffix. A tool literally named<anything>model_operations(any string glued directly to an allowlisted suffix) would also match. This applies to the remote read verbs too, not only the*_operationsmultiplexers: an unaudited tool namedBatchExecuteQueryorExportExecuteQueryends withexecutequeryand would be auto-allowed, as would anything ending invaluesearch/getsemanticmodelschema/getreportmetadata. No tool in the current Power BI inventory collides this way, but the short verb suffixes are the likelier future collision; if your deployment needs stricter matching, replace the suffix entries with the exact full gateway tool names.- Generic object suffixes weaken drift detection for future renames. Because
matching is by suffix, a future or renamed preview-server tool whose name
ends in an allowlisted object token — e.g. a hypothetical
snapshot_table_operationsending intable_operations, oradvanced_model_operationsending inmodel_operations— would be auto-allowed rather than surfaced as drift. The drift-detection guarantee holds for tools that stop matching a suffix and for brand-new object types (dataflow_operationsdoes not end with any allowlisted suffix and is denied), not for a new tool that starts ending in an existing object token. If that matters to you, pin the exact full gateway tool names instead of the bare*_operationssuffixes. - Community and Fabric servers are not covered by the starter list. The
community server's
execute_dax/cloud_list_*/pbip_*names and the Fabric server's OneLake/item-CRUD tools are intentionally excluded — they are denied until audited and added. If you run one of those servers, audit and pin its verified names. - ASCII-only tool names. Matching requires the raw name to be
[A-Za-z0-9._-]. This is deliberate (it blocks Unicode case-fold and homoglyph spoofing), but a deployment whose configured MCP server name contains other characters (spaces,@,/, non-ASCII) would see even its legitimate tools denied; rename the server to an ASCII slug, or relax the character class, if so. - Exact upstream suffixes unverified for your gateway. The bare names are
verified from Microsoft's README and
skills-for-fabric, but the string your gateway actually sends depends on the configured server name. Confirm with dump-input before pinning. - No identity-based exemptions. All callers face the same allowlist. If you
need a platform-admin break-glass group that can call an unaudited tool during
a controlled window, add a separate
allow ifbranch gated oninput.subject.claimsgroups (placeholder group names — replace with your IdP's group name at import time).
Compliance note. This policy supports alignment with the cited framework controls on the MCP path only. No policy or bundle makes an organization compliant with any framework; web-UI, native-API, and in-app access are outside the gateway's reach by design. Validate against your own compliance program before relying on it.
Policy source (Rego)
package power_bi.ingress.default_deny_unknown_modeling_ops
# Deny-by-default: a tool call is allowed only if its name ends with an audited
# allowlist suffix below. A missing, empty, non-string, or non-ASCII tool name
# matches nothing and is therefore denied (fail closed).
default allow := false
# Audited Power BI tool-name suffixes — STARTER SET. Pin to the suffixes YOUR
# tenant has reviewed, and re-run dump-input after every Power BI MCP upgrade
# (the modeling server is preview and may rename tools). Matched with endswith
# so one bare suffix also covers any gateway <server-name>- prefix
# (powerbi-modeling-mcp-model_operations).
#
# Sources: microsoft/powerbi-modeling-mcp README (the 21 *_operations names) and
# microsoft/skills-for-fabric (remote wire-level names). Deliberately EXCLUDED —
# default-deny by design, audit before adding any:
# - Remote Copilot generator: GenerateQuery (consumes Copilot capacity)
# - Unverified discovery: DiscoverArtifacts, ResolveReportIdFromUrl
# (presence on /mcp/powerbi unverified)
# - Community server: execute_dax, desktop_*, cloud_*, pbip_*, delete_*
# - Fabric server: OneLake file delete, item CRUD
allowed_tool_suffixes := [
# Modeling server — session/infra multiplexers
"connection_operations",
"database_operations",
"transaction_operations",
"trace_operations",
# Modeling server — model read/write metadata CRUD multiplexers
"model_operations",
"table_operations",
"column_operations",
"measure_operations",
"relationship_operations",
"partition_operations",
"user_hierarchy_operations",
"calculation_group_operations",
"perspective_operations",
"named_expression_operations",
"function_operations",
"culture_operations",
"object_translation_operations",
"calendar_operations",
"query_group_operations",
# Modeling server — governance-sensitive write multiplexer (name-gated only;
# pair with freeze-rls-role-edits to constrain the operation enum)
"security_role_operations",
# Modeling server — DAX query multiplexer (read)
"dax_query_operations",
# Remote hosted server — verified read tools (lowercased for the match below)
"executequery", # ExecuteQuery
"valuesearch", # ValueSearch
"getsemanticmodelschema", # GetSemanticModelSchema
"getreportmetadata", # GetReportMetadata
]
# Raw tool name straight from the request. Missing resource/name resolves to ""
# via object.get and matches nothing (fail closed).
raw_tool_name := object.get(object.get(input, "resource", {}), "name", "")
# Tool name, lowercased. A non-string name (null, number, object, array — a
# malformed or hostile request) is coerced to "" instead of being handed to
# lower(), which would raise a built-in type error and leave allow/reason
# undefined. Coercing keeps the decision a clean, reasoned deny (fail closed).
tool_name := lower(raw_tool_name) if is_string(raw_tool_name)
tool_name := "" if not is_string(raw_tool_name)
# Character-class guard on the RAW (pre-lowercase) name. Real Power BI tool names
# and gateway <server-name>- prefixes use only ASCII letters, digits, underscore,
# dot, and hyphen. Checking the raw name BEFORE lower() closes a Unicode
# case-folding evasion: lower() folds some non-ASCII code points onto ASCII
# letters (e.g. the Kelvin sign U+212A -> "k"), so an allowlisted suffix could be
# spoofed with a folded character and slip past the default-deny gate despite
# being a visibly different, un-audited name. The regex is not multiline in
# OPA/Go, so a newline in the raw name breaks the whole-string match (^...$).
# Guarded by is_string so a non-string name still yields a clean, reasoned deny.
raw_name_is_plain_ascii if {
is_string(raw_tool_name)
regex.match(`^[A-Za-z0-9._-]+$`, raw_tool_name)
}
# Allow only when the raw name is plain ASCII AND ends with an audited suffix.
allow if {
raw_name_is_plain_ascii
some suffix in allowed_tool_suffixes
endswith(tool_name, suffix)
}
reason := "This Power BI tool is not on the audited allowlist, so the gateway denies it by default and surfaces the call as tool drift. The Power BI Modeling MCP server is preview and can add or rename tools between upgrades, and this gate is the only enforcement point that cannot be bypassed with the server's --skipconfirmation flag or unimplemented client elicitation. Re-run the dump-input technique after the upgrade, audit the tool, and if it is safe for agents add its verified suffix to the allowlist. Contact your data-governance team if this is a false positive." if not allow Canonical source: policy.md on GitHub · raw · raw on this site (.md)
Related policies
Airtable: Redact PII in Record Reads
Scans the responses of the Airtable record-read tools — the calls that return row fields values — and rewrites high-confidence PII shapes to a fixed…
Asana: Redact PII in Task & Comment Reads
On the Asana MCP read path, this transform scans the free-text business fields that ride back in task, comment/story, and status-update responses — notes,…
BigQuery: Redact PII in Query Results
Scans the content returned by BigQuery's result-returning tools and rewrites high-confidence PII shapes to fixed, non-recoverable redaction tokens before the…
Block Agent Email to External Recipients
Blocks agent-initiated Microsoft 365 email sends when any recipient address falls outside a corporate-domain allowlist.
Block BigQuery Exfiltration and Cross-Project Writes
Inspects the raw GoogleSQL string carried by BigQuery SQL tools and denies any statement that moves data out of the tenant's own project — even when the call…
bigqueryguard-warehouse-exportingresssqlexfiltrationsoc2pci-dssgdpr-ccpa
Block Bulk Export & External Staging (Snowflake)
Blocks Snowflake SQL-execution tool calls whose query text moves whole tables off the Snowflake perimeter — bulk export to cloud storage or a stage, and…
snowflakeguard-warehouse-sqlexportexfiltrationingresssoc2pci-dssgdpr-ccpa