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
45Risk
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

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
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.

02EvidenceTEMPORAL PATTERN
When this fires
On every browser startup

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

03EvidenceNETWORK CAPTURE
Captured request
GEThttps://dsnetx.web.app/apps/firelinks/msg.json
200 OK observed during dynamic analysis; no request body was recorded for this GET.
04EvidenceFIELD TABLE
Remote message fields that control page insertion
FieldValueWhy it matters
Enable switch
show: trueThis decides whether a remote message is eligible to appear on pages you visit.
Version and browser match
ExifClassic-CH-v301@chromeThis 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 millisecondsThis controls how long the inserted notification stays visible on the page.
Message ID
static-test-301This is stored locally so the same non-repeating message is not shown again immediately.
05EvidenceCODE COMPARE
The code that does this

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

What it actually does
Remote message hosts and fetch pathscripts/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 renderingscripts/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 schedulerscripts/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)
  })
}
06EvidenceTHIRD PARTY LIST
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. 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.

Updated 30 September 2026nafpfdcmppffipmhcpkbplhkoiekndck