A while back I published a simple tool I use to scan unfamiliar repositories before I trust them: repo_malware_scan.

I originally built it because, as a blockchain developer, I was seeing enough suspicious repositories that opening random code from the Internet started to feel like an occupational hazard. That hasn’t exactly improved.

If anything, I am seeing more developers talking about being targeted with malicious repositories, fake coding tests, questionable GitHub projects, and interview assignments designed to get code onto a developer’s machine.

I’ve run into it myself more than once.

So I’ve expanded the scanner.

The original version concentrated on roughly 16 categories of suspicious behavior. It now performs 27 heuristic checks, including a number of attack techniques that have increasingly been discussed in security circles.

This isn’t because I expect every repository to contain malware. It’s because attackers increasingly understand how developers work.

And that means they don’t necessarily need you to knowingly run a malicious executable.

Sometimes they just need you to trust the repository.

Malware Doesn’t Have to Look Like Malware

The obvious things are still checked.

The scanner looks for suspicious uses of things like dynamic code execution, shell commands, encoded payloads, unusually long or high-entropy strings, dangerous package installation scripts, external IP addresses, crypto miners, credential harvesting, and attempts to read files such as .env, SSH keys, AWS credentials, or private keys.

Those are still important.

But there are many other ways to turn a repository into an attack vector.

Some of the additions are intended to catch those less obvious paths.

CI/CD Can Be an Attack Surface

One major addition is checking CI/CD configuration.

Developers tend to think of GitHub Actions, GitLab pipelines, Jenkins, CircleCI, or Buildkite as infrastructure rather than executable code.

Attackers don’t make that distinction.

A malicious repository can potentially contain workflow instructions that download scripts, expose secrets, inject user-controlled values into shell commands, or execute third-party actions that can later change underneath you.

The scanner now checks for suspicious patterns in those workflow files, including things like downloading and immediately executing remote scripts, exposing secrets, unsafe expression use, and third-party GitHub Actions that aren’t pinned to a specific commit.

The important point isn’t that CI/CD is dangerous.

It’s that configuration is code when configuration can execute commands.

Developer Tools Can Execute Code Too

One of the attack paths I have become increasingly interested in is the developer environment itself.

VS Code, Vim, Neovim, JetBrains tools, Dev Containers, and similar environments all support project-level configuration.

That’s useful.

It’s also potentially exploitable.

A repository might contain configuration that launches commands when a folder opens, changes the terminal or interpreter being used, runs container lifecycle commands, injects development scripts, or abuses local Vim configuration.

That means the dangerous action might happen before you ever type:

npm install

or:

npm start

Simply opening the project in your normal development environment can sometimes be enough to expose additional attack surfaces.

The scanner now checks more aggressively for that kind of editor and IDE auto-execution behavior.

More Obfuscation Detection

Malware authors obviously don’t write:

steal_credentials_and_send_them_to_attacker()

Obfuscation is expected.

The scanner already looked for Base64 blobs, high-entropy strings, minified files, eval(), dynamic functions, and similar patterns.

I’ve expanded that to look for other ways JavaScript can hide what it is doing.

That includes constructor tricks, prototype tricks, dynamically locating require(), building dangerous strings out of hexadecimal or Unicode escapes, reversing strings at runtime, unusual access to global objects, and common string-shuffling patterns used by obfuscators.

None of those things individually prove malware.

That’s an important distinction.

This is a heuristic scanner, not a malware oracle.

The goal is to tell me:

“You should probably look at this file before trusting it.”

Dynamic require() Tricks

Another addition detects attempts to reach Node’s module loader indirectly.

Normally you expect to see something straightforward such as:

require('child_process')

But JavaScript gives attackers a surprising number of ways to reach the same functionality without writing it that way.

For example, code can reach require() through parent modules, global objects, internal module references, or bundler escape mechanisms.

The scanner now flags several of those techniques.

Again, legitimate software occasionally does unusual things.

But unusual ways of finding require() inside an unfamiliar coding assignment deserve inspection.

Containers Aren’t Automatically Safe

Developers sometimes treat Docker as if it automatically means isolation.

That’s not always true.

A malicious Docker configuration can download and execute remote scripts, mount sensitive host directories, request privileged access, or mount the Docker socket itself.

Giving a container access to /var/run/docker.sock, for example, can give it far more control over the host than many developers realize.

The scanner now examines Dockerfiles and Compose files for several of these patterns.

Containers can be part of a security strategy.

They shouldn’t be treated as a substitute for one.

Reverse Shell Detection

I’ve also added signatures for common reverse-shell and bind-shell techniques.

These are the kinds of commands attackers use to make a compromised machine connect back to them and provide a remote command shell.

There are variations using Bash, Python, Perl, Ruby, netcat, named pipes, and other standard tools that may already exist on a developer machine.

Seeing one of those patterns buried inside a normal-looking Node or Python repository is obviously something worth investigating.

WebSocket and DNS Exfiltration

Not every attacker sends stolen information using a normal HTTP POST request.

The scanner now checks for suspicious combinations involving WebSockets and DNS.

For example, code that collects environment variables or machine information and then establishes an outbound WebSocket deserves attention.

Likewise, DNS requests can sometimes be abused to sneak small amounts of information out of a compromised environment.

These checks intentionally look for combinations of behavior rather than simply declaring every use of WebSockets or DNS suspicious.

Hardcoded Credentials

I’ve added broader credential detection as well.

The scanner looks for several recognizable credential formats, including AWS keys, GitHub tokens, private keys, Stripe production keys, and some generic API-key assignments.

This isn’t strictly malware detection.

But a repository containing real credentials creates two problems.

It may indicate an accidental secret leak.

Or those credentials may be part of something the repository is attempting to abuse.

Either way, I want to know about them before running the project.

Prototype Pollution Writes

JavaScript’s prototype system also creates some interesting attack possibilities.

Earlier versions of the scanner looked for suspicious prototype and constructor access.

The new checks distinguish some actual write operations to things like Object.prototype or __proto__.

Those modifications can sometimes change the behavior of objects throughout an application and have been involved in real-world security vulnerabilities.

There are legitimate reasons to manipulate prototypes.

There are considerably fewer legitimate reasons for an unfamiliar coding-test repository to do it unexpectedly.

Package Manager Attacks Keep Evolving

The package ecosystem remains one of the biggest areas of concern.

The scanner already checked package.json, npm lifecycle scripts, custom npm registries, and suspicious lockfile URLs.

I’ve expanded that coverage to include pnpm lockfiles, questionable package bin definitions, and more supply-chain-related behavior.

A malicious package does not necessarily need to exploit a vulnerability.

If it convinces your package manager to execute something during installation, it already has an opportunity to do damage.

Typosquatting

One of the simpler attacks is still one of the most effective.

Misspell a popular package name.

If enough developers type the wrong thing—or if a malicious repository quietly includes the typo—someone eventually installs it.

The scanner now checks for a curated set of typo variants around common packages such as React, Express, Axios, Lodash, Webpack, TypeScript, ESLint, Chalk, and others.

This isn’t sophisticated malware detection.

It is protection against a very human vulnerability:

not noticing one character is wrong.

The Scanner Is Now 27 Checks — But It Still Isn’t a Guarantee

The biggest mistake would be treating a scanner like this as proof that a repository is safe.

It isn’t.

The script performs 27 heuristic checks looking for suspicious patterns.

A clean scan means:

“I didn’t find the things I’m currently looking for.”

It does not mean:

“This repository is safe.”

There will always be ways to disguise malicious behavior that pattern matching won’t catch.

Static scanners also can’t necessarily determine intent. Some completely legitimate development tools will trigger warnings because they do things malware also does.

That’s why the output is meant for review rather than automatic judgment.

My Rule for Unknown Repositories Hasn’t Changed

Every developer should be cautiously paranoid about code from the wild.

Especially developers working around cryptocurrency, wallets, infrastructure, cloud credentials, production systems, or anything else where a compromised development machine has significant value.

I still recommend using a disposable or clean VM for unfamiliar repositories.

Don’t blindly run install scripts.

Don’t assume that because something is on GitHub it is safe.

Look at package scripts.

Look at editor configuration.

Look at CI configuration.

Look at Docker configuration.

And if someone in a hiring process sends you a repository and insists you run it immediately, that’s an especially good time to become suspicious.

The attack surface has grown considerably beyond malicious source code.

Today, the repository itself can be the delivery mechanism.

repo_malware_scan is still deliberately simple: a portable POSIX shell script designed to give me a fast first look at a repository before I decide how much I trust it.

It started with around 16 areas of concern.

It’s now at 27.

Unfortunately, I don’t expect that number to stop growing.