From a7d32cba7d25a2cf5a7793e577d1bcf0c3a5dbcb Mon Sep 17 00:00:00 2001 From: Caio Ribeiro Date: Sun, 28 Jun 2026 23:03:42 +0000 Subject: [PATCH] fix: require full Gmail scope for permanent deletes --- README.md | 9 ++++++--- src/delete-scopes.test.ts | 28 ++++++++++++++++++++++++++++ src/scopes.ts | 2 ++ src/tools.ts | 8 ++++---- 4 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 src/delete-scopes.test.ts diff --git a/README.md b/README.md index 3f4c3fae..00052322 100644 --- a/README.md +++ b/README.md @@ -267,10 +267,13 @@ The server automatically filters available tools based on your authorized scopes | `list_email_labels` | `gmail.readonly`, `gmail.modify`, or `gmail.labels` | | `send_email`, `draft_email`, `reply_all`, `send_draft` | `gmail.modify`, `gmail.compose`, or `gmail.send` | | `delete_draft`, `update_draft` | `gmail.modify` or `gmail.compose` | -| `modify_email`, `delete_email`, `batch_modify_emails`, `batch_delete_emails`, `modify_thread`, `report_phishing`, `batch_report_phishing` | `gmail.modify` | +| `modify_email`, `batch_modify_emails`, `modify_thread`, `report_phishing`, `batch_report_phishing` | `gmail.modify` | +| `delete_email`, `batch_delete_emails` | `gmail.full` (`https://mail.google.com/`) | | `create_label`, `update_label`, `delete_label`, `get_or_create_label` | `gmail.modify` or `gmail.labels` | | `list_filters`, `get_filter`, `create_filter`, `delete_filter`, `create_filter_from_template` | `gmail.settings.basic` | +`gmail.full` is intentionally separate from the default scopes because it grants permanent-delete capability. Prefer `modify_email` / `batch_modify_emails` for archive, mark-read, label, or inbox cleanup flows; re-authenticate with `--scopes=gmail.full,gmail.settings.basic` only when the assistant should be able to permanently delete mail. + ### Re-authenticating To change your scopes, simply run the auth command again with different scopes. This will replace your existing credentials. @@ -459,7 +462,7 @@ Adds or removes labels from emails (move to different folders, archive, etc.). ``` ### 7. Delete Email (`delete_email`) -Permanently deletes an email. +Permanently deletes an email. This tool requires `gmail.full` (`https://mail.google.com/`); the default `gmail.modify` scope is not enough for Gmail's permanent-delete endpoint. ```json { @@ -530,7 +533,7 @@ Modifies labels for multiple emails in efficient batches. ``` ### 14. Batch Delete Emails (`batch_delete_emails`) -Permanently deletes multiple emails in efficient batches. +Permanently deletes multiple emails in efficient batches. This tool requires `gmail.full` (`https://mail.google.com/`); the default `gmail.modify` scope is not enough for Gmail's permanent-delete endpoint. ```json { diff --git a/src/delete-scopes.test.ts b/src/delete-scopes.test.ts new file mode 100644 index 00000000..101ccd03 --- /dev/null +++ b/src/delete-scopes.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest'; +import { getAvailableScopeNames, hasScope, scopeNameToUrl } from './scopes.js'; +import { getToolByName } from './tools.js'; + +describe('permanent delete scope requirements', () => { + it('recognizes gmail.full as the permanent-delete Gmail scope', () => { + expect(getAvailableScopeNames()).toContain('gmail.full'); + expect(scopeNameToUrl('gmail.full')).toBe('https://mail.google.com/'); + }); + + it('does not expose permanent delete tools with only gmail.modify', () => { + const deleteEmail = getToolByName('delete_email')!; + const batchDeleteEmails = getToolByName('batch_delete_emails')!; + + expect(deleteEmail.scopes).toEqual(['gmail.full']); + expect(batchDeleteEmails.scopes).toEqual(['gmail.full']); + expect(hasScope(['gmail.modify'], deleteEmail.scopes)).toBe(false); + expect(hasScope(['gmail.modify'], batchDeleteEmails.scopes)).toBe(false); + }); + + it('exposes permanent delete tools with gmail.full shorthand or URL scope', () => { + const deleteEmail = getToolByName('delete_email')!; + const batchDeleteEmails = getToolByName('batch_delete_emails')!; + + expect(hasScope(['gmail.full'], deleteEmail.scopes)).toBe(true); + expect(hasScope(['https://mail.google.com/'], batchDeleteEmails.scopes)).toBe(true); + }); +}); diff --git a/src/scopes.ts b/src/scopes.ts index 8a9fed3b..6a9c3c38 100644 --- a/src/scopes.ts +++ b/src/scopes.ts @@ -7,6 +7,7 @@ // - gmail.send: Send emails only // - gmail.labels: Manage labels only // - gmail.settings.basic: Manage filters and settings +// - gmail.full: Full mailbox access, including permanent deletion // // Note: gmail.modify includes all capabilities of gmail.readonly, // so you don't need both scopes together. @@ -18,6 +19,7 @@ export const SCOPE_MAP: Record = { "gmail.compose": "https://www.googleapis.com/auth/gmail.compose", "gmail.send": "https://www.googleapis.com/auth/gmail.send", "gmail.labels": "https://www.googleapis.com/auth/gmail.labels", + "gmail.full": "https://mail.google.com/", "gmail.settings.basic": "https://www.googleapis.com/auth/gmail.settings.basic", "gmail.settings.sharing": "https://www.googleapis.com/auth/gmail.settings.sharing", }; diff --git a/src/tools.ts b/src/tools.ts index 920b699f..f56a5dd9 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -320,9 +320,9 @@ export const toolDefinitions: ToolDefinition[] = [ }, { name: "delete_email", - description: "Permanently deletes an email", + description: "Permanently deletes an email. Requires gmail.full because Gmail's delete endpoint is not covered by gmail.modify.", schema: DeleteEmailSchema, - scopes: ["gmail.modify"], + scopes: ["gmail.full"], annotations: { title: "Delete Email", destructiveHint: true }, }, { @@ -348,9 +348,9 @@ export const toolDefinitions: ToolDefinition[] = [ }, { name: "batch_delete_emails", - description: "Permanently deletes multiple emails in batches", + description: "Permanently deletes multiple emails in batches. Requires gmail.full because Gmail's batchDelete endpoint is not covered by gmail.modify.", schema: BatchDeleteEmailsSchema, - scopes: ["gmail.modify"], + scopes: ["gmail.full"], annotations: { title: "Batch Delete Emails", destructiveHint: true }, },