Box: Role-Gated Writes (Read-Only Default)
Makes Box read-only by default on the MCP path.
- Direction
- ingress
- Rego package
box.ingress.role_gate_writes- App
- box
- Bundles
- soc2gdpr-ccpa
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:402fdf79b6e7b9fd5df7fe444218b926ed74c5d7c7748b0b41c33c0df2be5d1a
boxrole-gate-writesaccess-controlleast-privilegeingresssoc2gdpr-ccpa
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny writes unless the caller is in the writer group; allow reads for everyone
Package: box.ingress.role_gate_writes
What it does
Makes Box read-only by default on the MCP path. Every mutating Box tool — uploads, folder
creation, copies, moves, renames, metadata and property updates, comments, hub and docgen
writes, collaboration grants, shared links, locks, retention dates, and deletes — is denied
unless the caller's IdP groups claim contains the placeholder group box-writers. Read and
search tools (who_am_i, get_file_content, get_file_details, list_*, search_*,
ai_qa_*, ai_extract_*, community box_file_info_tool, box_search_tool, and the other
read-only tools of both server dialects) pass for everyone.
Unknown tools whose names look mutating (any create/update/set/add/upload/move/
copy/delete/remove/rename/lock/unlock/clear verb segment) are also gated, so new
upstream write tools fail closed instead of slipping through until someone classifies them.
The check runs at ingress, before the call reaches the Box MCP server, so a denied write never executes and has no side effects.
Compliance alignment
- SOC 2 CC6.1 — supports logical access security over protected assets: Box content cannot be mutated over the agent channel without an explicit role grant. CC6.3 — supports role-based access and least privilege: write capability is tied to a live IdP group, and removing the group in the IdP removes write access on the next call.
- HIPAA §164.308(a)(4) — supports information access management for Box tenants holding ePHI: write authorization is role-scoped. §164.312(a)(1) — supports technical access control with per-call identity from the caller's JWT.
- GDPR Art. 25 — supports data protection by design/default on the agent channel: the default posture is read-only. Art. 29 — supports processing only on the controller's instructions: unauthorized principals cannot alter personal data in Box through the agent.
Tool name matching
The DTwo gateway prefixes tool names with the configured MCP server name (e.g.
box-remote-upload_file), and the prefix is not standardized — so all matching is
case-insensitive and by suffix or name segment, covering both Box MCP dialects:
- Official remote server (mcp.box.com), verified suffixes:
upload_file,upload_file_version,get_upload_url,create_folder,copy_file,copy_folder,move_file,move_folder,update_file_properties,update_folder_properties,set_file_metadata,set_folder_metadata,create_metadata_template,update_metadata_template,create_file_comment,create_hub,copy_hub,update_hub,add_items_to_hub,create_docgen_template,create_docgen_batch, plus the sharing toolscreate_collaboration,update_collaboration,add_file_shared_link,add_folder_shared_link. - Community server (box-community/mcp-server-box), verified suffixes:
box_file_upload_tool,box_file_copy_tool,box_file_move_tool,box_file_rename_tool,box_file_delete_tool,box_file_lock_tool,box_file_unlock_tool,box_file_retention_date_set_tool,box_file_retention_date_clear_tool,box_file_set_download_open_tool,box_folder_create_tool,box_folder_move_tool,box_folder_delete_tool,box_folder_set_collaboration_tool,box_folder_set_upload_email_tool. - Community collaboration stem: any tool name containing
box_collaboration_is treated as a write — the collaboration-create variants (box_collaboration_file_user_by_user_login_tooland its by-id/group/folder siblings) carry no mutating verb in their names, so the whole stem is gated. The official readlist_item_collaborationsdoes not contain this stem and stays allowed. - Mutating-verb net: any remaining tool whose hyphen/underscore-separated name segments
include a mutation verb (
create,update,set,add,upload,move,copy,delete,remove,rename,lock,unlock,clear) is gated. This is what catches the community shared-link writes (box_shared_link_*_create_or_update_tool,box_shared_link_*_remove_tool) while their_get_/_find_by_shared_link_url_read variants pass, and what keeps future upstream write tools fail-closed.
Verify the exact names your gateway sends with the dump-input debug technique before relying on
this in production; if your Box server exposes a write tool whose name carries none of the
verbs above, add it to the suffix lists in policy.md.
Argument shape
The decision uses only the tool name (input.resource.name) and the caller's identity
(input.subject.claims.groups). Tool arguments are not inspected, so the policy cannot be
bypassed by unusual argument keys, nesting, or encodings — and it works identically whether or
not a tool's argument schema is documented.
Identity
Group membership is read fail-closed via
object.get(object.get(object.get(input, "subject", {}), "claims", {}), "groups", []):
a missing subject, missing claims, a missing groups claim, or a groups claim that is not an
array all mean "not a writer", and every write is denied. Reads are unaffected by identity.
Examples
Allowed — read tool, no identity required
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "box-remote-get_file_content", "type": "tool" },
"payload": { "name": "box-remote-get_file_content", "args": { "file_id": "12345" } }
}
}
allow = true, no reason.
Denied — write tool, caller not in the writer group
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "box-remote-upload_file", "type": "tool" },
"subject": { "sub": "auth0|alice", "claims": { "groups": ["engineering"] } },
"payload": {
"name": "box-remote-upload_file",
"args": { "parent_folder_id": "0", "name": "report.pdf" }
}
}
}
allow = false, reason = "Box write tools are restricted to members of the 'box-writers' group ...".
Composition
This policy is the Box least-privilege baseline; it gates who may write, not what they may write. Useful companions:
- An external-sharing guard that inspects
create_collaboration/add_*_shared_linkarguments (collaborator domain,access: open) so that even authorized writers cannot share content outside the organization. - A destructive-op gate that keeps the community server's
box_file_delete_tool/box_folder_delete_tool(especiallyrecursive: true) behind a stricter admin group than ordinary writes. - An egress PII/PHI redaction policy on
get_file_content,ai_qa_*,ai_extract_*, and search responses, since this policy leaves the read path open.
Known limitations
- Group names are placeholders — replace
box-writerswith your IdP's group name at import time. The policy expectsgroupsto be an array claim in the caller's JWT; if your IdP emits roles under a different or namespaced claim (e.g.https://acme.com/groups), updatecaller_groupsinpolicy.md. - Reads are open to everyone, including Box AI tools (
ai_qa_*,ai_extract_*) that send file content through Box AI, and content egress tools likeget_file_content. Pair with a read fence and/or egress redaction if your Box tenant holds regulated content. - Verb-net over-matching on shared pipelines. The mutating-verb net inspects every tool
name on the pipeline, so non-Box tools with mutating-looking names (including management
tools such as
dtwo-create-policy) are gated too when this policy is attached to a pipeline that fronts more than the Box server. Attach it to a Box-scoped pipeline, or add an explicit passthroughallow ifrule for your management prefix. - Verb-net under-matching. A write tool whose name carries none of the listed verbs and is
not on a verified list slips through as a "read". Known candidates: the community server's
tag tools, whose exact names the landscape research could not verify — verify with a live
tools/listand add them to the suffix lists if present in your deployment. - Unverified community names. The folder variants of the community collaboration-create
tools are gated via the
box_collaboration_stem because their exact names are unverified; the community tag-tool names are likewise unverified (see above). - A server named with a mutating verb (e.g. an MCP server configured as
box-uploads) would make every one of its tools match the verb net and require the writer group — a fail-closed false positive; rename the server or add a passthrough.
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 box.ingress.role_gate_writes
# Deny-by-default: reads are explicitly allowed below; every write requires
# membership in the writer group.
default allow := false
# Placeholder IdP group permitted to perform Box writes.
# Replace "box-writers" with your IdP's group name at import time.
writer_group := "box-writers"
# Lowercased tool name. The gateway prefixes tool names with the configured
# MCP server name (e.g. `box-remote-upload_file`), so matching below is
# case-insensitive and suffix/segment based to stay portable.
tool_name := lower(object.get(object.get(input, "resource", {}), "name", ""))
# Raw (un-lowercased) name, kept so camelCase tool names can be split on their
# case boundaries by the mutating-verb net below.
raw_tool_name := object.get(object.get(input, "resource", {}), "name", "")
# Separator-normalized name: every run of non-alphanumerics collapses to a
# single `_`, so stem matching fires whether a server uses `_` or `-`.
normalized_tool_name := lower(regex.replace(raw_tool_name, `[^A-Za-z0-9]+`, "_"))
# --- Identity (fail closed) ---
# Missing subject, missing claims, a missing groups claim, or a groups claim
# that is not an array all yield "not a writer" — writes then deny.
caller_groups := object.get(object.get(object.get(input, "subject", {}), "claims", {}), "groups", [])
# `is_array` guard is load-bearing: `some group in caller_groups` iterates the
# *values* of an object, so a groups claim shaped as `{"x": "box-writers"}`
# would otherwise match and fail OPEN. Requiring an array keeps every non-array
# shape (object, string, number) fail-closed, as the Identity section promises.
caller_is_writer if {
is_array(caller_groups)
some group in caller_groups
group == writer_group
}
# --- Write-tool detection ---
# Verified write tools on the official remote server (mcp.box.com), matched by
# suffix so any gateway server-name prefix still matches.
official_write_suffixes := [
"upload_file",
"upload_file_version",
"get_upload_url",
"create_folder",
"copy_file",
"copy_folder",
"move_file",
"move_folder",
"update_file_properties",
"update_folder_properties",
"set_file_metadata",
"set_folder_metadata",
"create_metadata_template",
"update_metadata_template",
"create_file_comment",
"create_hub",
"copy_hub",
"update_hub",
"add_items_to_hub",
"create_docgen_template",
"create_docgen_batch",
"create_collaboration",
"update_collaboration",
"add_file_shared_link",
"add_folder_shared_link",
]
# Verified write tools on the community server (box-community/mcp-server-box).
community_write_suffixes := [
"box_file_upload_tool",
"box_file_copy_tool",
"box_file_move_tool",
"box_file_rename_tool",
"box_file_delete_tool",
"box_file_lock_tool",
"box_file_unlock_tool",
"box_file_retention_date_set_tool",
"box_file_retention_date_clear_tool",
"box_file_set_download_open_tool",
"box_folder_create_tool",
"box_folder_move_tool",
"box_folder_delete_tool",
"box_folder_set_collaboration_tool",
"box_folder_set_upload_email_tool",
]
is_write_tool if {
some suffix in official_write_suffixes
endswith(tool_name, suffix)
}
is_write_tool if {
some suffix in community_write_suffixes
endswith(tool_name, suffix)
}
# Community collaboration tools (grant/update/delete collaborations) share the
# `box_collaboration_` stem, and the create variants carry no mutating verb in
# their names, so the whole stem is gated. The official read
# `list_item_collaborations` does not contain this stem and stays allowed.
is_write_tool if {
contains(normalized_tool_name, "box_collaboration_")
}
# Fail-closed net for unknown mutating-looking tools: if any name segment is a
# mutation verb, treat the tool as a write so new upstream write tools are
# gated before anyone classifies them. Also catches the community shared-link
# create/update/remove tools, while their get/find read variants pass.
mutating_verbs := {
"create", "update", "set", "add", "upload",
"move", "copy", "delete", "remove", "rename",
"lock", "unlock", "clear",
}
# Split the tool name into segments. First insert a boundary at every
# lowercase/digit -> uppercase transition so camelCase names (`uploadFile`)
# split into verb segments (`upload`, `file`); snake_case and ALL-CAPS names
# are unaffected. Then lowercase and split on any run of non-alphanumeric
# characters (covers both the gateway's `-` prefixing and Box's `_` naming).
name_segments := {segment |
some segment in regex.split(`[^a-z0-9]+`, lower(regex.replace(raw_tool_name, `([a-z0-9])([A-Z])`, `$1 $2`)))
segment != ""
}
is_write_tool if {
some segment in name_segments
mutating_verbs[segment]
}
# --- Decision ---
# Reads (and anything that is not a verified or mutating-looking write) pass
# for everyone.
allow if {
not is_write_tool
}
# Writes pass only for members of the writer group.
allow if {
is_write_tool
caller_is_writer
}
reasons contains msg if {
is_write_tool
not caller_is_writer
msg := sprintf("Box write tools are restricted to members of the '%s' group — this account has read-only Box access through the gateway. Ask your identity admin to add you to '%s', or hand this step to a teammate with Box write access. If this tool is actually read-only, contact your InfoSec team to update the policy.", [writer_group, writer_group])
}
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