Is easySearch safe?

Critical risk

easySearch is critical risk. Code analysis shows easySearch can fetch engine definitions from api.jubt.me and fallback hosts, base64-decode non-default templates, and pass them into JS function constructors. Testing found no live response since hosts were down.

chuanliangv1.3.1.1Chrome Web Store
95Risk

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

Publishers can request a review.

Findings

Remote templates become JavaScript in easySearch

Code analysis shows easySearch can fetch engine definitions from api.jubt.me and fallback hosts, base64-decode non-default templates, and pass them into JS function constructors.

Testing found no live response since hosts were down.

Severity
Critical unwanted
Type
Unexpected
CWE
CWE-829
Source
Dynamic sandbox
What actually happens
You did this

You use the extension's search feature while its engine-update path is available.

The update path depends on configured API hosts returning engine definitions.

The extension did this

The extension can turn a server-provided template into JavaScript that runs inside its search flow.

Non-default parser and searcher templates are decoded and passed into function constructors.

Captured request
GEThttps://api.jubt.me/index1.3.1.php?upgrade=1&plugin_version=1.3.1

Dynamic analysis did not capture a live response; two test sessions observed zero traffic to api.jubt.me and all configured API domains failed DNS resolution.

Inputs available to server-supplied templates
  • Search URL
    https://www.google.com/search?q=ubuntu+iso (illustrative)

    Lets generated code see the page or search endpoint being processed.

  • Search keyword
    ubuntu iso

    Shows what you searched for inside the extension.

  • Fetched page content
    <html><body><a href="https://example.org/download">Ubuntu ISO</a></body></html> (illustrative)

    Lets generated parser code read the returned page content from a search provider.

  • Result source label
    <a href='https://www.google.com' target='_blank'>Google</a> (illustrative)

    Adds context about which search engine or source produced a result.

The code that does this

Server templates are decoded and passed to JavaScript constructors

Readable version

Readable equivalent of the engine update loop

js/utils.js
const pluginVersion = "1.3.1";const apiHosts = [  "https://api.jubt.me",  "https://api.ijubt.net",  "https://api.ijubt.ga",  "https://api.ijubt.cf",  "https://api.ijubt.gq",  "https://api.ijubt.ml",  "https://api.jubt.ml",  "https://api.jubt.ga",  "https://api.jubt.cf",  "https://api.jubt.gq"];async function fetchLatestEngineDefinitions(config) {  for (const host of apiHosts) {    const versionUrl = `${host}/version.php?plugin_version=${pluginVersion}`;    const updateUrl = `${host}/index${pluginVersion}.php?upgrade=1&plugin_version=${pluginVersion}`;    const result = await fetchEngineData(config, versionUrl, updateUrl);    if (result && Object.keys(result).length > 0) return result;  }  return {};}

Readable equivalent of storing returned engines

js/utils.js
if (storedVersion < serverVersion) {  const response = await httpGet(updateUrl);  const parsed = JSON.parse(response.body);  const keys = parsed.keys;  const engines = parsed.engines;  if (engines) {    chrome.storage.local.set({      engines_keys: keys,      engines: engines,      latest: serverVersion    });  }  return {    upgrade,    upgrade_url: upgradeUrl,    keys,    engines,    version: serverVersion,    latest: storedVersion  };}

Readable equivalent of template decoding

background.js
if (engine.method === "NEWTAB" || engine.method === "NEWTAB_IFRAME") {  if (engine.searcher !== "default") {    const decodedSearcherBody = base64DecodeUnicode(engine.searcher);    const searcher = buildSearcher(searcherName, decodedSearcherBody);    if (typeof searcher !== "function") return [];  }}if (engine.parser !== "default") {  const decodedParserBody = base64DecodeUnicode(engine.parser);  const parser = buildParser(parserName, engine.type, decodedParserBody);  if (typeof parser !== "function") return [];}

Readable equivalent of dynamic construction

js/utils.js
function buildParser(parserName, type, templateBody) {  const body = templateBody    .replace("%parserName%", parserName)    .replace("%type%", type);  return new Function("content", "url", "source", "keyword", body);}function buildSearcher(searcherName, templateBody) {  const body = templateBody.replace("%searcherName%", searcherName);  return new Function("url", "keyword", body);}
Configured API hosts for engine updates
    • api.jubt.me

    Primary host used to check versions and request engine updates.

    • api.ijubt.net
    • api.ijubt.ga
    • api.ijubt.cf
    • api.ijubt.gq
    • api.ijubt.ml
    • api.jubt.ml
    • api.jubt.ga
    • api.jubt.cf
    • api.jubt.gq

    Fallback API host in the shipped domains list.

Reproduce it yourself

Recreates the extension's base64-template path locally so you can see how a returned template becomes a JavaScript function.

Requires
  • Node.js 18+
easysearch-template-reproducer.js · js
function base64EncodeUnicode(str) {  return Buffer.from(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (_match, p1) {    return String.fromCharCode('0x' + p1);  }), 'binary').toString('base64');}function base64DecodeUnicode(str) {  return decodeURIComponent(Buffer.from(str, 'base64').toString('binary').split('').map(function (c) {    return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);  }).join(''));}function buildSearcher(searcherName, bodyTemplate) {  bodyTemplate = bodyTemplate.replace('%searcherName%', searcherName);  let myfunc = new Function('url', 'keyword', bodyTemplate);  return myfunc;}function buildParser(parserName, type, bodyTemplate) {  bodyTemplate = bodyTemplate.replace('%parserName%', parserName);  bodyTemplate = bodyTemplate.replace('%type%', type);  let myfunc = new Function('content', 'url', 'source', 'keyword', bodyTemplate);  return myfunc;}const returnedSearcherBody = "return { searcherName: '%searcherName%', url, keyword };";const returnedParserBody = "return { parserName: '%parserName%', type: '%type%', url, source, keyword, contentLength: content.length };";const encodedSearcher = base64EncodeUnicode(returnedSearcherBody);const encodedParser = base64EncodeUnicode(returnedParserBody);const decodedSearcher = base64DecodeUnicode(encodedSearcher);const decodedParser = base64DecodeUnicode(encodedParser);const searcher = buildSearcher('remoteSearcher', decodedSearcher);const parser = buildParser('remoteParser', 'searchengine', decodedParser);console.log(searcher('https://www.google.com/search?q=ubuntu+iso', 'ubuntu iso'));console.log(parser('<html><body>result</body></html>', 'https://www.google.com/search?q=ubuntu+iso', "<a href='https://www.google.com' target='_blank'>Google</a>", 'ubuntu iso'));
How to run it
  1. 1node easysearch-template-reproducer.js

What it can do

Permissions this extension asks for, as declared in version 1.3.1.1. 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

    <all_urls>

  • Add items to the right-click menu

    contextMenus

  • Keep running in the background while your browser is open

    background

  • Watch every request your browser makes

    webRequest

  • Watch, block and rewrite every request your browser makes

    webRequestBlocking

  • Store data in your browser

    storage

  • Store an unlimited amount of data in your browser

    unlimitedStorage

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

    webNavigation

Updated 21 September 2026igieognglkjfefoldlaachcoimjecojk