Note
This repository has not yet undergone a full independent third-party security review end-to-end. The hardening layer described below has been landed incrementally (path jails with realpath + O_NOFOLLOW, CRLF sanitization on both email-assembly paths, OAuth scope filtering at startup, Zod bounds on all Gmail IDs and batch sizes, crypto MIME boundary, credentials at 0o600, opt-in redacted JSONL audit log with counterparty-PII elision by default (GMAIL_MCP_AUDIT_LOG + GMAIL_MCP_AUDIT_LOG_VERBOSE opt-out), per-bucket daily+monthly write rate limits (send/delete/modify/drafts/labels/filters), LLM-output defense-in-depth fence (<untrusted-tool-output>) with control / zero-width / BiDi-override stripping on every tool response, attachment-filename neutralization before disk write, protocol-error flagging (isError: true) on tool failures, Sigstore + SLSA + SBOM-signed releases, fast-check fuzz suite) and is tested on every CI run. Against the two parent forks (GongRzhe/Gmail-MCP-Server and ArtyMcLabin's intermediate fork), klodr/gmail-mcp is already a meaningful step forward on prompt-injection and supply-chain posture. For mission-critical or high-sensitivity deployments, treat the server as carefully as any third-party MCP exposed to an LLM host: prefer a narrowly-scoped OAuth token, enable human-in-the-loop confirmation on write tools, and follow this repo's release notes for security-relevant updates.
- OAuth scope filtering at startup. The tool list returned to the LLM is intersected with the scopes the user granted at
authtime. Requestinggmail.readonlymeanssend_email,delete_email, etc. are literally not registered — a prompt-injected agent cannot call what is not in the list. - Attachment source jail.
send_email/draft_email/reply_allrefuse to attach any file whose realpath is not insideGMAIL_MCP_ATTACHMENT_DIR(default~/GmailAttachments/, mode0o700). Closes the headline prompt-injection vector: a crafted inbound email instructing the agent to attach~/.ssh/id_rsa,~/.gmail-mcp/credentials.json,~/.claude.json, or similar. - Download destination jail.
download_emailanddownload_attachmentwrite exclusively insideGMAIL_MCP_DOWNLOAD_DIR(default~/GmailDownloads/, mode0o700). The leaf is opened withO_NOFOLLOW— a pre-existing symlink at the destination cannot be used to escape the jail. AftermkdirSyncthe resolved path is re-verified against the jail root to defeat the TOCTOU window between the pre-check and themkdir. - Input validation (Zod). Every tool input is validated before it reaches a Gmail client call. Bounds on
maxResults,batchSize, andmessageIdsarray length block resource-exhaustion prompts. - CRLF header injection sanitization. Every user-supplied RFC-822 header value (
From,To,Cc,Bcc,Subject,In-Reply-To,References) is stripped of\r,\n,\0before the message is assembled — applied on both the attachment-less path (createEmailMessage) and the attachment path (createEmailWithNodemailer) so the invariant is in-tree and covered by the same tests on both paths. - Cryptographic MIME boundary.
crypto.randomBytes(16).toString('hex')— notMath.random()— so a crafted body cannot collide with the multipart boundary and inject synthetic MIME headers. - Credentials at rest. Both
~/.gmail-mcp/credentials.json(refresh token) and~/.gmail-mcp/gcp-oauth.keys.json(client_id + client_secret) are written with mode0o600— when the OAuth keys are copied in from the current directory, the mode is forced to0o600regardless of the source file's mode.~/.gmail-mcp/directory is0o700. The jail directories (GmailAttachments,GmailDownloads) are0o700. - No silent overwrite inside the download jail.
safeWriteFileusesO_CREAT | O_EXCL | O_NOFOLLOW(notO_TRUNC), so a prompt-injected agent cannot clobber a user file that happens to share a name with an incoming attachment (~/GmailDownloads/report.pdf). On collision the filename is suffixed " (1)", " (2)", … like a browser does. - Supply-chain integrity. Every release artifact is (or will be) signed with Sigstore, ships an SLSA in-toto attestation, and carries npm provenance. All GitHub Actions in
.github/workflows/are pinned by full commit SHA. - Least-privilege CI.
permissions:is set per-workflow withcontents: readat the top level; individual jobs widen only the specific scopes they need.
- Compromise of the host environment. If your shell, terminal, or MCP client is compromised, your Gmail refresh token can be stolen. This MCP cannot detect or prevent that.
- Malicious LLM prompts (prompt injection) on write tools. An LLM that exposes write tools to untrusted email content can be tricked into calling them. Mitigations: scope the OAuth token tightly (
gmail.readonly+gmail.sendinstead ofgmail.modify), require human-in-the-loop confirmation for any write tool, or use a read-only token when serving untrusted channels. - Prompt injection through Gmail response data. Gmail returns user-controlled fields verbatim — subject lines, display names, message bodies, filter criteria. This MCP forwards those bytes to the LLM without additional sanitization. A counterparty who controls one of those values can embed instructions that your agent may follow. The LLM host is responsible for treating tool-result content as untrusted input.
- Account-level Gmail security. 2FA, account recovery, suspicious-activity detection, and Google's side of OAuth consent are Google's responsibility, not this MCP's.
- Network-level attackers beyond what TLS provides. This MCP relies on Node's built-in
fetch(viagoogleapis) and the system trust store. It does not pin certificates. - Logging downstream of this MCP. The MCP emits an opt-in JSONL audit trail of its own (
GMAIL_MCP_AUDIT_LOG=/abs/path.jsonl, file mode0o600, counterparty PII elided by default with aGMAIL_MCP_AUDIT_LOG_VERBOSE=trueescape hatch — seesrc/audit-log.ts). However, if your MCP client (Claude Desktop, Cursor, etc.) records tool inputs/outputs to its own log, that storage is outside this project's control.
Please open a private security advisory at https://github.com/klodr/gmail-mcp/security/advisories/new. Do not open a public issue for security findings.
Response target: acknowledgment within 48 hours, fix or mitigation plan within 7 days for anything rated High or Critical.
@klodr/gmail-mcp supports Node.js ≥ 22.22.2 (the patched release on the Node 22 "Jod" Maintenance LTS, in maintenance through 2027-04-30). The floor is pinned to the exact patch — not just 22.22.x — because the seven CVEs landed in 22.22.2 specifically (two high-severity: TLS/SNI callback handling and HTTP header validation; three medium, two low); 22.22.0 and 22.22.1 predate those fixes. The 0.x series on npm previously shipped with >=20.11, then >=22.11. Older pinned versions of the package remain installable but will not receive back-ported security fixes.
Every published release of @klodr/gmail-mcp is cryptographically signed via Sigstore (keyless, via GitHub OIDC → Fulcio → Rekor). Three independent ways to verify:
npm view @klodr/gmail-mcp@<version> --json | jq .dist.attestations
npm install --ignore-scripts @klodr/gmail-mcp@<version>
npm audit signaturesgh release download v<version> --repo klodr/gmail-mcp --pattern 'index.js*'
gh attestation verify index.js --repo klodr/gmail-mcpcosign verify-blob-attestation \
--bundle index.js.sigstore \
--certificate-identity-regexp '^https://github\.com/klodr/gmail-mcp/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
index.jsEvery GitHub Release ships two SBOMs generated from the runtime dependency tree (devDependencies pruned before syft walks the tree) by anchore/sbom-action:
sbom.spdx.json— SPDX 2.3 JSONsbom.cdx.json— CycloneDX 1.6 JSON
Each SBOM carries its own Sigstore attestation binding it to the dist/index.js of the same release run. The attestation subject is the artifact (dist/index.js), not the SBOM file itself — gh attestation verify therefore expects the artifact path plus an explicit --predicate-type selecting which SBOM flavor to check:
# Download the release artifact + SBOMs first
gh release download v<version> --repo klodr/gmail-mcp \
--pattern 'index.js' --pattern 'sbom.*.json'
# SPDX
gh attestation verify index.js --repo klodr/gmail-mcp \
--predicate-type https://spdx.dev/Document/v2.3
# CycloneDX
gh attestation verify index.js --repo klodr/gmail-mcp \
--predicate-type https://cyclonedx.org/bomThen feed the SBOMs into grype, trivy, dependency-track, or any SPDX/CDX-aware scanner.
- Scope the OAuth token as tightly as possible. At
authtime, request only the scopes the agent actually needs —gmail.readonlyfor reading,gmail.readonly + gmail.sendfor drafting replies, and only addgmail.modifywhen label / trash operations are needed. The server's startup scope filter hides every tool whose scope is not granted, so an unaskeddelete_emailcannot be called even by a prompt-injected agent. Broader scopes (mail.google.com,https://mail.google.com/) expose the entire surface and should be a last resort. - Never commit OAuth keys or credentials to version control.
~/.gmail-mcp/gcp-oauth.keys.json(client_id + client_secret) and~/.gmail-mcp/credentials.json(refresh token) are both written at mode0o600by the server, but keeping them inside~/.gmail-mcp/— not alongside project files — is what keeps them out ofgit add .and backup snapshots. - Require human-in-the-loop confirmation on write tools.
send_email,reply_all,draft_email,delete_email,batch_modify_emails,batch_delete_emails, and the label / filter mutations can all be triggered by a prompt-injected counterparty. Configure your MCP host (Claude Desktop, Cursor, etc.) to prompt the human before executing write tools, or useGMAIL_MCP_DRY_RUN=trueto intercept and review payloads before they ship. - Enable the audit log in production. Set
GMAIL_MCP_AUDIT_LOG=/abs/path/to/audit.jsonlto capture a tamper-evident JSONL trail of every tool call, its redacted arguments, and its terminal state (ok/error/rate_limited/dry-run— the last surfaces whenGMAIL_MCP_DRY_RUN=trueintercepts a write). Counterparty PII (subject, body, to/cc/bcc, snippet) is elided by default; flipGMAIL_MCP_AUDIT_LOG_VERBOSE=trueonly on isolated dev machines where the log will not land in a SIEM. - Watch the rate-limit rejections. The per-bucket daily+monthly caps (
send,delete,modify,drafts,labels,filters) exist precisely to bound the blast radius of a prompt-injection-driven write loop. Tool calls rejected withmcp_rate_limit_*_exceededare recorded in the audit log withresult: "rate_limited"— a sudden spike is a strong signal of abusive prompting upstream and should be investigated. - Contain the attachment and download jails.
GMAIL_MCP_ATTACHMENT_DIR(outbound) andGMAIL_MCP_DOWNLOAD_DIR(inbound) default to~/GmailAttachments/and~/GmailDownloads/respectively, both0o700. If you run the MCP in a multi-user environment or inside a container, point both to a directory that is not the operator's home — a jail directory shared with other local processes defeats the path-traversal protection. - Revoke immediately on suspected token exposure. If a refresh token may have leaked, revoke it at https://myaccount.google.com/permissions and delete
~/.gmail-mcp/credentials.jsonbefore re-runningauth. The MCP does not maintain a remote revocation channel. - Keep this package updated. Vulnerable versions surface via Dependabot alerts on projects that depend on
@klodr/gmail-mcp; keep Dependabot security updates enabled on the consuming repo. The npm tarball ships with a Sigstore attestation (npm audit signaturesverifies it), so a tampered build on the registry mirror will not pass verification.
Thanks for helping keep @klodr/gmail-mcp and its users safe.