Is it safe to run code from GitHub? What runs and how to check
Not automatically. GitHub hosts code but doesn't review it. See what runs when you install a repo, real attacks on developers, and how to check and isolate it.
By the pyrovue team · · 11 min read
Running code from GitHub is not automatically safe. GitHub hosts code; it doesn't review it. Anyone can publish a repository, nobody checks what's inside before you clone it, and GitHub removes malware only after someone reports or finds it.
Cloning a repository with an up-to-date Git is low risk. Running it is the risky part. npm install, pip install ., make or a command pasted from the README executes code written by the author and by the authors of every dependency, as your user, with access to everything your user can read. On a developer laptop that usually means SSH keys, cloud credentials and tokens.
Most repositories are fine. The risk gathers in recognisable places: install scripts, repos pushed at you by strangers (a "take-home test" from a recruiter is the classic), packages named one letter away from a popular one, and the occasional popular package whose maintainer account gets taken over. If you can't read what runs, run it somewhere that holds nothing of yours.
Is GitHub code safe? What GitHub checks and what it doesn't
GitHub scans for leaked secrets, warns about dependencies with known vulnerabilities and bans malware in its policies. None of that is a review of a repository's code before it goes public.
Popularity signals don't help much either. In 2024 Check Point documented the Stargazers Ghost Network, more than 3,000 GitHub accounts that starred, forked and watched malicious repositories so they would look established. Stars, forks and a polished README are cheap to fake. An account's history, a link from the project's official website or package registry, and the code itself are not.
What runs when you install a GitHub project
npm, pnpm and Bun install scripts
A plain npm install in a project runs that project's preinstall, install, postinstall and prepare scripts, plus the preinstall, install and postinstall scripts of every dependency. A dependency with a binding.gyp file and no install script gets node-gyp rebuild, which compiles and runs native build steps.
npm 12, released in July 2026, skips dependencies' install scripts unless the package is listed in an allowScripts field in package.json. As of November 2026, Node 22, 24 and 26 still bundle npm 10 or 11, which run them all. pnpm (since version 10) and Bun also skip dependency scripts unless they're allowed, though Bun keeps a default list of popular packages that may run them.
These allow lists live in the repository's own package.json, which the repository's author wrote. They protect you from a hijacked dependency inside a project you trust. They do nothing against a malicious repository, which can allow its own payload, and the project's own scripts run regardless.
npm install --ignore-scripts skips every lifecycle script, the project's included. Commands you invoke by name, such as npm start or npm test, still run their script.
Python: setup.py, build backends and .pth files
pip install ., pip install -e . and any install from a source distribution (sdist) run the project's build backend. With setuptools, that means executing setup.py, which can do anything Python can.
pip install --only-binary :all: -r requirements.txt refuses sdists, so no dependency build code runs. It narrows the risk without removing it:
- Packages that publish no wheel for your platform fail to install.
- The project itself still builds from source when you run
pip install .. - A wheel can ship a
.pthfile, and Python executes itsimportlines every time the interpreter starts. Any module also runs code when it's imported.
Makefiles, shell scripts and curl-pipe-bash
make, ./install.sh and just setup run arbitrary shell commands. make -n is not a safe preview: GNU Make still evaluates $(shell ...) calls while it reads the Makefile, and it runs recipe lines that call $(MAKE).
A README line such as curl -fsSL https://example.com/install.sh | bash runs whatever that server returns at that moment. Download it, read it, then run the copy you read:
curl -fsSL -o install.sh https://example.com/install.sh
less install.sh
bash install.sh
VS Code tasks, dev containers and workspace trust
A task in .vscode/tasks.json with "runOptions": { "runOn": "folderOpen" } starts when the folder opens in a trusted workspace. Current VS Code never runs automatic tasks in Restricted Mode and asks before running them (the task.allowAutomaticTasks setting). In January 2026, the Security Alliance described the North Korean "Contagious Interview" campaign using exactly this: a task labelled eslint-check ran a JavaScript file disguised as a font. Victims didn't run anything. They clicked "Trust".
Open unknown repositories in Restricted Mode. Trusting a folder turns on its tasks, debug configurations, workspace settings and extensions. "Reopen in Container" builds the repo's Dockerfile and runs its postCreateCommand on your Docker, and devcontainer.json can request extra mounts, capabilities or a privileged container.
Git hooks
Hooks in .git/hooks are not copied when you clone. Two caveats. Tools such as Husky install hooks from the repo when npm install runs its prepare script, and from then on they run on every commit. And Git bugs have let a crafted repository with submodules run a hook during a recursive clone, for example CVE-2024-32002 (fixed in May 2024) and CVE-2025-48384 (fixed in July 2025). Keep Git updated and don't clone unknown repos with --recursive.
What code on your laptop can reach
A process running as you needs no sudo to read any of this:
| Location | What's there |
|---|---|
~/.ssh/ | Private keys, often with no passphrase, and known_hosts, a list of servers you use |
~/.aws/credentials, ~/.config/gcloud/, ~/.azure/ | Cloud access keys and refresh tokens |
~/.kube/config | Kubernetes cluster credentials |
~/.npmrc, ~/.pypirc, ~/.docker/config.json | Registry tokens that can publish packages |
~/.config/gh/hosts.yml, Git credential files | GitHub tokens, when they aren't in the system keychain |
Environment variables, .env files, shell history | API keys you exported or pasted into a command |
| Browser profile folders | Cookies and saved sessions, a standard target for infostealers |
| The local network | Your router, NAS, databases on localhost, and on a cloud VM the metadata endpoint at 169.254.169.254 |
Real attacks on developers that started with a repo or a package
Fake recruiters and take-home tests
In July 2023, GitHub warned about a campaign in which actors posing as developers and recruiters on LinkedIn, Slack and Telegram invited targets to collaborate on a repository and talked them into cloning and running it. Its npm dependencies downloaded second-stage malware, and the attackers often published those packages only when they sent the invitation, to stay ahead of scanning. A separate North Korean campaign, Contagious Interview, has sent job seekers fake coding tests since at least 2023 and later moved to the VS Code task trick above. The risk runs both ways: if you review candidates' take-home submissions, opening them in a box keeps their install scripts off your laptop.
Typosquatted packages
In July 2017, a package called crossenv imitated the popular cross-env and sent the installer's environment variables to a remote server. npm removed about 40 packages from the same publisher. The pattern hasn't changed since: a name one keystroke away from a real one.
Hijacked popular packages
In 2018, a new maintainer of event-stream added a dependency, flatmap-stream 0.1.1, whose code targeted the Copay bitcoin wallet and harvested private keys from accounts holding more than 100 bitcoin. In September 2025, the self-replicating Shai-Hulud worm injected post-install scripts into popular npm packages, stole tokens and secrets, and used stolen npm tokens to publish infected versions of the victims' own packages. GitHub removed more than 500 packages. A second wave in November 2025 ran from a preinstall script, so it executed before the install finished, even when the install failed.
The xz backdoor
CVE-2024-3094 put a backdoor into xz 5.6.0 and 5.6.1, a compression library that sshd ends up loading on several Linux distributions. Andres Freund found it in March 2024 because SSH logins on his Debian unstable systems were using too much CPU. His report shows that the payload hid in test files committed to Git, while the build step that injected it existed only in the release tarballs. Reading the repo is not the same as reading what you install.
How to check a GitHub repo for malicious code before you run it
-
Ask how it reached you. A repo sent by a recruiter, a "cracked" tool from a video, or a package suggested by an issue comment deserves more suspicion than one you found through the project's own website.
-
Check the author and the history. Look at the account's age and other work, and at the commit log. A repo with a few days of history and one large initial commit, or an account with nothing else, is a warning sign.
-
Read everything that runs on install. Clone without submodules and look at the scripts, the Python build files, the Makefile and the editor config:
git clone --depth 1 https://github.com/OWNER/REPO.git && cd REPO jq .scripts package.json ls -a .vscode .devcontainer .husky 2>/dev/null grep -A3 '\[build-system\]' pyproject.toml -
Look for hidden code. Minified or encoded code in a source repo,
eval,child_processin a front-end project, and fonts or images that are really scripts:grep -rnE 'eval\(|new Function\(|child_process|fromCharCode' --exclude-dir=node_modules --exclude-dir=.git . find . -path ./node_modules -prune -o -type f \( -name '*.woff*' -o -name '*.ttf' -o -name '*.png' -o -name '*.jpg' \) -exec file {} + | grep -i text -
Install without scripts, then list who wanted to run one. npm can query its installed tree:
npm install --ignore-scripts npm query ':attr(scripts, [preinstall]), :attr(scripts, [install]), :attr(scripts, [postinstall])' \ | jq -r '.[] | "\(.name)@\(.version) \(.scripts | tostring)"' -
Check recent changes to the files that matter.
git log -p -5 -- package.json setup.py pyproject.tomlshows who changed install scripts or dependencies, and when. A new maintainer followed by a new dependency is the event-stream pattern. -
Treat scanners as a floor.
npm auditandpip-auditreport advisories that already exist, including reported malware. A clean result means nothing is known yet, not that nothing is there.
How to run untrusted code in isolation
If you can't read everything that runs, separate it from your credentials. The options differ in what they share with your laptop:
| Option | Separate kernel | Your files or keys inside | Reaches your local network |
|---|---|---|---|
| Container with no mounts | No | No | Yes, by default |
| Dev container, default setup | No | Your clone is mounted; SSH agent and Git credentials are forwarded | Yes |
| VM on your laptop | Yes | Only through shared folders you turn on | Usually, through NAT |
| Throwaway cloud VM | Yes | No | No |
| GitHub Codespaces | Yes, one VM per codespace | A GitHub token for that repo, plus dotfiles if you set them up | No |
| pyrovue box | No, a container | No | No, private ranges are blocked |
A container, set up carefully
Containers share the host's kernel, so a kernel bug or a careless flag (--privileged, a mounted Docker socket) is a way out. That makes a container a weaker boundary than a VM, and a far better place for unknown code than your laptop. Clone inside the container rather than mounting your checkout: code that can write to a mounted clone can plant .git/hooks/post-checkout or .vscode/tasks.json, which then run on your laptop.
docker run --rm -it --user node -w /home/node \
--cap-drop ALL --security-opt no-new-privileges \
--memory 4g --pids-limit 1024 \
-p 127.0.0.1:3000:3000 \
node:22 bash
Then, inside the container:
git clone --depth 1 https://github.com/OWNER/REPO.git && cd REPO
npm install && npm run dev
The app has to listen on 0.0.0.0 for the published port to reach it. The default bridge network can still reach your router and other devices, and closing that off takes extra firewall rules.
A dev container is a convenience, not a sandbox
VS Code's Dev Containers extension bind-mounts your folder, forwards your SSH agent and copies your .gitconfig into the container by default. That's right for your own projects and wrong for code you don't trust. "Clone Repository in Container Volume" avoids the bind mount; credential forwarding still applies.
A virtual machine
A VM has its own kernel, which makes it the stronger boundary. Multipass gives you a disposable Ubuntu VM in a minute. Use a named instance: Multipass's default "primary" instance mounts your home folder.
multipass launch --name scratch --cpus 2 --memory 4G --disk 20G
multipass shell scratch
When you're done, multipass delete --purge scratch removes it for good.
In a cloud account, a small VM with no IAM role or service account attached is just as good. Delete it afterwards. GitHub Codespaces also runs each codespace in its own VM; for an untrusted repo, open it in the browser with Settings Sync off.
The shortcut: run it in a box
pyrovue runs a public GitHub or gitlab.com repository in a box in its cloud: paste the link, and it works out how to install and start the project, then gives you a live preview, a terminal and the setup logs. Nothing of yours is inside. No files, keys or tokens are mounted, and there is no Docker socket. Each box has its own network with traffic between boxes off, and it can reach the internet but not private address ranges or the cloud metadata endpoint. CPU, memory and process limits apply, previews are served from a separate site (pyrovue.app), and the Hobby plan gives you 2 vCPU, 4 GB and 20 box-hours a month for free.
The limits, stated plainly: a box is a container on the standard runc runtime, so it shares the host's kernel and is a weaker boundary than a VM (gVisor is planned, not live). It can send anything it can read to the internet, so don't type real credentials into a box running code you don't trust. pyrovue does not scan for malware; a watchdog only stops crypto miners and boxes over the disk limit. How pyrovue handles untrusted code covers the details, and the security page lists what isn't done yet. For analysing something you already think is malware, use a dedicated VM and report the repo to GitHub.
What to do if you already ran something you don't trust
- Rotate credentials from another device: GitHub tokens and SSH keys (remove them from your GitHub settings, not only your laptop), npm and PyPI tokens, cloud keys and anything in
.envfiles on that disk. - Sign out of every session on GitHub, your email and cloud consoles. Stolen session cookies skip passwords and two-factor prompts.
- Look for persistence: your shell's startup files,
crontab -l,~/Library/LaunchAgentson macOS,~/.config/autostartand~/.config/systemd/useron Linux, and new entries in~/.ssh/authorized_keys. - Tell your employer's security team if a work laptop or work credentials were involved, and report the repository or package to GitHub or npm. For a targeted lure, reinstalling the operating system is the safer end point.
For projects you do trust, the usual steps to run a GitHub project apply. A stranger's pull request carries the same risks; how to test a pull request covers reviewing one without handing it your secrets.