Is MailProtect: Protect your Gmail privacy for free. safe?

High risk

MailProtect sends opened Gmail message details to app.mailprotect.ai when it detects tracking pixels.

When a Gmail message is opened, MailProtect inspects the message body for zero-size tracking images. If it detects one, it sends the message subject, visible body text, sender, recipients, Gmail message and thread IDs, and timestamps to its backend.

pimpmysaasv1.0.34Chrome Web Store
72Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

Gmail tracker reports include opened email content

When you open a Gmail message with a zero-width/zero-height image, the extension builds a report with message text, sender, recipients, subject, Gmail message/thread IDs, timestamps, then sends it to app.mailprotect.ai/api/threats/create.

Severity
High unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You open a Gmail message that contains a zero-size image.

The content script is registered only for mail.google.com pages.

The extension did this

The extension creates a report from the opened message and sends it to MailProtect.

The report includes message body text and Gmail message metadata.

Fields assembled into the MailProtect report
  • Opened message text
    Hi Alex, the quarterly planning notes are below. (illustrative)

    This can include the readable text from the Gmail message you opened.

  • Sender address
    sender@example.com (illustrative)

    This identifies who sent the opened email.

  • Recipient addresses
    alex@example.com, team@example.com (illustrative)

    This identifies people included on the opened email.

  • Email subject
    Q3 planning notes (illustrative)

    This can reveal the topic of the message you opened.

  • Gmail message ID
    18f7c2a9d4b6e123 (illustrative)

    This lets the report refer back to a specific Gmail message.

  • Gmail thread ID
    thread-a:r7123456789012345678 (illustrative)

    This links the report to the Gmail conversation thread.

  • Read and block timestamps
    2026-07-13T08:35:27.978Z (illustrative)

    This records when the extension processed the opened message.

Captured request
POSThttps://app.mailprotect.ai/api/threats/create
Headers
Content-Type
application/json
Authorization
Bearer <redacted>
The code that does this

The message handler builds the report and the API client posts it

Readable version

Readable equivalent of the Gmail message handler

assets/js/index.51566434.js
useEffect(() => {  async function handleOpenedGmailMessage(messageView) {    const messageId = await messageView.getMessageIDAsync();    setMessageId(messageId);    const bodyElement = messageView.getBodyElement();    const images = bodyElement.querySelectorAll("img");    let trackingPixelDetected = false;    images.forEach((image) => {      if (image.width <= 0 || image.height <= 0) {        image.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";        image.style.display = "none";        trackingPixelDetected = true;      }    });    if (trackingPixelDetected) {      const categoryInput = {        object: messageView.getThreadView().getSubject(),        payload: messageView.getBodyElement().innerText,        sender: messageView.getSender().emailAddress      };      const categoryResult = detectCategory(categoryInput);      const recipients = messageView.getRecipients().map((recipient) => recipient.emailAddress);      const sender = messageView.getSender().emailAddress;      const subject = messageView.getThreadView().getSubject();      setTrackingDetected(true);      setRecipients(recipients);      setSubject(subject);      setSender(sender);      setMessageView(messageView);      let report = {        recipients: messageView.getRecipients().map((recipient) => recipient.emailAddress),        object: messageView.getThreadView().getSubject(),        payload: messageView.getBodyElement().innerText,        sender: messageView.getSender().emailAddress,        blockedAt: new Date().toISOString(),        readAt: new Date().toISOString(),        gmailMessageId: await messageView.getMessageIDAsync(),        gmailThreadId: await messageView.getThreadView().getThreadIDAsync(),        category: categoryResult.category      };      report = await GmailProtectAPI.getDefaultInstance().createThreat(report);      setMessages((oldMessages) => {        const oldMessageIndex = oldMessages.findIndex((oldMessage) => report.gmailThreadId === oldMessage.gmailThreadId);        if (oldMessageIndex > -1) {          oldMessages[oldMessageIndex] = report;          oldMessages[oldMessageIndex].status = isOverUsageLimit(user) ? STATUS.NOT_BLOCKED : STATUS.BLOCKED;        } else {          report.status = isOverUsageLimit(user) ? STATUS.NOT_BLOCKED : STATUS.BLOCKED;          oldMessages.push(report);        }        return [...oldMessages];      });      if (user) {        user.monthlyBlockedThreatsCount = (user.monthlyBlockedThreatsCount || 0) + 1;        setUser({ ...user });      }    } else {      setTrackingDetected(false);    }  }  const unregister = inbox?.Conversations.registerMessageViewHandler(handleOpenedGmailMessage);  return () => {    unregister?.();  };}, [inbox]);

Readable equivalent of the API post path

assets/js/Flex.82e34646.js
let domain = "https://app.mailprotect.ai";if (isDevMode && isDevMode()) {  domain = "https://mailprotect.ngrok.io";}const Config = { domain };let instance = null;class GmailProtectAPI {  constructor(api) {    this.api = api;    if (!api) {      throw new Error("No axios instance provided");    }  }  async me() {    const response = await this.api.get("/auth/initSession");    return {      ...response.data?.session?.user,      ip: response.data?.ip,      locationData: response.data?.localisationData    };  }  async ip() {    return (await this.api.get("/ip")).data;  }  async createThreat(report) {    try {      const response = await this.api.post("/threats/create", report);      return response.data;    } catch (error) {      console.error({ message: "Error creating threat", description: error.message });      throw error;    }  }  async getMessageStatus(threadId) {    return (await this.api.get("/threats/status", { params: { gmailThreadId: threadId } })).data;  }}GmailProtectAPI.getInstanceNotForGP = function(api) {  return new GmailProtectAPI(api);};GmailProtectAPI.getDefaultInstance = function() {  const token = localStorage.getItem("gmailprotect-token");  if (!token) {    instance = new GmailProtectAPI(GmailProtectAPI.getAxios(""));  } else if (!instance) {    instance = new GmailProtectAPI(GmailProtectAPI.getAxios(token || ""));  }  return instance;};GmailProtectAPI.getAxios = function(token) {  logInfo("Running API on " + Config.domain);  const api = axios.create({    baseURL: Config.domain + "/api",    headers: {      "Content-Type": "application/json",      Authorization: `Bearer ${token}`    },    withCredentials: true  });  api.interceptors.response.use(async (response) => {    if (response.status === 202) {      console.log("HTTP 202 received, polling operation...");      console.log("Operation running at " + response.headers.location);      let pollingResponse = await api.get(response.headers.location);      console.log("Operation status is " + pollingResponse.data.status);      for (let i = 0; i < 600; i++) {        pollingResponse = await api.get(response.headers.location, { params: { timestamp: Date.now() } });        console.log("Operation status in loop is " + pollingResponse.data.status);        if (window?.progressCallback) {          window.progressCallback(pollingResponse.data);        }        if (pollingResponse.data.status === "completed" || pollingResponse.data.status === "failed") {          break;        }        await new Promise((resolve) => setTimeout(resolve, 2000));      }      if (pollingResponse.data.status === "completed") {        return pollingResponse;      }    }    return response;  });  return api;};
Network destination used by this report flow
    • app.mailprotect.ai

    Receives the Gmail message report at /api/threats/create.

What it can do

Permissions this extension asks for, as declared in version 1.0.34. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on mail.google.com

    *://mail.google.com/

  • Read and change your data on app.mailprotect.ai

    *://app.mailprotect.ai/

  • Show you desktop notifications

    notifications

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • Run its own code inside the pages you visit

    scripting

Where it sends data

Destinations our analysis observed The ultimate Gmail privacy guardian contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • app.mailprotect.ai

    The ultimate Gmail privacy guardian sends data to app.mailprotect.ai. No other extension we have analysed sends data here.

Updated 21 September 2026gbfbccnaimhdaakcfghepopldamldlkk