Security and Supply Chain
Topic 60

Authentication

Security

Git authenticates over two transports — SSH with a key pair, or HTTPS with a token — and GitHub stopped accepting your account password for Git operations years ago. Every push and fetch now rides on either a key or a token, and the only real question is which credential goes where and how tightly it is scoped.

Scope is the whole game. A credential that can reach every repository your account can see is a single point of failure: leak it once and the blast radius is your entire account. A credential bound to one repository with read-only permission leaks into a much smaller hole. The mechanics below all reduce to choosing the narrowest credential that still does the job.

SSH Keys

An SSH key pair lives on one machine: the private half stays on disk (ideally passphrase-protected and held by an agent), and you upload the public half to your account. From then on Git talks to GitHub over git@github.com with no token prompts. This is the right default for an interactive developer machine, where one human owns one device.

Keep one key per machine rather than copying a single private key onto every device. When a laptop is lost or decommissioned, you remove just that one public key from your account and every other machine keeps working — there is nothing to rotate elsewhere.

HTTPS with Tokens

Over HTTPS, Git authenticates with a personal access token in place of the removed password. When a tool prompts for a password on an HTTPS remote, it wants a token, not your login. The token is a bearer credential: whoever holds the string can act as it, so it deserves the same care as a password and a tighter scope.

PAT Classic vs Fine-Grained

A classic PAT picks from coarse scopes like repo or workflow, and those scopes apply to every repository your account can reach. There is no required expiry, so a forgotten classic token is a permanent, account-wide credential. A fine-grained PAT instead targets a chosen set of repositories, grants per-resource permissions (for example contents: read, issues: write), and can be governed by org admins who can enforce a maximum token lifetime. An expiration is strongly encouraged but, since October 2024, no longer mandatory for personal-account tokens.

For anything new, reach for a fine-grained PAT and grant the minimum permissions on the minimum repositories. Treat any remaining classic PAT as a high-blast-radius credential to be retired, not extended.

Deploy Keys

A deploy key is a single SSH key bound to exactly one repository, set read-only or read-write. It is the correct credential for a server or CI job that touches one repo, because it does not ride on any human's account — offboarding a person never silently breaks the deployment, and a leak exposes only that one repository.

Credential Storage

How the credential is stored matters as much as its scope. A token written in plaintext to ~/.git-credentials or a .netrc is readable by any process and trivially committed by accident. Use a credential helper backed by the OS keychain (Git Credential Manager, or the platform keychain helper) so the secret is encrypted at rest and never sits in a flat file in your home directory.

Classic PAT vs Fine-Grained PAT

Classic PAT — picks coarse scopes (repo, workflow) that apply to every repository your account can reach, with no required expiry. One leak exposes everything you can access, and a forgotten token stays valid forever.

Fine-grained PAT — targets a chosen set of repositories, grants per-resource permissions, and can be approved or restricted by org admins, who can enforce a maximum token lifetime. The blast radius of a leak is limited to the repositories and permissions you named.

Common Mistakes
  • Using a classic PAT with full repo scope for a script that only reads one repository — a leak of that token exposes every repository your account can access.
  • Creating a PAT with no expiration, so a token forgotten in a CI config or a shell history stays valid indefinitely with nothing forcing a rotation.
  • Putting a personal SSH key on a shared CI server instead of a repo-scoped deploy key — shared infrastructure now authenticates as one human, and offboarding that person breaks the pipeline.
  • Committing a .netrc or ~/.git-credentials containing a token, leaking a live credential into history where it survives in every clone.
  • Granting a deploy key write access when the consumer only needs to read — a compromise of that server can now push to the repository.
Best Practices
  • Prefer fine-grained PATs scoped to specific repositories with the minimum permissions and a short expiry for any new token.
  • Use a read-only deploy key for any server or CI job that touches a single repository, instead of a personal or account-wide credential.
  • Set an expiration on every token and rotate on a schedule so a missed leak cannot stay live forever.
  • Use one SSH key per developer machine and remove the public key from your account the moment a device is lost or retired.
  • Store credentials in an OS-keychain credential helper, never in a plaintext .netrc or ~/.git-credentials.
Comparable toolsGitLab personal, project, and group access tokens plus deploy keys and deploy tokensBitbucket app passwords and repository access keysAzure DevOps personal access tokens and SSH keys

Knowledge Check

A script only needs to read one repository. Which credential has the smallest blast radius if leaked?

  • A read-only deploy key bound to that single repository
  • A classic PAT carrying the full repo scope across your account
  • Your reusable GitHub account password sent over HTTPS
  • A personal SSH key copied onto the shared CI server

What is the key difference between a classic and a fine-grained PAT?

  • A fine-grained PAT scopes to chosen repositories with per-resource permissions, while a classic PAT's coarse scopes reach every repository
  • A classic PAT is always stored encrypted at rest, whereas a fine-grained PAT is kept in plain text by the local credential helper on the disk
  • A fine-grained PAT works only over the SSH transport, whereas a classic PAT works only over HTTPS
  • There is no functional difference between them beyond the name on the settings page

When prompted for a password on an HTTPS GitHub remote, what should you supply?

  • A personal access token — account passwords are no longer accepted for Git operations
  • Your usual GitHub account login password
  • Your SSH private key pasted in at the prompt
  • A current six-digit one-time code generated by your two-factor authenticator app on your phone

Why use a deploy key rather than a personal SSH key on a shared CI server?

  • A deploy key is bound to one repository and to no human, so a leak stays contained and offboarding never breaks the pipeline
  • Deploy keys never expire and cannot be revoked, so once installed they need no rotation or ongoing maintenance
  • A deploy key grants the shared CI server standing read and write access to every repository in the whole org at once, the same broad reach a personal account key would carry
  • Personal SSH keys are unable to authenticate Git operations over HTTPS at all, so the CI server would silently fall back to unauthenticated clones

You got correct