This document details the GitHub App architecture, permission requirements, authentication token exchange, rate limit management, and webhook handling.
CodeForensics deliberately uses a GitHub App for the following architectural reasons:
- High Rate Limits: GitHub Apps receive an hourly rate limit of 5,000 requests per hour per installation (scaling up for large organizations), compared to only 60 req/hr for unauthenticated calls and 1,000 for OAuth apps.
- Organization-Level Access: Administrators can install the app on an organization and select specific repositories without granting access to their entire GitHub account.
- Short-Lived Installation Access Tokens: Instead of long-lived personal access tokens stored in databases, CodeForensics signs a JWT with its RSA private key and requests short-lived (1-hour) installation access tokens on-demand.
- Least-Privilege Principle: The app requests strictly read-only permissions on metadata, contents, issues, and pull requests.
When creating your GitHub App in GitHub Developer Settings, configure these exact permissions:
| Resource | Access Level | Why It Is Required |
|---|---|---|
| Issues | Read-only |
Ingests issue titles, descriptions, closing references, labels, and state transitions. |
| Pull requests | Read-only |
Ingests PR descriptions, branch references (baseRef, headRef), and merge commits. |
| Contents | Read-only |
Reads commit logs, file contents, and line ranges for Code Archaeology. |
| Metadata | Read-only |
Mandatory default for reading repository name, owner, and visibility. |
All other permissions (Administration, Secrets, Checks, Actions, Packages, etc.) must remain set to No access.
installation(created, deleted)installation_repositories(added, removed)issues(opened, closed, labeled)pull_request(opened, closed, merged)
GitHub signs all webhook payloads using HMAC-SHA256 with the shared GITHUB_WEBHOOK_SECRET:
import crypto from 'node:crypto';
export function verifyWebhookSignature(payload: string, signature: string, secret?: string): boolean {
if (!signature || !secret) return false;
try {
const hmac = crypto.createHmac('sha256', secret);
const expected = 'sha256=' + hmac.update(payload).digest('hex');
const expectedBuf = Buffer.from(expected);
const actualBuf = Buffer.from(signature);
if (expectedBuf.length !== actualBuf.length) return false;
return crypto.timingSafeEqual(expectedBuf, actualBuf);
} catch {
return false;
}
}Timing-safe string comparison (crypto.timingSafeEqual) prevents timing attacks.
- Private Key Storage: The RSA private key (
.pemformat) is stored in./secrets/github-private-key.pemand referenced viaGITHUB_PRIVATE_KEY_PATH. - JWT Generation: The
octokitSDK signs a JSON Web Token with a maximum expiration of 10 minutes. - Installation Token Exchange: The JWT is exchanged with GitHub's
/app/installations/:installation_id/access_tokensendpoint for a 1-hour bearer token. - Zero Client Leakage: Installation tokens are held strictly in memory by
apps/apiandapps/workerand never exposed over the REST API toapps/web.