redact-secret · Report
GitHub App server-to-server token
GitHub App installation (server-to-server) token, prefixed ghs_.
Research record
Format revisions
- Revision 1
github:app-server-to-server-token@1historical · draft, not reviewed · superseded by @2 - Revision 2
github:app-server-to-server-token@2current · draft, not reviewed · supersedes @1 · issued from 2026-04-27 · the family's current revision
5 events in the review history: 4 observed, 1 reviewed. Latest: observed on 2026-10-03 by automation, project maintainer. Project-maintained review is not independent validation. The family record at this release.
Format
What it looks like
- Prefixes
ghs_- prefix (prefix)
ghs_ marks an installation access token for a GitHub App; the prefix is unchanged by the stateless format.- app-id (other)
Stateless form only: the APPID of ghs_APPID_JWT, followed by an underscore. Its alphabet and length are not documented.- jwt (body)
Stateless form only: a JWT signed with a GitHub-internal issuer, about 520 characters in total for the token, containing two dots. Clients must not parse or validate it.- opaque-body (body)
Stateful (opaque) form: no dots; the provider describes these tokens as 40 characters long.- Separators
_ · .- Length
min 36- Alphabet
other · A-Za-z0-9.-_- Checksum
Not documented for the stateless form. The 2021 CRC32 checksum in the last six characters of the opaque body is carried by contract revision 1 only and is not restated here.- Descriptive pattern
^ghs_[A-Za-z0-9.\-_]{36,}$
Parts are shown as recorded. Evidence classes belong to the facts below; no class is assigned to a part.
Format facts
Provider documented ·
stateless-rollout· current · observed 2026-10-03GitHub states that from 2026-04-27 it began a staged rollout of a stateless installation-token format, written ghs_APPID_JWT, for newly minted GitHub App installation tokens, that the ghs_ prefix is unchanged, and that existing installation tokens keep working until they expire. The rollout is stated for GitHub Enterprise Cloud and Data Residency, not GitHub Enterprise Server.
- GitHub changelog 2026-04-24: Notice about upcoming new format for GitHub App installation tokensprovider-documentation · last read 2026-10-03 · latest outcome read · supports Staged rollout starting 2026-04-27; token format changes to ghs_APPID_JWT; prefixes are not changing; existing tokens continue to work until they expire; GitHub Enterprise Server is not impacted. · What is changing?; Scope
- docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-githubprovider-documentation · last read 2026-10-04 · latest outcome read · supports Note under GitHub's token formats: staged rollout of a stateless format (ghs_APPID_JWT) to all newly minted installation tokens beginning 2026-04-27. · #githubs-token-formats
Provider documented ·
stateless-length-and-dots· current · observed 2026-10-03GitHub describes the stateless installation token as longer than the previous form (about 520 characters, varying with the data stored in it) and as a ghs_-prefixed JWT with two dots, and the stateful token as a short opaque string with no dots; it says to tell them apart by counting the dots after the ghs_ prefix. Two dots is consistent with the three-part JWS compact serialization used by RFC 7519 JWTs.
- GitHub changelog 2026-05-15: GitHub App installation tokens, per-request override headerprovider-documentation · last read 2026-10-03 · latest outcome read · supports A stateless token is a ghs_-prefixed JWT, longer (~520 characters), containing two dots; a stateful token is a short opaque string with no dots; verify the type by the number of dots after the prefix. · How to use it; Verifying the token type
- GitHub changelog 2026-04-24: Notice about upcoming new format for GitHub App installation tokensprovider-documentation · last read 2026-10-03 · latest outcome read · supports The overall length will be longer (~520 characters) and will vary based on the data stored within it. · What is changing?
- www.rfc-editor.org/rfc/rfc7519provider-documentation · last read 2026-09-23 · latest outcome read · supports A JWT is a sequence of URL-safe parts separated by period characters, each base64url-encoded; the JWS compact serialization has three parts, hence two periods. · Section 3
Provider documented ·
previous-form-40-characters· current · observed 2026-10-03GitHub warns that applications which expect installation tokens to be exactly 40 characters long may mishandle the new format, which states that the previous (stateful, opaque) form is 40 characters, that is ghs_ plus 36 characters.
- GitHub changelog 2026-04-24: Notice about upcoming new format for GitHub App installation tokensprovider-documentation · last read 2026-10-03 · latest outcome read · supports If your application expects or relies on installation tokens being exactly 40 characters long, it may not handle this new token format correctly. · Opening paragraph
- docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-githubprovider-documentation · last read 2026-10-04 · latest outcome read · supports The same warning in the note under GitHub's token formats. · #githubs-token-formats
Provider documented ·
recommended-regex· current · observed 2026-10-03GitHub recommends ghs_[A-Za-z0-9\.\-_]{36,} to match both the new and the current format, and asks that any validating regex handle additional underscores and the presence of a JWT. The page records an editor's note dated 2026-05-26 that updated this regex guidance, so earlier wording of it differed.
- GitHub changelog 2026-05-15: GitHub App installation tokens, per-request override headerprovider-documentation · last read 2026-10-03 · latest outcome read · supports Our recommended regex to match both new and current format tokens is ghs_[A-Za-z0-9\.\-_]{36,}; Editor's note (May 26th, 2026): Updated the regex format guidance. · How to use it
Provider documented ·
per-request-override-header· current · observed 2026-10-03GitHub provides a temporary request header, X-GitHub-Stateless-S2S-Token, on POST /app/installations/:installation_id/access_tokens: enabled returns a stateless token, disabled a stateful one, absent means normal rollout, and any other value is ignored. The header is temporary: the 2026-05-15 entry said it would stop being respected at a deprecation point to be announced, after which eligible apps always receive stateless tokens, and the 2026-10-02 entry sets that date at 2026-11-30 (see rollout-complete-header-deprecation).
- GitHub changelog 2026-05-15: GitHub App installation tokens, per-request override headerprovider-documentation · last read 2026-10-03 · latest outcome read · supports Header name, values enabled and disabled, temporary status and the planned deprecation point. · What is the header?; Scope
Provider documented ·
rollout-complete-header-deprecation· current · observed 2026-10-04GitHub states, in a changelog entry dated 2026-10-02, that the staged rollout begun on 2026-04-27 is complete: by default all newly minted GitHub App installation tokens are now in the stateless ghs_APPID_JWT format, about 520 characters long instead of 40, with the ghs_ prefix, one-hour expiration and the installation access token endpoint unchanged, and tokens minted earlier working until they expire. It says the temporary X-GitHub-Stateless-S2S-Token header will be deprecated on 2026-11-30, after which GitHub no longer respects it and all eligible apps always receive stateless tokens. Until that date the header value disabled is still described as available (in the 2026-05-15 entry), so the opaque form can still be minted on request until then.
- GitHub changelog 2026-10-02: Stateless GitHub App installation tokens rolled outprovider-documentation · last read 2026-10-04 · latest outcome read · supports The staged rollout that began on 2026-04-27 is complete; by default newly minted installation tokens are stateless ghs_APPID_JWT, about 520 characters instead of 40; the header is deprecated on 2026-11-30 and eligible apps then always receive stateless tokens; earlier tokens work until they expire. · What's changed; What to expect going forward
- GitHub changelog 2026-05-15: GitHub App installation tokens, per-request override headerprovider-documentation · last read 2026-10-03 · latest outcome read · supports Header value disabled returns a stateful (classic opaque) token even if the integration is already in the rollout. · What is the header?
Provider documented ·
rollout-phase-plan· historical · observed 2026-10-04In its 2026-04-24 notice GitHub planned the staged rollout in two phases: 2026-04-27 to mid-May 2026 for the GitHub Actions GITHUB_TOKEN and the installation tokens of GitHub's first-party featured integrations, then mid-May to late June 2026 for all GitHub App installation tokens, with a brownout period before broad enablement. These are planned windows from the announcement; the 2026-10-02 entry states only that the rollout is complete, not the dates of each phase or of any brownout. The stateless format applies to installation server-to-server tokens including the Actions GITHUB_TOKEN; user-to-server tokens are not in scope in these entries.
- GitHub changelog 2026-04-24: Notice about upcoming new format for GitHub App installation tokensprovider-documentation · last read 2026-10-03 · latest outcome read · supports Planned phases: April 27 to mid-May 2026 for Actions GITHUB_TOKEN and first-party featured integrations; mid-May to late June 2026 for all installation tokens with a brownout period; scope is installation server-to-server tokens including Actions GITHUB_TOKEN; user-to-server tokens not in scope yet. · Scope; What to expect over the next few weeks
- GitHub changelog 2026-10-02: Stateless GitHub App installation tokens rolled outprovider-documentation · last read 2026-10-04 · latest outcome read · supports The rollout is complete; no phase dates are restated. · What's changed
Provider documented ·
storage-length-lower-bound-only· current · observed 2026-10-04GitHub's guidance on storage is a lower bound, not an upper bound on the token: database columns for installation tokens should fit at least a 520 character string (2026-04-24 entry), and columns, secret stores or environment variables with a fixed or small maximum, and proxies that truncate or reject long Authorization headers, are named as things to check (2026-10-02 entry). Neither states a maximum token length, so a stateless token longer than about 520 characters is not excluded or confirmed.
- GitHub changelog 2026-04-24: Notice about upcoming new format for GitHub App installation tokensprovider-documentation · last read 2026-10-03 · latest outcome read · supports Any database columns for access tokens can fit at least a 520 character string; do not take a dependency on tokens being a certain length. · How to prepare for this change
- GitHub changelog 2026-05-15: GitHub App installation tokens, per-request override headerprovider-documentation · last read 2026-10-03 · latest outcome read · supports Database columns for token storage and header settings accept at least 520 characters. · How to use it
- GitHub changelog 2026-10-02: Stateless GitHub App installation tokens rolled outprovider-documentation · last read 2026-10-04 · latest outcome read · supports Check for columns, secret stores or environment variables with a fixed or small maximum length, and proxies that truncate or reject long Authorization headers. · Check your integrations
Unresolved ·
app-id-and-jwt-contents-undocumented· current · observed 2026-10-03The provider documents neither the alphabet nor the length of the APPID segment, nor whether every stateless token carries it, and says the JWT contents are not for clients to depend on. The recommended regex also accepts a ghs_ value without an APPID segment, so the APPID-less shape is described by the regex but not by the ghs_APPID_JWT notation.
- GitHub changelog 2026-04-24: Notice about upcoming new format for GitHub App installation tokensprovider-documentation · last read 2026-10-03 · latest outcome read · supports The JWT is signed with a GitHub-internal issuer and client apps must not take a dependency on its contents; no statement on the APPID segment's alphabet or length. · What is changing?
- GitHub changelog 2026-05-15: GitHub App installation tokens, per-request override headerprovider-documentation · last read 2026-10-03 · latest outcome read · supports The regex guidance covers additional underscores and a JWT; no statement on the APPID segment. · How to use it
Open questions
github:app-server-to-server-token@2#app-id-segment-grammar· raised 2026-10-03What are the alphabet and length of the APPID segment of ghs_APPID_JWT, and is the segment present in every stateless token?
github:app-server-to-server-token@2#stateless-length-bounds· raised 2026-10-03Is there a documented upper bound or exact range for the stateless token length beyond "about 520 characters, varying with its contents"? The 2026-10-02 entry and the storage guidance (columns must fit at least 520 characters) give no maximum.
github:app-server-to-server-token@2#opaque-form-checksum-current· raised 2026-10-03Does the 2021 CRC32 checksum still apply to the opaque (stateful) form, and does any equivalent apply to the stateless form?
Benchmark dossier notes
- Basis
- prefix and role from the token-formats page; body from GitHub's 2021-04-05 token-format post; contracted per family in #517 (T1; body tool-corroborated). The #223 pass notes that GitHub announced a new
ghs_APPID_JWTinstallation-token format (variable length, about 520 characters, rollout from 2026-04-27), which belongs to this family and not to the fine-grained one.
In this benchmark
- Fixtures
- 42
- Left readable
- 0
- Redacted too much
- 0
- False alarms
- 0
42 fixtures: 29 expect a redaction, 12 must stay quiet, 1 record project policy. See every row
| Evidence level | Fixtures | Left readable | Too much | False alarms |
|---|---|---|---|---|
| T1Provider-documented | 32 | 0 | 0 | 0 |
| T2Tool-corroborated | 3 | 0 | 0 | 0 |
| T3Project policy | 4 | 0 | 0 | 0 |
| T0Pending review | 3 | 0 | 0 | 0 |
Every scanner on the same fixtures
In run order. Counts are what each scanner recorded on this family's fixtures, whichever rules it has; a scanner with no rule for the family has nothing to report on it.
| Scanner | Fixtures | Left readable | Too much | False alarms |
|---|---|---|---|---|
| flare-redactRuntime library · 1.6.1 · Published npm package · secrets-only (pii, generic_assignment disabled) · JavaScript engine1 rule targets it | 42 | 6 | 2 | 0 |
| gitleaksRepository scanner · 8.30.1 · Directory scan · default rules1 rule targets it | 42 | 8 | 0 | 0 |
| redact-secretProduct measured here · 0.1.0-beta.14 · Published npm package · default detectors1 detector mapped | 42 | 0 | 0 | 0 |
| trufflehogRepository scanner · 3.97.4 · Filesystem scan · verification disabled1 rule targets it | 42 | 20 | 0 | 1 |
Benchmark dossier questions
- Open caveat
- Only the prefix and token role are provider-documented; the 36-character body follows GitHub's 2021-04-05 token-format post (36-byte body, CRC32 in the last six characters), and core does not verify the checksum.
Scanner rules for this family
| Scanner | Rule | What the rule matches |
|---|---|---|
| flare-redact · rules 1.6.1 | github_token | ghp_, gho_, ghu_, ghs_, ghr_ + 36 or github_pat_ + 82 characters |
| gitleaks · rules 8.30.1 | github-app-token | ghu_ or ghs_ + 36 alphanumerics |
| openredaction · rules 1.1.5 | GITHUB_TOKEN | ghp_, gho_, ghu_, ghs_ or ghr_ + 36 or more characters |
| trufflehog · rules 3.97.4 | github/v2 | ghp_, gho_, ghu_, ghs_, ghr_ or github_pat_ + 36-255 characters |
Fixtures in this family
| Fixture | Kind and evidence | redact-secret |
|---|---|---|
installation-token-lifetime-noteenvironment-and-test-values-authored · app-installation-token-with-one-hour-lifetime-note | Project policyT3 · Project policy | Redacted |
github-token-short-body-35-twingithub · wrong-length | Must not flagT2 · Tool-corroborated · twin | Quiet |
add-mask-literal-argumentgithub-actions-workflow-commands-authored · workflow-command-literal-credential-in-command-text | Must redactT1 · Provider-documented | Redacted |
base-tokengithub-actions-workflow-commands-authored · workflow-command-literal-credential-in-command-text | Must redactT1 · Provider-documented | Redacted |
env-file-echogithub-actions-workflow-commands-authored · workflow-command-literal-credential-in-command-text | Must redactT1 · Provider-documented | Redacted |
multiline-record-eof-delimitergithub-actions-workflow-commands-authored · workflow-command-file-multiline-record-value | Must redactT1 · Provider-documented | Redacted |
multiline-record-guid-delimitergithub-actions-workflow-commands-authored · workflow-command-file-multiline-record-value | Must redactT1 · Provider-documented | Redacted |
multiline-record-written-by-brace-groupgithub-actions-workflow-commands-authored · workflow-command-file-multiline-record-value | Must redactT1 · Provider-documented | Redacted |
output-file-echogithub-actions-workflow-commands-authored · workflow-command-literal-credential-in-command-text | Must redactT1 · Provider-documented | Redacted |
base-opaquegithub-installation-token-authored · opaque-installation-token-forty-characters-literal | Must redactT1 · Provider-documented | Redacted |
base-stateless-agithub-installation-token-authored · stateless-jwt-installation-token-literal | Must redactT1 · Provider-documented | Redacted |
base-stateless-bgithub-installation-token-authored · stateless-jwt-installation-token-literal | Must redactT1 · Provider-documented | Redacted |
projection-opaque-crlfgithub-installation-token-authored · opaque-installation-token-forty-characters-literal | Must redactT1 · Provider-documented | Redacted |
projection-opaque-envgithub-installation-token-authored · opaque-installation-token-forty-characters-literal | Must redactT1 · Provider-documented | Redacted |
projection-opaque-jsongithub-installation-token-authored · opaque-installation-token-forty-characters-literal | Must redactT1 · Provider-documented | Redacted |
projection-stateless-a-authorizationgithub-installation-token-authored · stateless-jwt-installation-token-literal | Must redactT1 · Provider-documented | Redacted |
projection-stateless-a-crlfgithub-installation-token-authored · stateless-jwt-installation-token-literal | Must redactT1 · Provider-documented | Redacted |
projection-stateless-a-git-urlgithub-installation-token-authored · stateless-jwt-installation-token-literal | Must redactT1 · Provider-documented | Redacted |
projection-stateless-a-jsongithub-installation-token-authored · stateless-jwt-installation-token-literal | Must redactT1 · Provider-documented | Redacted |
projection-stateless-a-korean-emojigithub-installation-token-authored · stateless-jwt-installation-token-literal | Must redactT1 · Provider-documented | Redacted |
projection-stateless-b-envgithub-installation-token-authored · stateless-jwt-installation-token-literal | Must redactT1 · Provider-documented | Redacted |
ghs-1github · documented-format-literal | Must redactT1 · Provider-documented | Redacted |
ghs-2github · documented-format-literal | Must redactT1 · Provider-documented | Redacted |
ghs-3github · documented-format-literal | Must redactT1 · Provider-documented | Redacted |
github-token-ghs-plaingithub · documented-format-literal | Must redactT1 · Provider-documented | Redacted |
github-token-ghs-plain-twingithub · wrong-length | Must not flagT2 · Tool-corroborated · twin | Quiet |
github-token-ghs-unicode-crlfgithub · documented-format-literal | Must redactT1 · Provider-documented | Redacted |
github-token-ghs-unicode-crlf-twingithub · wrong-length | Must not flagT2 · Tool-corroborated · twin | Quiet |
github-token-installation-clone-urlgithub · documented-format-literal | Must redactT1 · Provider-documented | Redacted |
github-token-shape-4-baregithub · documented-format-literal | Must redactT1 · Provider-documented | Redacted |
github-token-shape-4-quotedgithub · documented-format-literal | Must redactT1 · Provider-documented | Redacted |
github-token-shape-4-unicode-crlfgithub · documented-format-literal | Must redactT1 · Provider-documented | Redacted |
expression-in-environment-file-echogithub-actions-workflow-commands-authored · workflow-command-reference-and-masked-value-in-place-of-credential | Must not flagT1 · Provider-documented | Quiet |
runner-mask-in-log-linegithub-actions-workflow-commands-authored · workflow-command-reference-and-masked-value-in-place-of-credential | Must not flagT1 · Provider-documented | Quiet |
unset-secret-expanded-to-empty-valuegithub-actions-workflow-commands-authored · workflow-command-reference-and-masked-value-in-place-of-credential | Must not flagT1 · Provider-documented | Quiet |
variable-reference-in-add-mask-and-outputgithub-actions-workflow-commands-authored · workflow-command-reference-and-masked-value-in-place-of-credential | Must not flagT1 · Provider-documented | Quiet |
base-ids-jsongithub-installation-token-authored · github-app-and-installation-ids-in-json | Must not flagT3 · Project policy | Quiet |
base-no-app-idgithub-installation-token-authored · installation-token-without-app-id-segment | Pending reviewT0 · Pending | Unscored |
base-prose-notationgithub-installation-token-authored · installation-token-notation-and-header-name-in-prose | Must not flagT3 · Project policy | Quiet |
mutation-incomplete-header-onlygithub-installation-token-authored · installation-token-with-incomplete-jwt-segments | Pending reviewT0 · Pending · twin | Unscored |
mutation-incomplete-no-signaturegithub-installation-token-authored · installation-token-with-incomplete-jwt-segments | Pending reviewT0 · Pending · twin | Unscored |
twin-body-35github-installation-token-authored · installation-token-body-shorter-than-documented-minimum | Must not flagT3 · Project policy · twin | Quiet |
Sources
Documentation and code
- docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats
Research log
- redact-secret/redact-secret#517Research issue
- Final evidence, pinned to a commit/redact-secret/redact-secret/blob/8b6a5fde52ecb4dfce13f09c7a947062d21483c7/docs/decisions/2026-09-20-map-github-token-families-onto-independent-finding-types.md