Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXIV · Git SecurityHTTPSTokens

HTTPS tokens and personal access tokens — PATs, fine-grained tokens, and OAuth apps

Advanced⏱ ~24 mingitgh

What you'll learn

  • Distinguish classic PATs, fine-grained PATs, and OAuth apps by scope and lifetime model
  • Issue a fine-grained PAT scoped to one repository with a specific permission and expiry
  • Choose between PATs and OAuth apps for a CI integration versus a one-off script
  • Recognise the failure modes of over-scoped tokens, missing expiry, and unrotated credentials

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

HTTPS authentication for Git uses bearer tokens. The forge issues a string, the client presents it in the Authorization header, and the forge authorises the request if the string is valid, unrevoked, and within scope. The token is the credential; the credential’s blast radius is the token’s scope. This lesson walks the three flavours of HTTPS token that Git forges offer and the scoping discipline that applies to all of them.

Classic personal access tokens

A classic personal access token (PAT) is the legacy HTTPS-credential model. The token is issued against a user account, has access to every repository the account can see, and remains valid until manually revoked. The token can be scoped at issuance time (read-only, write, admin) but the scope is repository-wide — a classic PAT cannot be limited to one repository.

The threat model:

  • Account-wide blast radius. A leaked classic PAT has the same rights as the issuing account against every repository the account can see. The token is a copy of the account’s identity for HTTPS purposes.
  • No expiry by default. A classic PAT does not expire unless the issuer sets an expiry date; tokens issued years ago and still valid are common in legacy repositories.
  • Visible only at creation. The token string is shown once at creation and never again. A lost token must be revoked and reissued.

Classic PATs are the right choice when no finer-grained option exists, when the issuing account is a service account scoped to a single repository anyway, and when the issuing team explicitly sets a short expiry. They are the wrong choice as a default: every modern forge offers a narrower alternative.

Fine-grained personal access tokens

A fine-grained PAT is the modern HTTPS-credential model. The token is issued against a user account (or a service account), scoped to one or more explicitly named repositories, scoped to specific permissions on those repositories (read contents, write contents, manage pull requests, etc.), and given an explicit expiry date.

flowchart LR
    A["fine-grained PAT"] --> B{"repository scope"}
    B -->|one repo| C["blast radius: one repo"]
    B -->|multiple repos| D["blast radius: explicit list"]
    A --> E{"permission scope"}
    E -->|read contents only| F["can clone, cannot push"]
    E -->|contents read/write| G["can clone and push"]
    A --> H{"expiry"}
    H -->|set| I["expires on date"]
    H -->|unset| J["must be revoked manually"]

The scoping discipline for fine-grained PATs is the same as the scoping discipline for SSH deploy keys: narrow scope, explicit expiry, rotation on a schedule. The forge enforces the scope at every request; a token scoped to read contents on one repository cannot push to that repository and cannot reach any other repository.

# Issue a fine-grained PAT scoped to one repository, read-only, 30-day expiry
gh auth login --scopes 'repo:read'
gh auth token

The gh auth login and gh auth token pair is the GitHub CLI’s interface to the same model: the CLI stores the token in its own secure backend (the platform’s credential store), presents it on every authenticated request, and refreshes it if the forge supports refresh.

The right use cases:

  • CI integrations that need to push. A fine-grained PAT scoped to one repository, contents: write, 24-hour expiry, used by a single release job.
  • Engineer workstations on a per-project basis. A fine-grained PAT scoped to the repositories the engineer actually contributes to, with an expiry that matches the project’s expected duration.
  • Service accounts for automated workflows. A fine-grained PAT scoped to the repository the service account owns, with the narrowest permissions the workflow requires.

OAuth apps

An OAuth app is a third-party authorisation model. The user authorises the app to act on their behalf; the app receives an access token (and, depending on the model, a refresh token) that it can use to make API and Git requests. The access token is short-lived; the refresh token is long-lived but can only be used to obtain new access tokens.

flowchart LR
    A["user authorises app"] --> B["forge issues access token"]
    B --> C["app uses access token"]
    C --> D{"token expired"}
    D -->|yes| E["app uses refresh token"]
    E --> F["forge issues new access token"]
    F --> C
    D -->|no| C

The scoping model is similar to fine-grained PATs at the API level but the lifetime model is different: the access token expires quickly, the refresh token can be revoked independently, and the user can revoke the app’s authorisation entirely from the forge’s settings.

OAuth apps are the right choice when:

  • A third-party tool needs Git access. The tool authorises via OAuth, never holds the user’s password, and the user can revoke the tool’s access without changing their own credentials.
  • The use case requires refresh tokens. Long-running integrations that need to act on the user’s behalf without the user being present can refresh their access token; a fine-grained PAT cannot be refreshed.
  • The integration spans multiple users. A team-wide tool that acts on behalf of any team member uses OAuth to authorise each member independently.

OAuth apps are the wrong choice when a fine-grained PAT would do: the OAuth model adds the app as a third party to the trust chain, and a simple CI integration does not need that complexity.

Expiry, rotation, and revocation

Every HTTPS token has three lifecycle properties that must be set deliberately:

  • Expiry. A token that does not expire is a credential that lives forever in any system that has ever seen it. An expiry date forces rotation: the token becomes invalid on a known date, and the team must issue a new one to continue operating.
  • Rotation. A token that has been in use for a year is a credential that has been in a year’s worth of backups, CI logs, and developer terminals. Rotation limits the window of exposure: a token rotated quarterly has at most three months of exposure to any leak.
  • Revocation. A leaked token must be revocable from the forge’s settings without touching the clients. The revocation is the incident response: the moment a leak is suspected, the token is revoked, every client fails the next time it tries to use it, and the team reissues.

The discipline is to set expiry at issuance (the shortest the use case allows), rotate on a schedule (quarterly is a common default), and revoke on any suspected compromise. A token without an expiry is a token without an incident response.

Production discipline

  1. Default to fine-grained PATs. The narrowest scope the use case allows; explicit expiry; rotation on a schedule.
  2. Avoid classic PATs unless the forge offers no alternative. When a classic PAT is the only option, set the shortest expiry the use case allows and document the rationale.
  3. Use OAuth apps for third-party integrations. Any tool that needs Git access on behalf of many users should authorise via OAuth, not by holding a long-lived user token.
  4. Treat every token as compromised the moment it appears in a log. A token in a CI log is a token in the next breach dump; rotate it and shorten the next token’s expiry.
  5. Audit the token inventory quarterly. The forge’s settings page lists every token issued against the account; review the list, revoke unused tokens, and rotate long-lived ones.

Cross-course references

  • Git, CI/CD & GitOps — Part XXXIV-01 (Authentication options) — the SSH-vs-HTTPS framing that this lesson extends.
  • Git, CI/CD & GitOps — Part XXVI-05 (Credential helpers) — the storage layer that backs HTTPS tokens; a fine-grained PAT in ~/.git-credentials is still plaintext.
  • Git, CI/CD & GitOps — Part XXXIV-04 (Credential storage and rotation) — the rotation cadence that applies to every HTTPS token.
  • Terraform for Production Sysadmins — Part XXXIV (StateSecurity) — the analogous scoping discipline for Terraform Cloud tokens.

Quiz

Knowledge check · 4 questions

  1. Q1. A CI job needs to push a release tag to one specific repository. The job runs once per release. Which token model is the right choice?

  2. Q2. A fine-grained personal access token scoped to `read contents` on one repository cannot be used to push to that repository, because the forge enforces the token's permission scope on every authenticated request.

  3. Q3. Name the three HTTPS-token models Git forges offer and the scope property that distinguishes each one.

  4. Q4. Diagnose why a leaked token has more rights than the use case required, and recommend the scoping change that limits future leaks to the same blast radius.

    A team's release pipeline uses a classic PAT issued two years ago with no expiry, scoped to the engineer's account. The PAT was originally for one repository but the engineer has since been added to several other repositories — all of which the PAT now also has access to. Last week the PAT appeared in a CI log that was inadvertently indexed by an internal search engine. The team now faces a credential leak whose blast radius is the engineer's entire account, not the one repository the token was originally issued for.

Passing score: 75%. Answers are checked in this browser.