Is Revoke.cash - web3 scam protection safe?

High risk

Revoke.cash - web3 scam protection is high risk. Starting a wallet transaction or signature request, the content script forwards details to the SW, logged as "Message received". Details can include addresses, calldata, signature data, chain ID, hostname, a user ID. Evidence is code-based.

Revoke.cashv1.5.3Chrome Web Store
72Risk
Who publishes it

Kalis Software B.V. - no other listings under this identity, 4 shared hostnames

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

Publisher
Revoke.cash
Declared legal entity
Kalis Software B.V.
Registered address
Jacob van Lennepstraat 78H, Amsterdam 1053 HM, NL
Registered contact
Rosco Kalis

Shared hosts - 4 hostnames

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.

app.chainpatrol.io
Also called by 1 other listing: Osprey
app.ethos.network
Also called by 1 other listing: Ethos Everywhere
api.ethos.network
Also called by 3 other listings, including Ethos Everywhere
api.tenderly.co
Also called by 5 other listings, including Tenderly Dev Toolkit

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

Publishers can request a review.

Findings

Wallet messages are forwarded to Amplitude analytics

Starting a wallet transaction or signature request, the content script forwards details to the SW, logged as "Message received".

Details can include addresses, calldata, signature data, chain ID, hostname, a user ID.

Evidence is code-based.

Severity
High unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You start a protected wallet action on a website.

The covered requests include transactions, typed signatures, personal-sign messages, wallet_sendCalls, and Coinbase wallet signing requests.

The extension did this

The extension forwards the wallet request details to its service worker and labels the full object as analytics.

The service worker calls the tracking helper with a Message received event whose message property is the original wallet-message object.

Fields the source code places in the wallet-message object
  • Website hostname
    app.uniswap.org (illustrative)

    Shows which site you were using when the wallet request was processed.

  • Wallet request type
    eth_sendTransaction (illustrative)

    Shows what kind of wallet action you were asked to approve or sign.

  • Wallet address fields
    from=0x742d35Cc6634C0532925a3b844Bc454e4438f44e, to=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 (illustrative)

    Can identify which wallet address was involved in the request.

  • Transaction calldata
    0x095ea7b30000000000000000000000001111111254eeb25477b68fb85ed929f73a9605820000000000000000000000000000000000000000000000000000000000000000 (illustrative)

    Can reveal the contract method and parameters behind the action you were asked to approve.

  • Signature message content
    personal_sign message: Sign in with Ethereum to app.uniswap.org (illustrative)

    Can include the message or typed data you were asked to sign.

  • Persistent extension user ID
    f3a742c1-8e10-44d2-bc6a-9d5e0c2a7814 (illustrative)

    Lets analytics associate separate wallet-message events with the same extension installation over time.

Captured request
POSThttps://api2.amplitude.com/2/httpapi

Dynamic analysis captured Amplitude POST traffic during onboarding, but those captured bodies did not contain the wallet-message indicators.

The code that does this

The content script packages wallet requests with hostname and chain ID

Readable version

Readable content-script excerpt

js/content-scripts/bypass-check.js
const os = (e, t) => {  const n = as(t);  e.postMessage({    requestId: n,    data: t  })};let is = 0;const as = e => {  const t = ++is;  return e.type === ns.TRANSACTION ? ss()({    ...e.transaction,    _nonce: t  }) : e.type === ns.BATCH_TRANSACTION ? ss()({    _batch: e.transactions,    _nonce: t  }) : e.type === ns.TYPED_SIGNATURE ? ss()({    ...e.typedData,    _nonce: t  }) : e.type === ns.UNTYPED_SIGNATURE ? ss()({    _msg: e.message,    _nonce: t  }) : ss()({    ...e,    _nonce: t  })};let cs = 1;const us = !0;window.addEventListener("message", e => {  const {    target: t  } = e?.data ?? {}, {    name: n,    data: r  } = e?.data?.data ?? {}, {    hostname: s  } = location, o = cs;  if ("metamask-provider" !== n || !r) return;  const i = e => {    Array.isArray(e) ? e.forEach(e => {      if (e)        if (ls(e, "eth_sendTransaction")) {          const [t] = e.params ?? [], n = ns.TRANSACTION, r = es().runtime.connect({            name: ts          });          os(r, {            type: n,            bypassed: us,            hostname: s,            transaction: t,            chainId: o          })        } else if (ls(e, "eth_signTypedData")) {        const [t, n] = e.params ?? [], r = JSON.parse(n), i = ns.TYPED_SIGNATURE, a = es().runtime.connect({          name: ts        });        os(a, {          type: i,          bypassed: us,          hostname: s,          address: t,          typedData: r,          chainId: o        })      } else if (ls(e, "eth_sign") || ls(e, "personal_sign")) {        const [t, n] = e.params ?? [], r = 40 === String(t).replace(/0x/, "").length ? n : t, o = ns.UNTYPED_SIGNATURE, i = es().runtime.connect({          name: ts        });        os(i, {          type: o,          bypassed: us,          message: r,          hostname: s        })      } else if (ls(e, "wallet_sendCalls")) {        const [t] = e.params ?? [], {          from: n = "0x0000000000000000000000000000000000000000",          calls: r        } = t ?? {}, i = ns.TRANSACTION, a = es().runtime.connect({          name: ts        });        for (const e of r) {          const t = {            from: n,            ...e          };          os(a, {            type: i,            bypassed: us,            hostname: s,            transaction: t,            chainId: o          })        }      }    }) : i([e])  };  "metamask-contentscript" === t && i(r), "metamask-inpage" === t && r?.method?.includes("chainChanged") && (cs = Number(r?.params?.chainId ?? cs))}), window.addEventListener("message", e => {  const {    type: t,    data: n  } = e?.data ?? {}, {    hostname: r  } = location;  if ("extensionUIRequest" === t && n) {    if ("signEthereumTransaction" === n.request?.method) {      const e = {          from: n.request.params.fromAddress,          to: n.request.params.toAddress,          data: n.request.params.data,          value: Number.parseInt(n.request.params.weiValue ?? "0").toString(16)        },        t = Number(n.request.params.chainId ?? 1),        s = ns.TRANSACTION,        o = es().runtime.connect({          name: ts        });      os(o, {        type: s,        bypassed: us,        hostname: r,        transaction: e,        chainId: t      })    }    if ("signEthereumMessage" === n.request?.method) {      const e = n.request.params.typedDataJson,        t = n.request.params.address;      if (e) {        const n = JSON.parse(e),          s = Number(n?.domain?.chainId ?? 1),          o = ns.TYPED_SIGNATURE,          i = es().runtime.connect({            name: ts          });        os(i, {          type: o,          bypassed: us,          hostname: r,          address: t,          typedData: n,          chainId: s        })      } else {        const e = n.request.params.message,          t = ns.UNTYPED_SIGNATURE,          s = es().runtime.connect({            name: ts          });        os(s, {          type: t,          bypassed: us,          hostname: r,          message: e        })      }    }  }});const ls = (e, t) => String(e?.method)?.toLowerCase()?.includes(t?.toLowerCase())
The code that does this

The service worker tracks the whole message object

Readable version

Readable Amplitude setup and tracking helper

js/background.js
var zo, Do = Bo.init,  Oo = Bo.track;const ac = () => Boolean("20f100b65efa90a8e1511c26488a4a83") && !0;(async () => {  if (!ac()) return;  const e = await vs("sync", "user:id");  Yo = e ?? (() => {    let e = () => Math.floor(65536 * (1 + Math.random())).toString(16).substring(1);    return e() + e() + "-" + e() + "-" + e() + "-" + e() + "-" + e() + e() + e()  })(), e || await (async (e, t, n) => {    await r().storage.sync.set({      [t]: n    })  })(0, "user:id", Yo), Do("20f100b65efa90a8e1511c26488a4a83", Yo, {    trackingOptions: {      ipAddress: !0    },    flushQueueSize: 1  })})();const ic = (e, t) => {  ac() && (Oo(e, t), ec(e, t))}

Readable Message received wrapper

js/background.js
ry = e => {  Of.includes(e.requestId) || (Of.push(e.requestId), ic("Message received", {    message: e  }))}
Remote hosts reached by the analytics paths
    • api2.amplitude.com

    Amplitude HTTP API endpoint embedded in the SDK; dynamic analysis captured POST traffic to this host.

    • api.eu.amplitude.com

    Alternate Amplitude HTTP API endpoint present in the bundled SDK endpoint selector.

    • api.fairside.io

    Extension backend endpoint used by the same tracking helper's queued event flush path.

Updated 30 September 2026nmniboccheadcclilkfkonokbcoceced