Is Ripple Tool safe?
Ripple Tool sends raw mental-health search queries to Mixpanel and injects server-controlled HTML into search-engine pages.
When a user's search term matches a crisis keyword list, the extension captures the raw search text and transmits it to Mixpanel's analytics service (api-js.mixpanel.com) along with a session ID and extension version. The extension also fetches HTML from cms.ripplesuicideprevention.com on each triggered page and inserts it directly via innerHTML into the active search-engine tab, with no integrity check on the returned content. Both behaviors occur on major search engines including Google, Bing, Yahoo, DuckDuckGo, YouTube, Ecosia, and Ask.
Who publishes itBLUE TEA LIMITED - no other listings under this identity
BLUE TEA LIMITED - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Crisis Search Terms Forwarded to Mixpanel Analytics
Searching a crisis term on Google, YouTube, Bing, DuckDuckGo, Yahoo, Ecosia, or Ask sends your exact text to Mixpanel (Twilio).
Confirmed: a POST to api-js.mixpanel.com carried the raw term and a session ID, on every keyword match.
You search for a mental-health crisis term on Google, YouTube, Bing, DuckDuckGo, Yahoo, Ecosia, or Ask.
The extension monitors search engine pages and compares each query against a keyword list fetched from its server.
The extension posts the raw search text you typed to Mixpanel's analytics servers before showing the help overlay.
The event payload includes the verbatim search term, a session identifier from localStorage, the full search engine URL, and the installed extension version.
| Field | Value | Why it matters | |
|---|---|---|---|
Crisis search query | howtokillmyself | The exact text you typed into the search engine, sent verbatim and in lowercase. | |
Session identifier | 1ff58d43-69b3-4365-ae98-9f08da578b90 | A UUID made on your first keyword match, stored in localStorage. Persists across searches in the session. | |
Search page URL | https://www.google.com/search?q=how+to+kill+myself | The full URL of the search engine page where you typed the query, including the query string. | |
Search term hash | 7c222fb2927d828af22f592134e8932480637c0d05f5960d5c68840f7b93a6ad | A SHA-256 digest of the search term, included alongside the plaintext version. | |
Extension version | 0.9.5 | The installed version of Ripple Tool, included with every analytics event. |
| Content-Type | application/x-www-form-urlencoded |
Raw POST body (URL form-encoded):
data=W3siZXZlbnQiOiJLZXl3b3JkIE1hdGNoZWQiLCJwcm9wZXJ0aWVzIjp7InNlYXJjaFRlcm1TdHJpbmciOiJob3d0b2tpbGxteXNlbGYiLCJzZXNzaW9uSWQiOiIxZmY1OGQ0My02OWIzLTQzNjUtYWU5OC05ZjA4ZGE1NzhiOTAiLCJleHRlbnNpb25WZXJzaW9uIjoiMC45LjUiLCJ0b2tlbiI6IjI2Y2E2NGJkZTUyNmVlNjRjMGQxNWUyODViZDA4ZTQ1IiwidHJpZ2dlcklkIjoxNywiY2F0ZWdvcnlJZCI6MSwiZnJvbVVSTCI6Imh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vc2VhcmNoP3E9aG93K3RvK2tpbGwrbXlzZWxmIiwibWVzc2FnZU9mSG9wZUlkIjpudWxsLCJpbWFnZUlkIjpudWxsfX1d
Base64-decoded (searchTermString and sessionId from dynamic analysis; triggerId, fromURL, messageOfHopeId, imageId are illustrative):
[
{
"event": "Keyword Matched",
"properties": {
"searchTermString": "howtokillmyself",
"searchTermDigest": "7c222fb2927d828af22f592134e8932480637c0d05f5960d5c68840f7b93a6ad",
"triggerId": 17,
"categoryId": 1,
"fromURL": "https://www.google.com/search?q=how+to+kill+myself",
"messageOfHopeId": null,
"imageId": null,
"sessionId": "1ff58d43-69b3-4365-ae98-9f08da578b90",
"extensionVersion": "0.9.5",
"token": "26ca64bde526ee64c0d15e285bd08e45"
}
}
]Analytics function in ripple_analytics.js that fires on every crisis keyword match
// Fires in ripple_analytics.js after matchingTrigger() confirms a crisis keyword match.
// trigger.searchTermString = raw lowercase text the user typed (e.g. "howtokillmyself").
// trigger.searchTermDigest = SHA-256 of that string (computed in storage.js).
// sessionId = UUID in localStorage created at first match; persists across the session.
export const trackKeyword = async (trigger, shadowRoot = null) => {
// DOM lookup for optional Mixpanel impression IDs — null if overlay not yet rendered.
let rippleMainContainer = shadowRoot
? shadowRoot.querySelector("#ripple-main-container")
: document.getElementById("ripple-main-container");
const messageOfHopeId = rippleMainContainer?.dataset.mixpanelMessageOfHopeId || null;
const imageId = rippleMainContainer?.dataset.mixpanelImageId || null;
const extensionVersion = chrome.runtime.getManifest().version; // "0.9.5"
const sessionId = localStorage.getItem("ripple_session_id"); // UUID, e.g. "1ff58d43-..."
// The raw crisis search term is sent here, verbatim, as searchTermString.
// The Mixpanel SDK base64-encodes the event array and POSTs it to api-js.mixpanel.com.
// The hardcoded token in config.js routes all events to a fixed Mixpanel project.
mixpanel.track("Keyword Matched", {
searchTermString: trigger.searchTermString, // raw text the user typed — sent in plaintext
searchTermDigest: trigger.searchTermDigest, // SHA-256 of the same text
triggerId: trigger.trigger_id,
categoryId: trigger.category_id,
fromURL: trigger.fromUrl, // full search engine URL including query string
messageOfHopeId,
imageId,
sessionId, // localStorage UUID
extensionVersion
});
// Mixpanel token 26ca64bde526ee64c0d15e285bd08e45 is hardcoded in config.js:3.
};- api-js.mixpanel.com
Mixpanel ingestion endpoint (Twilio Inc., TWLO). Raw crisis search terms are stored under token 26ca64bde526ee64c0d15e285bd08e45.
Typed ChatGPT/Claude/Gemini/Copilot Prompts Sent to Mixpanel on Keyword Match
Ripple Tool watches for crisis keywords and shows a help panel on a match.
Its script now also runs on ChatGPT, Gemini, Claude.ai, and Copilot, reading your prompt on Send.
A match sends Mixpanel the plaintext words.
Confirmed on ChatGPT.
You type a message containing a mental-health or self-harm phrase into ChatGPT, Claude, Gemini, or Copilot's chat box and submit it.
This extension's content script now runs on those chat sites in addition to search engines.
Reads chat input text, checks it locally against a keyword list, and on a match sends the matched words, not just a hash, to Mixpanel's API.
The same request also includes a session ID and the extension's version number.
data=[{"event":"Keyword Matched","properties":{"searchTermString":"howtokillmyself","searchTermDigest":"1ad28fed...","triggerId":135,"categoryId":1,"sessionId":"ffa5497d-f6ba-4519-8212-ebf045f94078","extensionVersion":"0.9.6.2","token":"<redacted>"}}]Where the plaintext prompt text is captured and sent, despite a local hash-based match
extractLLMText(element) {
if (!element) return "";
const method = this.domainConfig.textExtractionMethod || "contenteditable";
let text = "";
switch (method) {
case "prosemirror":
text = this.extractProseMirrorText(element);
break;
case "contenteditable":
text = element.innerText || element.textContent || "";
break;
case "input":
text = element.value || "";
break;
default:
text = element.innerText || element.textContent || element.value || "";
}
const placeholder = this.domainConfig.placeholderText;
if (placeholder && text === placeholder) {
text = "";
}
return text.trim();
}export const matchingTrigger = async (searchTerm, triggers) => {
const searchTermString = searchTerm.join(" ").toLowerCase();
const searchTermDigest = await digestMessage(searchTermString);
let matchedObject = triggers[searchTermDigest];
if (matchedObject) {
return {
match: true,
locale: matchedObject?.locale,
category_id: matchedObject?.category_id || Category.SUICIDE,
severity: matchedObject?.severity || DEFAULT_SEVERITY,
trigger_id: matchedObject?.trigger_id || null,
searchTermDigest,
searchTermString,
fromUrl: true
};
}
...
}export const trackKeyword = async (trigger, shadowRoot = null) => {
const mainDocument = window.top?.document || document;
let rippleMainContainer = shadowRoot
? shadowRoot.querySelector("#ripple-main-container")
: mainDocument.getElementById("ripple-main-container");
const messageOfHopeId =
rippleMainContainer?.dataset.mixpanelMessageOfHopeId || null;
const imageId =
rippleMainContainer?.dataset.mixpanelImageId || null;
const extensionVersion = chrome.runtime.getManifest().version;
const sessionId = localStorage.getItem("ripple_session_id")
mixpanel.track("Keyword Matched", {
searchTermString: trigger.searchTermString,
searchTermDigest: trigger.searchTermDigest,
triggerId: trigger.trigger_id,
categoryId: trigger.category_id,
fromURL: trigger.fromUrl,
messageOfHopeId,
imageId,
sessionId,
extensionVersion
});
};| Field | Value | Why it matters | |
|---|---|---|---|
The words you typed | howtokillmyself | The plaintext words from your ChatGPT/Claude/Gemini/Copilot prompt that matched a crisis keyword -- not a hash, the actual text. | |
Session ID | ffa5497d-f6ba-4519-8212-ebf045f94078 | A per-browser identifier stored in localStorage that ties this event to your other activity in the same session. | |
Category and trigger IDs | categoryId: 1, triggerId: 135 | Internal IDs identifying which keyword category (e.g. suicide/self-harm) and which specific trigger phrase matched. | |
Extension version | 0.9.6.2 | The installed version of the extension, sent with every event. |
- api-eu.mixpanel.com
Mixpanel's EU ingestion endpoint. Receives 'Keyword Matched'/'no_help_items_listed' events with the plaintext searchTermString on every matched prompt or search.
What it can do
Permissions this extension asks for, as declared in version 0.9.5. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 0.9.6.2, which we have not unpacked yet.
Store data in your browser
storage
Where it sends data
Destinations our analysis observed Ripple Tools 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.
- api-js.mixpanel.comwidely used
Ripple Tools sends data to api-js.mixpanel.com. A widely used service: 47 other extensions we have analysed send data here.
- cms.ripplesuicideprevention.com
Ripple Tools sends data to cms.ripplesuicideprevention.com. No other extension we have analysed sends data here.