summaryrefslogtreecommitdiffstats
path: root/SECURITY.md
diff options
context:
space:
mode:
Diffstat (limited to 'SECURITY.md')
-rw-r--r--SECURITY.md55
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
3Ship 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
11All 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
14The `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
21Git'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
26The `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
29Repos 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
32The `.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
40Environment 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
43Running `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
46The 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
52Simultaneous 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
55Ports are never reclaimed when apps are removed. After ~57,000 create/remove cycles, ports would be exhausted. Not a practical concern.