Cap Google Drive Search & Listing Page Sizes
Clamps the page size of Google Drive search and listing calls to a documented cap (25 results per call).
- Direction
- ingress
- Rego package
google_drive.ingress.cap_bulk_export- App
- google-drive
- Bundles
- soc2hipaagdpr-ccpa
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:2d8f86deafa5b239539f29e322abd233c66f03b1beec1b1ee201ce917f022898
google-drivecap-bulk-exportdata-minimizationingresssoc2hipaagdpr-ccpa
What this policy does
Direction: ingress (tool_pre_invoke)
Default: allow (transform-only — never denies)
Package: google_drive.ingress.cap_bulk_export
What it does
Clamps the page size of Google Drive search and listing calls to a documented cap (25 results per call). When an agent asks a Drive enumeration tool for more than 25 results in one page, the policy rewrites the page-size argument down to 25 before the call reaches the MCP server. Requests with no page-size argument, or with a page size at or under the cap, pass through untouched (the server's own defaults apply). The call is never denied.
Why this matters: search is the recon step of a Drive exfiltration.
Drive's full query syntax (fullText contains, 'folderId' in parents)
lets an agent enumerate sensitive material fast, and repeated large pages
are the amplifier that turns read_file_content /
download_file_content sweeps into bulk exfiltration. Capping page size
slows mass enumeration and forces breadth to show up as many calls in the
audit log instead of a few large ones.
No identity gating: the cap applies to every caller, for minimum-necessary/data-minimisation alignment — no subject needs 100-row recon pages by default.
Compliance alignment
- SOC 2 CC6.7 — supports the restriction on transmission/movement of information by bounding how much Drive content inventory an agent can pull per call over the MCP path.
- HIPAA §164.502(b) / §164.514(d) — supports the minimum-necessary standard: an agent working in a Drive that holds PHI gets result pages sized for the task at hand, not bulk sweeps.
- GDPR Art. 5(1)(c) — supports data minimisation on the agent channel; CCPA 11 CCR §7002 — supports proportionality of collection relative to purpose.
Tool name matching
The DTwo gateway prefixes tool names with the configured MCP server name
(e.g. gdrive-mcp-gdrive_search), so the policy matches
case-insensitively by suffix:
*gdrive_search— isaacphi/mcp-gdrive (itspageSizeargument is verified from source)*search_files,*list_recent_files— Google official Drive MCP server (parameter names unverified — see Known limitations)*-search,*listfolder— piotr-agier/google-drive-mcpsearch/listFolder(tool names verified; page-size parameter name unverified)*google_drive_search— legacy claude.ai built-in Drive integration (tool name unverified — reported from published system prompts, effectively dead but may still appear in older Claude traffic)
The -search suffix assumes the gateway's <server-name>-<tool-name>
prefixing. Verify the exact names your gateway sends with the dump-input
debug technique before relying on this in production.
Argument shape
The policy checks a candidate set of page-size-style argument keys on
matched tools — pageSize, page_size, pagesize, limit,
maxResults, max_results — each read with object.get, so missing
keys are simply skipped. Values may be numbers or numeric strings
("100", including ones with surrounding whitespace like " 100", which
are trimmed before parsing); both are clamped to the numeric cap 25.
Non-numeric values and requests without any candidate key pass through
unchanged. Key matching is case-sensitive (object.get exact match) —
see Known limitations.
Examples
Transformed (page size over the cap)
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "gdrive-mcp-gdrive_search", "type": "tool" },
"payload": {
"name": "gdrive-mcp-gdrive_search",
"args": { "query": "fullText contains 'salary'", "pageSize": 100 }
}
}
}
allow = true, transform rewrites args to
{ "query": "fullText contains 'salary'", "pageSize": 25 }.
Untouched (no page-size argument)
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "gdrive-mcp-gdrive_search", "type": "tool" },
"payload": {
"name": "gdrive-mcp-gdrive_search",
"args": { "query": "name contains 'roadmap'" }
}
}
}
allow = true, no transform — the server's default page size applies.
Composition
This policy is single-purpose. Useful companions:
redact-pii-egress— sanitizes whatever content the capped pages still return.guard-acl-recon— closes the parallel recon channel throughget_file_permissions.
Known limitations
- Cannot bound call count. The cap limits results per call, not
calls per minute — an agent can still paginate through everything with
pageToken, just slower and more visibly. Rate limiting is a platform property, not a policy job. Treat this as friction plus audit amplification, not a hard exfiltration stop. - Unverified parameter names. Only isaacphi's
gdrive_searchpageSizeis verified from source. The Google official server does not publish per-tool parameter schemas, and piotr-agier's page-size parameter name is undocumented — verify viatools/listthrough your gateway before relying on the clamp there. A server whose page-size parameter is named outside the candidate set is not clamped. - Case-sensitive argument keys. Tool names are matched
case-insensitively, but the candidate page-size keys are matched
exactly (
object.get). A server that accepts a case-variant key —PageSize,LIMIT,MaxResults— would not be clamped. All known Drive servers use the documented casing (pageSize,maxResults,limit,page_size), so this is a residual only for a case-insensitive server; add the variant topage_size_keysif yours is. - Server coercion outstrips the parser. The clamp fires only when
OPA's
to_numbercan read the value (after whitespace trimming). A value OPA cannot parse but a lenient server still coerces to a large integer — e.g. an exotic numeric literal, a locale-formatted string ("1,000"), or a nested/typed wrapper — is skipped and passes uncapped. This is the residual behind the "friction, not a hard stop" framing: the clamp is only as tight as the parser. - Non-positive page sizes pass through. The clamp fires only on
values strictly greater than the cap, so
0,-1, or any non-positive number is left untouched (numeric(v) > page_size_capis false). The known Drive servers reject non-positive page sizes (Google's API range is 1–1000), but a lenient or non-standard server that reads0/-1as "unbounded / return everything" would not be clamped. This is another facet of the "friction, not a hard stop" posture; if your server treats non-positive as unbounded, pair this policy with a server-side request-validation rule. - Type rewrite on numeric strings. A numeric-string page size
(
"100") is replaced with the number25. Servers that strictly require a string type for that parameter may reject the rewritten call. - Generic-verb collision. The
-searchsuffix can match search tools of other MCP servers if this policy is attached to a mixed pipeline. The effect is only a page-size clamp (never a deny), but scope the attachment to Drive pipelines if that matters. - Cap is a tuning point.
25is a conservative default; adjustpage_size_capinpolicy.mdto your environment's needs.
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 google_drive.ingress.cap_bulk_export
# Transform-only policy — never denies, only clamps oversized page sizes.
default allow := true
# Maximum results per page on Drive search/listing calls. Tune per environment.
page_size_cap := 25
# Candidate argument keys that carry a page-size value across known Drive MCP
# servers. `pageSize` is verified for isaacphi/mcp-gdrive's gdrive_search; the
# rest are defensive candidates for the Google official and piotr-agier
# servers, whose parameter names are unverified (confirm via tools/list).
page_size_keys := ["pageSize", "page_size", "pagesize", "limit", "maxResults", "max_results"]
# --- Tool matching --------------------------------------------------------
# The gateway prefixes tool names with the configured MCP server name
# (e.g. `gdrive-mcp-gdrive_search`), so match case-insensitively by suffix.
# isaacphi/mcp-gdrive search (pageSize argument verified from source)
is_enumeration_tool if {
endswith(lower(input.resource.name), "gdrive_search")
}
# Legacy claude.ai built-in Drive integration `google_drive_search`
# (reported from published system prompts — tool name UNVERIFIED, effectively
# dead but may still appear in older Claude traffic). Full suffix, so it does
# not collide with isaacphi's `gdrive_search`.
is_enumeration_tool if {
endswith(lower(input.resource.name), "google_drive_search")
}
# Google official Drive MCP server (parameter names unverified)
is_enumeration_tool if {
endswith(lower(input.resource.name), "search_files")
}
is_enumeration_tool if {
endswith(lower(input.resource.name), "list_recent_files")
}
# piotr-agier/google-drive-mcp `search` — generic verb, so anchor on the
# gateway's `-` server-name separator to avoid matching `gdrive_search` or
# `search_files` twice or unrelated `*_search` tools by substring.
is_enumeration_tool if {
endswith(lower(input.resource.name), "-search")
}
# piotr-agier/google-drive-mcp `listFolder`
is_enumeration_tool if {
endswith(lower(input.resource.name), "listfolder")
}
# --- Page-size clamping ----------------------------------------------------
# Tool arguments, defaulting to {} so missing payloads mean "nothing to clamp".
call_args := object.get(object.get(input, "payload", {}), "args", {})
# Interpret a page-size value: numbers pass through, numeric strings are
# parsed (leading/trailing whitespace trimmed first, since a lenient server
# would coerce `" 100"` to 100 and we must clamp what it would honor);
# anything else is undefined and the key is skipped.
numeric(v) := v if is_number(v)
numeric(v) := to_number(trim_space(v)) if is_string(v)
# Every candidate key present on the call whose value exceeds the cap,
# mapped to the cap. Empty when nothing needs clamping.
capped_overrides := {k: page_size_cap |
some k in page_size_keys
v := object.get(call_args, k, null)
numeric(v) > page_size_cap
}
# Rewrite the oversized page-size argument(s) down to the cap; all other
# arguments are preserved as-is.
transform := {"transformed_payload": object.union(call_args, capped_overrides)} if {
input.action == "tool_pre_invoke"
is_enumeration_tool
count(capped_overrides) > 0
} 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