Sandboxing curl | sh Installers Before They Touch Your System

Many tools an SRE team uses ship with the same install line: curl -fsSL https://example.com/install.sh | sh. It is fast, but it hands a remote script your shell, your home directory and often sudo, and leaves no record of what it changed. Six months later, removing the tool is guesswork.
installctl is a new Linux tool that puts a review step in the middle. It runs the installer in a sandbox, shows every file it created, modified or deleted, lets you apply all, some or none of it, and writes an uninstall script. This post covers how it works, where it is blind, and how it handles npm.
The Threat Model of curl | sh
Piping a remote script into a shell has several distinct risks. They need different defenses, so it helps to list them before picking a tool.
| Risk | What happens | Does installctl help? |
|---|---|---|
| Silent system changes | Files in /usr/local/bin, /opt, lines appended to ~/.bashrc, new services |
Yes, this is its main job |
| No uninstaller | Removal is guesswork | Yes, it writes uninstall-<name>.sh |
| Root actions | sudo apt-get install, useradd, systemctl enable |
Yes, for the commands it intercepts |
| Truncated download | The connection drops and sh runs half a script |
Partly: you see the file changes of the half run before they land |
| Different payload when piped | The server detects the pipe and sends other content | Only for file changes: you review what the hidden payload wrote |
| Secret theft | The script reads ~/.ssh, ~/.aws or tokens and sends them out |
No |
| Malicious binary | The installed program does damage on first launch | No, review covers install time only |
The truncation risk is why careful installers wrap their logic in a function. The rustup installer defines main() near the top and calls main "$@" only on its last lines, so a partial download defines functions but runs nothing.
A 2016 write-up on detecting curl | bash server side shows that a server can start the script with a sleep, fill the TCP buffers, and watch for the pause that happens only when bash executes the stream. It then serves a harmless script to curl alone and a different one to curl | bash.
What installctl Is
installctl is written in Rust and licensed under Apache-2.0. The first release, v0.1.0, was published on 2026-11-08 with a .deb for Ubuntu 24.04 and an .rpm for EL9. It is very new, so treat it as a tool to evaluate, not one to trust blindly.
The README is direct about scope: "It is not a security sandbox. The installer can read your files and use the network." It is about transparency and control over well-behaved installers, not protection from malware.
How It Works
The sandbox code creates three Linux namespaces: user, mount and PID. There is no network namespace, so the installer has your normal network access.
- Filesystem. The installer's
/is a FUSE copy-on-write overlay. Reads come from your real system. Writes go to a staging directory under~/.local/state/installctl. - Fake root.
sudoinside the sandbox is emulated, so the installer can "write" to/usrwithout a password. Those writes are staged like any other. - Command shims. Calls to package managers (
apt,dnf,pacman,snap,flatpakand others), account tools (useradd,groupadd), service tools (systemctl,service), scheduling (crontab,at) and system tools (ldconfig,modprobe,sysctl) are replaced with shims. installctl pauses and asks you to skip, run for real, skip it and every later call of it, or abort. - Review. When the installer exits, you get a list of created, modified and deleted files with sizes, owners and line changes. Background processes the installer left running are listed and stopped.
- Apply. Accepted changes are written atomically. If some need root, installctl calls the real
sudoonce, for those paths only.
The generated uninstall script reverts only the installer's own edits to files like ~/.bashrc, so lines you added later stay. It warns before deleting a file that changed since the install and removes directories only when they are empty.
Installation
On Ubuntu or Debian, download the package from the releases page and install it:
sudo apt install ./installctl_*_amd64.deb
grep "^$USER:" /etc/subuid /etc/subgid # you need subordinate IDs
The package pulls in libfuse3-3 and uidmap. Ubuntu introduced AppArmor restrictions on unprivileged user namespaces in 23.10, so on 24.04 and later the .deb also installs an AppArmor profile for installctl. On EL9, install the .rpm with dnf.
A Worked Example
installctl --name mytool "curl -fsSL https://example.com/install.sh | sh"
A typical session from the README: the script asks for sudo apt-get install -y libfuse2, you press s to skip it (the installer is told it succeeded), and the review shows two created files, ~/.local/bin/mytool and /usr/local/bin/mytool-helper, plus one modified file, ~/.bashrc (+1 line). Press d to see the diff of ~/.bashrc before you decide. Then a applies everything, or s lets you enter numbers to exclude, such as 2 to drop the root-owned helper.
That diff step is where most of the value is. A line appended to a shell profile runs in every future shell, and it is easy to miss because the installer output rarely mentions it.
What the Sandbox Cannot See
The overlay only covers processes inside the sandbox's mount namespace. Anything else writes to the real disk. From the README, the mount setup in init.rs and the namespace design, these are the blind spots:
- Separate daemons. If the installer asks a process that runs outside the sandbox to do work, for example a service it reaches over a socket or the network, that process writes to your real filesystem. installctl does not see those files. The network is shared, so this path is open.
- Unknown commands. Only the listed commands are intercepted. A tool that changes the system through another program or direct syscalls is not caught, although its file writes inside the sandbox still are.
- Temporary directories.
/tmpand/var/tmpare fresh tmpfs mounts inside the sandbox. Anything the installer leaves there disappears and never shows in the review. /devand/sys. These are bind mounts of the host's own directories, not part of the overlay.- Reads. Every file you can read, the installer can read and send out.
- Later behaviour. The review covers what the script did at install time. The binary you approve can download more code the first time you run it.
- Hard links are applied as separate copies.
A related trap: an installer that starts its own background process without systemctl gets that process stopped at the end of the sandbox run. After you apply the changes, start the service yourself.
npm and Other Package Managers
npm is not in the intercept list, so installctl "npm install -g some-cli" runs npm inside the overlay like any other program. Per the npm folders docs, a global install writes to {prefix}/lib/node_modules and links executables into {prefix}/bin, and the cache lives in ~/.npm. Expect the review to list the whole package tree and the new cache entries, which can be a long list.
The useful part is lifecycle scripts. preinstall and postinstall hooks run inside the sandbox, so a hook that edits ~/.bashrc or drops a file in ~/.config shows up as a change you can reject. What the sandbox does not stop is the pattern from the Keyv npm compromise: a hook that reads tokens and sends them over the network. For that, you need an install with no secrets in reach and no network beyond the registry.
System package managers like apt-get and dnf are intercepted: skip them, or run them for real, outside the review.
When to Use What
| Situation | Better choice |
|---|---|
| You trust the vendor and want a record and an uninstaller | installctl |
| You want to see where an unfamiliar script puts files | installctl, with the diff view |
| You do not trust the script | A throwaway VM or container with no secrets mounted |
| You need to block network access or file reads | A real sandbox, such as bubblewrap with --unshare-net and only the paths you bind in |
| Production servers | A distro package, a pinned release binary with a checksum, or a container image |
A quick checklist before any curl | sh:
- Download the script to a file and read it, including what it downloads next.
- Check that the logic runs from a function called on the last line.
- Run it as a user without cloud credentials or SSH keys loaded.
- Use installctl or a disposable environment to see what it changes.
- Keep the uninstall script next to your runbook for that host.
Agent-Installed Tooling
An AI agent that runs curl | sh to get a CLI inherits all the risks above, with nobody watching the review menu. The safest setup gives the agent no secrets to leak in the first place. In Akmatori, tools run through the MCP Gateway, which holds the credentials and the authorization, so the agent never sees a password, token or key.
Conclusion
installctl turns curl | sh into a reviewed change with an uninstaller. It shows what an honest installer does to your filesystem, but it does not contain a hostile one: reads, the network and outside daemons stay open. Use it for transparency, and a disposable environment for trust.
If your team wants AI-assisted incident workflows on your own infrastructure, Akmatori helps SRE teams investigate alerts, coordinate response, and automate safe infrastructure actions. Powered by Gcore for global infrastructure reliability.
