Is AI Grammar and Spell Checker by Ginger safe?
Ginger is high risk. Text you type into editable fields on any site is sent to api-extension.gingersoftware.com for correction. In testing, two canary phrases on w3schools.com appeared verbatim in POSTs to /correction/v1/document, plus a user identifier.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Text you type in any web form is sent to Ginger's servers for grammar checking
Text you type into editable fields on any site is sent to api-extension.gingersoftware.com for correction.
In testing, two canary phrases on w3schools.com appeared verbatim in POSTs to /correction/v1/document, plus a user identifier.
You type text into a textarea, comment box, or other editable field on any website.
The content script copies the sentence and sends it in the body of a POST request to api-extension.gingersoftware.com.
This happens on every site, not only on Ginger's own pages. The extension also includes an identifier (the userIdentifier of a logged-in account, or an anonymous UUID) in the URL of the request.
| Origin | https://www.w3schools.com |
| Content-Type | text/plain |
| X-Reverso-Origin | ginger.ext.chrome |
CANARYTEXT_GINGERBIRD_99887
| Field | Value | Why it matters | |
|---|---|---|---|
The sentence you typed | CANARYTEXT_GINGERBIRD_99887 | The exact text you entered into the field, sent in the body of the request. | |
Your Ginger user identifier | fddbb18c-11c0-4238-9ab5-200676b5b94b | If you are signed in, your account identifier travels with each sentence. If not, an anonymous UUID generated by the extension is used. | |
Dialect | US | Which English dialect the extension is configured for. | |
Client version | 2.15.357 | Which version of the Ginger extension you have installed. | |
API origin marker | ginger.ext.chrome | A header identifying the request as coming from the Ginger Chrome extension. |
The sentence-submission code in the content script.
// For each sentence the user types in any editable field on any site:
function submitSentence(sentence) {
// Build the request URL with dialect, API key, and (if signed in) the user's Ginger account ID.
let url = config.urlDocument // api-extension.gingersoftware.com/correction/v1/document
+ `?lang=${config.dialect}&apiKey=${config.apiKey}`;
if (!isAnonymUser) {
url += `&userIdentifier=${session.userId}`; // logged-in user's ID is appended to the URL
}
// POST the raw sentence text as the request body.
return ajax({
url, method: 'POST',
contentType: 'text/plain',
data: sentence.text, // <-- the raw user-typed text
headers: { 'X-Reverso-Origin': 'ginger.ext.chrome' }
});
}- api-extension.gingersoftware.com
Primary grammar/spelling endpoint operated by Ginger Software (the publisher). Receives the sentence text and a Ginger user identifier for every editable field typed into.
- orthographe.reverso.net
Alternate spelling endpoint used for some languages. Reverso is operated by Theadwise Corp. (Ginger and Reverso are part of the same group).
Drop a canary phrase into a textarea on any page and watch your browser's network panel for an outbound POST to api-extension.gingersoftware.com containing the exact phrase in the request body.
// ginger-sentence-canary.js
// Types a uniquely identifiable canary string into the first textarea
// on the page and dispatches input/change events so the Ginger content
// script's input watcher picks it up.
(function() {
const canary = 'CANARYTEXT_GINGERBIRD_' + Math.floor(Math.random() * 1e6);
const ta = document.querySelector('textarea, [contenteditable]');
if (!ta) { console.warn('No editable field found on this page.'); return; }
if (ta.tagName === 'TEXTAREA' || ta.tagName === 'INPUT') {
ta.focus();
ta.value = canary + '. This is a sentence that should trigger Ginger.';
ta.dispatchEvent(new Event('input', { bubbles: true }));
ta.dispatchEvent(new Event('change', { bubbles: true }));
} else {
ta.focus();
ta.textContent = canary + '. This is a sentence that should trigger Ginger.';
ta.dispatchEvent(new InputEvent('input', { bubbles: true }));
}
console.log('[ginger-canary] Planted:', canary);
console.log('[ginger-canary] Now watch the Network panel for a POST to api-extension.gingersoftware.com containing this string in the request body.');
})();- 1Install Ginger, sign in or use anonymously.
- 2Open a page with a textarea (e.g. w3schools.com).
- 3DevTools Network tab, filter 'gingersoftware.com'.
- 4Run this script.
- 5Watch for a POST to /correction/v1/document; check the body.
Selected text sent to Ginger's rephrase server in the cleartext URL
Selecting text and using Rephrase places it into the query string of a request to rephrasesrv.gingersoftware.com, not the body.
Riding in the URL, it can persist in access logs.
A marker sentence was confirmed verbatim in the 's' parameter.
You select a sentence on a web page and click the Rephrase control.
The extension sends the selected text to rephrasesrv.gingersoftware.com with the text placed in the request URL itself.
The text is URL-encoded into the 's' query parameter rather than carried in a request body.
| Field | Value | Why it matters | |
|---|---|---|---|
Your selected text | Please rephrase this sentence for me now. | The exact sentence or passage you highlighted and asked to rephrase. | |
Account identifier | userIdentifier=8f21c4a7-0b3e-4d9a-bc12-7e5519a0d3f1 | A persistent userIdentifier added when you are signed in, tying the request to your account. | |
Client version | 2.15.357 | Which version of the extension issued the request. | |
Platform and API key | platform=chrome, apiKey=ChromeExtension | Static values identifying the client app to the server; the same for all users. |
The code that builds the request, from the extension's shipping content script.
function getRephrase(text) {
const url = config.get('urlRephrase'); // rephrasesrv.gingersoftware.com/rephrase/rephrase
const params = {
platform: config.get('platform'),
clientVersion: config.get('clientVersion'),
apiKey: config.get('apiKey'),
s: (text || selectionInfo.text).trim(), // the selected text
size: 8
};
if (!session.get('isAnonym')) {
params.userIdentifier = session.get('userId'); // persistent account id
}
let fullUrl = url + '?';
for (const k in params) {
fullUrl += k + '=' + encodeURI(params[k]) + '&'; // text goes into the URL
}
fullUrl = fullUrl.slice(0, -1);
return window.fetch(fullUrl, { cache: 'default' }).then(r => r.json());
}Detailed analytics events sent to Google Analytics with a persistent client ID
The background worker sends GA4 events to google-analytics.com/mp/collect for most interactions.
Each includes a persistent client_id UUID, account status, event name; client_id, measurement ID, api_secret are hardcoded, reused per install.
You interact with the extension, for example, the login banner appears in the mini writer, you accept a correction, or you rephrase a sentence.
The worker POSTs a JSON event to google-analytics.com/mp/collect, including a persistent client_id, account status, event name, triggering surface, and version.
The Google Analytics measurement_id and api_secret are passed in the URL of every request, both hardcoded in the extension source.
| Origin | chrome-extension://kdfieneakcjfaiglcfcgkidlkmlijjnh |
| Content-Type | text/plain;charset=UTF-8 |
{
"client_id": "3b4e6544-69cc-4264-bb86-ba8f3983947e",
"events": [
{
"name": "Login_display",
"params": {
"category": "Login",
"trigger": "banner",
"interface": "mini-writer",
"engagement_time_msec": 1,
"user_status": "free",
"version": "2.15.357",
"platform": "Windows",
"browser": "chrome"
}
}
]
}| Field | Value | Why it matters | |
|---|---|---|---|
Persistent client ID | 3b4e6544-69cc-4264-bb86-ba8f3983947e | A UUID identifying this install, reused across every event and session, so your interactions can be linked over time. | |
Account status | free | Whether you are anonymous (free), signed in (registered), or paying (premium). | |
Event name | Login_display | Which interaction occurred, for example, displaying the login banner, accepting a correction, or opening a definition. | |
Event category | Login | A higher-level grouping for the event. | |
Trigger | banner | How the action was initiated (button, banner, hotkey, etc.). | |
Interface | mini-writer | Which extension UI the action happened in (mini-writer, popup, full writer, etc.). | |
Extension version | 2.15.357 | Which version of the Ginger extension you have installed. | |
Operating system | Windows | Whether the extension is running on a Mac or a Windows machine. | |
Browser platform | chrome | A label identifying the browser family (chrome, edge, etc.), used by Ginger's analytics dashboards. |
The analytics submission code in the service worker.
// Sends one Google Analytics 4 event for an extension interaction.
function sendGA4Event(props) {
// Hardcoded GA4 destination + credentials, both baked into the extension.
const url =
'https://www.google-analytics.com/mp/collect'
+ '?measurement_id=G-PYJPH0C6FG'
+ '&api_secret=<redacted>';
// Hardcoded client_id (widgetUid) — same UUID across sessions.
const client_id = 'f882d471-53f2-431f-8fe7-ad08beb3370c';
// Map session.license to GA user_status.
const user_status = !session ? 'free'
: session.isAnonym ? 'free'
: session.license !== 2 ? 'registered'
: 'premium';
const event = {
name: props.event, // e.g. 'Login_display'
params: {
...props, // category, trigger, interface, eventValue
engagement_time_msec: 1,
user_status,
version: '2.15.357',
platform: navigator.platform.includes('MAC') ? 'Mac' : 'Windows',
browser: 'chrome',
},
};
fetch(url, {
method: 'POST',
body: JSON.stringify({ client_id, events: [event] }),
});
}- www.google-analytics.com
Receives the GA4 Measurement Protocol POSTs. The events are routed to Ginger Software's GA4 property (measurement_id G-PYJPH0C6FG).