Security
shipcheck runs a branch's code on your machine. Whoever writes that code, a teammate, a dependency or an agent, decides what runs there. So shipcheck treats every job as untrusted code.
What a job can reach
| act mode | Host mode | |
|---|---|---|
| Where it runs | A Docker container, never on the machine itself. -self-hosted runs only inside the VM shipcheck starts for a colima job | Natively, as your user, in sandbox-exec on macOS or bubblewrap on Linux |
| Files it writes | Its own checkout and the repository's cache volumes | Its own clone, a new empty HOME and TMPDIR, and the repository's tool caches |
| Files it reads | Its checkout and volumes | The system and your toolchains, such as ~/.cargo/bin or ~/.nvm/versions. Nothing else in your home. Credential files such as ~/.ssh, ~/.npmrc, ~/.netrc and shipcheck's own token and keys stay hidden |
| Docker | No socket | No socket. It cannot connect to a Unix socket outside its own folders, except the DNS resolver's |
| Environment | No host environment and no GitHub token | PATH, USER, LANG and a few more, the names in host.env, and CI=true. host.env refuses names that carry credentials |
| Git | A .git of its own with only the tested commit's history. git-lfs and submodules never run on the host | Its own clone |
| Secrets | Only the values the owner put in shipcheck's secrets file for that repository | None |
| Network | None by default. network = "internet" gives the internet and the local network. On a Mac with Docker Desktop it also reaches services on the Mac's 127.0.0.1 through host.docker.internal. A colima job reaches the internet and the machine's 127.0.0.1 whatever network says | The machine's network: the internet, the LAN and local services |
A job with engine = "colima" runs on a VM of its own, with no shared folders and no host environment. It can also change act's result inside the VM, so use colima only for code you trust. Its end-to-end tests check that a port the job listens on in the VM does not show up on the Mac's 127.0.0.1 and that every VM is deleted after its job, even when the run is killed (engines).
act mode also refuses Docker options that reach the host, such as --privileged, and refuses reusable workflows. It refuses Docker Desktop before 4.44.3 and runc versions with known container escapes.
Host mode gives up the container, so use it only for your own and your agents' work. A host check with sandbox = false on main also gives up the sandbox and runs with your full access once you run shipcheck trust in the repository, so use it only where you trust every author (outside the sandbox). When the config has host checks, the daemon never checks a fork's PR, not even with a stored result. Modes has the full list of what a host check gets.
Hooks run only your own commits and pushes. The daemon runs a PR only when you, or a login daemon_authors on main lists, opened it and pushed its head, and never a fork's (when a PR changes on GitHub).
What the App can do on GitHub
The shipcheck App asks for three permissions:
| Permission | For |
|---|---|
| Checks: write | Posting the job checks and the shipcheck summary |
| Pull requests: write | Listing a repository's open PRs for the daemon, finding the PRs of a commit, learning who pushed a PR's head from its timeline and the pull request webhook, and keeping one report comment on a PR |
| Metadata: read | Basic repository data, which every App has, and the repository's events, where the daemon looks last for who pushed a head |
The App has no Contents permission, so it cannot read or change your code. Its private key lives only in shipcheck's service. A job never gets the App's token or any GitHub token.
What the hosted service stores
shipcheck post sends the check text and the report comment to shipcheck's service, which posts them with the App and keeps no copy. The service first checks that your GitHub user has write, maintain or admin access to the repository. Each App token it makes covers one repository and only the permission that request needs.
It stores, for each signed-in machine, a SHA-256 hash of the machine token, your GitHub user id and login, the machine name, and when the token was made and when it expires. It never stores the token itself, and it revokes the GitHub token from the sign-in once it knows who you are. A token stops working after 90 days without a post. It also counts failed sign-ins per IP address and locks an address out for 15 minutes after three. For a push to a PR, it keeps what GitHub's pull request webhook says for 7 days: the repository id, the PR number, the head SHA, the head repository id and ref, and the pusher's login and user id, so the daemon can learn who pushed.
Results and logs stay on your machine. shipcheck sends no telemetry.
Keys on your machine
On macOS, shipcheck keeps the machine token and the secrets files in your login keychain, where an agent running as you gets a prompt instead of the key. On Linux they are files of mode 0600, which any program you run can read (where shipcheck keeps its keys).
Report a vulnerability
Use GitHub's private vulnerability reporting. Do not open a public issue. You get an answer within a few days.