Is Homey: Productivity New Tab safe?

Medium risk

Homey is medium risk. The extension sets/reads a recommendations UUID, sent to homey.place in a GET: homey.place/api/discovery/collection?lang=en&uid=36d8dbb5-afe7-4fd5-a0d1-7b8d838b6fef. Created once, appended with the interface language each load.…

Alexander Ostapenkov4.27.7Chrome Web Store
52Risk
Who publishes it

Alexander Ostapenko - no other listings under this identity, 1 shared hostname

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Alexander Ostapenko
Registered address
Erevanska St. 21, 21, Kyiv, місто Київ 03087, UA
Registered contact
Oleksandr Ostapenko

Shared hosts - 1 hostname

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

framework7.io
Also called by 5 other listings, including IHome(bdtab)新标签页, Mh25 Desk

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

Publishers can request a review.

Findings

Recommendation requests reuse a UUID

The extension sets/reads a recommendations UUID, sent to homey.place in a GET: homey.place/api/discovery/collection?lang=en&uid=36d8dbb5-afe7-4fd5-a0d1-7b8d838b6fef.

Created once, appended with the interface language each load.

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

You load the recommendation panel in the new-tab page.

The extension did this

The extension sends a stored recommendations UUID to its server with the language setting.

Query fields in the recommendation request
  • Recommendations UUID
    36d8dbb5-afe7-4fd5-a0d1-7b8d838b6fef

    Lets the server recognize the same browser profile across recommendation requests.

  • Interface language
    en

    Tells the server what language to use for recommendation content.

  • Storage key
    recommendations.recommendationsUid

    Shows where the extension keeps the reusable identifier on your device.

What's stored on your device

The identifier is kept locally and reused the next time recommendations are requested.

Location
chrome.storage.local wrapper 'recommendations'
Contents
{  "recommendations": null,  "recommendationsUid": "36d8dbb5-afe7-4fd5-a0d1-7b8d838b6fef",  "suggestionsTimestamp": null,  "recommendationsTimestamp": null}
Captured request
GEThttps://homey.place/api/discovery/collection?lang=en&uid=36d8dbb5-afe7-4fd5-a0d1-7b8d838b6fef
The code that does this

UUID creation, storage, and recommendation request

Readable version

Stored recommendations UUID

static/js/background.js
function getRecommendationsStorage() {    return new __WEBPACK_IMPORTED_MODULE_1__components_Storage_chromeStorage__["a" /* default */]('recommendations', {        recommendations: null,        recommendationsUid: null,        recommendationsTimestamp: null,        suggestionsTimestamp: null    });}var getRecommendationUid = function () {    var _ref = _asyncToGenerator( /*#__PURE__*/__WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.mark(function _callee() {        var recommendationsStorage;        return __WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.wrap(function _callee$(_context) {            while (1) {                switch (_context.prev = _context.next) {                    case 0:                        _context.next = 2;                        return getRecommendationsStorage();                    case 2:                        recommendationsStorage = _context.sent;                        if (recommendationsStorage.data.recommendationsUid) {                            _context.next = 7;                            break;                        }                        recommendationsStorage.data.recommendationsUid = Object(__WEBPACK_IMPORTED_MODULE_2_uuid__["a" /* v4 */])();                        _context.next = 7;                        return recommendationsStorage.save();                    case 7:                        return _context.abrupt("return", recommendationsStorage.data.recommendationsUid);                    case 8:                    case "end":                        return _context.stop();                }            }        }, _callee, this);    }));    return function getRecommendationUid() {        return _ref.apply(this, arguments);    };}();

Recommendation load uses the UUID

static/js/background.js
var loadRecommendations = function () {    var _ref4 = _asyncToGenerator( /*#__PURE__*/__WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.mark(function _callee4() {        var uid, recommendationsStorage, lang, recommendation;        return __WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.wrap(function _callee4$(_context4) {            while (1) {                switch (_context4.prev = _context4.next) {                    case 0:                        _context4.next = 2;                        return getRecommendationUid();                    case 2:                        uid = _context4.sent;                        _context4.next = 5;                        return getRecommendationsStorage();                    case 5:                        recommendationsStorage = _context4.sent;                        lang = __WEBPACK_IMPORTED_MODULE_5__components_app_chromeHelper__["a" /* default */].instance().i18n.getMessage("locale");                        _context4.next = 9;                        return __WEBPACK_IMPORTED_MODULE_3__api_recommendations__["getCollection"](uid, lang);                    case 9:                        recommendation = _context4.sent;                        if (!(!!recommendation && (typeof recommendation === "undefined" ? "undefined" : _typeof(recommendation)) === "object" && !!recommendation.collection && _typeof(recommendation.collection) === "object" && recommendation.collection.recommendations)) {                            _context4.next = 19;                            break;                        }                        _context4.next = 13;                        return getRecommendationsStorage();                    case 13:                        recommendationsStorage = _context4.sent;                        recommendationsStorage.data.recommendations = recommendation.collection;                        recommendationsStorage.data.recommendationsTimestamp = Date.now();                        _context4.next = 18;                        return recommendationsStorage.save();                    case 18:                        return _context4.abrupt("return", recommendation.collection);                    case 19:                        _context4.next = 21;                        return getRecommendationsStorage();                    case 21:                        recommendationsStorage = _context4.sent;                        recommendationsStorage.data.recommendationsTimestamp = Date.now();                        _context4.next = 25;                        return recommendationsStorage.save();                    case 25:                        return _context4.abrupt("return", null);                    case 26:                    case "end":                        return _context4.stop();                }            }        }, _callee4, this);    }));    return function loadRecommendations() {        return _ref4.apply(this, arguments);    };}();

GET request query construction

static/js/main.js
function getFetchUrl(args) {  return "https://homey.place" + args.endpoint + (args.query ? '?' + __WEBPACK_IMPORTED_MODULE_1_query_string__["stringify"](args.query) : '');}var getCollection = function () {    var _ref = _asyncToGenerator( /*#__PURE__*/__WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.mark(function _callee(uid, lang) {        var response;        return __WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.wrap(function _callee$(_context) {            while (1) {                switch (_context.prev = _context.next) {                    case 0:                        _context.next = 2;                        return Object(__WEBPACK_IMPORTED_MODULE_1__helpers_apiRequestHelper__["b" /* default */])({                            endpoint: '/api/discovery/collection',                            type: 'GET',                            query: {                                'uid': uid,                                'lang': lang                            }                        });                    case 2:                        response = _context.sent;                        return _context.abrupt("return", response.json());                    case 4:                    case "end":                        return _context.stop();                }            }        }, _callee, _this);    }));    return function getCollection(_x, _x2) {        return _ref.apply(this, arguments);    };}();
Recommendation destination
    • homey.place

    Extension server receiving UUID-tagged discovery recommendation requests.

Amplitude events reuse a device ID

A POST to api2.amplitude.com/2/httpapi fired after the new-tab experience loaded.

Evidence recorded 10 Amplitude events under one persistent device ID, incl. session_start, set as deviceId with locale and subscription status attached.

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

You open the extension's new-tab page.

The extension did this

The extension sends analytics events that reuse the same device identifier.

Fields present in the Amplitude analytics flow
  • Analytics project key
    c32b70ea091457a622de197e24fb3461

    Ties the event batch to this extension's Amplitude project.

  • Device identifier
    8fbba8c3-1f6c-4e5a-9c7b-2e04d5a6f901 (illustrative; captured evidence recorded prefix 8fbba8c3)

    Lets analytics events from the same browser profile be connected over time.

  • Event names
    session_start, [Amplitude] Page Viewed, Welcome Dialog Started

    Describes what happened in the new-tab page during the session.

  • User locale
    en

    Adds language or locale context to the analytics profile.

  • Extension version
    4.27.2

    Ties the events to the installed extension build.

  • Account user ID
    user_id_count=0

    No account user ID value was observed in the captured batch.

Captured request
POSThttps://api2.amplitude.com/2/httpapi
The code that does this

Amplitude initialization uses a persistent device identifier

Readable version

New-tab page initializer

static/js/main.js
key: 'initAmplitude',value: function () {    var _ref31 = _asyncToGenerator( /*#__PURE__*/__WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.mark(function _callee31() {        var analyticsEnabled, anonymousId, licenseType, subscriptionStatus, identifyEvent;        return __WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.wrap(function _callee31$(_context31) {            while (1) {                switch (_context31.prev = _context31.next) {                    case 0:                        _context31.next = 2;                        return Object(__WEBPACK_IMPORTED_MODULE_67__helpers_analyticsHelper__["a" /* getAnalyticsEnabled */])();                    case 2:                        analyticsEnabled = _context31.sent;                        if (analyticsEnabled) {                            _context31.next = 6;                            break;                        }                        __WEBPACK_IMPORTED_MODULE_59__amplitude_analytics_browser__["d" /* setOptOut */](true);                        return _context31.abrupt('return');                    case 6:                        _context31.next = 8;                        return Object(__WEBPACK_IMPORTED_MODULE_67__helpers_analyticsHelper__["b" /* getOrCreateAnonymousId */])();                    case 8:                        anonymousId = _context31.sent;                        _context31.next = 11;                        return __WEBPACK_IMPORTED_MODULE_59__amplitude_analytics_browser__["c" /* init */]("c32b70ea091457a622de197e24fb3461", undefined, {                            defaultTracking: {                                sessions: true,                                pageViews: true,                                formInteractions: false,                                fileDownloads: false                            },                            deviceId: anonymousId,                            minIdLength: 1,                            appVersion: chrome.runtime.getManifest().version                        });                    case 11:                        licenseType = Object(__WEBPACK_IMPORTED_MODULE_50__helpers_licenseHelper__["c" /* getLicenseType */])();                        subscriptionStatus = Object(__WEBPACK_IMPORTED_MODULE_50__helpers_licenseHelper__["d" /* getSubscriptionStatus */])();                        identifyEvent = new __WEBPACK_IMPORTED_MODULE_59__amplitude_analytics_browser__["a" /* Identify */]();                        identifyEvent.set('App Platform', "CHROME");                        identifyEvent.set('User Locale', chrome.runtime.getManifest().current_locale);                        identifyEvent.set('License Type', licenseType);                        identifyEvent.set('Subscription Status', subscriptionStatus);                        __WEBPACK_IMPORTED_MODULE_59__amplitude_analytics_browser__["b" /* identify */](identifyEvent);                        Object(__WEBPACK_IMPORTED_MODULE_60__helpers_offerImpressionTracker__["a" /* initImpressionTracker */])();                    case 20:                    case 'end':                        return _context31.stop();                }            }        }, _callee31, this);    }));    function initAmplitude() {        return _ref31.apply(this, arguments);    }    return initAmplitude;}()

Background initializer

static/js/background.js
var amplitudeInit = function () {    var _ref4 = _asyncToGenerator( /*#__PURE__*/__WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.mark(function _callee4() {        var anonymousId;        return __WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.wrap(function _callee4$(_context4) {            while (1) {                switch (_context4.prev = _context4.next) {                    case 0:                        _context4.next = 2;                        return getOrCreateAnonymousId();                    case 2:                        anonymousId = _context4.sent;                        _context4.next = 5;                        return __WEBPACK_IMPORTED_MODULE_3__amplitude_analytics_browser__["c" /* init */]("c32b70ea091457a622de197e24fb3461", undefined, {                            minIdLength: 1,                            appVersion: chrome.runtime.getManifest().version,                            deviceId: anonymousId                        }).promise;                    case 5:                    case "end":                        return _context4.stop();                }            }        }, _callee4, this);    }));    return function amplitudeInit() {        return _ref4.apply(this, arguments);    };}();
External analytics destination
    • api2.amplitude.com

    Amplitude analytics endpoint receiving extension event batches with the persistent device identifier.

Embedded pages lose security headers

On install, the extension adds a rule for sub-frame requests, stripping CSP, CSP-report-only, X-Frame-Options, and report-to headers, setting Sec-Fetch-Dest to document; matches Google, YouTube, ChatGPT, Gemini, WhatsApp, TikTok, Instagram.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You install or update the extension.

The extension did this

The extension registers a browser rule that changes response headers for selected embedded pages.

Headers changed by the dynamic rule
  • Frame-blocking header
    X-Frame-Options

    Helps sites decide whether their pages may be displayed inside another page.

  • Content policy header
    content-security-policy

    Helps sites control what content and scripts their pages may load.

  • Report-only policy header
    content-security-policy-report-only

    Lets a site test content policy changes and receive reports without enforcing the policy.

  • Reporting header
    report-to

    Tells the browser where policy and network reports should be sent.

  • Request destination header
    Sec-Fetch-Dest: document

    The extension also marks matching requests as document loads.

The code that does this

Dynamic rule and site list used for header modification

Readable version

Installed dynamic rule

static/js/background.js
chrome.runtime.onInstalled.addListener(_asyncToGenerator( /*#__PURE__*/__WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.mark(function _callee3() {    var rules;    return __WEBPACK_IMPORTED_MODULE_0_babel_runtime_regenerator___default.a.wrap(function _callee3$(_context3) {        while (1) {            switch (_context3.prev = _context3.next) {                case 0:                    rules = [{                        id: 1,                        priority: 1,                        action: {                            type: "modifyHeaders",                            requestHeaders: [{ header: "Sec-Fetch-Dest", operation: "set", value: "document" }],                            responseHeaders: [{ operation: "remove", header: "X-Frame-Options" }, { header: "report-to", operation: "remove" }, { header: "content-security-policy-report-only", operation: "remove" }, { header: "content-security-policy", operation: "remove" }]                        },                        condition: {                            resourceTypes: ["sub_frame"],                            requestDomains: __WEBPACK_IMPORTED_MODULE_3__components_pageViewer_PageViewerSitesList__["a" /* default */].filter(function (item) {                                return item.modifyHeaders;                            }).map(function (site) {                                return new URL(site.url).host;                            }),                            initiatorDomains: [chrome.runtime.id]                        }                    }];                    _context3.next = 3;                    return chrome.declarativeNetRequest.updateDynamicRules({                        removeRuleIds: rules.map(function (r) {                            return r.id;                        }),                        addRules: rules                    });                case 3:                case "end":                    return _context3.stop();            }        }    }, _callee3, _this);})));

Panel sites filtered by modifyHeaders

static/js/main.js
var sitesList = [{    id: 'google-translate',    category: 'google',    url: 'https://translate.google.com/',    dropCache: false,    modifyHeaders: true,    "css": ["content-scripts/google-app.css"],    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_translateProduct")}, {    id: 'google-drive',    category: 'google',    url: 'https://drive.google.com/',    dropCache: false,    modifyHeaders: true,    "css": ["content-scripts/google-app.css"],    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_driveProduct")}, {    id: 'google-keep',    category: 'google',    url: 'https://keep.google.com/',    dropCache: false,    modifyHeaders: true,    "css": ["content-scripts/google-app.css"],    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_keepProduct")}, {    id: 'google-maps',    category: 'google',    url: 'https://www.google.com/maps',    dropCache: true,    modifyHeaders: true,    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_mapsProduct")}, {    id: 'google-news',    category: 'google',    url: 'https://news.google.com/',    dropCache: false,    modifyHeaders: true,    "css": ["content-scripts/google-app.css"],    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_newsProduct")}, {    id: 'google-photos',    category: 'google',    url: 'https://photos.google.com/',    dropCache: false,    modifyHeaders: true,    "css": ["content-scripts/google-photos.css"],    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_photosProduct")}, {    id: 'google-finance',    category: 'google',    url: 'https://www.google.com/finance',    dropCache: false,    modifyHeaders: false,    "css": ["content-scripts/google-finance.css"],    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_financeProduct")}, {    id: 'google-youtube',    category: 'google',    url: 'https://www.youtube.com/',    dropCache: false,    modifyHeaders: true,    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_youtubeProduct")}, {    id: 'google-youtube-music',    category: 'google',    url: 'https://music.youtube.com/',    dropCache: false,    modifyHeaders: true,    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_youtubeMusicProduct")}, {    id: 'aliexpress',    category: 'shopping',    url: 'https://s.click.aliexpress.com/e/_A9f4PI',    dropCache: false,    modifyHeaders: false,    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("sites_aliexpress")}, {    id: 'chat-gpt',    category: 'ai',    url: 'https://chatgpt.com/',    dropCache: false,    modifyHeaders: true,    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("sites_chatGpt")}, {    id: 'google-bard',    category: 'ai',    url: 'https://gemini.google.com/',    dropCache: false,    modifyHeaders: true,    "css": ["content-scripts/google-app.css"],    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("googleProductsGrid_bardProduct")}, {    id: 'whatsapp',    category: 'chat',    url: 'https://web.whatsapp.com/',    dropCache: false,    modifyHeaders: true,    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("sites_whatsapp")}, {    id: 'tiktok',    category: 'social',    url: 'https://www.tiktok.com/',    dropCache: false,    modifyHeaders: true,    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("sites_tikTok")}, {    id: 'instagram',    category: 'social',    url: 'https://www.instagram.com/',    dropCache: false,    modifyHeaders: true,    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("sites_instagram")}, {    id: 'youtube-shorts',    category: 'social',    url: 'https://www.youtube.com/shorts',    dropCache: false,    modifyHeaders: true,    title: __WEBPACK_IMPORTED_MODULE_0__app_chromeHelper__["a" /* default */].instance().i18n.getMessage("sites_youtubeShorts")}];
Embedded site hosts with header changes enabled
    • translate.google.com
    • drive.google.com
    • keep.google.com
    • news.google.com
    • photos.google.com
    • music.youtube.com
    • chatgpt.com
    • gemini.google.com
    • web.whatsapp.com
    • www.tiktok.com

    Listed panel host with modifyHeaders enabled.

    • www.google.com

    Listed panel host for Maps with modifyHeaders enabled.

    • www.youtube.com

    Listed panel host with modifyHeaders enabled for YouTube and Shorts.

Updated 30 September 2026lllnjdmfnfjifcfpppjmcnanpokikcpl