t0ul

Git and GitHub the Safe Way, Ch. 9: GitHub Hygiene

Part of Git and GitHub the Safe Way, a short book on git, GitHub, and the habits that keep a repository from hurting you.

Promise four: nobody acts as you. Everything in the previous chapters assumed that a push from your account was you. This chapter is the settings that make that true, and the few repository switches that let GitHub watch your back. Most of it is done once; the last section is a yearly habit. Button names follow GitHub’s documentation on the day of writing, linked in each section.

Your account

Two-factor authentication. A password alone means that a password leaked from any other site you reused it on is a push to every repository you can write to. Turn on a second factor: your profile picture, Settings, then Password and authentication in the sidebar, then Enable two-factor authentication. GitHub recommends an authenticator app over text messages, which can be intercepted. Scan the QR code, type the six-digit code, then Download your recovery codes and put them somewhere that is not your laptop before clicking I have saved my recovery codes. The recovery codes are how you get back in when the phone is gone; without them, neither can you.

Passkeys. Once two-factor is on, add a passkey (same page): Face ID, Touch ID, Windows Hello or a hardware key signs you in without a password at all, and cannot be phished, because it only works on the real github.com.

Keys and sessions. Twice a year, open SSH and GPG keys and delete every key for a machine you no longer have; each one is a door. Open Sessions and sign out anything you do not recognise.

Tokens: small, short-lived, named

Sometimes a program needs to act as you: a script that opens issues, a CI job that publishes a release. Never give it your password. Give it a fine-grained personal access token, which is a password with a name, an expiry date and a tiny scope.

Following GitHub’s steps: Settings, Developer settings, Personal access tokens, Fine-grained tokens, Generate new token. Then:

  • Token name: what it is for and where it lives (“release script, laptop”).
  • Expiration: the shortest that works. Thirty days for anything you touch by hand; a token that cannot expire is a password that cannot be rotated.
  • Repository access: Only select repositories, and select the one it needs.
  • Permissions: the least that works, read-only unless it must write.

Click Generate token, copy it once, put it in the place it is needed (an environment variable, chapter 3 of the Go book’s Part 0; a CI secret; a password manager), and never in a file git watches. GitHub shows it exactly once. When it expires, the thing that used it fails loudly, which is the design: you find out what still depends on it and decide again.

Let the repository watch itself

Three switches on each repository, all under Settings then Advanced Security in the “Security and quality” section of the sidebar:

  • Dependabot alerts: Enable. GitHub reads your manifests and lockfiles and tells you when a dependency has a known vulnerability, with the version that fixes it. (Docs.) The alerts are only useful if someone reads them; make it part of the weekly look.
  • Secret Protection: Enable, then Push protection: Enable inside it. GitHub scans pushes for the formats of hundreds of providers’ keys and refuses the push before it lands, which is chapter 4’s hook running on the server for everyone, including the contributor who did not install the hook. (Docs.)
  • Dependabot security updates, where offered, open the version-bump pull request for you. Chapter 7 says how to read it; it is still a pull request.

Public repositories get these free. Private repositories need a paid plan for some of them; the settings page says which. All three are switched on for the book’s practice repository, so a pull request there that contains a live credential is refused twice: by the hook on your machine, if you installed it, and by push protection on GitHub’s side regardless.

Actions: least privilege for robots

A GitHub Actions workflow is code that runs with a token for your repository. Two settings and two habits keep that from being the weakest door in the house. The settings live under Settings, Actions, General (docs):

  • Workflow permissions: the read-only choice, “read repository contents and packages permissions,” is the default for personal accounts; keep it. A workflow that needs to write asks for exactly that in its own file, as chapter 6’s permissions: contents: read showed. Then a compromised step cannot push, cannot edit releases, cannot do anything the file did not grant.
  • Fork pull request workflows: for a public repository, “Require approval for all external contributors” means a stranger’s pull request does not run your workflows until you have looked at it. Fork pull requests never receive your secrets anyway, but your runners and your minutes are yours.

The two habits are about the actions you use:

  • Pin third-party actions to a commit. uses: someone/action@v3 follows a tag that the publisher can move; uses: someone/action@<40-character commit id> # v3 cannot be changed under you. GitHub’s own actions (actions/checkout, actions/setup-go) are the usual exception people make; for anything else, pin.
  • Read an action before you use it. It is a repository like any other. Open it, look at what it does with the token and the secrets it is given, look at who maintains it and how recently. Five minutes once beats a surprise later.

What a fork can and cannot reach

Anyone can fork a public repository, and that is fine: a fork is a copy under their account. It cannot push to yours, cannot read your secrets, and its workflows do not run on your repository without the approval above. What a fork can do is open pull requests, which is chapter 7’s job to read, and keep copies of anything that was ever public in your repository, which is why chapter 4 said that removing a leaked secret from history is tidying and rotating it is the fix.

Remove what you no longer use

The yearly habit, ten minutes:

  • Settings, Applications: every app and OAuth grant you authorised. Revoke the ones you do not remember installing.
  • Personal access tokens: delete the expired and the unexplained.
  • SSH and GPG keys: as above.
  • Each repository’s Collaborators: people who moved on still have write access until someone removes it.
  • Repositories themselves: archive what is finished so nobody mistakes it for maintained.

Every entry you remove is a credential that cannot leak because it does not exist.

Conclusion

Second factor and a passkey; tokens that are named, scoped and short-lived; keys and sessions pruned; Dependabot, secret scanning and push protection switched on; workflows read-only by default, actions pinned and read; forks understood; the yearly clean-out. Set once, checked yearly, this is promise four kept.

That is the book. The cheat sheet has every command and setting on one page.

← Back to the blog