What it promises
- Only main's newest commit that passed CI is ever deployed.
- A message can't make the server run a command.
- Access to GitHub doesn't give access to the server.
- One product can't read or change another's code, data, or keys, or the server's.
- A bad deploy doesn't take a product down.
Each one is covered by the measures below, with tests, including an end-to-end test on Ubuntu with systemd.
How
| Against | What it does |
|---|---|
| A forged deploy message | Each product has its own secret; every message must carry a valid HMAC-SHA256 signature of its exact body, compared in constant time |
| A message for anything but a passed CI run on main | Only a finished, successful run of the product's CI workflow, for a push to main, in its own repository, deploys |
| An old message sent again | Only main's newest commit is ever deployed, and a commit that failed isn't tried again by a message, only by hand |
| A message that names something to run | The listener runs nothing: it leaves a request holding a known product's name and a commit checked to be 40 hex characters |
| Someone in GitHub, the repository, or a workflow | GitHub holds no key to the server, only the webhook secret, which no workflow can read |
| A server pretending to be GitHub | GitHub's host keys are fetched from GitHub's API over HTTPS at install; nothing is trusted on first use |
| A product taking another port | Each product can listen only on its own port (on Ubuntu 26.04 LTS, tested end to end) |
| A product reaching another, or the server | Its own user; a read-only system and code; private /tmp; no new privileges; other users' processes hidden; the server's keys out of reach; write access to its own data only |
| A package's install script | Dependencies installed exactly as the lockfile says, with install scripts off, by a separate user |
| A repository's own npm or git settings | Its .npmrc is removed before npm runs, and npm's and git's settings are pinned, so nothing it carries chooses what runs |
| A build reaching another product | Each product has its own build user, which alone can read its deploy key and its copy of the repository |
| A build that ran code of its own | All the build user's processes stopped after every step, before root touches its files; each deploy starts it clean; hard links refused |
| A commit filling the shared disk | Releases over 1GB or 200,000 files refused, sizes counted before anything is written, and 1GB always kept free |
| A bad deploy | The product is checked once started, and the version before comes back if it doesn't answer |
| An unsafe server | check looks at SSH, automatic updates, and what's listening, and install and add refuse until it's fixed |
| A key committed to this public repository | CI fails on IP addresses, private keys, and tokens |
The threat model has every threat, its measure, and where in the code it lives.
What it doesn't do yet
- The server is only as safe as its setup. modest-deploy checks the main points, but can't see everything, such as a provider's firewall.
- Products trust their dependencies. Install scripts are off, but a compromised package still runs when the product does, inside its sandbox.
- On Ubuntu 24.04 LTS, a product that's been broken into could listen on another port, to the outside or on another product's port while that one restarts. modest-deploy lets each product listen only on its own port, but that needs a systemd built with its BPF framework: Ubuntu 26.04 LTS's is, 24.04 LTS's isn't. On 24.04 LTS, the firewall blocks the outside, and
checkand the nightly run report it. - A fetch isn't limited in size, so someone who can push to a product's main could fill the disk as it's fetched. Deploys stop once less than 1GB is free.
- The deploy service runs as root, to own releases and restart products. It runs git and npm as a separate user, and takes only a checked name and commit from a request.
How the project stays secure
- No runtime dependencies, and CI fails if one is added, or if anything but Node's own modules is imported.
- CI refuses invisible or direction-changing characters in code, and any address, key, or token committed.
- CodeQL checks every change, OpenSSF Scorecard grades the project weekly, and GitHub Actions are pinned to exact commits.
- An end-to-end test runs it on Ubuntu with systemd: install, launch, deploy, sandbox, rollback, and backups.
Reporting a vulnerability
Please don't describe it in public. Use the repository's Security tab, then Report a vulnerability, with what you found, how to make it happen, and what an attacker could do with it. You'll hear back within 5 working days.