Is Send to Kindle for Google Chrome™ safe?
Send to Kindle is medium risk. Clicking Send to Kindle while signed in starts page extraction and queues a POST to Amazon's endpoint with rating 999 and the document URL. Testing without a valid login didn't reach this path; fields come from shipped code.
Who publishes itAMZN Mobile LLC - 2 other listings from the same operator, none carrying a finding
AMZN Mobile LLC - 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
1 other listing published from this account, 70k+ users between them, none of them carrying a finding.
Same operator - 1 listing
Published under a different store account, but sharing the registered address, contact or declared legal entity this one gave the store.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Page URL sent when Send to Kindle extraction starts
Clicking Send to Kindle while signed in starts page extraction and queues a POST to Amazon's endpoint with rating 999 and the document URL.
Testing without a valid login didn't reach this path; fields come from shipped code.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You click Send to Kindle while signed in.
The popup sends an extract request after the account check succeeds.
The extension starts extraction and immediately prepares a POST containing the current page URL.
This happens before the content extraction finishes, so cancelling later does not remove the already-started metrics call.
- Current page URLhttps://www.nytimes.com/2026/07/14/technology/browser-extensions.html?campaign_id=9 (illustrative)
This identifies the page you were viewing when the Send to Kindle attempt began.
- Rating marker999
This marks the request as the extension's low-rating metrics path, even though it is sent at extraction start.
No live response was recorded because dynamic analysis without a valid Amazon login did not reach the extraction path.
- Accept
- application/json
- Content-Type
- application/json
Popup click, content extraction, and Amazon POST path
Popup Send button starts extraction after auth
deobfuscated/s2k-popup.jsonSendToKindle: function () { chrome.runtime.sendMessage({ action: "metrics-add-counter", data: { metricsName: MetricsNames["pop-main-send-click"] } }); // Check auth status before send $S2K.onCheckAuth(false, function (isAuthed) { if (isAuthed) { chrome.runtime.sendMessage({"action": "extract", "preview": false, "selected": false}, function () { // Close window. $S2K.onClose(); }); } else { // Change to un authed UI $S2K.changeUIWithId($S2K.ID_UN_AUTHED_DIV, false); } });},Worker injects the extraction function into the active tab
deobfuscated/s2k-worker.jsextractContent: function (preview, selected, shortcut) { chrome.tabs.query({active: true, lastFocusedWindow: true}, function (queryResults) { let tab = queryResults[0]; if (tab && (tab.url.indexOf("chrome://") === -1) && (tab.url.indexOf("chrome-extension://") === -1)) { // Execute background logic. chrome.scripting.executeScript({ target: {tabId: tab.id}, func: function (preview, selected, shortcut) { globalThis.launchExtraction(preview, selected, shortcut); }, args: [preview, selected, shortcut] }); } });},Content script sends rating 999 and the document URL at extraction start
deobfuscated/s2k-content.jsconst launchExtraction = function (previewOnly, selectedOnly, usedShortcut) { // Load extension state for current window. const state = getState(); if (state.inProgress) { return; } // Somehow, if we're trying to extract content before the // content script has been fully injected and the state transition // between the SW and the content script has finished, try again in 250ms if (!state.isInjected) { setTimeout(launchExtraction, 250); return; } // Set desired action in state. state.isPreviewOnly = previewOnly || false; state.isSelectedOnly = selectedOnly || false; state.isShortcut = usedShortcut || false; // Initialize progress information. state.statusMessage = state.statusMessage || new StatusMessage("status"); // Reset progress state state.resetProgress(); // Register cancel event state.statusMessage.registerEvent("cancelProgress", function () { // Stop monitoring unload action removeUnloadListener(preventFromUnload); state.cancelProgress = true; chrome.runtime.sendMessage({ action: "set-cancel-sending", data: { cancel: true } }); }); // Perform action only, if the content has a body and it is not already a preview. if (state.stateDocument.body && !state.stateDocument.location.pathname.match(/^\/gp\/sendtokindle\/(reader|setup)/i)) { // Record metrics chrome.runtime.sendMessage({ action: "metrics-add-counter", data: { metricsName: MetricsNames["extract-count"] } }); chrome.runtime.sendMessage({ action: "metrics-start-timer", data: { metricsName: MetricsNames["extract-time"] } }); // Use `report-low-rating` metrics to record all url when starting extraction, rating as "999" chrome.runtime.sendMessage({ action: "report-low-rating", data: { rating: "999", url: state.stateDocument.URL || "" } }); // Prevent from unload page addUnloadListener(preventFromUnload); // Show analyzing alert state.statusMessage.showAlert("progress", "analyzing"); // Release UI thread for drawing the spinner. setTimeout(function () { // Run extraction process, if necessarily. if (!state.content) { // New logic (call web-extraction) will be executed first let newExtractor = new S2KWebExtractor(); let task = { timeoutTimeInMillis: Date.now() + EXTRACTION_TIMEOUT_MILLIS, isTimedOut: function () { return Date.now() > this.timeoutTimeInMillis; } }; task.progress = launchProgress.bind(task); // This `backup` is old logic, it's a backup logic for now. If the web-extraction failed, will trigger this task.backup = function () { let extractor = createExtractor(state); extractor.extract(task); }; // Execute new extract logic newExtractor.extract(task); } else { launchProgress({"success": true, "data": state.content, "value": 1.0}); } }, 0); }};Worker routes the low-rating message to the POST helper
deobfuscated/s2k-worker.js} else if (message.action === "report-low-rating") { reportLowRating(message.data.rating, message.data.url).then(function () { if (sendResponse ) { sendResponse(); } });} else if (message.action === "extract-epub") {POST helper serializes rating and url to recordLowRatingUrl
deobfuscated/s2k-worker.jsasync function reportLowRating(rating, url) { let path = "/extension/recordLowRatingUrl"; let res = await ajax({ type: "POST", path: path, dataType: "json", isJSONContentType: true, isCsrfRequired: true, cache: false, timeout: 10000, data: { "rating": rating, "url": url }, }); return res.success;}- www.amazon.com
Amazon Send to Kindle service receives the low-rating metrics request for the signed-in extraction flow.
What it can do
Permissions this extension asks for, as declared in version 2.1.1.7. 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 every site you visit
*://*/*
See the address and title of every tab you have open
tabs
Act on the current tab, but only after you click the extension
activeTab
Store data in your browser
storage
Store an unlimited amount of data in your browser
unlimitedStorage
Schedule its own background tasks
alarms
Run its own code inside the pages you visit
scripting
See every page you navigate to, as you navigate to it
webNavigation