dtwo Policy Store

Role-Gate Dropbox Writes to the Writers Group

Establishes the per-app least-privilege write floor for Dropbox.

Direction
ingress
Rego package
dropbox.ingress.role_gate_writes
App
dropbox
Bundles
soc2gdpr-ccpa
Published
Minimum gateway
1.0.0b24
Schema version
1.0.0
Checksum
sha256:44b33582ffce15781a7162dbd4e365dc45a16c43e986adea9363546bb9fc858f

dropboxrole-gate-writesrbacleast-privilegeingresssoc2gdpr-ccpa

What this policy does

Direction: ingress (tool_pre_invoke) Default: deny write tools without the writers group, allow everything else Package: dropbox.ingress.role_gate_writes

What it does

Establishes the per-app least-privilege write floor for Dropbox. Every write tool on the Dropbox MCP surface is denied at ingress unless the caller's IdP groups (input.subject.claims.groups) include the placeholder dropbox-writers group. Every read tool passes through untouched, so a caller with no groups claim — or a claim that lacks dropbox-writers — keeps read-only Dropbox access on the agent path.

Write tools gated by this policy, across the three Dropbox MCP dialects:

  • Folder / file creationCreateFolder, CreateFile (official); create_folder, upload_file (dbx-mcp-server); dropbox_create_folder, dropbox_upload (ngs).
  • Copy / move (move also renames)Copy, Move (official); copy_item, move_item (dbx); dropbox_copy, dropbox_move (ngs).
  • Restore toolsRestoreFileRevision, RestoreFolder (official); dropbox_restore_file (ngs).

The policy fails closed (default allow := false): the only paths to allow are (a) the tool is not a gated write, or (b) it is a gated write and the caller is in dropbox-writers. A missing subject, missing claims, or a missing / empty / non-array groups claim therefore never grants write access.

This is the RBAC baseline that the share-link, destructive-freeze, and path-fencing policies layer on top of. It is kept as its own policy so a tenant can attach the write floor without the sharper controls — which means, by design, this policy does not gate Delete or the external-sharing writes (CreateSharedLink / DownloadLink / CreateFileRequest). Those pass through here and are governed by their companion policies (see Composition and Known limitations).

Compliance alignment

  • SOC 2 CC6.1 — supports logical access security over protected assets by restricting Dropbox writes to an authorized role on the agent path; CC6.3 — supports role-based access and least privilege by granting write tools only to the dropbox-writers group; CC6.2 — supports tying authorization to live IdP-issued group claims, so de-provisioning in the IdP removes agent write access.
  • HIPAA §164.308(a)(4) — supports information access management: write access to ePHI-bearing Dropbox content is authorized by IdP group; §164.312(a)(1)/(a)(2)(i) — supports technical access control and unique-user identification, since the decision is made per call against the caller's own JWT-derived groups; §164.502(b)/§164.514(d) — supports the minimum-necessary standard by keeping the write surface off for read-only roles.
  • GDPR Art. 25 — supports data protection by design and by default: the agent write path is off unless a group explicitly turns it on; Art. 29 / 32(4) — supports processing only on the controller's instructions by binding write capability to controller-managed IdP groups; Art. 5(1)(b) — supports purpose limitation; CCPA §1798.100(e) — supports reasonable security.

Tool name matching

Tool names are matched case-insensitively against lower(input.resource.name), as an exact name or by a -/_-separated suffix, so the policy tolerates any gateway server-name prefix (e.g. dropbox-CreateFile, dropbox-mcp-server-upload_file) and resolves both the official PascalCase names and the community snake_case names. Requiring a separator before the suffix avoids over-matching (e.g. the copy suffix does not match copy_item, which has its own suffix entry).

Read tools (ListFolder, GetFileMetadata, GetFileContent, Search, WhoAmI, GetUsageAndQuota, CheckJobStatus, ListSharedLinks, GetSharedLinkMetadata, ListFileRequests, GetFileRequest, ListFileRevisions, ListRestoreEvents, and the community equivalents) are not matched and always pass through. Verify the exact names your gateway sends with the dump-input debug technique before relying on this in production.

Argument shape

This policy inspects no arguments — the decision is made purely on tool identity and the caller's group membership. That is deliberate: Dropbox publishes no MCP JSON schemas, so argument names are unverified (see the landscape note). A write is a write regardless of its path or content, so the RBAC floor does not need to read arguments; path- and content-sensitive controls live in the companion path-fence and DLP policies.

Examples

Allowed — read tool passes through (no group needed)

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "dropbox-ListFolder", "type": "tool" },
    "subject": { "sub": "google-apps|dev@example.com", "claims": { "groups": ["engineering"] } },
    "payload": {
      "name": "dropbox-ListFolder",
      "args": { "path": "/Projects" }
    }
  }
}

allow = true, no reason.

Allowed — writers-group member uploads a file

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "dropbox-CreateFile", "type": "tool" },
    "subject": { "sub": "google-apps|ops@example.com", "claims": { "groups": ["dropbox-writers"] } },
    "payload": {
      "name": "dropbox-CreateFile",
      "args": { "path": "/Projects/notes.txt", "content": "hello" }
    }
  }
}

allow = true, no reason.

Denied — write tool, caller not in the writers group

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "dropbox-CreateFolder", "type": "tool" },
    "subject": { "sub": "google-apps|dev@example.com", "claims": { "groups": ["engineering"] } },
    "payload": {
      "name": "dropbox-CreateFolder",
      "args": { "path": "/Projects/new" }
    }
  }
}

allow = false, reason = "This Dropbox tool writes to the workspace (...)".

Composition

This policy is single-purpose: the least-privilege write floor. It is designed to be attached alone or under the sharper Dropbox controls:

  • guard-share-links-external — gates CreateSharedLink / DownloadLink / CreateFileRequest, which this policy deliberately leaves alone (they are externally-visible writes with their own group, dropbox-sharing).
  • A destructive-op / destructive-freeze policy for Delete (and, if you want a tighter posture, the restore tools) — this baseline lets Delete pass through so it can be governed independently.
  • fence-sensitive-paths — adds path-prefix fences (by a different group per tree) on top of the write floor.
  • An egress PII/PHI/DLP redaction policy on GetFileContent / download_file and Search responses.

Known limitations

  • Delete and the external-sharing writes are out of scope by design. A read-only caller (no dropbox-writers) can still call Delete, CreateSharedLink, DownloadLink, and CreateFileRequest as far as this policy is concerned — they are governed by the destructive-freeze and share-link companions. Attach those alongside this baseline for full write containment; this policy alone is the create/copy/move/restore floor, not a complete write lockdown.
  • Group names are placeholders. Replace dropbox-writers with your IdP's group name at import time. Callers with no groups claim, an empty claim, or a non-array claim are simply not in the group (fail-closed for the grant).
  • groups claim must be an array of strings. A string-valued or otherwise malformed groups claim makes the membership iteration fail, which fails closed (write tools deny). If your IdP emits group membership under a different claim name (e.g. roles or a namespaced custom claim), update caller_groups in the Rego.
  • Suffix matching assumes a - or _ prefix separator. The DTwo gateway joins the configured server name to the tool name with a hyphen (e.g. dropbox-mcp-server-CreateFile), which this policy matches. If a deployment somehow surfaces a tool name whose prefix is joined by a different character (e.g. dropbox.CreateFile, dropbox:CreateFile) or with no separator at all (dropboxCreateFile), the -/_ suffix test does not fire and the write would pass through ungated. Surrounding whitespace/newlines are handled (trim_space), but non-standard internal separators are not. Confirm the exact tool-name shape your gateway emits with the dump-input debug technique; the separator requirement is a deliberate trade to avoid a short suffix like copy over-matching copy_item.
  • Tool names are unverified beyond the landscape note. The official PascalCase names and the community snake_case names come from the Dropbox help docs and community READMEs, not a live tools/list. If your server exposes a write tool under a different name, add its suffix to write_tool_suffixes in the Rego and confirm with the dump-input debug technique before production. A write tool whose name is not in the list would pass through (fail-open for that specific unrecognized tool) — the flip side of keeping the read surface unrestricted. Pair with PF-28 default-deny-unknown-tools if you need every unrecognized tool denied.
  • No argument inspection. The decision does not depend on path or content, so the floor cannot express "writers may only write under /Team" — compose with fence-sensitive-paths for path scoping.

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 dropbox.ingress.role_gate_writes

# Deny-by-default: only the explicit allow rules below permit the request.
default allow := false

# Placeholder IdP group whose members may run Dropbox write tools. Replace
# `dropbox-writers` with your IdP's group name at import time. Stored lowercase
# because the membership test lowercases each claimed group before comparing.
writers_group := "dropbox-writers"

# Normalized tool name, safe against a missing resource/name. The gateway
# prefixes tool names with the configured MCP server name (e.g.
# `dropbox-CreateFile`), so all matching below is case-insensitive and by
# `-`/`_`-separated suffix (or exact name) to stay portable across the official
# PascalCase server and both community snake_case servers. `trim_space` strips
# surrounding whitespace/newlines so a padded name (`"dropbox-CreateFile "`,
# `"dropbox-CreateFile\n"`) cannot slip past the suffix match and reach the
# server ungated.
tool_name := trim_space(lower(object.get(object.get(input, "resource", {}), "name", "")))

# Match a suffix as the exact tool name, or after a `-` or `_` separator. The
# separator requirement prevents a short suffix like `copy` from matching
# `copy_item` (which carries its own suffix entry).
tool_matches(suffix) if tool_name == suffix

tool_matches(suffix) if endswith(tool_name, sprintf("-%s", [suffix]))

tool_matches(suffix) if endswith(tool_name, sprintf("_%s", [suffix]))

# Write tools gated by the least-privilege floor, across the three dialects.
# Deliberately EXCLUDES Delete and the external-sharing writes (CreateSharedLink
# / DownloadLink / CreateFileRequest) — those are governed by the
# destructive-freeze and share-link companion policies.
write_tool_suffixes := [
	# official (PascalCase, no separator) -> lowercased
	"createfolder",
	"createfile",
	"copy",
	"move",
	"restorefilerevision",
	"restorefolder",
	# community amgadabdelhafez/dbx-mcp-server (snake_case)
	"create_folder",
	"upload_file",
	"copy_item",
	"move_item",
	# community ngs/dropbox-mcp-server (dropbox_ prefix)
	"dropbox_create_folder",
	"dropbox_upload",
	"dropbox_copy",
	"dropbox_move",
	"dropbox_restore_file",
]

is_write_tool if {
	some s in write_tool_suffixes
	tool_matches(s)
}

# Caller's IdP groups, via object.get chains so a missing subject/claims/groups
# fails closed (no group -> read-only).
caller_groups := object.get(object.get(object.get(input, "subject", {}), "claims", {}), "groups", [])

# True when the caller's groups claim (an array of strings) contains the writers
# group. A malformed (non-array/string) claim makes the iteration fail -> fail
# closed. Compared case-insensitively.
caller_in_writers_group if {
	some g in caller_groups
	lower(g) == writers_group
}

# --- Allow rules --------------------------------------------------------------

# Pass through every tool that is not a gated write (all reads, Delete, and the
# externally-visible sharing writes handled by companion policies).
allow if not is_write_tool

# Permit gated write tools only for members of the writers group.
allow if {
	is_write_tool
	caller_in_writers_group
}

# --- Deny reasons -------------------------------------------------------------

reasons contains "This Dropbox tool writes to the workspace (create, upload, copy, move, or restore) and is limited to members of the 'dropbox-writers' group; your account has read-only Dropbox access on the agent path. Ask your Dropbox administrator to add you to the 'dropbox-writers' group if you need write access. Contact your InfoSec team if this restriction looks wrong." if {
	is_write_tool
	not caller_in_writers_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)