--- name: Freeze Destructive Google Drive Operations tags: - google-drive - freeze-destructive-ops - integrity - ingress - soc2 publishedAt: 2026-07-12 description: | # google-drive / freeze-destructive-ops **Direction:** ingress (`tool_pre_invoke`) **Default:** deny on matched delete tools, allow otherwise **Package:** `google_drive.ingress.freeze_destructive_ops` ## What it does Blocks Google Drive delete operations issued by agents. Any tool call whose name ends in one of the destructive suffixes exposed by the `piotr-agier/google-drive-mcp` community server — `deleteItem`, `deleteSheet`, `deleteRange`, `deleteGoogleSlide`, `deleteComment` — is denied unless the caller's IdP `groups` claim contains `drive-admins`. All other tool calls pass through unchanged. The check runs at ingress, before the call reaches the MCP server, so a blocked deletion never executes. This matters most for `deleteRange`, `deleteSheet`, and `deleteGoogleSlide`: deletions *inside* a document are easy to miss and effectively irreversible once they age past version history, unlike whole-file deletes which sit in trash for ~30 days. The exemption fails closed: if `input.subject`, `claims`, or the `groups` claim is missing (no IdP configured, audience mismatch, claim not issued), the caller is treated as a non-admin and the deletion is denied. The same holds for a malformed `groups` claim — a string, object/map, number, or null instead of a JSON array of strings: the policy requires an array before it will honor any group membership, so no non-array shape can grant the exemption. ## Compliance alignment - **SOC 2 PI1.5** — supports integrity of stored records by removing the agent's ability to destroy them. - **HIPAA §164.312(c)** — supports the integrity standard (protection of ePHI from improper alteration or destruction) for PHI stored in Drive; **§164.530(c)** — supports privacy safeguards over PHI records. - **GDPR Art. 5(1)(d)** — supports accuracy by preventing agent-driven mass corruption/destruction of personal-data records. ## Tool name matching The policy matches destructive tools case-insensitively by suffix: - `*deleteitem` — files and folders - `*deletesheet` — a sheet within a spreadsheet - `*deleterange` — a cell range within a sheet - `*deletegoogleslide` — a slide within a presentation - `*deletecomment` — a comment thread These are the verified destructive tool names of the `piotr-agier/google-drive-mcp` server (v2.2.0). The DTwo gateway prefixes tool names with the configured MCP server name (e.g. `google-drive-mcp-deleteItem`), and that prefix is not standardized, so the policy matches on the suffix to stay portable. Verify the exact names your gateway sends with the dump-input debug technique before relying on this in production. The Google official Drive MCP server and the Anthropic-hosted Claude connector expose **no** delete, move, rename, or permission-editing tools at all — this policy chiefly hardens deployments of community servers with a broad write surface. It is safe to attach in front of any Drive server: on servers with no delete tools it simply never fires. ## Argument shape None assumed. The decision is made entirely on the tool name and the caller's identity claims; `input.payload.args` is not inspected. ## Examples ### Allowed — read tool passes through ```jsonc { "input": { "action": "tool_pre_invoke", "resource": { "name": "google-drive-mcp-readGoogleDoc", "type": "tool" }, "payload": { "name": "google-drive-mcp-readGoogleDoc", "args": { "documentId": "1AbC" } } } } ``` `allow = true`, no reason. ### Allowed — delete by a drive-admins member ```jsonc { "input": { "action": "tool_pre_invoke", "subject": { "sub": "google-apps|ops@example.com", "claims": { "groups": ["drive-admins"] } }, "resource": { "name": "google-drive-mcp-deleteItem", "type": "tool" }, "payload": { "name": "google-drive-mcp-deleteItem", "args": { "itemId": "1AbC" } } } } ``` `allow = true`, no reason. ### Denied — delete by a non-admin ```jsonc { "input": { "action": "tool_pre_invoke", "subject": { "sub": "google-apps|analyst@example.com", "claims": { "groups": ["engineering"] } }, "resource": { "name": "google-drive-mcp-deleteRange", "type": "tool" }, "payload": { "name": "google-drive-mcp-deleteRange", "args": { "spreadsheetId": "1AbC", "range": "Sheet1!A1:C10" } } } } ``` `allow = false`, `reason = "Deleting Google Drive content from an agent is blocked. ..."`. ## Composition This policy is single-purpose: it freezes deletes only. Useful companions: - **`apps/google-drive/role-gate-writes`** — gates the non-destructive write surface (create/upload/update/move/rename) by IdP group; together the two give a full write-side posture where deletes are frozen and other writes are role-gated. - An egress PII/secret redaction policy on content-returning read tools for the read side. ## Known limitations - **Group name is a placeholder.** Replace `drive-admins` with your IdP's real group name at import time, and confirm your IdP actually emits a `groups` claim in the access token (many require explicit configuration). - **Trash-recoverable file deletes are still denied — by design.** `deleteItem` sends files to trash where they are recoverable for ~30 days, but the policy blocks it anyway: a mass-delete still disrupts collaborators, and trash can be emptied. The deny reason routes the agent to a human. - **`deleteCalendarEvent` is out of scope.** The same piotr-agier server also exposes `deleteCalendarEvent`; that tool belongs to the `google-calendar` catalog directory, not this policy. - **Suffix matching can over-match.** A hypothetical tool named e.g. `undeleteItem` (a restore) would also end with `deleteitem` and be denied. No current Drive server exposes such a name; if yours does, switch the affected entry to an exact-name match. - **Only these five names are covered, and only as exact trailing tokens.** Because matching is `endswith`, a delete tool bypasses if its name ends in a *different* token (a differently-verbed `delete_file`) **or** carries a trailing qualifier after the token (a hard-delete named `deleteItemPermanently` does not end in `deleteitem`, so it is not matched). Trailing whitespace, a newline, or other control-character padding on the tool name (`deleteItem\n`, `deleteItem `) is the same class of trailing qualifier and likewise defeats `endswith`. No current Drive server (piotr-agier v2.2.0, isaacphi, Google official, Anthropic connector) exposes such a name, and MCP dispatches tools by their exact registered name — so a padded or requalified name does not resolve to the real destructive tool at the server and is not a live bypass. If your server does expose such a name, add the exact suffix to `destructive_suffixes` in `policy.md` for your environment. > **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. direction: ingress apps: - google-drive industries: [] bundles: - soc2 experimental: true schemaVersion: 1.0.0 minimumGatewayVersion: 1.0.0b24 --- ```rego package google_drive.ingress.freeze_destructive_ops # Deny-by-default: only the explicit allow rules below permit the request. default allow := false # Destructive tool-name suffixes exposed by piotr-agier/google-drive-mcp # (v2.2.0, verified from its README). The gateway prefixes tool names with # the configured MCP server name (e.g. `google-drive-mcp-deleteItem`), so we # match case-insensitively on the suffix to stay portable across naming # conventions. `deleteCalendarEvent` from the same server is deliberately # absent — it belongs to the google-calendar catalog directory. destructive_suffixes := [ "deleteitem", # files and folders (trash-recoverable ~30 days) "deletesheet", # a sheet within a spreadsheet "deleterange", # a cell range within a sheet "deletegoogleslide", # a slide within a presentation "deletecomment", # a comment thread ] is_destructive_tool if { name := lower(input.resource.name) some suffix in destructive_suffixes endswith(name, suffix) } # Allow any tool that isn't a Drive delete operation. allow if { not is_destructive_tool } # Allow delete operations only for members of the drive-admins IdP group. allow if { is_destructive_tool caller_is_drive_admin } # `drive-admins` is a placeholder — replace it with your IdP's group name at # import time. The object.get chain fails closed: a missing subject, claims # object, or groups claim yields an empty list, so the caller is not exempt. # The `is_array` guard is load-bearing: without it, `some group in groups` # would iterate the *values* of a `groups` claim shaped as an object/map, so a # claim like {"groups": {"role": "drive-admins"}} would fail OPEN and grant the # exemption. Requiring an array forces every non-array shape (string, object, # number, null) to fail closed, matching the documented guarantee. caller_is_drive_admin if { claims := object.get(object.get(input, "subject", {}), "claims", {}) groups := object.get(claims, "groups", []) is_array(groups) some group in groups group == "drive-admins" } reasons contains "Deleting Google Drive content from an agent is blocked. Ask a human collaborator to perform the deletion in the Drive UI instead. If this is a false positive, ask your gateway administrator to add you to the drive-admins IdP group or contact your InfoSec team." if { is_destructive_tool not caller_is_drive_admin } reason := joined if { count(reasons) > 0 reason_list := sort([r | some r in reasons]) joined := concat("; ", reason_list) } ```