· development · 8 min read
How to safely approach a JavaScript interview test project
Interview take-home projects can contain malicious code that runs the moment you type npm install. Whether it's a React frontend or an Express API, here's a complete safety checklist — from inspecting package.json to running everything inside a container.

The hidden risk nobody talks about
You’ve applied for a developer position, passed the initial screening, and the recruiter sends you a GitHub repo: “Here’s our take-home project. Clone it, run it, and extend it with feature X.” Sounds routine, right?
What most developers don’t realize is that the moment you run npm install on an unknown project, you’re executing arbitrary code on your machine. Lifecycle scripts like preinstall, postinstall, and prepare fire automatically during installation — and they can do anything the current user can do. Read your SSH keys. Exfiltrate environment variables. Install a backdoor.
This isn’t a theoretical concern. Supply chain attacks through npm packages have been well-documented, and interview projects are a particularly effective delivery mechanism because they come with an implicit trust signal: a company you want to work for sent it to you.
I recently came across a discussion where someone shared the “safe” steps their friend recommended before running an interview project. The advice was well-intentioned — generate a lock file, run npm audit, fix vulnerabilities — but it missed the most critical threat entirely. Let me walk you through a complete approach.
Step 1: Inspect before you install
Before you run a single command, read the project manually. You’re looking for red flags that no automated tool will catch at this stage.
Read package.json carefully
Open package.json and check two things:
The scripts section. Look for preinstall, postinstall, prepare, and install scripts. These run automatically when you execute npm install — no confirmation, no warning. A legitimate project might use postinstall to run a build step or compile native modules. A malicious one might use it to download and execute a remote payload.
{
"scripts": {
"postinstall": "node scripts/setup.js",
"start": "node server.js"
}
}If you see a postinstall script, read the file it points to before installing anything. If it fetches remote content or runs obfuscated code, stop immediately.
The dependencies. Go through every package name in dependencies and devDependencies. Look each one up on npmjs.com and check:
- Download counts — legitimate packages have consistent, substantial download numbers
- Last publish date — a package published days ago for the first time is suspicious in a “production” project
- Maintainer count — single-maintainer packages with low downloads deserve extra scrutiny
- Name similarity — typosquatting is real. A package called
webpack-css-loader-branchorlodash-utils-v2is almost certainly malicious. Legitimate packages don’t have-branchor random suffixes
Look at the actual code
Skim through the source files. A genuine interview project will have coherent, readable code that matches the job description. If you find minified blobs, base64-encoded strings being decoded and evaluated, or files that seem completely unrelated to the project’s stated purpose — those are red flags.
Step 2: Use an isolated environment
This is the most important step, and it’s the one most developers skip. Never run an unknown project directly on your main machine.
Docker is the simplest option
A Docker container gives you filesystem isolation, network control, and disposability. If something malicious runs, it stays inside the container.
docker run -it --rm -v $(pwd):/app -w /app node:20-slim bashThis drops you into a JavaScript runtime environment with your project mounted. You can install dependencies, run the project, and when you’re done, the container disappears along with anything it might have installed.
For extra paranoia, restrict network access:
docker run -it --rm --network none -v $(pwd):/app -w /app node:20-slim bashWith --network none, even if a malicious script tries to exfiltrate data, it has nowhere to send it.
Virtual machines for maximum isolation
If Docker feels too lightweight for your threat model, use a full VM through VirtualBox, VMware, or UTM (for Apple Silicon Macs). A VM provides complete hardware-level isolation — the project can’t touch your host filesystem, network, or credentials.
Cloud sandboxes for zero setup
If you don’t want to configure anything locally, cloud-based development environments are perfect:
- GitHub Codespaces — spins up a full VS Code environment in the cloud. The project runs on GitHub’s infrastructure, not yours.
- StackBlitz — runs JavaScript in the browser via WebContainers. Nothing touches your local machine.
- CodeSandbox — another browser-based option with solid JavaScript and TypeScript support.
Any of these give you complete isolation with minimal effort. For an interview project you’ll spend a few hours on, the free tiers are more than sufficient.
Step 3: Generate a lock file and audit
Once you’re inside your isolated environment, follow the audit steps — but in the right order and with the right expectations.
npm i --package-lock-onlyThis generates package-lock.json without actually installing anything. No lifecycle scripts run, no code executes. You now have a complete dependency tree to inspect.
npm auditThis checks the resolved dependency tree against the GitHub Advisory Database for known vulnerabilities. It’s useful, but understand its limitations: npm audit only catches known vulnerabilities in published packages. A custom malicious package created specifically for this interview project won’t appear in any advisory database.
npm audit fixThis attempts to resolve vulnerabilities by updating to patched versions within the allowed semver ranges. Note the emphasis — without the --force flag. Running npm audit fix --force allows major version bumps that can introduce breaking changes or, worse, pull in entirely different code.
If you want to see what --force would do before committing to it:
npm audit fix --force --dry-runThis shows you exactly what changes --force would make without actually applying them.
Step 4: Scan with specialized tools
npm audit is a baseline, not a comprehensive security check. Purpose-built tools catch things it misses.
Socket.dev
Socket analyzes packages for supply chain risks — not just known CVEs, but behavioral signals like network access, filesystem operations, obfuscated code, and install scripts. It’s specifically designed to catch the kind of threat interview projects might carry.
npx socket npm lsbetter-npm-audit
A wrapper around npm audit with better filtering, reporting, and the ability to ignore specific advisories that don’t apply to your use case:
npx better-npm-audit auditManual lock file review
For the truly security-conscious, you can also review the package-lock.json directly. Look for:
- Packages resolving to unusual registries (anything other than
registry.npmjs.org) - Packages with
resolvedURLs pointing to GitHub tarballs or custom servers - Dependencies that don’t appear in the top-level
package.jsonbut seem out of place in the tree
Step 5: Install and run safely
You’ve inspected the code, you’re inside an isolated environment, and the audit looks clean. Time to install — but stay vigilant.
Skip lifecycle scripts on install
If you’re still uneasy about postinstall scripts, you can skip them entirely:
npm install --ignore-scriptsThis installs all dependencies without running any lifecycle scripts. Some packages legitimately need them (native modules that compile C++ bindings, for example), so the project might not work fully — but it lets you install safely and then selectively run only the scripts you’ve reviewed.
Read scripts before running them
Before you run npm start or npm run dev, check what those commands actually execute. Open package.json, find the script, and trace through the code it invokes. A legitimate start script runs your application server. It shouldn’t be downloading remote files, spawning background processes, or making network requests to domains unrelated to the project.
Monitor network activity
An interview project — a todo app, a REST API, a React frontend — has no business making outbound network calls on startup (except maybe to a local database or a dev server’s HMR socket). If you see unexpected network activity, something is wrong.
On macOS, you can use Little Snitch or the built-in nettop command. On Linux, ss or nethogs will show you active connections. Inside Docker, the --network none flag from Step 2 eliminates this concern entirely.
Never run as root
This should go without saying, but don’t run the project with sudo or as the root user. If a malicious script does execute, running as a non-privileged user limits the damage it can do.
A practical workflow
Here’s my complete workflow when I receive an interview project:
- Clone the repo — don’t install anything yet
- Read
package.json— check scripts and dependencies manually - Look up unfamiliar packages — verify them on npmjs.com
- Skim the source code — look for obfuscated code, base64 strings, or suspicious files
- Spin up a Docker container (or open GitHub Codespaces)
- Generate a lock file —
npm i --package-lock-only - Run audits —
npm auditandnpx socket npm ls - Install with
--ignore-scriptsif anything felt off, or install normally if everything checked out - Read the
start/devscripts before running them - Run the project and work on the assignment
The whole process adds maybe 10-15 minutes to your setup time. That’s a small price for not handing your SSH keys, browser cookies, and AWS credentials to a stranger.
Don’t feel paranoid — feel professional
If this feels like overkill, consider the asymmetry: a company sends you a project, you invest hours of unpaid work, and in return you might get a job offer. The power dynamic already favors the employer. Adding basic security hygiene to your workflow isn’t paranoia — it’s professionalism.
Legitimate companies will understand if you ask questions about unfamiliar dependencies. And if a “company” gets defensive about you inspecting their code before running it? That tells you everything you need to know about the opportunity.
Conclusion
The JavaScript ecosystem’s greatest strength — a massive package registry with millions of modules — is also its biggest attack surface. Interview test projects exploit the trust we place in professional contexts, and the standard advice of “just run npm audit” doesn’t address the most dangerous vector: code that executes automatically during installation.
Protect yourself by inspecting before installing, isolating the execution environment, and treating every unknown project with the same scrutiny you’d apply to a dependency in production. Your machine, your credentials, and your data are worth those extra 15 minutes of caution.