Is Send to Kindle for Google Chrome™ safe?

Medium risk

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.

AMZN Mobile LLCv2.1.1.7Chrome Web Store
48Risk
Who publishes it

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.

Publisher
AMZN Mobile LLC
Declared legal entity
AMZN Mobile LLC
Registered address
410 Terry Ave N, Seattle, WA 98109-5210, US

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
What actually happens
You did this

You click Send to Kindle while signed in.

The popup sends an extract request after the account check succeeds.

The extension did this

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.

Fields prepared for the low-rating metrics request
  • Current page URL
    https://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 marker
    999

    This marks the request as the extension's low-rating metrics path, even though it is sent at extraction start.

Captured request
POSThttps://www.amazon.com/sendtokindle/extension/recordLowRatingUrl

No live response was recorded because dynamic analysis without a valid Amazon login did not reach the extraction path.

Headers
Accept
application/json
Content-Type
application/json
The code that does this

Popup click, content extraction, and Amazon POST path

Readable version

Popup Send button starts extraction after auth

deobfuscated/s2k-popup.js
onSendToKindle: 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.js
extractContent: 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.js
const 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.js
async 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;}
Destination reached by this code path
    • 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

Updated 30 September 2026cgdjpilhipecahhcilnafpblkieebhea