dtwo Policy Store

Default-Deny Unknown Tableau Tools

Fails closed on tool drift. The policy carries a pinned allowlist of the 39 tools in the verified official Tableau web toolset (tableau/tableau-mcp v2.24.

Direction
ingress
Rego package
tableau.ingress.default_deny_unknown_tools
App
tableau
Bundle
soc2
Published
Minimum gateway
1.0.0b24
Schema version
1.0.0
Checksum
sha256:6b9d333dc2593955153061b79c78472e08986cbcb9bf30cb2f7c26453d7c047d

tableaudefault-denyunknown-toolsallowlistingresssoc2

What this policy does

Direction: ingress (tool_pre_invoke) Default: deny — allow only pinned, verified tool names Package: tableau.ingress.default_deny_unknown_tools

What it does

Fails closed on tool drift. The policy carries a pinned allowlist of the 39 tools in the verified official Tableau web toolset (tableau/tableau-mcp v2.24.x, Jul 2026). A Tableau call is allowed only when the suffix of lower(input.resource.name) matches an allowlisted tool name; every other name is denied and surfaced with an actionable reason for operator review.

The hosted mcp.tableau.com server ships new tools automatically as Tableau releases them, so an un-pinned gateway silently gains ungoverned tool surface between releases. This policy makes a new, renamed, or misspelled upstream tool name fail closed until an operator adds it to the allowlist and re-verifies against the server's tools/list after each upgrade — turning a silent capability expansion into an explicit, reviewed change. The one exception is a new name that suffix-extends a pinned entry (e.g. a future force-delete-workbook ending with the pinned -delete-workbook); suffix matching lets that through the existence gate — see Known limitations for why, and how the danger-scoped companions still catch it.

This is a PF-28 (default-deny-unknown-tools) ingress control. default allow := false is the whole point: the allowlist is the only thing that grants access.

Per-tenant pinning

allowed_tool_suffixes is a per-tenant constant — it is the pinned inventory for one tenant's Tableau deployment at one point in time. It is not self-updating. The intended operational loop is:

  1. After every Tableau MCP server upgrade (hosted or self-hosted), call tools/list on the server.
  2. Diff the returned tool names against allowed_tool_suffixes.
  3. For each new tool, decide whether it belongs on the allowlist, add its kebab-case suffix, and re-publish this policy version. Denied names in your deny logs are the review queue.

The shipped list covers only the official web toolset. Tableau Next (the Agentforce-platform product, analytics/tableau-next) exposes a disjoint snake_case toolset (analyze_data, list_dashboards, search_assets, …) that is intentionally not on this list — it is a separate product requiring its own allowlist policy. If you run both products behind one gateway, attach a second default-deny policy for the Tableau Next server rather than merging the lists.

Compliance alignment

  • SOC 2 CC6.1 — supports logical access security over protected assets by ensuring only an audited, named set of tools can reach the analytics backend on the agent channel.
  • SOC 2 CC6.6 — supports boundary protection: an upstream server that grows new tools cannot expand the gateway's reachable surface without an explicit allowlist change.
  • SOC 2 CC6.8 — supports the "prevent unauthorized software/functionality" control by denying tool functionality that has not been reviewed and pinned.
  • SOC 2 CC7.2 / CC7.3 — the deny-and-surface behaviour feeds anomaly monitoring: a denied, unknown tool name in the audit stream is the drift-detection signal for a new or renamed tool.
  • GDPR Art. 25 — supports data protection by design and by default on the agent channel: the default posture is deny, and new data-reaching capabilities are off until deliberately enabled.

Tool name matching

Matching is on the suffix of lower(input.resource.name), case-insensitive. The DTwo gateway prefixes every tool name with the configured MCP server name (e.g. tableau-mcp-query-datasource), and that prefix is not standardized across deployments — suffix matching keeps the policy portable. The official server uses kebab-case with no vendor prefix (query-datasource, delete-workbook), so each allowlist entry is the leading-hyphen kebab suffix (-query-datasource, -delete-workbook).

The 39 pinned tools, by group:

  • Data reads: -query-datasource, -get-datasource-metadata, -list-datasources, -get-view-data, -get-custom-view-data, -get-view-image, -get-custom-view-image
  • Catalog / metadata reads: -list-workbooks, -get-workbook, -get-view, -list-views, -list-custom-views, -list-projects, -search-content, -list-jobs, -list-users, -list-extract-refresh-tasks
  • Pulse reads: -list-all-pulse-metric-definitions, -list-pulse-metric-definitions-from-definition-ids, -list-pulse-metrics-from-metric-definition-id, -list-pulse-metrics-from-metric-ids, -list-pulse-metric-subscriptions, -generate-pulse-metric-value-insight-bundle, -generate-pulse-insight-brief
  • Admin-insights reads: -query-admin-insights-ts-events, -query-admin-insights-site-content, -query-admin-insights-job-performance, -get-stale-content-report
  • Token / session: -get-embed-token, -revoke-access-token, -reset-consent
  • Mutations + their confirm- twins: -delete-datasource, -confirm-delete-datasource, -delete-workbook, -confirm-delete-workbook, -delete-extract-refresh-task, -confirm-delete-extract-refresh-task, -update-cloud-extract-refresh-task, -confirm-update-cloud-extract-refresh-task

Every destructive tool has a confirm- twin registered as a separate tool; both the base and the -confirm- name are pinned so the preview→confirm protocol works end to end. This policy is a gate on existence, not on danger — it allows the mutation and token tools so they remain usable; pair it with the danger-scoped policies below to actually restrict them.

Argument shape

This policy inspects only the tool name (input.resource.name). It does not read input.payload.args, so it is insensitive to argument shape and to the confirm preview/execute distinction. Argument-level control is the job of the companion policies.

Examples

Allowed — a pinned read tool (server-prefixed)

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "tableau-mcp-query-datasource", "type": "tool" },
    "payload": {
      "name": "tableau-mcp-query-datasource",
      "args": { "datasourceLuid": "abc-123", "query": { "fields": [] } }
    }
  }
}

allow = true, no reason.

Denied — a new/renamed upstream tool not yet pinned

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "tableau-mcp-list-flows", "type": "tool" },
    "payload": { "name": "tableau-mcp-list-flows", "args": {} }
  }
}

allow = false, reason names the tool and tells the operator to verify against tools/list and add it to the allowlist.

Denied — a Tableau Next (snake_case) tool

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "tableau-next-analyze_data", "type": "tool" },
    "payload": { "name": "tableau-next-analyze_data", "args": { "utterance": "top accounts" } }
  }
}

allow = false — Tableau Next tools are a separate product and belong on their own allowlist.

Composition

This policy governs which tools exist; it does not restrict how an allowed tool is used. Attach it alongside the danger-scoped Tableau policies:

  • freeze-destructive-content / a mutation-admin gate — restrict the delete/update tools and their confirm- twins by IdP group and by the confirm flag.
  • fence-datasource-scope — allowlist datasourceLuid on query-datasource.
  • A token-management deny — block get-embed-token, revoke-access-token, reset-consent.
  • An egress PII-redaction / image-deny policy for query-datasource, get-view-data, and the image tools.

Because a default-deny allowlist denies everything not on the list, attach this policy to the Tableau MCP server's pipeline only — not gateway-wide. Attached gateway-wide it would deny every non-Tableau tool (including your DTwo management tools, which can self-lock the gateway — see Known limitations).

Known limitations

  • Suffix matching is portable but broad — including against same-server variants. Because the gate matches on the end of the tool name, any name that merely ends with a pinned suffix is allowed. Two cases matter. (a) An unrelated tool on another server (e.g. something-query-datasource) — avoided by scoping this policy to the Tableau pipeline only. (b) More importantly, a new, more-dangerous variant of a pinned Tableau tool: a future force-delete-workbook, bulk-delete-datasource, or hard-delete-extract-refresh-task ends with the pinned -delete-workbook / -delete-datasource / -delete-extract-refresh-task suffix and is therefore allowed automatically — even though it is exactly the kind of new, unreviewed tool PF-28 exists to catch. So the "a new upstream tool fails closed" guarantee holds only for names that do not suffix-extend an existing entry; a verb-prefixed superstring (<verb>-<pinned-stem>) slips past the existence gate. This is confirmed by the force-delete-workbook test case. Two things bound the blast radius: (1) this is an existence gate, not a danger gate — the danger-scoped companions below (mutation-admin gate, token deny) still catch such a tool by argument/identity even when this gate lets its name through; and (2) for a strict posture, replace the endswith checks with exact-name comparisons once you have confirmed the exact server-prefixed names your gateway emits via the dump-input debug technique. Names that are renamed rather than suffix-extended (purge-workbook, remove-workbook, misspellings) still fail closed as intended.
  • Generic suffixes collide across servers. -list-users, -list-jobs, and -search-content are not distinctive to Tableau. If this policy were (incorrectly) attached gateway-wide, those suffixes would allow same-named tools on other MCP servers. Keep it scoped to the Tableau pipeline.
  • Self-lock risk. As a default allow := false allowlist, this policy denies dtwo-* management tools if they route through the same gateway. Attach it only to the Tableau pipeline, or add a dtwo- management passthrough, to avoid locking yourself out (recover by detaching via the DTwo web UI).
  • The list is a snapshot, not a subscription. It reflects the verified v2.24.x inventory (source-verified from src/tools/web/toolName.ts). It does not update itself — a Tableau upgrade that adds tools requires a manual re-verification against tools/list and a new policy version.
  • Name only. This policy does not inspect arguments, identity claims, or the confirm flag — an allowed mutation tool is still allowed to execute unless a companion policy restricts it.
  • No identity placeholders. The allowlist is the same for every caller; this policy is not identity-gated.

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 tableau.ingress.default_deny_unknown_tools

# Default-deny-unknown-tools (PF-28). Only the pinned allowlist below grants access;
# every unpinned, new, renamed, or misspelled tool name fails closed.
default allow := false

# Per-tenant pinned allowlist: the 39 tools of the verified official Tableau web
# toolset (tableau/tableau-mcp v2.24.x, Jul 2026 — source: src/tools/web/toolName.ts).
# Entries are leading-hyphen kebab suffixes because the gateway prefixes each tool
# with the configured MCP server name (e.g. `tableau-mcp-query-datasource`).
# This is a SNAPSHOT: re-verify against the server's tools/list after each upgrade
# and add new tools here explicitly. Tableau Next's snake_case tools are a separate
# product and are intentionally excluded.
allowed_tool_suffixes := {
    # Data reads
    "-query-datasource",
    "-get-datasource-metadata",
    "-list-datasources",
    "-get-view-data",
    "-get-custom-view-data",
    "-get-view-image",
    "-get-custom-view-image",
    # Catalog / metadata reads
    "-list-workbooks",
    "-get-workbook",
    "-get-view",
    "-list-views",
    "-list-custom-views",
    "-list-projects",
    "-search-content",
    "-list-jobs",
    "-list-users",
    "-list-extract-refresh-tasks",
    # Pulse reads
    "-list-all-pulse-metric-definitions",
    "-list-pulse-metric-definitions-from-definition-ids",
    "-list-pulse-metrics-from-metric-definition-id",
    "-list-pulse-metrics-from-metric-ids",
    "-list-pulse-metric-subscriptions",
    "-generate-pulse-metric-value-insight-bundle",
    "-generate-pulse-insight-brief",
    # Admin-insights reads
    "-query-admin-insights-ts-events",
    "-query-admin-insights-site-content",
    "-query-admin-insights-job-performance",
    "-get-stale-content-report",
    # Token / session
    "-get-embed-token",
    "-revoke-access-token",
    "-reset-consent",
    # Mutations and their confirm- twins
    "-delete-datasource",
    "-confirm-delete-datasource",
    "-delete-workbook",
    "-confirm-delete-workbook",
    "-delete-extract-refresh-task",
    "-confirm-delete-extract-refresh-task",
    "-update-cloud-extract-refresh-task",
    "-confirm-update-cloud-extract-refresh-task",
}

# Lowercased tool name, safe against a missing resource.name (missing -> "" -> deny).
tool_name := lower(object.get(input.resource, "name", ""))

# Allow only when the tool name ends with a pinned allowlist suffix.
allow if {
    some suffix in allowed_tool_suffixes
    endswith(tool_name, suffix)
}

# Single deny condition: anything not on the allowlist. Name the offending tool and
# tell the operator exactly what to do.
reason := sprintf(
    "Tableau tool '%s' is not on the pinned allowlist of verified official web tools (tableau-mcp v2.24.x, 39 tools). It may be new, renamed, or misspelled upstream. An operator must re-verify the server's tools/list after the latest upgrade and add the tool's kebab-case name suffix to allowed_tool_suffixes before it can be used. Tableau Next's snake_case tools belong on their own allowlist. Contact your InfoSec team if this is a legitimate tool that should be allowed.",
    [object.get(input.resource, "name", "<unknown>")],
) if not allow

Canonical source: policy.md on GitHub · raw · raw on this site (.md)