Description
gh auth refresh --remove-scopes gist completes successfully but the gist scope is not actually removed from the token.
Motivation
Removing the gist scope is useful as a defense against prompt injection attacks, where a malicious prompt could instruct an AI agent to exfiltrate sensitive files (e.g. .env) by publishing them as a Gist. Revoking the scope at the credential level ensures that even a compromised agent cannot perform this action, regardless of what it is instructed to do.
Steps to reproduce
gh auth refresh --remove-scopes gist
gh auth status
The gist scope remains in the token after the command completes.
Expected behavior
The gist scope is removed from the token, as with any other scope passed to --remove-scopes.
Actual behavior
The gist scope is silently preserved. No error or warning is shown.
Root cause
internal/authflow/flow.go always prepends gist to the scope list as a minimum scope:
minimumScopes := []string{"repo", "read:org", "gist"}
scopes := append(minimumScopes, additionalScopes...)
So even after refresh.go removes gist from additionalScopes, AuthFlow adds it back unconditionally.
However, HeaderHasMinimumScopes (used to validate tokens) only requires repo and read:org — gist is not actually a minimum requirement for gh to function. It is only needed for gh gist subcommands.
The --remove-scopes help text documents this as intentional ("The minimum set of scopes (repo, read:org, and gist) cannot be removed"), but this appears to be describing the current broken behavior rather than a deliberate design decision.
Related: #4690
Environment
gh version 2.95.0 (2026-06-17)
Description
gh auth refresh --remove-scopes gistcompletes successfully but thegistscope is not actually removed from the token.Motivation
Removing the
gistscope is useful as a defense against prompt injection attacks, where a malicious prompt could instruct an AI agent to exfiltrate sensitive files (e.g..env) by publishing them as a Gist. Revoking the scope at the credential level ensures that even a compromised agent cannot perform this action, regardless of what it is instructed to do.Steps to reproduce
The
gistscope remains in the token after the command completes.Expected behavior
The
gistscope is removed from the token, as with any other scope passed to--remove-scopes.Actual behavior
The
gistscope is silently preserved. No error or warning is shown.Root cause
internal/authflow/flow.goalways prependsgistto the scope list as a minimum scope:So even after
refresh.goremovesgistfromadditionalScopes,AuthFlowadds it back unconditionally.However,
HeaderHasMinimumScopes(used to validate tokens) only requiresrepoandread:org—gistis not actually a minimum requirement forghto function. It is only needed forgh gistsubcommands.The
--remove-scopeshelp text documents this as intentional ("The minimum set of scopes (repo,read:org, andgist) cannot be removed"), but this appears to be describing the current broken behavior rather than a deliberate design decision.Related: #4690
Environment