GitHub App auth for agent repos
The agent reads scopes and actions from the repos you list in AGENT_REPO. By default, a private repo is cloned with a static token embedded in its URL:
AGENT_REPO="https://<git-token>@github.com/your-org/private-repo.git#main"
That token is long-lived, tied to a user account, and stops working the day it expires or gets rotated. A GitHub App replaces it with short-lived installation tokens that the agent mints on demand and renews on its own, with no restart and no token in the URL.
You declare credentials per GitHub organization, so an agent that serves repos from several orgs uses a different App for each one.
How it works
Each time the agent runs a git operation against a repo whose org has an App configured, it asks for a fresh installation token and authenticates with it. Tokens are cached per organization and re-minted before they expire, so a long-running agent keeps pulling without any manual rotation.
This applies only to HTTPS github.com remotes. SSH remotes, GitHub Enterprise, and other git providers are not affected and keep using the credentials you already configured for them.
Set up the GitHub App
In the GitHub organization that owns your agent repo, create a GitHub App with:
- Repository permissions:
Contents: Read-onlyandMetadata: Read. - A new private key. Download the
.pemfile when GitHub shows it, since you can't retrieve it later.
Install the App on the organization and grant it access to the repos the agent needs. Then note the App ID from the App's settings page. You can also note the Installation ID (the numeric ID at the end of the URL after installing), but it's optional — the agent resolves it from the organization when you leave it out.
Repeat this for every organization whose repos the agent clones.
Configure the agent
Pass one --github-app flag per organization:
agent \
--github-app "org=acme-core,app-id=123456,installation-id=78901234,private-key=/etc/np/keys/acme-core.pem" \
--github-app "org=acme-labs,app-id=654321,private-key-ssm-parameter=/np/gh/acme-labs-key"
Flag keys
| Key | Required | Description |
|---|---|---|
org | Yes | GitHub organization the App is installed in. Case-insensitive |
app-id | Yes | The App's App ID or Client ID |
installation-id | No | Pins the installation and skips the lookup. Resolved from the org when omitted |
private-key | One of the two | Path to the App's private key .pem file |
private-key-ssm-parameter | One of the two | Name of an AWS SSM SecureString parameter holding the PEM |
Each organization can appear only once, and each entry needs exactly one key source.
Inline PEM values aren't accepted. Point the agent at a file path or an SSM parameter name.
On Kubernetes
Don't set the flag by hand. The nullplatform-agent chart takes the same credentials under github.apps, one entry per organization, and renders them into the agent's NP_GITHUB_APPS environment variable for you.
In production, keep the PEMs in a Secret you manage and point the chart at it:
github:
secret:
create: false
name: my-github-app-keys
apps:
- org: acme-core
appId: "123456"
installationId: "78901234"
privateKeySecretKey: acme-core.pem
- org: acme-labs
appId: "654321"
privateKeySsmParameter: /np/gh/acme-labs-key
| Value | Description |
|---|---|
github.apps[].org | GitHub organization the App is installed in |
github.apps[].appId | The App's App ID or Client ID |
github.apps[].installationId | Optional. Resolved from the org when unset |
github.apps[].privateKeySecretKey | PEM file from the Secret. Defaults to <org>.pem |
github.apps[].privateKeySsmParameter | AWS SSM parameter holding the PEM. Needs IRSA on the service account |
github.apps[].privateKey | Inline PEM. Requires github.secret.create: true, so dev only |
github.secret.create | Whether the chart creates the Secret from inline privateKey values |
github.secret.name | Existing Secret holding one PEM per org |
github.mountPath | Where the PEMs are mounted. Defaults to /etc/nullplatform/github-apps |
Each entry needs exactly one key source. Leave github.apps empty to disable GitHub App auth entirely.
github.apps requires chart version 3.0.0 or later. It replaces githubTokenInit, which was removed in that release.
AWS permissions for SSM private keys
If you store a private key with private-key-ssm-parameter (or privateKeySsmParameter in the chart), the agent reads it from AWS Systems Manager at startup. Store the PEM in a parameter of type SecureString, and grant the agent's IAM identity:
| Action | On | Why |
|---|---|---|
ssm:GetParameter | Each parameter holding a PEM | Reads the parameter |
kms:Decrypt | The KMS key that encrypts those parameters | The agent always requests the value with decryption enabled |
A minimal policy for two organizations looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ssm:GetParameter",
"Resource": [
"arn:aws:ssm:us-east-1:123456789012:parameter/np/gh/acme-core-key",
"arn:aws:ssm:us-east-1:123456789012:parameter/np/gh/acme-labs-key"
]
},
{
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:us-east-1:123456789012:key/abcd1234-a123-456a-a12b-a123b4cd56ef"
}
]
}
Keep in mind:
-
Scope the resources. List only the parameters the agent actually reads, rather than granting
parameter/*. -
Customer managed keys need both sides. If your parameters use your own KMS key, allow the agent's role in the key policy too. An IAM policy alone isn't enough.
-
On EKS, use IRSA. Attach the policy to a role and annotate the agent's service account with it:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/nullplatform-agent-irsa -
Outside EKS, the agent uses the standard AWS credential chain, so an instance profile or the environment's configured credentials work as usual.
These are the same permissions the agent needs for --ssm-apikey-parameter. If you already use that, extend the existing policy with the new parameter ARNs instead of creating a second role.
If you'd rather not grant SSM access at all, use private-key and mount the PEM as a file instead.
Migrate an existing agent
You can move one organization at a time, and the agent keeps running the whole way through.
-
Add the App entry for the org and restart the agent.
-
Drop the token from the URL. Once the org has an App configured, the agent strips any credentials embedded in the repo URL and authenticates with the installation token instead. You can simplify
AGENT_REPOto a clean URL:AGENT_REPO="https://github.com/your-org/private-repo.git#main" -
Repeat for the remaining orgs. Any org without an entry keeps using the token in its URL, and the agent logs a warning naming that org so you can see what's still pending.
Repos already on disk are re-addressed in place, so there's no need to wipe the agent's working directory.
Rolling back is just as simple. Remove the org's --github-app entry, put the token back in the repo URL, and restart.
Security notes
- Private keys and minted tokens stay in memory. They're never written to disk or to logs.
- Clone and fetch errors have any credentials in the repo URL redacted before they reach the logs.
- Installation tokens are short-lived and scoped to the repos you granted the App, unlike a personal access token tied to a user account.
Related pages
- Install the agent with Helm — where
AGENT_REPOis configured. - Authenticate private repos with a GitHub App — the chart's init container flow, which clones the repo once at pod startup.
- Refresh agent sources — pull new commits into a running agent.