Is View Manuals safe?

Medium risk

View Manuals sends every keystroke typed in its search bar to viewmanuals.com in real time.

Each character entered into the new tab search box triggers an immediate request to the operator's autosuggest endpoint, transmitting the partial query without debouncing or opt-out. Additionally, the new tab page fetches server-supplied HTML from viewmanuals.com on first load and inserts it into the DOM without sanitization, allowing operator-controlled content including third-party tracking pixels.

View Manualsv1.0.0.1Chrome Web Store
49Risk

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

Publishers can request a review.

Findings

Search keystrokes sent to View Manuals

MediumunwantedCWE-359Dynamic sandbox

When you type in either View Manuals search box, the extension sends the current text to viewmanuals.com as the searchTerm parameter.

What actually happens

You did this

You type into the View Manuals search box on the new tab page.

The extension did this

The extension sends the current search text to View Manuals for autosuggest results.

Fields carried by the autosuggest request

  • Search box text
    printer manual

    The words you type into the new tab search field are sent as the autosuggest term.

  • Request URL
    https://viewmanuals.com/admin/public/autosuggest?searchTerm=printer%20manual

    Putting the term in the URL can expose the text to browser, proxy, and server logs along the request path.

  • Input selectors
    #search-box and #search-top

    Both visible search fields use handlers that can send the text you type.

How typing maps to outbound autosuggest requests

  1. on input

    You did

    You type a non-empty value into the main search box.

    The extension did

    The extension sends the current value to the autosuggest endpoint.

  2. on input

    You did

    You type a non-empty value into the top search box.

    The extension did

    The extension sends that current value to the same autosuggest endpoint.

Captured request

GEThttps://viewmanuals.com/admin/public/autosuggest?searchTerm=printer%20manual

The code that does this

Both search input handlers send the current field value

Readable version

Main search box input handler

script.js
let searchTerm;$("#search-box").on("input", async function (e) {  searchTerm = e.target.value;  let removeSpace = searchTerm.trim();  if (removeSpace.length > 0) {    const rawResponse = await axios      .get(        "https://viewmanuals.com/admin/public/autosuggest?searchTerm=" +        searchTerm,      );    const content = await rawResponse.data;    const data = content.gossip.results;    let word;    // Display search suggestions    const suggestionList = data      .map((suggestion) => {        var listitem = suggestion.key;        word = "<span>" + listitem.substr(0, removeSpace.length) + "</span>";        word += "<b>" + listitem.substr(removeSpace.length) + "</b>";        if (word == searchTerm) {          return "";        } else {          return `<li class="autocomplete"><a data-id='${listitem}'><i class="fa-solid fa-magnifying-glass"></i>${word}</a></li>`;        }      })      .join("");    $('#suggestions').html(suggestionList);  }  const element = searchBox.value.length;  if (removeSpace.length && element > 0) {    $(".search_results").addClass("open");    $(".modal_search_results").addClass("open");    $('body').addClass('body_height');    $("#suggestions").removeClass('clo')    $("#suggestions").removeClass('closed')    $(".for_open").addClass("open");    $(".search_icon").addClass("close");    $(".icon_show").removeClass("close");    $("#search-icon").html(      `<a class="search__suggestions" target="_blank" data-id='${searchTerm}'><i class="fa-solid fa-magnifying-glass"></i></a>`    );    $("input").parent().addClass("focus");    $(".search_bar").removeClass("empty");    if ($(".search_results").is(":empty")) {      $(".search_bar").addClass("empty");    }  } else {    // input value null to show fist like input    $("#suggestions").addClass('closed')    $(".search_results").removeClass("open");    $(".modal_search_results").removeClass("open");    $(".for_open").removeClass("open");    $(".search_icon").removeClass("close");    $(".icon_show").addClass("close");    $("input").parent().removeClass("focus");    $('body').removeClass('body_height');  }  $(".autocomplete").click(function (e) {    e.preventDefault();    const url = $(this).find('a').attr('data-id')    chrome.storage.local.get(['date'], results => {      if (results && results.date) {        executeSearch(url, 'chrome');      } else {        handlesuggestions(e, url);      }    });  });  $(".search__suggestions").click(function (e) {    e.preventDefault();    const val = $(this).attr('data-id');    chrome.storage.local.get(['date'], results => {      if (results && results.date) {        executeSearch(val, 'chrome');      } else {        handlesuggestions(e, val);      }    });  });});

Top search box input handler

script.js
$("#search-top").on("input", async function (e) {  let searchTop;  searchTop = e.target.value;  let removeSpace = searchTop.trim();  if (removeSpace.length > 0) {    const rawResponse = await axios      .get(        "https://viewmanuals.com/admin/public/autosuggest?searchTerm=" +        searchTop,      );    const content = await rawResponse.data;    const data = content.gossip.results;    let word;    // Display search suggestions    const suggestionList = data      .map((suggestion) => {        var listitem = suggestion.key;        word = "<span>" + listitem.substr(0, removeSpace.length) + "</span>";        word += "<b>" + listitem.substr(removeSpace.length) + "</b>";        if (word == searchTop) {          return "";        } else {          // add target to navigate new-tab link          return `<li class="autocomplete-top"><a data-id='${listitem}'><i class="fa-solid fa-magnifying-glass"></i>${word}</a></li>`;        }      })      .join("");    $('#sub-suggestions').html(suggestionList);  }  const element = searchTopBox.value.length;  if (removeSpace.length && element > 0) {    $(".sub-search_results").addClass("open");    $("#sub-suggestions").removeClass('closed')    $(".sub-for_open").addClass("open");    $(".sub-search_icon").addClass("close");    $("#sub-search-icon").removeClass("close");    $("#sub-search-icon").html(      `<a class="sub-search__suggestions" target="_blank" data-id='${searchTop}'><i class="fa-solid fa-magnifying-glass"></i></a>`    );    $("#top-search-bar").addClass("top");    // $(".search_bar").removeClass("empty");    // if ($(".search_results").is(":empty")) {    // $(".search_bar").addClass("empty");    // }  } else {    // input value null to show fist like input    $("#sub-suggestions").addClass('closed')    $(".sub-search_results").removeClass("open");    $(".sub-for_open").removeClass("open");    $(".sub-search_icon").removeClass("close");    $("#sub-search-icon").addClass("close");    $("#top-search-bar").removeClass("top");  }  $(".autocomplete-top").click(function (e) {    e.preventDefault();    const url = $(this).find('a').attr('data-id');    chrome.storage.local.get(['date'], results => {      if (results && results.date) {        executeSearch(url, 'chrome');      } else {        handlesuggestions(e, url);      }    });  });  $(".sub-search__suggestions").click(function (e) {    e.preventDefault();    const val = $(this).attr('data-id');    chrome.storage.local.get(['date'], results => {      if (results && results.date) {        executeSearch(val, 'chrome');      } else {        handlesuggestions(e, val);      }    });  });});

The code that does this

The extension routes the browser new tab to the page with these handlers

Readable version

New-tab redirect bootstrap

refresh.js
const version = parseInt(    /Chrome\/([0-9.]+)/.exec(window.navigator.userAgent)[1]);if (version > 82) {    window.location.href = `/index.html`;} else {    const meta = document.createElement("meta");    meta.httpEquiv = "refresh";    meta.content = "0;url=extension://index.html";    document.querySelector("head").appendChild(meta);}

Search inputs in the rendered new tab page

index.html
<input id="search-top" type="text" placeholder="Search"><input id="search-box" type="text" placeholder="Search or type a URL"><script src="./script.js?version=2"></script>

External host contacted by autosuggest

    • viewmanuals.com

    Receives autosuggest GET requests whose searchTerm query parameter is the current text from the new-tab search field.

Remote HTML inserted on the new tab page

MediumunwantedCWE-829Dynamic sandbox

When View Manuals' new tab loads for a profile without its local display flag set, the extension requests HTML from viewmanuals.com and inserts the response as markup.

What actually happens

You did this

You open the View Manuals new tab page for the first time in a browser profile.

The extension did this

The extension requests remote pixel content and writes the response into the page as HTML.

Captured request

GEThttps://viewmanuals.com/admin/public/pixels

Concrete values used by the pixel-loading path

  • Pixel endpoint
    https://viewmanuals.com/admin/public/pixels

    This is the remote service the new tab page contacts before adding markup to the page.

  • Response field
    res.data.data

    This field is treated as page markup, so whatever the server returns there can change what appears in the new tab page.

  • Local display flag
    displayPixel=true

    This browser-side flag controls whether the pixel request runs again after a successful load.

  • DOM target
    #setPixelImage

    This is the page location where the server response is inserted as HTML.

The code that does this

The new tab page requests remote HTML and inserts it into the DOM

Readable version
if (!localStorage.getItem("displayPixel")) {  axios    .get(`${apiUrl}/admin/public/pixels`)    .then((res) => {      $("#setPixelImage").html(res.data.data);      localStorage.setItem("displayPixel", true);    })    .catch((err) => {      console.log(err);    });}

The code that does this

The install handler also contacts the pixel endpoint

Readable version
chrome.runtime.onInstalled.addListener(function (details) {  if (details.reason === "install") {    fetch(`${apiUrl}/admin/public/install`);    fetch(`${apiUrl}/admin/public/pixels`);    chrome.windows.getAll((wins) =>      wins.forEach(        (win) => win.type === "popup" && chrome.windows.remove(win.id)      )    );    const currentDate = new Date();    const getFormateDate = formatDate(currentDate);    chrome.storage.local.set({ date: getFormateDate });    chrome.tabs.create({}, function (tab) { });  } else if (details.reason === "chrome_update") {    chrome.tabs.create({}, function (tab) { });  } else if (details.reason === "update") {    chrome.tabs.create({}, function (tab) { });  }});

External host contacted by this behavior

    • viewmanuals.com

    Receives the pixel request and returns the HTML data that the new tab page inserts into #setPixelImage.

What it can do

Permissions this extension asks for, as declared in version 1.0.0.1. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Run searches with your default search engine

    search

  • Store data in your browser

    storage

Where it sends data

Destinations our analysis observed View Manuals 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.

  • viewmanuals.com

    View Manuals sends data to viewmanuals.com. No other extension we have analyzed sends data here.

Updated 30 September 2026gnjggbjjfgjdcnognnfjgcgjbpjdephi