News

GitHub Enterprise Credential Inventory: Turn the Export Into an Incident Triage Runbook

GitHub's enterprise-wide credential inventory gives responders a read-only map of users, apps, keys, tokens, organizations, and repositories. It does not decide what is compromised or revoke anything. Preserve one timestamped export, normalize its authorization rows, correlate only where identifiers support it, and route each action to the owner who can reverse it.

2026-09-22

A dark tabletop display of keys arranged on transparent panels, connected by glowing cyan circuit lines to data screens and trays.
The separated trays and linked panels reflect the runbook's central distinction: one credential identity can connect to several authorization and evidence records.

Start with the Boundary GitHub Actually Gives You

GitHub announced enterprise credential inventory exports on September 21, 2026. Its release note describes an enterprise-wide view spanning SSH keys, classic and fine-grained personal access tokens, OAuth App access tokens, and GitHub App user and installation access. The feature is available for GitHub Enterprise Cloud; the announcement describes Enterprise Server support as upcoming rather than current.

The important incident-response boundary is in GitHub's credential review guide: the inventory is read-only. Depending on credential type, remediation belongs at enterprise, organization, application, or user level. An export can tell you which control plane to investigate, but it is not proof of compromise and it is not a revocation button.

Do not revoke from the row count

The CSV has one row for every credential and authorizing-organization combination. A credential authorized to three organizations can therefore appear three times. Counting those rows as three secrets inflates the incident, splits one owner across multiple tickets, and makes broad revocation look safer than it is.

Freeze a Timestamped Evidence Export

Have an enterprise owner or a user with the fine-grained View enterprise credentials permission create the export under an incident identifier. The UI path is Enterprise settings, Authentication security, Credentials, then Export CSV. GitHub says the CSV download begins when processing finishes and also sends a link to the account's notification email address.

For automation, the credential inventory REST API provides an on-demand list and an asynchronous CSV export. The list uses opaque cursor pagination through the Link header, reports no total count, and permits up to 100 results per page. Walk every page; a first page with 100 objects is not a complete enterprise inventory.

incident_id=IR-2026-0922
captured_at_utc=2026-09-22T09:40:00Z
enterprise=example-enterprise
source=GET /enterprises/example-enterprise/credentials?per_page=100
pages_collected=all Link rel="next" cursors exhausted
raw_artifact=credential-inventory-IR-2026-0922.json
transform_version=normalize-credentials-v1
collector=security-response@example.invalid

Retain the raw response or CSV read-only, hash the artifact with your normal evidence tooling, and write transformations to a separate file. This is an operational recommendation, not a GitHub requirement, but it keeps later sorting and deduplication from erasing what the responder actually received. The REST documentation says CSV exports are limited to a small number per enterprise per day without giving an exact number, so preserve a good incident snapshot rather than repeatedly requesting exports.

Normalize Identity Before Authorization

Build one canonical credential record with a child set of authorizations. Do not use credential_id alone as the primary key: GitHub says it is unique only when combined with credential_type, and the field is empty for SSH keys, GitHub App installations, and federated credentials. Use the strongest available type-specific identity, retain GitHub's fields unchanged, and add your own deterministic incident key only for this evidence set.

Inventory itemIdentity evidenceAuthorization handlingCommon mistake
PAT or user tokencredential_type + credential_id; retain hashed_token when presentCollect all organization rows under one recordUsing the numeric ID without its type
SSH keyfingerprintCollect organizations and enterprise authorizationWaiting for a token hash that does not apply
GitHub App installationInstallation record and app owner fieldsPreserve organizations, permissions, and repository selectionTreating the row as one short-lived installation token
Federated credentialExported type and available owner/context fieldsPreserve empty fields as unknown or not applicableInventing a durable ID from blank columns

The installation distinction is operationally significant. GitHub represents the GitHub App installation that can issue access tokens, not every short-lived installation token. Your remediation question is therefore about the installation's permissions, repositories, organizations, and app ownership—not whether one exported row equals one captured bearer token.

Keep expiry_status with expires_at. The status distinguishes expires, never, and unknown; a blank timestamp must not silently become “never expires.” Likewise, empty cells mean unavailable or not applicable. Preserve that uncertainty through triage instead of scoring missing data as low risk.

Rank the Queue with Evidence, Not One Magic Score

Separate state from priority. An active, currently authorized credential deserves examination, but priority also depends on reachable organizations or repositories, scopes or permissions, owner confidence, age, expiration, and observed use. A revoked credential may still matter to timeline reconstruction, while an old credential with no observed use may still operate through a path your audit query did not cover.

QueueExample evidenceFirst responseExit evidence
P0: suspected active compromiseActive authorization plus supported audit match during incident windowConfirm owner and dependency, contain at the narrowest control planeAccess blocked, affected automation verified, replacement or rollback recorded
P1: high reach, unclear useBroad scopes, all repositories, multiple organizations, unknown ownerAssign app/user and organization owners; expand log and dependency searchUse explained or scoped remediation approved
P2: stale or policy exceptionHigh age, past policy, never/unknown expiry, no supported match foundSchedule owner validation and rotation reviewDated keep, rotate, reduce, or retire decision
Evidence onlyExpired, revoked, or deleted during the incident windowRetain for timeline and prior-access analysisDisposition linked to incident record

This queue is a proposed runbook, not a severity model supplied by GitHub. Record why each factor changed priority. If you need a broader method for constraining credentials before an incident, the API key creation governance guide separates preventive policy from existing-key cleanup, while the authentication migration plan provides a provider-neutral replacement sequence.

Correlate Only the Identifiers the Logs Support

GitHub documents three direct correlation paths. For tokens, compare exported credential_id with audit-event token_id, or search for the exported hashed_token. For SSH keys, compare fingerprint. You do not need the original token value, and the export does not include it.

Coverage differs by type. The hashed_token field is empty for fine-grained PATs, so a hash query cannot prove their inactivity. App installation rows describe an issuer, not each token instance. Record every query with its identifier, time window, enterprise or organization scope, result count, and limitation. “No supported match found” is accurate; “unused” is stronger than the evidence when identifiers or retained logs are incomplete.

credential_case=IR-2026-0922-C017
credential_type=classic_pat
inventory_identity=classic_pat:48217
audit_query=token_id:48217
window_utc=2026-09-15T00:00:00Z..2026-09-22T10:15:00Z
result=4 matching events
organizations=engineering,platform
limitation=audit retention and event coverage not independently established
analyst=security-response@example.invalid

Use last-used time as a lead, not a verdict. Compare it with deployment history, scheduled jobs, repository access, and owner statements. If an automation owner reports a weekly job but your incident window covers only two days, absence of activity is expected rather than exculpatory.

Send Remediation to the Right Owner

For each canonical credential, name the credential owner, every affected organization, the application or workload owner, the person authorized to revoke, and the rollback owner. GitHub explicitly warns that revoking SSO authorization or deleting credentials can disrupt users and automation. A response ticket should therefore carry both the proposed containment and the dependency evidence that makes its blast radius legible.

case_id: IR-2026-0922-C017
finding: supported audit activity from unexpected network during incident window
proposed_action: revoke organization SSO authorizations for engineering and platform
mutation_owner: enterprise-iam@example.invalid
workload_owner: release-platform@example.invalid
replacement: short-lived workload credential staged in release canary
precheck: canary publish succeeded; legacy path paused
rollback_owner: release-oncall@example.invalid
verification: old authorization denied; canary and scheduled job healthy
evidence_links: inventory snapshot, audit query, change ticket, run logs

Prefer the smallest action that contains the supported risk. One organization's authorization may be the correct boundary; another incident may require disabling an app installation or deleting a managed user's key. The inventory cannot choose for you. Make the choice from identity, authorization, observed activity, business dependency, and the control plane that actually owns the credential.

Close with a Reconciled Second Snapshot

After containment, create a second timestamped inventory when export limits and incident procedure allow it. Normalize it with the same transform, compare canonical records rather than raw row counts, and explain every state transition. Confirm that targeted authorizations disappeared or changed state, intended replacements have the expected scope, and unrelated credentials did not move.

The completed incident record should contain two immutable snapshots, the transform version, pagination completion, audit queries and their limitations, a decision owner for every prioritized credential, mutation and rollback evidence, and a reconciliation report. That turns GitHub's export into something more useful than a spreadsheet: a reviewable chain from discovery to narrowly scoped remediation without pretending the inventory itself proves compromise.

Sources checked 2026-09-22