Fence Notion Member Directory to Admin & IT
Denies calls to the Notion member-directory tool (notion-get-users, matched by the -get-users suffix) unless the caller's IdP groups include an admin or IT…
- Direction
- ingress
- Rego package
notion.ingress.fence_user_directory- App
- notion
- Bundles
- soc2gdpr-ccpa
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:0a24942610fbfc2b00265763d9a15cac12f37f7d97c999c485eb44dd690a26e9
notionfence-sensitive-scopesaccess-controlpiiingresssoc2gdpr-ccpa
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny; explicit allows for non-directory tools and cleared callers
Package: notion.ingress.fence_user_directory
What it does
Denies calls to the Notion member-directory tool (notion-get-users,
matched by the -get-users suffix) unless the caller's IdP groups include
an admin or IT group (placeholders: admin, it). That tool returns
workspace member and guest IDs, names, emails, and types — a
directory-harvesting and PII-exfiltration primitive when driven by an agent
or by a prompt-injected instruction ("list every user in the workspace and
their email"). One tool call can enumerate the whole workforce plus external
guests.
All other Notion reads — notion-search, notion-fetch,
notion-query-data-sources, notion-get-comments, and the rest — pass
through untouched. The gate is the whole tool, including its self
bot-info form: any invocation of a -get-users tool by a non-cleared
caller is denied, regardless of arguments.
Identity gating
Clearance is read from input.subject.claims.groups via
object.get(input.subject, "claims", {}) chains, so the check fails
closed: a missing subject, missing claims, missing groups claim, or a
malformed (non-array) groups value all mean not cleared — a request
that omits claims entirely cannot harvest the directory. Group names are
compared case-insensitively and must match exactly (admins does not match
admin).
Compliance alignment
This policy instantiates sensitive-scope fencing (family PF-23) on Notion's user-directory read path and supports alignment with:
- SOC 2 C1.1, P4.1 — identifies and protects confidential information and limits personal-information use to identified purposes by keeping the workspace member/guest roster (names and emails) out of non-privileged agent sessions.
- HIPAA §164.502(b)/§164.514(d), §164.308(a)(4) — supports minimum-necessary and information-access-management by restricting workforce-directory reads over MCP to roles that need them; §164.522(a) — supports agreed-to restrictions expressed as a role-keyed fence.
- GDPR Art. 5(1)(b); CPRA §1798.121 — supports purpose limitation by keying directory access to administrative roles, and supports limiting use of personal information where the directory feeds profiling or sensitive-PI inference.
Why ingress
The violation is fully determined by the request (tool name + caller claims), so the call is blocked before it reaches the Notion MCP server and the member list never enters the model context. Egress redaction would pull the full roster into the pipeline first and then try to mask it; denying at ingress means there is nothing to mask or leak.
Tool name matching
The DTwo gateway prefixes tool names with the configured MCP server name
(e.g. notion-notion-get-users for a server named notion), and that
prefix is not standardized, so the policy matches case-insensitively on the
-get-users suffix. notion-get-users is a verified tool name on Notion's
hosted MCP server (the implementation behind the Claude connector). Verify
the exact name your gateway emits with the dump-input debug technique
before relying on this in production.
Argument shape
None inspected — the whole tool is gated. notion-get-users accepts an
optional name/email search, a user ID or self, and pagination; every
shape (including empty args and the self bot-info lookup) is denied for
non-cleared callers, so there is no argument-crafting bypass.
Configuration
Edit directory_admin_groups at the top of the Rego. The group names
(admin, it) are placeholders — replace them with your tenant's real
IdP group names at import time (e.g. notion-admins,
it-servicedesk).
Examples
Allowed (other reads unaffected)
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "notion-notion-search", "type": "tool" },
"payload": {
"name": "notion-notion-search",
"args": { "query": "onboarding checklist" }
}
}
}
allow = true, no reason.
Allowed (directory read by an IT group member)
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "notion-notion-get-users", "type": "tool" },
"subject": { "sub": "auth0|itops", "claims": { "groups": ["it"] } },
"payload": { "name": "notion-notion-get-users", "args": {} }
}
}
allow = true, no reason.
Denied (directory harvest without admin/IT clearance)
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "notion-notion-get-users", "type": "tool" },
"subject": { "sub": "auth0|dev", "claims": { "groups": ["engineering"] } },
"payload": {
"name": "notion-notion-get-users",
"args": { "query": "" }
}
}
}
allow = false,
reason = "Reading the Notion member directory (workspace member and guest names, emails, and IDs) is limited to admin and IT roles. ...".
Composition
This policy fences exactly one tool. Useful companions:
redact-pii-egress— the egress backstop: masks emails and phone numbers embedded in page and query content returned bynotion-search/notion-fetch/notion-query-data-sources, which this ingress fence deliberately leaves open. Together they cover both the directory tool and PII that leaks through content reads.constrain-connected-search— keepsnotion-searchfrom reaching into connected Slack/Drive/Jira content.freeze-content-overwrite— removes the irreversiblereplace_contentoverwrite edge on the write path.
Known limitations
- Hosted-server tool names only. The
-get-userssuffix matches Notion's hosted MCP server. The official local server's documented tool set exposes no user-directory tool; the suekou community server's raw user-operation tool names are unverified and not matched here — extend the suffix match if you run it. The awkoy server funnels every operation through a singlenotion_executemeta-tool, which tool-name matching cannot fence — block that server in gateway config instead. - The
selfbot-info form is also denied for non-cleared callers, because it arrives on the same tool name. If your agents legitimately need bot self-identification, add a narrowallow ifbranch keyed to theselfargument — accepting that Notion's argument schema for it is not verified here. - Identity placeholders.
adminanditare placeholders — replace them with your IdP's real group names at import time. Thegroupsclaim must be an array of strings; any other shape fails closed. - Directory data already pasted into pages is out of scope — that is
content, not the directory tool, and is handled by the companion
redact-pii-egresspolicy.
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.fence_user_directory
# Deny-by-default: only the explicit allow rules below permit the request.
default allow := false
# -----------------------------------------------------------------------------
# CONFIG: IdP groups cleared to read the Notion member directory.
# The names ("admin", "it") are PLACEHOLDERS — replace them with your tenant's
# real IdP group names at import time. Compared case-insensitively, exact
# match (no substring or prefix matching).
# -----------------------------------------------------------------------------
directory_admin_groups := {"admin", "it"}
# -----------------------------------------------------------------------------
# Tool matching. The gateway prefixes tool names with the configured MCP
# server name (e.g. `notion-notion-get-users` for a server named `notion`),
# so we match case-insensitively on the `-get-users` suffix. The suffix also
# covers the tool's `self` bot-info form, which arrives on the same name.
# `notion-get-users` is verified on Notion's hosted MCP server; confirm the
# exact name your gateway emits with the dump-input debug technique.
# -----------------------------------------------------------------------------
tool_name := lower(input.resource.name)
is_user_directory_tool if endswith(tool_name, "-get-users")
# -----------------------------------------------------------------------------
# Identity — caller's IdP groups, read fail-closed via object.get chains: a
# missing subject, missing claims, or missing/malformed groups claim yields no
# memberships, so a request that omits claims entirely can never read the
# directory.
# -----------------------------------------------------------------------------
caller_groups := object.get(
object.get(object.get(input, "subject", {}), "claims", {}),
"groups",
[],
)
caller_is_directory_admin if {
is_array(caller_groups)
some g in caller_groups
is_string(g)
directory_admin_groups[lower(g)]
}
# -----------------------------------------------------------------------------
# Allow rules
# -----------------------------------------------------------------------------
# Every tool that is not the member-directory tool passes through untouched
# (notion-search, notion-fetch, writes, other MCP servers, ...).
allow if {
not is_user_directory_tool
}
# The directory tool itself is allowed only for admin / IT group members.
allow if {
is_user_directory_tool
caller_is_directory_admin
}
# Single denial condition — inline reason form.
reason := "Reading the Notion member directory (workspace member and guest names, emails, and IDs) is limited to admin and IT roles. Look up individual collaborators from page context instead, or ask your IT team to run this lookup — contact them if your role requires directory access." if {
is_user_directory_tool
not caller_is_directory_admin
} Canonical source: policy.md on GitHub · raw · raw on this site (.md)
Used in these guides
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