Is EXIF Viewer Classic safe?

Medium risk

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.

Rodriguev3.0.1Chrome Web Store
47Risk
Who publishes it

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.

Publisher
Rodrigue
Registered address
620 Park View Dr, Santa Clara, CA 95054, United States
Registered contact
Rodrigue

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.

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

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 did this

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.

When this fires

On every browser startup

The remote message check is scheduled once shortly after the content script finishes page setup.

Captured request
GEThttps://dsnetx.web.app/apps/firelinks/msg.json

200 OK observed during dynamic analysis; no request body was recorded for this GET.

Remote message fields that control page insertion
  • 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 code that does this

The shipped code fetches remote msg.json and inserts msg as HTML

Readable version

Remote message hosts and fetch path

scripts/index.js
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  })}

Remote HTML passed into notification rendering

scripts/index.js
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)  }}

Five-second scheduler

scripts/index.js
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)  })}
Remote hosts used for the message configuration
    • 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. 2 other extensions we have analyzed send data here.

  • dsnetx.web.app

    EXIF Viewer Classic sends data to dsnetx.web.app. 2 other extensions we have analyzed send data here.

Updated 30 September 2026nafpfdcmppffipmhcpkbplhkoiekndck