Is WPS PDF - Read, Edit, Fill, Convert, and AI Chat PDF with Ease safe?

High risk

WPS PDF transmits persistent device and user identifiers to Google Analytics on every extension event.

Each time the extension is installed, activated, or used to view a PDF, background.js collects a user_id, device_id, install_id, chrome_instance_id, country code, OS, and browser fingerprint and sends them via POST to Google Analytics. The extension also requests broad permissions — <all_urls>, webRequest, cookies, and nativeMessaging — which allow it to intercept HTTP and file:// PDF downloads across every website. An opt-out preference (sendUsageData) exists but is not surfaced prominently.

www.wps.comv1.0.0.61Chrome Web Store
75Risk
Who publishes it

www.wps.com - 1 other listing 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
www.wps.com

Same store account

1 other listing published from this account, 6.0M+ 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

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

Server-Controlled Script Injection on Competitor PDF Sites

WPS PDF's service worker POSTs to ap.wps.com on start for promotion rules.

Campaign 8130 targeted 12 competitor sites (ilovepdf.com, smallpdf.com, etc) plus 130 Google/Bing patterns; the server sets targets at runtime, none in manifest.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open your browser with WPS PDF installed.

The extension's service worker starts and fetches promotion configuration from the WPS server before any tab is visited.

The extension did this

Content scripts are registered at runtime on competitor PDF sites and search pages using match patterns supplied by the server.

During dynamic analysis, the server directed registration on six competitor PDF services (ilovepdf.com, smallpdf.com, pdf24.org, sejda.com, pdfcandy.com, pdfgear.com) and 130 Google and Bing search URL patterns, none of which are listed in the extension's manifest.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://ap.wps.com/tiance/dce/exec/api/market/activity?lang=en-US
Returns a promotion config array; the observed response carried campaign_id=8130 containing url_match rules (12 competitor PDF site match patterns targeting ilovepdf.com, smallpdf.com, pdf24.org, sejda.com, pdfcandy.com, pdfgear.com) and keyword_match rules (130 Google/Bing search URL patterns). Rules are stored in chrome.storage.local under the key promotion_config_cache and expire after 6 hours.
Headers
Content-Typeapplication/json
Body
{
  "channel_code": "WPWP1001",
  "platform": 8
}
03EvidenceCODE COMPARE
The code that does this

Dynamic content script registration using server-supplied match patterns (url_match type)

What it actually does
// Constants (background.js):
//   Ut = 'url-match-promotion-dynamic'
//   ot = 'https://ap.wps.com/tiance/dce/exec/api/market/activity'
//   nt = 'promotion_config_cache'

async function registerUrlMatchScripts(rules) {
  // Keep only url_match rules that are enabled
  const urlMatchRules = (rules || []).filter(
    r => r.type === 'url_match' && r.enabled
  );

  if (!urlMatchRules.length) {
    // No rules — remove any previously registered script
    try {
      await chrome.scripting.unregisterContentScripts(
        { ids: ['url-match-promotion-dynamic'] }
      );
    } catch {}
    return;
  }

  // Build match patterns from server-supplied fields.
  // Priority: match_patterns field → fallback to domains/sites fields.
  const matchPatterns = [...new Set(
    urlMatchRules.flatMap(rule =>
      Array.isArray(rule.match_patterns) && rule.match_patterns.length > 0
        ? rule.match_patterns             // server explicit patterns
        : buildMatchPatterns(rule.domains || rule.sites || [])  // Ft()
    )
  )];

  if (matchPatterns.length) {
    // Replace old registration with new server-supplied patterns
    try {
      await chrome.scripting.unregisterContentScripts(
        { ids: ['url-match-promotion-dynamic'] }
      );
    } catch {}

    // Inject promotion.js on every server-specified URL — not from manifest
    await chrome.scripting.registerContentScripts([{
      id: 'url-match-promotion-dynamic',
      matches: matchPatterns,   // <-- entirely server-controlled
      js: ['content-scripts/promotion.js'],
      runAt: 'document_end'
    }]);
  }
}
04EvidenceTHIRD PARTY LIST
Sites receiving dynamically registered content scripts (observed campaign 8130)
  • ap.wps.com

    WPS Office server supplying promotion rules, controlling which sites get content-script injection. Rules cache up to 6 hours, applied on each page visit.

  • ilovepdf.com

    Competitor PDF service. Received promotion.js injection via url-match-promotion-dynamic during observed session.

  • smallpdf.com

    Competitor PDF service. Received promotion.js injection via url-match-promotion-dynamic during observed session.

  • pdf24.org

    Competitor PDF service. Received promotion.js injection via url-match-promotion-dynamic during observed session.

  • sejda.com

    Competitor PDF service. Received promotion.js injection via url-match-promotion-dynamic during observed session.

  • pdfcandy.com

    Competitor PDF service. Received promotion.js injection via url-match-promotion-dynamic during observed session.

  • pdfgear.com

    Competitor PDF service. Received promotion.js injection via url-match-promotion-dynamic during observed session.

  • google.com

    Search pages. Received search-promotion.js via keyword-match-promotion-dynamic. 36 country Google TLDs (co.uk, co.jp, de, fr, etc.) appear, covering /search* and /webhp* paths.

  • bing.com

    Search pages. Received search-promotion.js injection via keyword-match-promotion-dynamic. Patterns cover *.bing.com, cn.bing.com, and global.bing.com on /search* paths.

05EvidenceARTIFACT
Check if you're affected

Queries the Chrome scripting API to reveal any dynamically registered content scripts that WPS PDF has installed at runtime. These scripts are not declared in the extension manifest and are not visible in the Chrome Web Store listing.

RequiresChrome 116+WPS PDF extension (kdpelmjpfafjppnhbloffcjpeomlnpah) installed and active
detect-wps-dynamic-scripts.js · js
/**
 * detect-wps-dynamic-scripts.js
 *
 * Reveals content scripts dynamically registered by WPS PDF (kdpelmjpfafjppnhbloffcjpeomlnpah)
 * that are not declared in the extension manifest.
 *
 * Run from the WPS PDF service worker DevTools console:
 *   1. Open chrome://extensions
 *   2. Enable Developer Mode (top-right toggle)
 *   3. Click "service worker" under WPS PDF
 *   4. Paste this script into the Console and press Enter
 */
(async () => {
  const DYNAMIC_IDS = [
    'url-match-promotion-dynamic',
    'keyword-match-promotion-dynamic'
  ];

  let registered;
  try {
    registered = await chrome.scripting.getRegisteredContentScripts();
  } catch (e) {
    console.error('[WPS PDF detector] Could not query content scripts:', e.message);
    return;
  }

  const dynamic = registered.filter(s => DYNAMIC_IDS.includes(s.id));

  if (dynamic.length === 0) {
    console.log('[WPS PDF detector] No dynamic promotion scripts currently registered.');
    console.log('  (Rules may have been cleared, or the server returned no rules this session.)');
    return;
  }

  console.log(`[WPS PDF detector] Found ${dynamic.length} dynamic script registration(s):`);
  for (const script of dynamic) {
    console.group(`  id: ${script.id}`);
    console.log('  js:', script.js);
    console.log(`  match patterns (${script.matches.length} total):`);
    script.matches.forEach((p, i) => console.log(`    [${i}] ${p}`));
    console.groupEnd();
  }

  // Also show the cached promotion config to see the originating campaign
  try {
    const stored = await chrome.storage.local.get(['promotion_config_cache']);
    const cache = stored['promotion_config_cache'];
    if (cache) {
      console.log('\n[WPS PDF detector] Cached promotion config:');
      console.log('  campaign_id:', cache.campaign_id);
      console.log('  rule count:', (cache.rules || []).length);
      console.log('  cached at:', new Date(cache.timestamp).toISOString());
      console.log('  expires at:', new Date(cache.timestamp + 216e5).toISOString(), '(6-hour TTL)');
    } else {
      console.log('\n[WPS PDF detector] No cached promotion config found in storage.');
    }
  } catch (e) {
    console.warn('[WPS PDF detector] Could not read storage:', e.message);
  }
})();
How to run it
  1. 1
    Open chrome://extensions → enable Developer Mode → click 'service worker' under WPS PDF → paste into the Console tab and press Enter.
SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-506
SourceAI SANDBOX

Browser Notifications Used as Server-Controlled Ad Channel

When you visit flagged pages, WPS PDF's worker fetches config from ap.wps.com and fires a notification whose title, message, button come from that response.

A Google 'pdf' search triggered a matching notification, capped at 2/category/day.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You search for "pdf" on Google (or visit another page matching a server-supplied keyword or URL rule).

The extension's background worker had already fetched and cached the current promotion rules before you navigated.

The extension did this

The extension fires a native Chrome notification whose title, message, and button text are pulled directly from the server's rule object.

In a captured session, the notification read "Local Conversion, No Formatting Loss / Perfectly preserve layouts and tables with blazing fast speed" with a "Try it now" button, matching the cached rule verbatim.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://ap.wps.com/tiance/dce/exec/api/market/activity?lang=en-US
During dynamic analysis, the server returned campaign_id=8130 with a keyword_match rule set (category="reactivation", popup_style="notification") including the exact keyword "pdf" among its match terms, plus a rule object carrying title, body, image_url, and button_text fields. The response is cached in chrome.storage.local under the key promotion_config_cache for up to 6 hours before a re-fetch.
Headers
Content-Typeapplication/json
Body
{
  "channel_code": "WPWP1001",
  "platform": 8
}
03EvidenceCODE COMPARE
The code that does this

Notification content comes from the server rule, not a built-in string

What it actually does
// background.js — bound to runtime message action 'showPromotionPopup'
const showPromotionPopup = async (msg) => {
  const { rule_id } = msg;
  try {
    // fetchOrCachedPromotionConfig() — reads promotion_config_cache,
    // re-fetches from ap.wps.com if the 6-hour TTL has expired
    const config = await fetchOrCachedPromotionConfig();
    if (!config?.rules) return;

    const rule = config.rules.find(r => r.id === rule_id);
    if (!rule) return;

    const notificationId = `promotion_${rule_id}`;
    const options = {
      type: "basic",
      iconUrl: rule.image_url,       // <-- server-controlled
      title: rule.title,             // <-- server-controlled
      message: rule.body,            // <-- server-controlled
      buttons: [{ title: rule.button_text || "OK" }],  // <-- server-controlled
      priority: 2,
      requireInteraction: true
    };

    chrome.notifications.clear(notificationId, () => {
      chrome.notifications.create(notificationId, options, () => { /* ... */ });
    });
  } catch (e) {}
};
04EvidenceTEMPORAL PATTERN
When this fires
When a batch threshold is hit

A per-category, per-style frequency counter (chrome.storage.local key promotion_frequency_state) caps notifications at 2 per day and 7 per week, with a minimum 2-hour gap between any two, before the extension will show another one. The counter resets on a rolling daily/weekly window computed client-side, independent of whether the server sends new rules.

05EvidenceARTIFACT
Check if you're affected

Reads the cached promotion configuration and frequency-tracking state from WPS PDF's own extension storage to show which notification rules are currently armed and how close each category is to its daily/weekly cap.

RequiresChrome 116+WPS PDF extension (kdpelmjpfafjppnhbloffcjpeomlnpah) installed and active
detect-wps-notification-rules.js · js
/**
 * detect-wps-notification-rules.js
 *
 * Reveals cached, server-controlled notification rules and frequency-cap
 * state inside WPS PDF (kdpelmjpfafjppnhbloffcjpeomlnpah).
 *
 * Run from the WPS PDF service worker DevTools console:
 *   1. Open chrome://extensions
 *   2. Enable Developer Mode (top-right toggle)
 *   3. Click "service worker" under WPS PDF
 *   4. Paste this script into the Console and press Enter
 */
(async () => {
  const stored = await chrome.storage.local.get([
    'promotion_config_cache',
    'promotion_frequency_state'
  ]);

  const cache = stored['promotion_config_cache'];
  if (!cache) {
    console.log('[WPS PDF detector] No cached promotion config found.');
    return;
  }

  console.log('[WPS PDF detector] campaign_id:', cache.campaign_id);
  console.log('[WPS PDF detector] cached at:', new Date(cache.timestamp).toISOString());
  console.log('[WPS PDF detector] expires at:', new Date(cache.timestamp + 216e5).toISOString(), '(6h TTL)');

  const notifRules = (cache.rules || []).filter(r => r.popup_style === 'notification');
  console.log(`\n[WPS PDF detector] ${notifRules.length} notification rule(s) cached:`);
  for (const r of notifRules) {
    console.group(`  id: ${r.id} (category: ${r.category})`);
    console.log('  title:', r.title);
    console.log('  message:', r.body);
    console.log('  button_text:', r.button_text);
    console.log('  button_url:', r.button_url);
    console.log('  keywords:', r.keywords || r.match_patterns || '(url_match rule)');
    console.groupEnd();
  }

  console.log('\n[WPS PDF detector] Frequency-cap state:');
  console.log(JSON.stringify(stored['promotion_frequency_state'] || {}, null, 2));
})();
How to run it
  1. 1
    Open chrome://extensions, enable Developer Mode.
  2. 2
    Click "service worker" under WPS PDF.
  3. 3
    Paste script, press Enter.
  4. 4
    Review the rule list and frequency counters for armed notifications.

What it can do

Permissions this extension asks for, as declared in version 1.0.0.52. 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 1.0.0.61, which we have not unpacked yet.

  • Read and change your data on every site you visit

    <all_urls>

  • Watch every request your browser makes

    webRequest

  • See every page you navigate to, as you navigate to it

    webNavigation

  • Store data in your browser

    storage

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

    tabs

  • Talk to a program installed on your computer, outside the browser's sandbox

    nativeMessaging

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • Receive push messages from its developer's servers

    gcm

  • Read and change cookies, including the ones that keep you signed in

    cookies

Where it sends data

Destinations our analysis observed WPS PDF 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.

  • www.google-analytics.comwidely used

    WPS PDF sends data to www.google-analytics.com. A widely used service: 346 other extensions we have analysed send data here.

Updated 30 September 2026kdpelmjpfafjppnhbloffcjpeomlnpah