Is xFanatical Safe Doc safe?
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.
Who publishes itxFanatical Inc. - no other listings under this identity
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.
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
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 authenticates the request with a Bearer credential packaged inside the extension.
The credential is embedded in background.js rather than generated per installation.
- Shared API credentialBearer static extension token, 128 characters (redacted)
This credential can authenticate the license request as the extension rather than as a specific person.
- License API endpointhttps://denali.xfanatical.com/api/v1/mtbaldy/license-certificates
This is the server endpoint that receives requests authenticated with the shared credential.
- Authentication versionX-Auth-Version: v1
This labels the API authentication scheme used by the request.
Stored dynamic-analysis evidence did not persist an outbound request body for this endpoint; the Authorization header shape is from the shipped source code.
- Content-Type
- application/json
- Authorization
- Bearer [redacted static extension token]
- X-Auth-Version
- v1
The packaged Authorization header in the license request
{ 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) })}Checks a local copy of background.js for the embedded Bearer credential without printing the credential value.
- Node.js 18+
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));- 1node safe-doc-bearer-detector.js /path/to/background.js
- denali.xfanatical.com
xFanatical licensing API that receives the Bearer-authenticated license registration request.