github-auth

v2026.09.25

This skill should be used when choosing, diagnosing, or configuring how something authenticates to GitHub — personal access tokens, GitHub Apps, OAuth, SSH and deploy keys, GITHUB_TOKEN in Actions, OIDC, and the permission model each one carries.

GitHub
Install command
npx skhub add thelobbi/github-auth
Markdown
SKILL.md

GitHub Authentication

Every credential type below carries a different permission model, a different blast radius, and a different failure message. Picking the wrong one is the most common reason a working script fails in CI.

The credential types

TypeIdentityExpiryScope modelUse for
Classic PATThe userOptionalCoarse OAuth scopes (repo = all repos, read+write)Legacy, and the few APIs fine-grained tokens still cannot reach
Fine-grained PATThe userRequired, max 1 yearPer-repo + per-permission (contents: read)The default for a human or a local agent
GitHub App installation tokenThe app, on an installation1 hourPer-permission, per-installationAutomation, bots, anything long-lived
GitHub App user access tokenThe user, via the app8 hours (refreshable)Intersection of app permissions and user accessActing as the user through an app
OAuth App tokenThe userNone by defaultCoarse OAuth scopesLegacy third-party integrations
GITHUB_TOKENgithub-actions[bot]The jobpermissions: in the workflowAnything inside Actions
OIDC (id-token)The workflow, cryptographicallyPer-requestTrust policy on the cloud sideCloud auth from Actions — no stored secret
SSH keyThe userNoneFull git access as the userGit transport only, never the API
Deploy keyOne repositoryNoneRead or read/write, one repoCI clone of a single repo

Choosing

Running inside GitHub Actions?
├── Talking to GitHub?          → GITHUB_TOKEN with a narrowed permissions: block
├── Talking to a cloud provider? → OIDC (id-token: write), never a stored cloud key
└── Need cross-repo access?     → GitHub App installation token, not a PAT

Running outside Actions?
├── Long-lived automation / bot → GitHub App installation token
├── Acting as a specific human  → GitHub App user access token
└── Local dev or a local agent  → fine-grained PAT, minimum permissions

Prefer a GitHub App over a PAT for anything that outlives a session. An installation token expires in an hour, is scoped per repository, is revocable without touching a human account, and does not die when its creator leaves the organization. A PAT in a shared secret is an outage waiting for someone's offboarding.

GITHUB_TOKEN and permissions:

The default token scope is far wider than any single workflow needs. Declare permissions: explicitly — a workflow-level floor, narrowed per job.

permissions:
  contents: read          # floor for the whole workflow

jobs:
  release:
    permissions:
      contents: write     # only this job can write
      id-token: write     # OIDC

Known behaviors that cause confusion:

  • GITHUB_TOKEN cannot trigger further workflows. A push or PR made with it does not fire push or pull_request events. This is deliberate loop protection. If you need the follow-on workflow to run, use a GitHub App token or a PAT — and understand you have re-armed the loop.
  • Fork PRs get a read-only token and no secrets on pull_request. That is the security boundary, not a bug.
  • pull_request_target runs with the base repo's token and secrets. Never combine it with a checkout of the PR head — that hands a fork write-scoped secrets. This is the single most exploited Actions misconfiguration.

Fine-grained tokens: the gaps

Fine-grained PATs are the right default, but they are not a superset of classic:

  • Some endpoints still require a classic token — notably parts of the Projects API for user-owned projects, which reject fine-grained PATs, App user tokens, and installation tokens outright.
  • Organization resources require the org to have enabled fine-grained tokens, and may require per-token approval by an owner.
  • A fine-grained token must be explicitly granted each repository. "It works on my repo" and "it works on the org's repo" are different questions.

Reading the failure

ErrorAlmost always means
401 Bad credentialsToken is wrong, revoked, or expired
403 Resource not accessible by integrationThe App/token lacks the permission, not the scope. Check the App's permission set
403 with X-Accepted-GitHub-Permissions headerThe header names exactly what was needed — read it
404 on a repo you know existsAlmost never missing — it is unauthorized. GitHub 404s private resources rather than confirming they exist
403 ... SAML enforcementThe token needs SSO authorization for that org, done in token settings
422 on a writePayload problem, not auth
Works locally, 403 in Actionspermissions: block, or the default token, not a credential problem

The 404-means-403 behavior matters for agents. Never conclude a repository does not exist from a 404 on an unauthenticated or under-scoped request. Attach the repo properly and retry before reporting it missing.

SAML SSO and IP allow lists

In an org with SAML enforced, a valid token still fails until it is authorized for that org. In an org with an IP allow list, a token from an unlisted address fails regardless of permissions — GitHub App installations can be configured to bypass the list, which is one more reason to prefer them for automation.

Git transport vs API

They are separate. An SSH key clones and pushes; it cannot call the API. A PAT can do both (over HTTPS). A deploy key is scoped to one repository and is the right answer for a CI clone that should not be able to reach anything else.

Never use a personal SSH key or PAT as a shared CI credential — the audit trail becomes useless and revocation breaks a human.

Storage

  • Never in source, config, or a PR body. The guard hook blocks credential-shaped strings for this reason.
  • Actions: repository/environment secrets, or OIDC so there is no secret at all.
  • Local: the OS credential helper or gh auth login, not .netrc in plaintext.
  • A secret that reaches a remote is compromised — rotate first, clean up second. See supply-chain-security.

See also

  • gh-mcp — how the MCP server authenticates and what it can reach
  • actions-authoring — permissions:, OIDC, and injection
  • ../commands/setup.md — detection and degraded-mode reporting
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.25

Published

Sep 25, 2026

Category

Uncategorized

License

MIT

Source path

plugins/delivery-orchestrator/skills/github-auth

Default branch

main

Latest commit

2f1269c

Tree SHA

629e050