Is Forwarded Email Importer by cloudHQ safe?
Forwarded Email Importer by cloudHQ reads every signed-in Google account email on Gmail and sends the list to cloudhq.net.
When Gmail loads, the extension checks Gmail account slots 0 to 20 to find all Google accounts signed in to the browser. It then sends that list of email addresses to cloudHQ's servers, at most once every two weeks, with no user action required. This is unrelated to its stated job of importing forwarded emails.
Who publishes itcloudHQ - 10 other listings from the same operator, 7 of them carrying a finding
cloudHQ - 10 other listings from the same operator, 7 of them carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
10 other listings published from this account, 9k+ users between them. 7 of them carry a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Gmail EML importer sends all your signed-in Google emails to cloudhq.net
Code analysis shows that on Gmail load the extension probes up to 21 signed-in account slots, collects each email, and POSTs the list to www.cloudhq.net.
The importer needs none of those accounts.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Static analysis
You open Gmail with several Google accounts signed in to the browser.
The extension looks up the email of each signed-in account and sends the full list to cloudhq.net.
The lookup is skipped only if a check ran in the last two weeks.
- Active account emailalex.morgan@gmail.com
The Gmail address of the tab you opened.
- All signed-in emailsalex.morgan@gmail.com, a.morgan@acme-corp.com, alex.m.home@gmail.com
Every Google account signed in to this browser, including personal and work ones.
- Extension namegmail_save_eml
Which cloudHQ product sent the check.
- Switch login flag1
Marks the request as covering multiple accounts.
Account enumeration and the POST that follows
Deobfuscated enumeration and pre-check
content.jsfn_get_switcher_account_emails = function(e, t) { var n = this; if (n.g_switcher_account_emails_index > n.g_switcher_account_emails_max_tries) return n.g_email_or_login && -1 == n.g_switcher_account_emails.indexOf(n.g_email_or_login.toLowerCase()) && n.g_switcher_account_emails.push(n.g_email_or_login.toLowerCase()), void e(); try { n.g_switcher_account_emails_index++; var i = "https://mail.google.com/mail/u/" + n.g_switcher_account_emails_index + "/?sw=2"; t ? (i = t + n.g_switcher_account_emails_index + "/?tgif=d", r().ajax({ url: i, context: document.body, cache: !1, success: function(r) { var i = /_docs_flag_initialData=([\s\S]*?)\;/gm; try { if ((a = i.exec(r)) && a.length > 1) { var o = JSON.parse(r.split("_docs_flag_initialData=")[1].trim().split("; _docs_flag_cek=")[0].trim()), s = null; o["docs-hue"] && n.fn_validate_email(o["docs-hue"]) && (s = o["docs-hue"].toLowerCase()) } } catch (e) { console.log("fn_get_switcher_account_emails:ex:1:", e) } if (!s) { i = /__initData = ([\s\S]*?)\;/gm; try { var a; (a = i.exec(r)) && a.length > 1 && (o = JSON.parse(r.split("__initData = ")[1].trim().split(";")[0]))[0][27][8] && n.fn_validate_email(o[0][27][8]) && (s = o[0][27][8].toLowerCase()) } catch (e) { console.log("fn_get_switcher_account_emails:ex:2:", e) } } try { return n.g_switcher_account_emails && n.g_switcher_account_emails[0] && n.g_switcher_account_emails[0] == s ? (n.g_email_or_login && -1 == n.g_switcher_account_emails.indexOf(n.g_email_or_login.toLowerCase()) && n.g_switcher_account_emails.push(n.g_email_or_login.toLowerCase()), void e()) : (-1 == n.g_switcher_account_emails.indexOf(s) && n.g_switcher_account_emails.push(s), void n.fn_get_switcher_account_emails(e, t)) } catch (e) { console.log("fn_get_switcher_account_emails:3: failed e=", e) } }, error: function(r) { console.log("error in callback or not logged in to #" + n.g_switcher_account_emails_index + " account!"), n.fn_get_switcher_account_emails(e, t) } })) : fetch(i, { method: "GET", mode: "cors" }).then((e => e.text())).then((t => { var r = /var GLOBALS=([\s\S]*?)\;/gm, i = null; try { for (; i = r.exec(t);) { var o = JSON.parse(JSON.stringify(i[1])).split(","); if (o.length) for (var s = 0; s < o.length; s++) if (o[s] && n.fn_validate_email(o[s].replaceAll('"', ""))) { var a = o[s].replaceAll('"', "").toLowerCase(); return n.g_switcher_account_emails && n.g_switcher_account_emails[0] && n.g_switcher_account_emails[0] == a ? (n.g_email_or_login && -1 == n.g_switcher_account_emails.indexOf(n.g_email_or_login.toLowerCase()) && n.g_switcher_account_emails.push(n.g_email_or_login.toLowerCase()), void e()) : (-1 == n.g_switcher_account_emails.indexOf(a) && n.g_switcher_account_emails.push(a), void n.fn_get_switcher_account_emails(e)) } } } catch (e) { console.log("fn_get_switcher_account_emails: failed e=", e) } })).catch((t => { console.log("error in callback or not logged in to #" + n.g_switcher_account_emails_index + " account!"), n.fn_get_switcher_account_emails(e) })) } catch (e) { console.log("fn_get_switcher_account_emails:error:", e) } }; fn_pre_user_check = function(e) { var t = this; try { var n = t.g_email_or_login + t.g_cloudHQ_feature_name + "_pre_check_obj", r = ((new Date).getTime(), { value: "true", timestamp: (new Date).getTime() }), i = {}; i[n] = r, chrome.storage.local.get(n, (function(r) { if (!r || !r[n] || !r[n].timestamp || r[n].timestamp < t.g_two_weeks_ago_timestamp) try { t.fn_get_switcher_account_emails((function() { t.fn_ajax({ type: "POST", url: t.g_server_url + "main_pre_user/check", dataType: "json", data: { email: t.g_email_or_login, email_switcher_list: t.g_switcher_account_emails, email_or_login: t.g_email_or_login, chrome_extension_name: t.g_cloudHQ_feature_name, switch_login: "1" }, complete: function(e, t) { if (-1 === ["error", "timeout", "abort"].indexOf(t)) try { chrome.storage.local.set(i, (function() {})) } catch (e) {} } }) }), e) } catch (e) { console.log("e:", e) } })) } catch (e) { console.log("precheck failed e=", e) } };- www.cloudhq.net
Vendor server; the only host the background PROXY_AJAX relay accepts, and the destination of the list.
- mail.google.com
Queried as /mail/u/N/?sw=2 to read each signed-in account's email.
Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.
What it can do
Permissions this extension asks for, as declared in version 1.0.0.6. 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
https://mail.google.com/
Read and change your data on mail-attachment.googleusercontent.com
https://mail-attachment.googleusercontent.com/
Read and change your data on inbox.google.com
https://inbox.google.com/
Read and change your data on www.cloudhq.net
https://www.cloudhq.net/
Run its own code inside the pages you visit
scripting
Store data in your browser
storage
Keep running in the background while your browser is open
background
Where it sends data
Destinations our analysis observed Forwarded Email Importer 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.
- www.cloudhq.netwidely used
Forwarded Email Importer sends data to www.cloudhq.net. A widely used service: 79 other extensions we have analysed send data here.