Short version for the impatient: if a "client" you have never met asks you to install a missing audio codec before a video call, close the laptop. If you want to know why, and why the Rust project is now writing blog posts about it, read on.
I get maybe two cold outreach emails a week. Most are recruiters with a Laravel role that pays less than my current rate. Some are agencies looking to subcontract. A few, lately, are "founders" with a small paid test task attached. I used to treat those last ones as mildly annoying. I now treat them as the most dangerous email in my inbox, because on 17 September the Rust team published a warning that this exact pattern is being used to take over the machines and accounts of crate owners, and the crates that got compromised in August were ones I have compiled on my own laptop.
What the Rust team is actually seeing
The post is short and worth reading in full: Be alert: targeted attacks on prominent Rustaceans. Adam Harvey, writing for the crates.io team and the security response group, describes a campaign against rust-lang members and owners of popular crates. The shape of it is a video call set up for something positive. A job, a contract, an advisory gig, a paid test. During or around the call, the target is pushed to either install something (the "missing audio codec" trick) or run a command that has been placed on their clipboard.
The part that should bother freelancers specifically: the attackers build new but plausible company profiles, including LinkedIn presences, so a quick background check passes. That is the exact check I do before a discovery call. Look up the company, look up the person, see if the profile has a few years of history. It turns out that is a low bar, and someone is clearing it on purpose.
The post links to an earlier wave in June that hit a number of well-known Rust developers, and to the August compromise of the arrayref crate. It also says, carefully, that the team does not know whether these are one campaign or several. I appreciate that. Most vendor security blogs would have already named an APT group and a catchy operation name.
arrayref: 86 minutes online
The August incident is documented in Supply chain attack on arrayref. The timeline is tight. At 07:15 UTC on 20 August someone reported that a crate called proc-macro1 was malicious; its build script downloaded a payload. The team then found that arrayref had been republished as version 0.3.10 with a new dependency on that crate. Other crates by the same author (internment, append-only-vec) got the same treatment. The malicious arrayref was live for 86 minutes before deletion. internment for 90. append-only-vec for 107.
The team's read is that the author of arrayref was not acting maliciously; their machine or credentials were likely compromised. Which is where the two stories connect. You do not need to break crates.io to publish malware on crates.io. You need one maintainer to take one video call and click one thing.
If you build Rust on your machine, the post gives a one-liner to check whether any of the bad versions ever landed in your registry cache. I ran it and got nothing back, which is the outcome you want:
find ~/.cargo/registry/cache -type f \( \
-name 'append-only-vec-0.1.9.crate' -o \
-name 'arrayref-0.3.10.crate' -o \
-name 'internment-0.8.7.crate' -o \
-name 'proc-macro1-*.crate' -o \
-name 'proc-macro-en-*.crate' -o \
-name 'aovine-*.crate' -o \
-name 'arone-*.crate' -o \
-name 'aronenao-*.crate' -o \
-name 'tinymember-*.crate' \
\) -print
arrayref is not an obscure crate. cargo tree -i arrayref on one of my projects shows it pulled in two levels down by a hashing library I did not pick and would not have thought about. That is how a software supply chain attack works in practice. Nobody targets you. They target something you depend on without knowing you depend on it.
The fake interview, from the inside
The clearest first-hand account I have read of this attack style is Matt Mastracci's Anatomy of a Failed (Nation-State?) Attack from June. He is a crate owner. He got an email from a person claiming to be at a Singapore VC, complete with a boring LinkedIn profile and two portfolio companies that had just enough web presence to look early-stage rather than fake. They agreed a call time. The call itself was unremarkable: a man with a German accent, taking it while travelling.
Then came the "test": a TypeScript repo themed as a ferry ticketing app, with a task list that ended by telling him to run the typecheck, tests and build before submitting.
That last instruction is the whole attack. The repo used patch-package with a pile of patch files as noise, and one of them injected a self-executing stub into the top of typescript.js and _tsc.js. Run tsc or the build and it fires. The loader pulled a second stage out of a PNG, ran a small WASM stub, and spawned a detached Node process carrying a 1.68 MB remote access trojan. It hid the patches from git status using git update-index --skip-worktree, rewrote itself to delete its own injected lines after first run, and deleted its temp directory on execution. Three layers of cleanup for a "take-home test".
He caught it partly by luck (a Rust person being sent a TypeScript interview didn't add up) and partly because he zipped the repo and had Claude scan it before running anything. The AI flagged that a patch-package setup with no root postinstall hook was odd, then found the base64 blob. I don't say that to sell you on AI code review. I say it because the human instinct was "this is annoying" and the tool's instinct was "why is TypeScript itself patched", and the second one was right.
Why freelancers are the soft target
Crate owners got the blog post. But look at who the lure is built for. A cold email offering paid work. A short call. A small test task. Paid, if you finish it this week. That is not a description of a Rust maintainer's inbox. That is a description of mine, and probably yours.
I wrote a while ago about the red flags I look for before taking a project, and every one of them was about money and scope. None of them was about the client trying to run code on my machine. I had a blind spot the size of a container ship, and I suspect most people who do contract work through a portfolio site have the same one. We are trained to clone the repo and run it, because that is how you evaluate a codebase and that is how you get paid.
The specific reason the fake test task works on us: an interview repo is the one codebase you run without reading. You assume it is boring by construction. Nobody audits node_modules/typescript for a take-home.
What I have changed, concretely
I want to be honest that I am not a security person and I got this wrong for years. Here is what I do now, and it took an afternoon to set up.
Any code from someone I have not worked with before gets run in a throwaway VM, not on my laptop. On a Mac that is a Linux VM with no access to my home directory or keychain. On a VPS that is a fresh Hetzner box I delete afterwards. It costs me under a euro and about four minutes. I already had most of the tooling from sandboxing AI-generated code, so the only new thing was the rule about when to use it.
Before running anything, I check for the exact trick in Matt's writeup. Skip-worktree flags show up as an S in the first column here:
git ls-files -v | grep '^S'
An empty result is what you want. A hit in an interview repo is a reason to stop.
I also grep for lifecycle hooks and patch directories across the whole tree, because the payload in that attack lived in a workspace package, not the root:
grep -rn --include=package.json -E '"(pre|post)install"' .
find . -type d -name patches -not -path '*/node_modules/*'
I set up the call myself, on my own Google Meet link, every time. The Rust post makes the same suggestion: be the one who picks the platform. If the other side insists on their tool and then tells you a codec is missing, that is the attack, not a technical hiccup. Real clients with real Zoom accounts do not need you to install anything.
And I turned on MFA on crates.io, npm, GitHub and Hetzner properly, with hardware keys rather than SMS, and reviewed the active sessions list on each. The Rust post asks people to do exactly this. It is unglamorous. It is also the difference between "my laptop got owned" and "my laptop got owned and someone published under my name for 86 minutes."
Where I am still unsure
I don't know how to tell a good client from a good fake at the email stage. The profiles are built to pass the checks I know how to do. The best heuristic I have is that legitimate clients rarely front-load a test task before any conversation about budget, and they never require that I run their code before we have agreed anything in writing. That is a weak signal and I know it.
I also don't know whether the fake-interview pattern will stay in the Rust and crypto worlds or move to Laravel and WordPress freelancers. My guess is that it already has and nobody is writing blog posts about it because there is no security response team for freelancers. If you have been sent a suspicious take-home this year, I would like to hear about it.
The one thing to do this week
Pick the last take-home or "quick test" repo someone sent you and run git ls-files -v | grep '^S' on it. Then go into the patches directory, if there is one, and read what was patched into typescript or node or esbuild. If you find anything you can't explain, don't run it, zip it, and send it to security@rust-lang.org or the equivalent for your ecosystem. It will take ten minutes, and the worst case is you learn your last client was real.
Originally published at abrarqasim.com. I write there about React, PHP, Rust, Go and the AI tooling around them.
Top comments (0)