Skip to content

Protecting main on GitHub

With main on GitHub, sluice pushes each landing with the credentials your own git push uses. Your agents run git with those same credentials, so GitHub can’t tell an agent’s git push origin HEAD:main from sluice’s landing. This guide gives sluice an identity of its own: a GitHub App. The app is the only bypass of main’s ruleset, and only sluice’s own fetches and pushes carry its token.

On GitHub, open Settings › Developer settings › GitHub Apps › New GitHub App, in the organization that owns the repository if it has one. Give it a name, such as sluice for acme, and any homepage URL. Clear Webhook › Active: sluice asks GitHub, and GitHub never calls sluice.

Under Repository permissions:

permissionaccess
ContentsRead and write
WorkflowsRead and write
Commit statusesRead and write
ChecksRead and write
Pull requestsRead and write
MetadataRead-only

sluice needs contents to land, and workflows to land any change under .github/workflows, since GitHub refuses an app’s push of one without it. The statuses, checks and pull requests are for pull requests. Under Where can this GitHub App be installed? choose Only on this account, then Create GitHub App. Note the App ID at the top of the app’s page.

On the app’s page, Install App, choose the account, then Only select repositories and pick the repository whose main sluice lands on.

On the app’s page, Private keys › Generate a private key downloads a .pem file. Move it next to sluice’s host config, where only you may read it:

Terminal window
$ mkdir -p ~/.config/sluice
$ mv ~/Downloads/sluice-for-acme.*.private-key.pem ~/.config/sluice/app.pem
$ chmod 600 ~/.config/sluice/app.pem

sluice refuses a key that anyone but you may read. The key never goes in a repository; anyone who holds it can push as sluice.

In ~/.config/sluice/config.toml:

[forge]
app_id = 123456 # the App ID from step 1
key = "~/.config/sluice/app.pem"

From the next round, sluice makes a token from the key for the repository, keeps it in memory, and gives it only to its own git fetch and git push. Nothing is written to .git/config, so the git your agents run never finds it. sluice status says who pushes:

Terminal window
$ sluice status main
main at 7c829a5 · 0 in the queue · testing 0 at once
pushed to origin as GitHub app 123456
nothing in the queue

5. Make the app the ruleset’s only bypass

Section titled “5. Make the app the ruleset’s only bypass”

In the repository’s Settings › Rules › Rulesets, open the ruleset on main from GitHub integration. Under Bypass list, Add bypass, choose the app, and set it to Always allow. Remove every other entry, your own account and any role included.

From now on a push to main from anyone but sluice is refused, yours too. To land work, submit it.

Do the same for the bolt/* ruleset if your bolts live on GitHub (bolts on GitHub). That ruleset also restricts creations and deletions, since sluice branch makes and deletes bolts as the app.

Terminal window
$ sluice forge check main
ok [forge] key /home/me/.config/sluice/app.pem reads
ok app 123456 makes a token for acme/widgets
ok the app may write acme/widgets's contents
ok the app may write acme/widgets's workflows
ok ruleset "main" on main lets only app 123456 through

GitHub shows who may bypass a ruleset only to the repository’s admins, so sluice forge check reads the ruleset with your own token, from gh or GH_TOKEN. It exits 1 and names what is wrong when anything is, such as a second bypass left on the ruleset.

The key is a file your user may read, and an agent runs as your user. The ruleset keeps agents off main because their git never uses the key, not because they can’t read it. Running agents as another user, or keeping the key in a secrets manager, would close that gap; sluice doesn’t do either yet.