Why a bash-js Sandbox Still Cares About HOME and cwd

The other day I was wiring up a bash-js sandbox inside a Next.js project and tripped over a question that sounds almost paradoxical:

If bash-js is a JavaScript emulator (no real shell process underneath), why do I need to set HOME at all?

Here’s the config that triggered the question:

sandbox = new Bash({
  customCommands: [cli.toCommand()],
  cwd: WORKSPACE_PATH,
  env: { HOME: '/home/user' },
  files,
});

bash-js is a pseudo-bash written in JavaScript. There’s no fork, no /bin/sh, no kernel call. So why does any of this (HOME, cwd, language flags) matter? Turns out: a lot.

bash-js is a simulation, but it has to pretend convincingly

The whole point of an emulated shell is that the code you run inside it should believe it’s running in a normal Unix environment. A surprising number of libraries and tools assume the standard environment is there, and they’ll happily misbehave if it isn’t.

That’s where HOME earns its place.

Why HOME still matters in a fake shell

Plenty of node modules check process.env.HOME (or env.HOME when invoked through a sandbox like this). They use it to:

  • find config files (~/.config, ~/.npmrc, ~/.gitconfig)
  • locate default cache directories (~/.cache)
  • resolve default project folders

On top of that, when bash-js impersonates a real shell it usually:

  • expands ~ using env.HOME
  • handles commands that depend on a home directory (cd ~, cd ~/.config, etc.)

Setting env: { HOME: '/home/user' } gives you a predictable, isolated environment. Every ~ resolves to the same path no matter where the host code actually runs: your laptop, a CI box, a Docker image.

cwd is the same idea, one level down

cwd is the working directory the sandbox uses to resolve relative paths. We set it for the same reason we set HOME:

  • ls, cd, cat, mkdir all behave as if they’re inside that specific project directory
  • tests and CLI flows become repeatable. They always start from the same place, regardless of where the JS app was launched from

The analogy

It’s worth keeping these two straight:

  • env: { HOME: '/home/user' }: tells the sandbox where the user lives (config files, cache, anything resolved through ~)
  • cwd: WORKSPACE_PATH: tells the sandbox where you are right now (the directory normal commands like git status, ls, cd operate against)

HOME is identity. cwd is location.

What javascript: true and python: true actually do

The other piece that puzzled me was this:

javascript: true,
python: true,

These are almost certainly config switches that enable interpreter access inside the pseudo-bash. In practice:

  • javascript: true: the sandbox accepts node, npm, npx, and can reason about package.json and node_modules in this environment
  • python: true: the sandbox accepts python, python3, pip, and can simulate basic Python tooling (venv, pip install)

There’s no magical interpreter here. These flags decide which commands the JS-side bash-js will accept and emulate. Flip them off and node or python simply become unknown commands, while the rest of the shell (file ops, ls, cd, mkdir) keeps working.

How this fits into a Next.js app

In a Next.js context, a sandbox like this usually lives server-side, inside a Route Handler, an API route, or a CLI tool wired into a build step or postinstall hook. The typical use cases:

  • running JS/TS/Python scripts in a controlled environment (node migration.js, python generate_data.py)
  • simulating a user “doing things in a terminal” without actually granting shell access
  • testing CLI-style tools in isolation

Turning off javascript or python is how you narrow what the sandbox is allowed to do. The rest of the shell still works; you just lose access to the interpreters.

The takeaway

Even when the shell isn’t real, the environment it exposes has to be. HOME, cwd, and the language flags aren’t ornamental. They’re the contract that makes the emulation usable. Skip them and you’re handing libraries an environment that lies about the basics, which is a great way to chase bugs that only reproduce on someone else’s machine.

Read more