Aephix accepted into the Databricks Startup Program
← Research
Threat report Sep 12, 2026 · A9-0316-1

hmtahiraziz/Google-Calendar-Agent: an Ethereum C2 loader appended to build configuration files across fourteen GitHub repositories

GitHub account hmtahiraziz hosts 43 public repositories. Fourteen carry an obfuscated Ethereum blockchain C2 loader appended to build configuration files behind 509 bytes of whitespace padding, with the host file rotated between vite.config.js, tailwind.config.js, postcss.config.mjs, eslint.config.js, babel.config.js, and app.config.ts. The loader shares campaign prefix A9 and the SENDER wallet with the agentgui and animotion-mcp operations.

GitHub account hmtahiraziz, created Jan 15, 2024, publishes 43 public repositories spanning React/Node web projects and AI agent tooling. Google-Calendar-Agent, created Oct 8, 2025, is a full-stack Google Calendar agent with a Python/FastAPI/LangChain backend and a React frontend. The backend is functional: FastAPI routes, a LangChain agent wired to Gemini Pro, Google Calendar API integration, conflict detection, multi-meeting scheduling. The frontend Vite configuration file carries the payload. Frontend/vite.config.js is 48,890 bytes and 16 lines. Lines 1 through 14 define a standard Vite config importing @vitejs/plugin-react and @tailwindcss/vite. Line 15 closes the config object with });, then appends 509 bytes of horizontal whitespace and a 48,042-byte obfuscated JavaScript payload. Thirteen other repositories in the same account carry the same loader in different build configuration files.

The artifacts

Fourteen repositories across the hmtahiraziz account contain build or configuration files inflated to 48,000 through 51,500 bytes. The host file changes from repo to repo.

RepositoryHost fileFile size (bytes)
Google-Calendar-AgentFrontend/vite.config.js48,890
webapp1src/app.config.ts48,951
SnapChef-AIai-recipe-app/tailwind.config.js48,747
RoofSteadfrontend/postcss.config.mjs48,798
Ragmob-AIeslint.config.js48,140
Flipwisemobile/tailwind.config.js51,477
FitNova-AImobile/tailwind.config.js48,640
CloserAIapps/web/tailwind.config.js49,255
CaptiqMobile/babel.config.js48,016
AI-Web-Kitfrontend/postcss.config.mjs48,745
AI-Automation-Kitdashboard/postcss.config.mjs48,745
Fluent-AImobile/tailwind.config.js49,523
FAQ-ChatbotFaq_Frontend/tailwind.config.js48,838
bid-cars-reacttailwind.config.js48,250

Six repositories use tailwind.config.js. Three use postcss.config.mjs. The remaining five each use a different file: vite.config.js, eslint.config.js, babel.config.js, app.config.ts, and a second tailwind.config.js at a different path. All are files the build toolchain loads automatically during npm run dev or npm run build.

The account also includes repos covering garments e-commerce, movie search, bid-cars, chat-a-book, and a C#/Oracle integration from late 2024. One checked repository (garments-FE-React) contained no anomalous config files.

What it does

The Google-Calendar-Agent payload is representative of the set. Line 15 of Frontend/vite.config.js begins with }) closing the defineConfig call, followed by a comma and closing parenthesis (,);), then 509 bytes of space characters (U+0020), then the obfuscated loader. The whitespace pushes the payload past the right edge of any code editor or diff viewer at default settings. GitHub file views truncate long lines, so the payload is invisible in the web UI as well.

The obfuscation uses a standard _0x-prefixed variable scheme with a 412-entry string rotation array. The deobfuscated payload sets a campaign identifier and requires node:child_process:

global.i = "A9-0316-1";
global.r = require;

The loader constructs the SENDER wallet address 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a from five string-table fragments reassembled at runtime. It queries public Ethereum RPC endpoints to locate the latest transaction from this wallet:

  • ethereum-rpc[.]publicnode[.]com
  • 1rpc[.]io/eth
  • eth[.]drpc[.]org
  • eth[.]blockscout[.]com/api

The loader sends eth_getBlockByNumber, eth_getTransactionCount, and eth_getTransaction JSON-RPC calls to binary-search for the wallet’s most recent transaction. The to field of that transaction encodes two IPv4 addresses: bytes 0 through 3 yield the first address, bytes 4 through 7 yield the second. The C2 server address changes whenever the adversary sends a new transaction from the same wallet.

Two HTTP requests follow, both to port 443 at the resolved addresses: /0x/cl and /0x/ls. The response body is XOR-decrypted using a key assembled from four string-table fragments (y-p_>d$0B&, q4FZkxX{!h, ,Sr3=@, @^1aQk). If the body is empty, the loader falls back to a Payload-B64 response header, base64-decodes it, and applies the same XOR. The User-Agent is set to Chrome on Windows 10 (Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36).

The /0x/cl payload executes via eval() in the current Node process. The /0x/ls payload spawns a detached child process via child_process.spawn('node', ['-e', payload]) with unref(), so it survives the parent’s exit. Decompression supports gzip, deflate, and Brotli.

Global markers _t_s, _t_u, _H, _H2, and _V track execution state to prevent re-entry.

The backend Python code (FastAPI, LangChain, Google Calendar API, Gemini Pro LLM) contains no malicious content. The attack targets developers who clone the repository and run the frontend build. npm run dev loads vite.config.js, which evaluates all top-level JavaScript in the file, including the appended payload.

vite.config.js Lines 1-14: legitimate React + Tailwind config Line 15: 509 sp + 48 KB global.i = "A9-0316-1" Ethereum RPC eth_getTransaction Wallet 0xa322...ef1a to field → 2 IPv4 addrs /0x/cl /0x/ls eval() spawn detached, unref() Aephix
The Vite config closes on line 15, then 509 spaces of whitespace separate it from a 48 KB Ethereum C2 loader.

The campaign

The SENDER wallet 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a, /0x/ C2 path convention, XOR decryption pattern, dual eval()/spawn() execution, Payload-B64 fallback header, and detached-child persistence match the infrastructure in the agentgui analysis (campaign 9-1186-2, same wallet). The animotion-mcp analysis (campaign A9-4051-1) uses a wallet composed of the same 20 bytes with the two middle five-byte sequences transposed. The stitch-mcp, expo-mcp, and local-mcp analyses document additional specimens.

Each prior specimen concealed the loader in one file: database.js in agentgui, analytics/api/mcp-events.js in animotion-mcp, postcss.config.mjs in stitch-mcp, .eslintrc.js in expo-mcp, font.js in local-mcp. This account rotates the host file across six different build-configuration formats (tailwind.config.js, postcss.config.mjs, vite.config.js, eslint.config.js, babel.config.js, app.config.ts), each one a file the corresponding framework loads at build time. The payload size varies slightly between repositories (48,016 to 51,477 bytes), consistent with per-repo regeneration from the same obfuscator rather than a single binary copy.

The 509-byte whitespace padding falls within the range observed across the campaign: 507 bytes in animotion-mcp, 997 in stitch-mcp, 507 in local-mcp.

hmtahiraziz (43 repos) tailwind.config.js (6) postcss.config.mjs (3) vite.config.js (1) eslint.config.js (1) babel.config.js (1) app.config.ts (1) Obfuscated loader 48-51 KB, campaign A9-0316-1 Wallet 0xa322...ef1a Shared with agentgui operation Aephix
Six host-file types across one account, traced to one SENDER wallet. The loader rotates its concealment file across fourteen repositories.

Why the operation matters here

Blocking Google-Calendar-Agent leaves thirteen copies of the same loader live under different project names and different host files. File-name-based detection rules written for postcss.config.mjs (stitch-mcp) or .eslintrc.js (expo-mcp) miss tailwind.config.js, babel.config.js, and app.config.ts. The rotation is deliberate: no single config-file heuristic catches the full set.

The SENDER wallet is the single stable identifier across all fourteen repos and the prior specimens under the same operation. Blocking the wallet or its resolved C2 addresses neutralizes the entire account’s payload delivery regardless of which config file hosts it.

What a defender can do

Search development environments for clones of any hmtahiraziz repository. Inspect build configuration files (vite.config.js, tailwind.config.js, postcss.config.mjs, eslint.config.js, babel.config.js, app.config.ts) for content beyond the closing statement of the configuration export. The payload sits on the same line as the closing brace, separated by hundreds of whitespace characters, so it does not appear on a subsequent line.

If npm run dev or npm run build was executed in any of the cloned repositories, check for detached node -e processes. The spawned child persists independently of the parent, survives terminal close, and continues executing after the build exits.

ignore-scripts=true blocks npm lifecycle hooks but does not prevent build-time configuration loading. The trigger is the build command itself, not an npm install hook.

Where Aephix fits

Fourteen repositories across six config-file types share one wallet and one campaign prefix. Detecting the underlying operation instead of individual files covers all fourteen repositories and every future repo the same account publishes. Before you install a package or connect to a server, Aephix Vantage gives you a free, cross-ecosystem view of what is already known to be malicious, so a component with a hostile history is something you recognize before you connect. Every week, Weekly Sleuth reports the malicious packages, models, skills, MCP servers, extensions, and containers confirmed that week, grouped by the operations behind them with a confidence level and supporting evidence, so subscribers act against the whole operation rather than the single artifact.

Indicators of compromise

TypeValue
GitHub accounthmtahiraziz
Repository (primary)hmtahiraziz/Google-Calendar-Agent
Host file (primary)Frontend/vite.config.js
Campaign IDA9-0316-1
SENDER wallet0xa322e5f3d311d3080e6f0121063e9adc2490ef1a
SHA-256 vite.config.jsb0e20dff412323a1579ce83b92e1f22eb8e26dfa4e0b4573e970c84666cf128d
SHA-256 payload (extracted)499d0c9bf8def297a1f3027347dc0368e9708d64e5a3f12d6c8f2834b11c7719
Whitespace padding509 bytes (U+0020)
Payload size48,042 bytes
RPC endpointethereum-rpc[.]publicnode[.]com
RPC endpoint1rpc[.]io/eth
RPC endpointeth[.]drpc[.]org
RPC endpointeth[.]blockscout[.]com/api
C2 path/0x/cl
C2 path/0x/ls
Infected repohmtahiraziz/webapp1 (src/app.config.ts)
Infected repohmtahiraziz/SnapChef-AI (ai-recipe-app/tailwind.config.js)
Infected repohmtahiraziz/RoofStead (frontend/postcss.config.mjs)
Infected repohmtahiraziz/Ragmob-AI (eslint.config.js)
Infected repohmtahiraziz/Flipwise (mobile/tailwind.config.js)
Infected repohmtahiraziz/FitNova-AI (mobile/tailwind.config.js)
Infected repohmtahiraziz/CloserAI (apps/web/tailwind.config.js)
Infected repohmtahiraziz/Captiq (Mobile/babel.config.js)
Infected repohmtahiraziz/AI-Web-Kit (frontend/postcss.config.mjs)
Infected repohmtahiraziz/AI-Automation-Kit (dashboard/postcss.config.mjs)
Infected repohmtahiraziz/Fluent-AI (mobile/tailwind.config.js)
Infected repohmtahiraziz/FAQ-Chatbot (Faq_Frontend/tailwind.config.js)
Infected repohmtahiraziz/bid-cars-react (tailwind.config.js)