GitHub Apps are widely used to automate development workflows, manage repositories, create pull requests, publish releases, and integrate external services with GitHub. They allow applications to interact with GitHub through APIs and webhooks without depending on a developer's personal account.
However, repository automation introduces an important security responsibility: deciding exactly what an application is allowed to access and modify.
A GitHub App that only needs to read repository contents should not receive permission to delete repositories, manage organization members, or modify repository settings. Similarly, an application that creates pull requests should not automatically receive access to every repository in an organization.
Overly broad permissions increase the potential damage if an application, private key, or access token is compromised.
GitHub Apps provide fine-grained permissions, repository-level installation access, and short-lived installation access tokens to help developers implement secure automation.
This article explains how GitHub App permissions work, how to select the minimum required access, how to generate restricted installation tokens, and how to manage permissions securely across multiple repositories.
Understanding GitHub App Permissions
A GitHub App operates as an application identity rather than simply using an individual developer's account.
When an organization installs a GitHub App, it grants the application access to selected repositories and resources according to the permissions requested by the app.
For example, consider an application that automatically creates pull requests whenever a dependency update is available.
The application may need to:
Read repository files.
Read existing pull requests.
Create new pull requests.
Add comments to pull requests.
Receive webhook notifications when repository events occur.
It does not necessarily need permission to delete repositories, manage organization members, or modify repository administration settings.
GitHub Apps allow these capabilities to be requested separately.
Permission categories
GitHub App permissions are organized into different categories.
Permission category | What it controls | Example |
|---|
Repository permissions | Access to resources inside repositories | Contents, Issues, Pull requests |
Organization permissions | Access to organization-level resources | Members, Organization administration |
Enterprise permissions | Access to enterprise-level resources | Enterprise organization management |
Account permissions | Access to user-related resources after authorization | User profile information |
The permissions available depend on the resources the application needs to access and the account where it is installed.
For most repository automation applications, repository permissions are the primary category.
Organization permissions should only be requested when the application genuinely needs to manage or read organization-level resources.
Repository permissions are different from repository access
There are two related but separate decisions when installing a GitHub App:
What operations can the application perform?
Which repositories can the application access?
For example, an application may request read access to repository contents and write access to pull requests.
An organization can then install that application on only three repositories rather than granting it access to every repository.
This separation is important because permission scope and repository scope control different aspects of access.
A narrowly permissioned application installed across hundreds of repositories still has broad repository reach. Likewise, an application installed on one repository but granted unnecessary administrative permissions may still have excessive access to that repository.
Secure automation requires controlling both.
Choosing the Minimum Required Permissions
The principle of least privilege means granting an application only the permissions required to complete its intended operations.
Before registering a GitHub App, list the operations it must perform.
For example, a pull request automation application may have the following requirements:
Operation | Required permission |
|---|
Read repository files | Contents: Read |
Read pull requests | Pull requests: Read |
Create pull requests | Pull requests: Write |
Add pull request comments through issue comments | Issues: Write |
Receive pull request events | Subscribe to the required webhook event |
The exact permissions depend on the GitHub API endpoints and operations used by the application.
A useful way to determine permissions is to start with the API endpoints rather than guessing based on the application name.
For example, if your application needs to create a pull request, review the permission requirements for the relevant REST API endpoint.
GitHub's REST API documentation specifies which GitHub App permissions are required for supported endpoints.
Example: A repository automation application
Suppose you are building an application that automatically creates a pull request when a configuration file changes.
The application needs to:
A possible permission configuration is:
Repository permissions
Contents:
Read and write
Pull requests:
Read and write
Metadata:
Read-only
The application does not need organization administration permissions for these operations.
If the application only creates pull requests without modifying repository files, it may not need write access to Contents at all.
Always verify the actual API operations before selecting write permissions.
Configuring Permissions When Registering a GitHub App
GitHub App permissions are configured during app registration.
To create or review an application:
Open GitHub.
Navigate to Settings.
Open Developer settings.
Select GitHub Apps.
Create a new GitHub App or open an existing one.
Locate the Permissions section.
Review repository, organization, and account permissions.
Select the minimum required access.
Configure only the webhook events needed by the application.
For an organization-owned application, use the organization's application settings and follow its installation and approval policies.
Configure repository permissions
Consider an application that reads repository files and creates pull requests.
Its permission configuration could look like this:
Repository permissions
Contents:
Read-only
Pull requests:
Read and write
Metadata:
Read-only
This is a starting point, not a universal permission template. If the application also needs to update files, Contents must have the required write access.
Avoid selecting broad permissions simply because they make development easier.
Subscribe only to required webhook events
GitHub Apps can receive webhook events when relevant repository or organization activities occur.
For example, a pull request automation application may need:
Webhook events
Pull request
Push
Installation
Installation repositories
The exact list should depend on the application's behavior.
An application that only reacts to pull request events does not need to subscribe to every available webhook.
Reducing unnecessary webhook subscriptions helps simplify application logic and reduces the amount of event data the application receives.
Restricting Which Repositories an App Can Access
After an application is created, an organization can control which repositories are available to it during installation.
GitHub Apps generally support two installation choices:
All repositories: The application can access all repositories within the installation account, subject to its permissions.
Only select repositories: The application can access only repositories explicitly selected during installation.
For most internal automation applications, selecting specific repositories is a safer starting point.
For example, consider an organization with these repositories:
company/
payment-service
customer-api
admin-dashboard
mobile-app
documentation
infrastructure
Suppose a release automation application only manages payment-service and customer-api.
Installing the application on all repositories gives it a wider scope than necessary.
Instead, select only:
payment-service
customer-api
If the application later needs access to another repository, an authorized administrator can update the installation.
This approach reduces unnecessary access and makes repository ownership easier to manage.
Generating Restricted Installation Access Tokens
A GitHub App uses its private key to authenticate as the application and generate an installation access token.
The installation access token is then used to make API requests on behalf of that installation.
An important security feature is that installation tokens can be further restricted when they are created.
You can limit:
This means the token can have narrower access than the overall application installation.
Understanding the authentication flow
The general authentication process looks like this:
GitHub App
|
v
Generate application JWT
|
v
Authenticate with GitHub
|
v
Request installation access token
|
v
Receive short-lived token
|
v
Call GitHub REST or GraphQL API
|
v
Token expires
The application JWT proves the identity of the GitHub App. The installation access token provides the actual access used for repository operations.
Generating an installation token using the REST API
GitHub provides an endpoint for creating installation access tokens:
POST /app/installations/INSTALLATION_ID/access_tokens
The request can include repository and permission restrictions.
For example:
{
"repository_ids": [12345678],
"permissions": {
"contents": "read",
"pull_requests": "write"
}
}
This requests a token limited to the specified repository and permissions.
The requested permissions must not exceed the permissions granted to the GitHub App installation.
If the application requires access to multiple repositories, include only the repository IDs necessary for that operation.
Why token-level restrictions matter
Consider an application installed on ten repositories.
A scheduled job may only need to update one repository.
Instead of generating a token with access to all ten repositories, request a token limited to the repository being processed.
This provides an additional layer of protection.
Even if the token is accidentally exposed, its access is restricted compared with a token that can access every installed repository.
Implementing GitHub App Authentication in C#
A backend service can generate an application JWT, exchange it for an installation access token, and use that token to call GitHub APIs.
The following example demonstrates the token-generation portion using C#.
First, install a JWT library:
dotnet add package System.IdentityModel.Tokens.Jwt
Create a service that generates the application JWT:
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
using System.Security.Claims;
public class GitHubAppTokenService
{
private readonly string _appId;
private readonly string _privateKey;
public GitHubAppTokenService(
string appId,
string privateKey)
{
_appId = appId;
_privateKey = privateKey;
}
public string GenerateJwt()
{
using var rsa = RSA.Create();
rsa.ImportFromPem(_privateKey);
var securityKey = new RsaSecurityKey(rsa);
var credentials = new SigningCredentials(
securityKey,
SecurityAlgorithms.RsaSha256
);
var now = DateTimeOffset.UtcNow;
var claims = new[]
{
new Claim(
JwtRegisteredClaimNames.Iss,
_appId
)
};
var token = new JwtSecurityToken(
claims: claims,
notBefore: now.AddSeconds(-30).UtcDateTime,
expires: now.AddMinutes(8).UtcDateTime,
signingCredentials: credentials
);
return new JwtSecurityTokenHandler()
.WriteToken(token);
}
}
GitHub App JWTs should use the application's identifier as the issuer, be signed using the registered private key, and have a short validity period. The example uses an eight-minute lifetime, within GitHub's supported JWT expiration limit.
Next, exchange the JWT for an installation access token:
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
public class GitHubInstallationTokenService
{
private readonly HttpClient _httpClient;
public GitHubInstallationTokenService(
HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> CreateTokenAsync(
string jwt,
long installationId,
long repositoryId,
CancellationToken cancellationToken)
{
var request = new HttpRequestMessage(
HttpMethod.Post,
$"https://api.github.com/app/installations/" +
$"{installationId}/access_tokens"
);
request.Headers.Authorization =
new AuthenticationHeaderValue(
"Bearer",
jwt
);
request.Headers.UserAgent.ParseAdd(
"RepositoryAutomationApp"
);
request.Headers.Accept.ParseAdd(
"application/vnd.github+json"
);
request.Content = JsonContent.Create(
new
{
repository_ids = new[] { repositoryId },
permissions = new
{
contents = "read",
pull_requests = "write"
}
}
);
using var response = await _httpClient.SendAsync(
request,
cancellationToken
);
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadFromJsonAsync<InstallationTokenResponse>(
cancellationToken
);
return result?.Token
?? throw new InvalidOperationException(
"GitHub did not return an installation token."
);
}
private sealed class InstallationTokenResponse
{
[JsonPropertyName("token")]
public string? Token { get; set; }
}
}
Register the HttpClient through dependency injection:
builder.Services.AddHttpClient<
GitHubInstallationTokenService>();
The example demonstrates the authentication and token-restriction flow. Production implementations should also validate the installation ID and repository ID, handle API errors, cache valid installation tokens, and manage private-key access securely.
The private key must never be included in source control, distributed to clients, or written to application logs.
Understanding Installation Tokens and User Access Tokens
GitHub Apps support different token types for different authentication scenarios.
The most important distinction is whether the application is acting independently or performing an operation on behalf of a user.
Token type | Purpose | Activity attribution |
|---|
Application JWT | Authenticates the GitHub App for application-level operations | Application identity |
Installation access token | Performs operations using an app installation's permissions | GitHub App |
User access token | Performs operations on behalf of an authorized user | User and application |
When to use an installation access token
Use an installation access token when an application performs automated operations independently of a specific user.
Examples include:
Automatically creating release pull requests.
Updating repository configuration.
Synchronizing repository metadata.
Publishing automated release information.
Responding to repository webhooks.
When to use a user access token
Use a user access token when an application needs to perform operations on behalf of an authenticated user.
For example, suppose a user signs in to a development portal and asks the application to create a pull request using their GitHub access.
A user access token is more appropriate because the operation should respect the user's permissions.
GitHub restricts a user access token based on both the app's permissions and the user's permissions.
An installation access token, on the other hand, is based on the app installation's permissions.
Choosing the wrong token type can result in an application performing operations with broader authority than the requesting user should have.
Securing GitHub App Credentials
A GitHub App's private key is one of its most sensitive assets.
The private key allows the application to generate JWTs and request installation access tokens for the accounts where the app is installed.
If an attacker obtains the private key, they may be able to authenticate as the application and request new installation tokens.
Follow these practices to protect it.
Store private keys in a secure vault
Avoid storing private keys directly in application source code or configuration files committed to Git.
For production applications, use a dedicated secret-management service such as Azure Key Vault or an equivalent platform.
The application should retrieve the key only when required and should restrict access to the service identity that needs it.
Rotate private keys
GitHub App private keys do not automatically expire.
You must manage their lifecycle and revoke keys that are no longer needed.
GitHub supports multiple private keys for an application, which allows rotation without requiring an immediate application outage.
A practical rotation process is:
Generate a replacement private key.
Store it securely.
Update the application to use the new key.
Verify that JWT generation and token creation work.
Revoke the old key.
Review logs and access activity.
Use short-lived installation access tokens
Installation access tokens expire after one hour.
This reduces the period during which a stolen token can remain usable.
Do not store installation tokens permanently or treat them like long-lived API credentials.
Cache them securely and request a replacement when they are close to expiration.
Never log authentication values
Avoid logging:
For troubleshooting, log safe metadata such as the installation identifier, repository identifier, operation name, response status, and correlation identifier.
Managing Permission Changes and Installation Approvals
Application permissions can change as automation requirements evolve.
For example, an application may initially read repository information but later need to create pull requests.
Adding new permissions can require installation owners to review and approve the changes.
Until the updated permissions are accepted, the installation continues to operate with the previously approved permissions.
This behavior matters when releasing new application functionality.
Do not assume that changing the permission configuration immediately grants new access to every existing installation.
Handle permission changes gracefully
A production application should be able to detect when it lacks the permissions required for an operation.
For example, an API request may return a 403 response when the application does not have sufficient permissions.
Instead of repeatedly retrying the request, the application should:
Identify the missing permission.
Record a safe diagnostic message.
Explain that the installation requires updated permissions.
Provide the appropriate installation or permission-review flow.
Continue supporting operations that still work with the existing permissions.
GitHub also provides installation-related webhook events that can help applications respond to installation changes and permission approval.
For example, the installation webhook can report when new permissions have been accepted.
This is useful when an application supports multiple organizations with different approval timelines.
Restricting GitHub App Installations Across an Organization
Organization owners can control who is allowed to install GitHub Apps.
By default, repository administrators may be able to install certain apps on repositories within an organization, depending on the permissions requested by the application and the organization's policies.
An organization can restrict installations so that only organization owners can install GitHub Apps.
This helps prevent individual repository administrators from introducing unapproved automation integrations.
A controlled approval process might look like this:
Developer requests GitHub App
|
v
Organization owner reviews app
|
v
Review requested permissions
|
v
Approve required repository access
|
v
Install on selected repositories
|
v
Monitor application activity
For larger organizations, maintain an approved-app inventory containing:
This inventory makes it easier to identify unnecessary integrations and applications that no longer require access.
Common Mistakes to Avoid
Mistake | Why it is risky | Better approach |
|---|
Granting all repository permissions | Increases the impact of compromised credentials | Request only the permissions required |
Installing an app on every repository | Expands the application's access unnecessarily | Select specific repositories |
Using installation tokens for user-driven operations | May bypass the intended user-level authorization boundary | Use user access tokens when acting on behalf of users |
Storing private keys in source control | Exposes persistent application credentials | Use a secure key vault |
Treating installation tokens as permanent | Tokens expire and can become invalid | Cache tokens and handle expiration |
Ignoring permission approval | New functionality may fail for existing installations | Detect and handle pending approvals |
Subscribing to every webhook event | Adds unnecessary event processing and data exposure | Subscribe only to required events |
Logging tokens during debugging | Creates another credential exposure point | Log safe operational metadata |
Best Practices for Secure GitHub App Automation
A secure GitHub App should be designed around restricted access, controlled authentication, and continuous monitoring.
The following practices provide a useful foundation:
Apply the principle of least privilege to repository, organization, and account permissions.
Separate permission scope from repository installation scope.
Install applications only on repositories they actually need.
Use installation access tokens for independent automation.
Use user access tokens for operations performed on behalf of users.
Restrict installation tokens to the required repositories and permissions.
Store private keys and client secrets in a secure secret-management service.
Rotate private keys and revoke credentials that are no longer required.
Use short-lived access tokens wherever supported.
Subscribe only to the webhook events required by the application.
Validate incoming webhook signatures.
Review application permissions periodically.
Monitor authentication, installation, and permission changes.
Maintain an incident-response plan for compromised credentials.
Conclusion
GitHub Apps provide a flexible way to automate repository operations, but their security depends on how permissions, installations, and credentials are managed.
Granting broad access may make initial development easier, but it also increases the impact of a compromised application or token. A better approach is to identify the exact operations an application needs, request only the required permissions, and restrict installation access to selected repositories.
Installation access tokens provide another security boundary because they can be limited to specific repositories and permissions. Combined with secure private-key storage, short-lived tokens, webhook validation, and organization-level approval controls, they help create a safer automation environment.
The most important principle is simple: a GitHub App should have enough access to perform its intended task, but no more than that. This makes repository automation easier to govern and reduces unnecessary security exposure as organizations scale their development workflows.
Join the conversation! Your thoughts help the community grow.