Constrain Notion Connected-Tool Search
Notion's hosted MCP server (notion-search) does not just search Notion pages — through Notion AI connectors it also searches connected Slack, Google Drive,…
- Direction
- ingress
- Rego package
notion.ingress.constrain_connected_search- App
- notion
- Bundle
- soc2
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:34cb0cc763d763e7b091bfe7ef6ef636d2e14f7bb0fb877f135449f039817008
notionconstrain-aggregatoringresssoc2
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny external-scoped searches, allow native Notion searches and all other tools
Package: notion.ingress.constrain_connected_search
What it does
Notion's hosted MCP server (notion-search) does not just search Notion pages — through Notion AI connectors it also searches connected Slack, Google Drive, and Jira content. That means a single Notion OAuth grant can read those systems' data while bypassing the per-app MCP governance (policies, pipelines, audit) you attached to their own connectors.
This policy closes that aggregator side-door. It denies search calls whose query_type or filters argument scopes the search to connected/external sources, forcing that traffic through each system's own governed MCP connector instead. Native Notion searches — the default scope, query_type: "internal" / "user", and native filters such as teamspace filters — pass through unchanged, as do all non-search tools.
The external-scope branch fails closed: a query_type value or filters shape the policy cannot positively recognize as native Notion scope is denied rather than silently allowed, so an unrecognized connector-scoping shape cannot reopen the cross-app path.
Compliance alignment
- SOC 2 CC6.6 — supports boundary protection: content from Slack, Google Drive, and Jira only crosses the gateway through each system's own connector, where its dedicated policies apply — not through a Notion side-channel.
- SOC 2 CC6.8 — supports preventing unauthorized software paths: the Notion AI connector fan-out is an un-vetted access route into three other systems; this policy keeps it shut on the MCP path.
- SOC 2 CC9.2 — supports vendor/business-partner risk management by preventing one vendor's connector (Notion) from becoming an unmanaged proxy for data held with other vendors.
- HIPAA §164.508 — supports authorization discipline: PHI residing in connected Slack/Drive/Jira cannot be pulled through the Notion aggregator, which would sidestep the PHI controls attached to those apps' connectors.
- GDPR Arts. 44/46 — supports control of agent-visible cross-system transfers: personal data held in Slack, Google Drive, or Jira is not re-exposed through a second processor's search surface without the safeguards configured on the primary path.
Tool name matching
The policy matches search tools by suffix:
*-search
This covers the hosted server's notion-search under any gateway server-name prefix (e.g. notion-notion-search), and the legacy official local server's plain search once the gateway prefixes it (e.g. notion-mcp-search). The DTwo gateway prefixes tool names with the configured MCP server name, and that prefix is not standardized — verify the exact name your gateway sends with the dump-input debug technique before relying on this in production.
Attach this policy to the Notion pipeline only. The -search suffix is deliberately broad and will also match other apps' search tools (e.g. an Atlassian *-search tool) if they share a pipeline, where a legitimate filter value like "jira" would be a false positive.
Argument shape
Read from input.payload.args with object.get (never direct indexing):
query_type(string) — must be absent or a recognized native value:"internal"(workspace content) or"user"(workspace people search). Any other value — including a non-string — is denied. These enum values come from Notion's hosted-server documentation but are not independently verified; extendnative_query_typesif your tenant observes other native values.filters(object) — walked recursively. Any key or string value that canonicalizes (trim surrounding whitespace, lowercase, hyphens/interior-spaces → underscores) to a connected-source token —slack,google_drive/googledrive/gdrive/drive,jira, or a generic scope word (connected,connected_sources,connected_tools,external) — is denied. Afiltersvalue that is not an object or array (a scalar the policy cannot inspect) is denied as an unrecognized shape.queryandteamspaceare treated as content/native scope and are never inspected — search text mentioning "jira" is not a denial trigger.
Examples
Allowed — native workspace search
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "notion-notion-search", "type": "tool" },
"payload": {
"name": "notion-notion-search",
"args": { "query": "Q3 launch retro", "query_type": "internal", "filters": { "teamspace_id": "ts_123" } }
}
}
}
allow = true, no reason.
Denied — search scoped to a connected source
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "notion-notion-search", "type": "tool" },
"payload": {
"name": "notion-notion-search",
"args": { "query": "customer contract", "filters": { "source": "google_drive" } }
}
}
}
allow = false, reason = "This Notion search is filtered to a connected external source (...)".
Composition
This policy is single-purpose. Useful companions on the Notion pipeline:
apps/notionPF-08-style bulk-export caps and PF-02 egress PII redaction on-search/-fetch/-query-data-sourcesresponses.- A PF-07-style guard on
-query-data-sources(raw SQL over Notion databases). - Meta-tool servers are out of scope by design: the community
awkoy/notion-mcp-serverfunnels 43 operations (including deletes and file upload) through a singlenotion_executetool with unverified per-operation schemas. Tool-name policies cannot govern it — block that server outright in gateway config and standardize on the hosted server.
Known limitations
- Connector filter shapes are unverified. Notion's public docs confirm that
notion-searchreaches connected Slack, Google Drive, and Jira, and confirm thequery_type/filters/teamspaceargument names, but do not publish the exact filter schema used to scope a search to a connector. The policy therefore combines token matching (keys and string values insidefilters) with fail-closed handling of unrecognizedquery_typevalues and uninspectablefiltersshapes. If Notion ships a connector-scoping shape expressed through argument names outsidequery_type/filters, it would not be caught — re-verify against live traffic with the dump-input technique. - Token equality, not substrings. A filter key like
slack_channel_idsdoes not canonicalize toslackand would pass; conversely a teamspace literally nameddrivewould false-positive. Both are deliberate trade-offs to keep false positives low — tuneexternal_source_tokensfor your workspace. - Default scope may still include connected content upstream. If your Notion workspace's AI connectors are enabled, an unscoped ("native default") search may still surface connected-source snippets server-side; this policy only blocks explicitly scoped connector searches on the MCP path. Disable or restrict Notion AI connectors in the Notion admin console for full coverage — connector configuration itself is outside MCP.
- Other implementations differ. The suekou community server uses
notion_find(underscore, no-searchsuffix) and is not matched; the awkoy meta-tool server is unmatchable by tool name (see Composition). Add per-implementation policies if you allow those servers. - Bare
searchis only caught when the gateway joins the server prefix with a hyphen. The hosted server's tool is literally namednotion-search, so it ends in-searchunder any prefix separator (notion-notion-search,notion.notion-search). But the legacy local server's tool is the bare wordsearch: it is matched only when the gateway prefixes it into…-search(e.g.notion-mcp-search). If your gateway joins names with a dot or no separator, that server's search surfaces asnotion.search/notionsearch, which does not end in-searchand is treated as a non-search tool (allowed). Confirm the exact emitted name with the dump-input technique; if it is not hyphen-joined, add the observed form tois_search_tool. This does not affect the hosted server (the primary target). - No identity-based exemptions. All callers are subject to the same check. If a specific team legitimately needs Notion connected search, add an
allow ifbranch gated oninput.subject.claimsgroups.
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 notion.ingress.constrain_connected_search
# Deny-by-default: only the explicit allow rules below permit the request.
default allow := false
# Connected-source tokens. Slack, Google Drive, and Jira are the connectors
# verified in the Notion landscape research; the generic scope words catch
# filter shapes that point a search at connected rather than native content.
# Compared by token equality (after canonicalization), not substring, to
# limit false positives. Extend if your workspace enables more connectors
# (e.g. GitHub, Microsoft Teams, SharePoint, OneDrive, Linear).
external_source_tokens := {
"slack",
"google_drive",
"googledrive",
"gdrive",
"drive",
"jira",
"connected",
"connected_sources",
"connected_tools",
"external",
}
# query_type values recognized as native Notion scope. "" covers the default
# (argument absent). "internal" (workspace content) and "user" (workspace
# people search) are the hosted server's documented values — unverified enum,
# extend if your tenant observes other native values. Anything else fails
# closed: an unrecognized query_type could be a connector scope.
native_query_types := {"", "internal", "user"}
# Match search tools by suffix. The gateway prefixes tool names with the
# configured MCP server name (e.g. `notion-notion-search`), and the legacy
# official local server exposes plain `search` (prefixed to e.g.
# `notion-mcp-search`). Attach to the Notion pipeline only — the suffix is
# broad enough to catch other apps' search tools on a shared pipeline.
is_search_tool if {
endswith(lower(input.resource.name), "-search")
}
args := object.get(object.get(input, "payload", {}), "args", {})
filters_arg := object.get(args, "filters", {})
# Allow any tool that is not a search call.
allow if {
not is_search_tool
}
# Allow native searches: recognized native query_type, an inspectable filters
# shape, and no connected/external source referenced anywhere in filters.
allow if {
is_search_tool
query_type_is_native
filters_shape_inspectable
not filters_scope_external
}
query_type_is_native if {
qt := lower(object.get(args, "query_type", ""))
native_query_types[qt]
}
# filters must be a container we can walk. A scalar filters value is an
# unrecognized shape and fails closed rather than opening the cross-app path.
filters_shape_inspectable if is_object(filters_arg)
filters_shape_inspectable if is_array(filters_arg)
# Canonicalize a token: strip surrounding whitespace first (so a padded
# " slack " / "slack\n" cannot dodge equality), then lowercase and map
# hyphens and interior spaces to underscores, so "Google-Drive" and
# "google drive" both resolve to `google_drive`.
canonical(s) := replace(replace(lower(trim_space(s)), "-", "_"), " ", "_")
is_external_token(x) if {
is_string(x)
external_source_tokens[canonical(x)]
}
# A string value anywhere inside filters names a connected source
# (e.g. {"source": "slack"} or {"sources": ["jira"]}).
filters_scope_external if {
walk(filters_arg, [_, value])
is_external_token(value)
}
# A key anywhere inside filters names a connected source
# (e.g. {"slack": {"channels": ["C123"]}}).
filters_scope_external if {
walk(filters_arg, [path, _])
some segment in path
is_external_token(segment)
}
reasons contains "This Notion search sets query_type to a value that is not a recognized native Notion scope, so it may reach connected Slack, Google Drive, or Jira content. Use the default workspace search, or query those systems through their own governed MCP connectors. Contact your InfoSec team if this was a false positive." if {
is_search_tool
not query_type_is_native
}
reasons contains "This Notion search is filtered to a connected external source (Slack, Google Drive, or Jira). Search that system through its own governed MCP connector instead. Contact your InfoSec team if this was a false positive." if {
is_search_tool
filters_scope_external
}
reasons contains "This Notion search uses a filters shape this policy cannot verify as native Notion scope. Re-run the search without filters or with native filters (for example teamspace filters). Contact your InfoSec team if this was a false positive." if {
is_search_tool
not filters_shape_inspectable
}
reason := joined if {
count(reasons) > 0
reason_list := sort([r | some r in reasons])
joined := concat("; ", reason_list)
} 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