Is 壹伴 · 小插件 safe?

High risk

壹伴 is high risk. The extension requests https://yiban.io/extension/proxy.pac at startup. Source shows that when yiban.io reports a newer version, upgrade-proxy is on, and a Google check fails, the downloaded PAC script becomes Chrome's browser-wide proxy.…

nightingales-developersv12.4.3Chrome Web Store
77Risk
Who publishes it

民营企业 - 5 other listings from the same operator, 2 of them carrying a finding

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

Publisher
nightingales-developers
Declared legal entity
民营企业
Registered address
武汉东湖新技术开发区关山大道355号, 光谷新世界中心A地块写字楼A栋28层01、02、12室(自贸区武汉片区), 武汉市, 湖北省 430000, CN
Registered contact
武汉夜莺科技有限公司

Shared hosts - 5 hostnames

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.

open-v3.gaoding.com
Also called by 3 other listings, including WeTab
st-gdx.dancf.com
Also called by 3 other listings, including WeTab
kancloud.cn
Also called by 4 other listings, including TTMCN Helper
yiban.io
Also called by 4 other listings, including Ruta - AI 搜索
soogif.com
Also called by 6 other listings, including 美编助手, Markdown Nice

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

Publishers can request a review.

Findings

Outdated Yiban installs can load proxy rules from yiban.io

HighunwantedCWE-829Dynamic sandbox

The extension requests https://yiban.io/extension/proxy.pac at startup.

What actually happens

You did this

The extension starts, checks the server-reported newest version, and keeps the upgrade-proxy feature enabled by default.

The proxy path depends on the installed version being older than the newest version stored from yiban.io.

The extension did this

It fetches a PAC script from yiban.io and can install that script as Chrome's browser-wide proxy configuration.

Applying the PAC is further gated by Chrome proxy control and a failed connectivity check to clients2.google.com.

Captured request

GEThttps://yiban.io/extension/proxy.pac

200 OK response observed; the PAC response body was not recorded in the evidence text.

Fields affected by the proxy-control path

  • Page URL
    https://mp.weixin.qq.com/cgi-bin/appmsg?action=edit&token=184213655&lang=zh_CN (illustrative)

    The PAC rules can make routing decisions based on the sites you visit.

  • Destination host
    mp.weixin.qq.com

    A browser proxy configuration can route traffic for sites across your browsing session, not just the extension's own pages.

  • Request metadata
    Host: mp.weixin.qq.com; User-Agent: Chrome/123.0 (illustrative)

    If the PAC selects an upstream proxy, that proxy can receive connection metadata and, for non-HTTPS traffic, request headers.

The code that does this

UpgradeProxy fetches a PAC script and can install it with chrome.proxy.settings.set

Readable version

Readable checkNeedUpgrade() fetches yiban.io/extension/proxy.pac

background-script.js
async checkNeedUpgrade() {  if (!this.enabled || ve.browser !== "chrome") return;  let {    newestVersion: e  } = await ne.asyncGet(["newestVersion"]), t = ve.extensionVersion;  if (!d.checkIfNeedUpdate(t, e)) return Pe.debug("already have latest version , clear proxy config"), this.clearProxyConfigs();  let n;  n = `${ke.hosts.web}/extension/proxy.pac`, fetch(n).then(s => {    if (s.status === 200) return s.text();    throw new Error("fetch pac script failed")  }).then(s => {    s && this.pacScript !== s && (Pe.debug("fetched new pac script"), this.pacScript = s), this.initProxy()  }).catch(() => {})}

Readable initProxy() checks Chrome proxy control before applying PAC

background-script.js
async initProxy() {  if (this.pacScript) try {    Pe.debug("need update, try to reach google api"), this.checkGoogleConnectability(e => {      if (e) Pe.debug("reached google api, skip proxy setting");      else {        if (Pe.debug("can't reach google api, try use proxy"), !this.enabled) return;        chrome.proxy.settings.get({}, ({          value: t,          levelOfControl: n        }) => {          if (n === "controlled_by_this_extension") return this.addProxyConfigs();          if (n === "controllable_by_this_extension") return this.addProxyConfigs()        })      }    })  } catch (e) {    Pe.debug("can't get latest extension version, wait until next pac auto-update loop"), console.error(e)  }}

Readable addProxyConfigs() calls chrome.proxy.settings.set

background-script.js
addProxyConfigs() {  let e;  e = {    mode: "pac_script",    pacScript: {      data: this.pacScript    }  }, chrome.proxy.settings.set({    value: e,    scope: "regular"  }, () => {    Pe.debug("proxy config initialized!", {      pac: this.pacScript    }), this.checkPacScriptIsValid()  })}

Parsed manifest permissions

manifest.json
{  "permissions": [    "sidePanel",    "tabs",    "storage",    "unlimitedStorage",    "cookies",    "notifications",    "contextMenus",    "webRequest",    "proxy",    "alarms",    "declarativeNetRequestWithHostAccess",    "declarativeNetRequestFeedback"  ],  "host_permissions": [    "<all_urls>"  ]}

External hosts in this proxy path

    • yiban.io

    Supplies the latest-version data and serves the PAC script at /extension/proxy.pac.

    • clients2.google.com

    Used as a connectivity probe before deciding whether to apply the downloaded proxy configuration.

UI Interaction Telemetry Sent to api.yiban.io on Every Action

MediumunwantedCWE-359Dynamic sandbox

Dynamic analysis captured POSTs to api.yiban.io/user/use_func and .../behavior/add each popup use. use_func gets the action, version, data; behavior/add gets the UI element type, page, name, plus your WeChat account ID (mpId) and nickname.

What actually happens

You did this

You open the 壹伴 popup or interact with a UI feature.

The extension did this

The extension sends your action name, extension version, and WeChat account details to api.yiban.io.

Two separate POST requests fire: one to /user/use_func with the action label and version, and one to /user/behavior/add with the UI element type, page, name, and your mpId and mpNickname.

Captured request

POSThttps://api.yiban.io/user/use_func

HTTP 200 OK. Captured as request ID 1422419.103 by the background service worker on popup open.

Body
{  "action": "插件打开popup",  "extension_version": "12.1.0",  "data": {}}

Captured request

POSThttps://api.yiban.io/user/behavior/add

HTTP 200 OK. Captured as request ID 1422419.38 by the background service worker on popup open.

Body
{  "func_type": "click",  "func_page": "entrance",  "func_name": "popup_bar",  "client_version": "12.1.0"}

Fields sent to api.yiban.io/user/use_func

  • Action name
    插件打开popup

    A label describing what you did inside the extension, written in Chinese.

  • Extension version
    12.1.0

    Which version of 壹伴 you have installed.

  • Action data
    {}

    Optional structured data accompanying the action. Contents depend on which feature you used.

Fields sent to api.yiban.io/user/behavior/add (authenticated session)

  • UI element type
    click

    Category of the UI interaction (e.g. 'click').

  • UI page
    entrance

    Which screen of the extension you were on.

  • UI element name
    popup_bar

    The specific element you interacted with.

  • WeChat account ID (mpId)
    gh_a3b2c1d4e5f6

    Your WeChat Official Account identifier. Present when you are logged in to 壹伴.

  • WeChat account nickname (mpNickname)
    我的公众号

    The display name of your WeChat Official Account. Sent alongside mpId in authenticated sessions.

  • Client version
    12.1.0

    Which version of 壹伴 you have installed.

The code that does this

The Gn tracker class that wraps all UI event reporting.

Readable version

Gn tracker class

// Global tracker used across the extension for all UI telemetry.class Gn {  // Called for standard feature-use events.  // Fires to /user/use_func after a 3-second delay.  track(action, data = {}, isAdEvent = false) {    try {      if (isBackground) {        if (isAdEvent) {          setTimeout(() => {            api.trackAdEvent(action, data, extensionVersion);          }, 4000);        } else {          setTimeout(() => {            // POST {action, extensionVersion, data} to /user/use_func            api.trackUserAction(action, data, extensionVersion);          }, 3000);        }      } else {        // Content-script path: relay via message bridge to background        messageProxy.trackUserAction({ action, data, isAdEvent });      }    } catch (err) {      console.error(err);    }  }  // Called for richer behavior events.  // Fires to /user/behavior/add, enriching with mpId + mpNickname.  async trackUserEventNew(payload) {    try {      if (isBackground) {        const { mpId, mpNickname } = await this.getCurrentMpIdForBg();        const enriched = {          ...payload,          remark: {            ...payload.remark,            version: extensionVersion,            vip_version: userVipVersion,            mpId,            mpNickname,          },        };        storage.get(['authed'], ({ authed }) => {          // POST enriched payload to /user/behavior/add (via saveUserAction)          api.saveUserAction(enriched).preventDefault();        });      } else {        messageProxy.trackUserEventNew({ data: payload });      }    } catch (err) {      console.error(err);    }  }}

API stubs

// POST {action, extensionVersion, data} to /user/use_funcfunction trackUserAction(action, data, extensionVersion) {  return post('/user/use_func', null, { action, extensionVersion, data }).markSilent();}// POST behavior object to /user/behavior/addfunction trackUserEvent(payload) {  return post('/user/behavior/add', null, payload);}

Where interaction telemetry is sent

    • api.yiban.io

    壹伴's own backend API. Receives feature-use events (/user/use_func) and detailed behavior events (/user/behavior/add) on every significant UI interaction.

Screen Dimensions Reported Daily to api.yiban.io

MediumunwantedCWE-359Dynamic sandbox

Dynamic analysis captured a POST to https://api.yiban.io/user/report/screen_size with screen width/height (1280x1024 in our test).

What actually happens

You did this

You install 壹伴 and open your browser.

The extension did this

The extension reads your screen dimensions and sends them to api.yiban.io once per day.

No user action beyond installation is required. The upload fires 30 seconds after the extension first starts.

Captured request

POSThttps://api.yiban.io/user/report/screen_size

HTTP 200 OK. Captured as request ID 1422419.20 by the background service worker on extension startup with no user interaction.

Body
{  "width": 1280,  "height": 1024}

What 壹伴 sends to api.yiban.io/user/report/screen_size

  • Screen width
    1280

    The pixel width of your monitor as reported by your browser.

  • Screen height
    1024

    The pixel height of your monitor as reported by your browser.

The code that does this

The content script reads screen dimensions and stores them; the background worker posts them daily.

Readable version

Content-script: read screen dimensions and store

// Runs 5 seconds after the content-script loads on every page (all_frames: true).setTimeout(() => { this.recordUserScreenSize(); }, 5 * 1000);recordUserScreenSize() {  try {    const { width, height } = window.screen;    // Writes to chrome.storage so the background can read it later    Ce.set({ userScreenSize: { width, height } });  } catch {}}

Background: post once per calendar day

// Called 30 seconds after the extension first installs.this.execFirstInstallCall(() => {  this.startAlarms();  this.registerHeaderModifier();  this.recordLatestUserId();  setTimeout(() => {    this.reportUserScreenSize();  }, 30 * 1000);});async reportUserScreenSize() {  const { userScreenSize, reportUserScreenSizeDate } = await ne.asyncGet(    ['userScreenSize', 'reportUserScreenSizeDate']  );  const today = d.parseTime(Date.now(), 'y-m-d'); // e.g. '2026-04-14'  if (!reportUserScreenSizeDate || reportUserScreenSizeDate !== today) {    // POST to api.yiban.io/user/report/screen_size    oe.reportUserScreenSize(userScreenSize).preventDefault();    // Mark today so it doesn't fire again until tomorrow    ne.set({ reportUserScreenSizeDate: today });  }}

API layer

// Wraps the actual fetch POST callfunction reportUserScreenSize(payload) {  return post('/user/report/screen_size', {}, payload);}

Where screen dimension data is sent

    • api.yiban.io

    壹伴's own backend API. Receives screen dimensions once per day per user. yiban.io is the vendor's primary domain (the extension ID prefix 'iban' matches the domain).

Updated 30 September 2026ibefaeehajgcpooopoegkifhgecigeeg