Is xFanatical Safe Doc safe?

Medium risk

Safe Doc is medium risk. xFanatical Safe Doc embeds a static Bearer credential in its background script for license API requests. The same packaged credential authenticates POSTs to xFanatical's license-certificates endpoint, not unique per user or install.

xFanaticalv1.64.8Chrome Web Store
45Risk
Who publishes it

xFanatical Inc. - no other listings under this identity, 1 shared hostname

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
xFanatical
Declared legal entity
xFanatical Inc.
Registered address
10080 N Wolfe Road, Suite SW3242, Cupertino, CA 95014, US
Registered contact
xFanatical Inc.

Shared hosts - 1 hostname

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

images.google.co.uk
Also called by 1 other listing: Neat URL

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

Publishers can request a review.

Findings

Shared Bearer token is embedded in the extension package

xFanatical Safe Doc embeds a static Bearer credential in its background script for license API requests.

The same packaged credential authenticates POSTs to xFanatical's license-certificates endpoint, not unique per user or install.

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

A license-registration action causes the extension to contact its license service.

The code path runs after a non-empty license key is supplied and a signed-in Google profile is available.

The extension did this

The extension authenticates the request with a Bearer credential packaged inside the extension.

The credential is embedded in background.js rather than generated per installation.

Credential and request fields in the license API call
  • Shared API credential
    Bearer static extension token, 128 characters (redacted)

    This credential can authenticate the license request as the extension rather than as a specific person.

  • License API endpoint
    https://denali.xfanatical.com/api/v1/mtbaldy/license-certificates

    This is the server endpoint that receives requests authenticated with the shared credential.

  • Authentication version
    X-Auth-Version: v1

    This labels the API authentication scheme used by the request.

Captured request
POSThttps://denali.xfanatical.com/api/v1/mtbaldy/license-certificates

Stored dynamic-analysis evidence did not persist an outbound request body for this endpoint; the Authorization header shape is from the shipped source code.

Headers
Content-Type
application/json
Authorization
Bearer [redacted static extension token]
X-Auth-Version
v1
The code that does this

The packaged Authorization header in the license request

Readable version
{  key: "registerLicenseKey",  value: (a = M(b().mark((function e(n) {    var r, o, M, i, p, a, s, O;    return b().wrap((function(e) {      for (;;) switch (e.prev = e.next) {        case 0:          if (!(0, c.isEmpty)(n)) {            e.next = 4;            break          }          throw new Error("invalid license key");        case 4:          return e.prev = 4, e.next = 7, Te.getState("userEmail");        case 7:          if (r = e.sent) {            e.next = 10;            break          }          throw new G("user not signed in, cannot register license key");        case 10:          return o = r.split("@")[1], M = {            licenseKey: n,            userDomain: o          }, e.next = 15, we("".concat("https://denali.xfanatical.com", "/api/v1/mtbaldy/license-certificates"), {            method: "POST",            mode: "cors",            headers: {              Authorization: "Bearer ".concat("[redacted static extension token]"),              "Content-Type": "application/json",              "X-Auth-Version": "v1"            },            body: JSON.stringify(M),            retries: 10          });        case 15:          return i = e.sent, p = i.data.certificate, e.next = 20, ee.storageRemove("sync", L);        case 20:          if (!this._isLicenseCertificateValid(p, o)) {            e.next = 25;            break          }          return e.next = 23, this.saveLicenseCertificate(p);        case 23:          e.next = 26;          break;        case 25:          throw new Error("invalid license certificate ".concat(p, ", userDomain=").concat(o, ", ") + "localTime=".concat(Math.floor(new Date / 1e3)));        case 26:          e.next = 43;          break;        case 28:          if (e.prev = 28, e.t0 = e.catch(4), "error= ".concat((0, c.has)(e.t0, "response.data") ? JSON.stringify(e.t0.response.data) : e.t0), !e.t0.response && e.t0.request, !((a = (0, c.get)(e.t0, "response.status")) >= 400 && a < 500)) {            e.next = 42;            break          }          return e.next = 37, ee.storageGet("sync", L);        case 37:          return s = e.sent, O = (0, c.get)(s, [L, "attempts"], 0), (0, c.isNumber)(O) || (O = 0), e.next = 42, ee.storageSet("sync", t({}, L, {            nextAttempt: z()().add(1, "d").unix(),            attempts: O + 1          }));        case 42:          throw e.t0;        case 43:        case "end":          return e.stop()      }    }), e, this, [      [4, 28]    ])  }))), function(e) {    return a.apply(this, arguments)  })}
Check if you're affected

Checks a local copy of background.js for the embedded Bearer credential without printing the credential value.

Requires
  • Node.js 18+
safe-doc-bearer-detector.js · js
const fs = require('fs');const crypto = require('crypto');const file = process.argv[2];if (!file) {  console.error('Usage: node safe-doc-bearer-detector.js /path/to/background.js');  process.exit(2);}const source = fs.readFileSync(file, 'utf8');const endpoint = '/api/v1/mtbaldy/license-certificates';const tokenMatch = source.match(/Authorization:\s*\"Bearer \"\.concat\(\"([A-Za-z0-9]{80,})\"\)/) || source.match(/Authorization:\s*"Bearer "\.concat\("([A-Za-z0-9]{80,})"\)/);console.log(JSON.stringify({  endpointPresent: source.includes(endpoint),  staticBearerTokenPresent: Boolean(tokenMatch),  tokenLength: tokenMatch ? tokenMatch[1].length : 0,  tokenSha256: tokenMatch ? crypto.createHash('sha256').update(tokenMatch[1]).digest('hex') : null}, null, 2));
How to run it
  1. 1node safe-doc-bearer-detector.js /path/to/background.js
Host authenticated with the packaged credential
    • denali.xfanatical.com

    xFanatical licensing API that receives the Bearer-authenticated license registration request.

Updated 30 September 2026mmhppocgfdombmapajfeafhecobfcjpk