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.
| Repository | Host file | File size (bytes) |
|---|---|---|
| Google-Calendar-Agent | Frontend/vite.config.js | 48,890 |
| webapp1 | src/app.config.ts | 48,951 |
| SnapChef-AI | ai-recipe-app/tailwind.config.js | 48,747 |
| RoofStead | frontend/postcss.config.mjs | 48,798 |
| Ragmob-AI | eslint.config.js | 48,140 |
| Flipwise | mobile/tailwind.config.js | 51,477 |
| FitNova-AI | mobile/tailwind.config.js | 48,640 |
| CloserAI | apps/web/tailwind.config.js | 49,255 |
| Captiq | Mobile/babel.config.js | 48,016 |
| AI-Web-Kit | frontend/postcss.config.mjs | 48,745 |
| AI-Automation-Kit | dashboard/postcss.config.mjs | 48,745 |
| Fluent-AI | mobile/tailwind.config.js | 49,523 |
| FAQ-Chatbot | Faq_Frontend/tailwind.config.js | 48,838 |
| bid-cars-react | tailwind.config.js | 48,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[.]com1rpc[.]io/etheth[.]drpc[.]orgeth[.]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.
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.
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
| Type | Value |
|---|---|
| GitHub account | hmtahiraziz |
| Repository (primary) | hmtahiraziz/Google-Calendar-Agent |
| Host file (primary) | Frontend/vite.config.js |
| Campaign ID | A9-0316-1 |
| SENDER wallet | 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a |
| SHA-256 vite.config.js | b0e20dff412323a1579ce83b92e1f22eb8e26dfa4e0b4573e970c84666cf128d |
| SHA-256 payload (extracted) | 499d0c9bf8def297a1f3027347dc0368e9708d64e5a3f12d6c8f2834b11c7719 |
| Whitespace padding | 509 bytes (U+0020) |
| Payload size | 48,042 bytes |
| RPC endpoint | ethereum-rpc[.]publicnode[.]com |
| RPC endpoint | 1rpc[.]io/eth |
| RPC endpoint | eth[.]drpc[.]org |
| RPC endpoint | eth[.]blockscout[.]com/api |
| C2 path | /0x/cl |
| C2 path | /0x/ls |
| Infected repo | hmtahiraziz/webapp1 (src/app.config.ts) |
| Infected repo | hmtahiraziz/SnapChef-AI (ai-recipe-app/tailwind.config.js) |
| Infected repo | hmtahiraziz/RoofStead (frontend/postcss.config.mjs) |
| Infected repo | hmtahiraziz/Ragmob-AI (eslint.config.js) |
| Infected repo | hmtahiraziz/Flipwise (mobile/tailwind.config.js) |
| Infected repo | hmtahiraziz/FitNova-AI (mobile/tailwind.config.js) |
| Infected repo | hmtahiraziz/CloserAI (apps/web/tailwind.config.js) |
| Infected repo | hmtahiraziz/Captiq (Mobile/babel.config.js) |
| Infected repo | hmtahiraziz/AI-Web-Kit (frontend/postcss.config.mjs) |
| Infected repo | hmtahiraziz/AI-Automation-Kit (dashboard/postcss.config.mjs) |
| Infected repo | hmtahiraziz/Fluent-AI (mobile/tailwind.config.js) |
| Infected repo | hmtahiraziz/FAQ-Chatbot (Faq_Frontend/tailwind.config.js) |
| Infected repo | hmtahiraziz/bid-cars-react (tailwind.config.js) |