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.
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 item | Identity evidence | Authorization handling | Common mistake |
|---|---|---|---|
| PAT or user token | credential_type + credential_id; retain hashed_token when present | Collect all organization rows under one record | Using the numeric ID without its type |
| SSH key | fingerprint | Collect organizations and enterprise authorization | Waiting for a token hash that does not apply |
| GitHub App installation | Installation record and app owner fields | Preserve organizations, permissions, and repository selection | Treating the row as one short-lived installation token |
| Federated credential | Exported type and available owner/context fields | Preserve empty fields as unknown or not applicable | Inventing 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.
| Queue | Example evidence | First response | Exit evidence |
|---|---|---|---|
| P0: suspected active compromise | Active authorization plus supported audit match during incident window | Confirm owner and dependency, contain at the narrowest control plane | Access blocked, affected automation verified, replacement or rollback recorded |
| P1: high reach, unclear use | Broad scopes, all repositories, multiple organizations, unknown owner | Assign app/user and organization owners; expand log and dependency search | Use explained or scoped remediation approved |
| P2: stale or policy exception | High age, past policy, never/unknown expiry, no supported match found | Schedule owner validation and rotation review | Dated keep, rotate, reduce, or retire decision |
| Evidence only | Expired, revoked, or deleted during the incident window | Retain for timeline and prior-access analysis | Disposition 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.