diff options
Diffstat (limited to 'SECURITY.md')
| -rw-r--r-- | SECURITY.md | 55 |
1 files changed, 55 insertions, 0 deletions
diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ad04094 --- /dev/null +++ b/SECURITY.md | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | # Security Model & Known Gaps | ||
| 2 | |||
| 3 | Ship is a single-user VPS deployment tool. The threat model assumes: | ||
| 4 | - You control the VPS and have root SSH access | ||
| 5 | - You trust everyone who has SSH push access (their keys are copied to the `git` user) | ||
| 6 | - The VPS runs only your own apps | ||
| 7 | |||
| 8 | ## Mitigations in place | ||
| 9 | |||
| 10 | ### App name validation | ||
| 11 | All app/project names are validated against `^[a-z][a-z0-9-]{0,62}$` before being used in shell commands, file paths, systemd units, or DNS labels. This prevents command injection via crafted names. | ||
| 12 | |||
| 13 | ### Scoped sudoers | ||
| 14 | The `git` user's sudo rules are restricted to specific paths: | ||
| 15 | - `systemctl restart/enable` only for services matching `[a-z]*` | ||
| 16 | - `cp` only from `.ship/` subdirectories to `/etc/systemd/system/` and `/etc/caddy/sites-enabled/` | ||
| 17 | - `mkdir` only under `/var/lib/` and `/var/www/` | ||
| 18 | - `chown` only for `git:git` under `/var/lib/` and `/var/www/` | ||
| 19 | |||
| 20 | ### Scoped safe.directory | ||
| 21 | Git's `safe.directory` is set only for the `www-data` user (not system-wide), preserving CVE-2022-24765 protection for other users. | ||
| 22 | |||
| 23 | ## Accepted risks (by design) | ||
| 24 | |||
| 25 | ### SSH key access = root access | ||
| 26 | The `git` user is in the `docker` group, which is root-equivalent (can mount the host filesystem). Additionally, `.ship/service` files pushed via git are installed as systemd units. Anyone with SSH push access effectively has root. This is intentional for a single-user tool. | ||
| 27 | |||
| 28 | ### Git repo visibility | ||
| 29 | Repos are private by default (not cloneable over HTTPS). Use `ship init --public` to make a repo publicly cloneable. This is controlled by the `git-daemon-export-ok` marker file in each bare repo. Only public repos are accessible via `go get` or `git clone` over HTTPS. | ||
| 30 | |||
| 31 | ### User-controlled systemd units | ||
| 32 | The `.ship/service` file in each repo is copied to `/etc/systemd/system/` on push. A malicious service file could run arbitrary commands as root. This is equivalent to the Docker access risk above. | ||
| 33 | |||
| 34 | ## Known gaps (not yet addressed) | ||
| 35 | |||
| 36 | ### SSH host key verification disabled | ||
| 37 | `ssh.InsecureIgnoreHostKey()` is used for all SSH connections, and `StrictHostKeyChecking=no` for scp/rsync. This makes connections vulnerable to MITM attacks on untrusted networks. A future improvement would use `known_hosts` verification. | ||
| 38 | |||
| 39 | ### Env files may have loose permissions | ||
| 40 | Environment files at `/etc/ship/env/{name}.env` are created via `sudo tee` and may be world-readable depending on umask. These files can contain secrets. The `deploy` flow does `chmod 600` but `ship init` does not. A future improvement would ensure consistent restrictive permissions. | ||
| 41 | |||
| 42 | ### host init is not idempotent | ||
| 43 | Running `ship host init` twice will overwrite `/etc/caddy/Caddyfile` and the base domain Caddy config, destroying any manual edits. No guard checks whether setup has already been completed. | ||
| 44 | |||
| 45 | ### No rollback on failed docker build | ||
| 46 | The post-receive hook installs `.ship/service` and `.ship/Caddyfile` before running `docker build`. If the build fails, the configs are updated but the old image is still running, creating a mismatch. The old container keeps running (due to `set -e`), but a manual restart would use the new (mismatched) unit file. | ||
| 47 | |||
| 48 | ### ship deploy vs git push ownership mismatch | ||
| 49 | `ship deploy` runs commands as root (the SSH user), while `git push` triggers the hook as the `git` user. Files checked out by `ship deploy` become root-owned, which can prevent subsequent `git push` deploys from overwriting them. | ||
| 50 | |||
| 51 | ### No concurrent push protection | ||
| 52 | Simultaneous pushes can race on the checkout directory and docker build. For single-user usage this is unlikely but not impossible. | ||
| 53 | |||
| 54 | ### Port allocation is monotonic | ||
| 55 | Ports are never reclaimed when apps are removed. After ~57,000 create/remove cycles, ports would be exhausted. Not a practical concern. | ||
