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.6Chrome Web Store
45Risk
Who publishes it

xFanatical Inc. - no other listings under this identity

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.

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