Introduction
GitHub API tokens are the keys to your kingdom—they grant access to repositories, organizational data, and critical development infrastructure. When an application using GitHub's @octokit packages mishandles these credentials, the consequences can be devastating: unauthorized code commits, data exfiltration, repository deletion, or complete organizational compromise.
A critical vulnerability was recently discovered in an application's GitHub API integration that left authentication tokens exposed through multiple attack vectors. This post examines how this vulnerability manifested, why it's so dangerous, and most importantly, how to implement proper secrets management to prevent similar issues in your own applications.
The Vulnerability Explained
What Went Wrong?
The application used @octokit packages to interact with GitHub's API for processing approved requests from issues—a common pattern in automated workflow tools. However, the implementation lacked any form of secure secrets management, creating multiple pathways for token exposure:
1. Hardcoded Credentials
// VULNERABLE: Token hardcoded in source file
const octokit = new Octokit({
auth: 'ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
});
2. Committed to Version Control
Configuration files containing tokens were committed to Git repositories, leaving a permanent history of credentials accessible to anyone with repository access—or worse, in public repositories.
3. Insecure Environment Variable Handling
// VULNERABLE: No validation or secure loading
const token = process.env.GITHUB_TOKEN;
// Token might be logged, exposed in error messages, or leaked through process inspection
4. Inadequate File Permissions
Configuration files stored tokens with world-readable permissions (e.g., chmod 644), allowing any user on the system to access them.
5. Exposure Through Logging
// VULNERABLE: Token leaked in error messages
console.error('Failed to authenticate with token:', token);
Real-World Impact: Attack Scenarios
Scenario 1: Repository Compromise
An attacker discovers a hardcoded token in a public GitHub repository. Using this token, they:
- Clone all private repositories
- Inject malicious code into the main branch
- Access sensitive intellectual property
- Delete critical repositories
Scenario 2: Supply Chain Attack
A token with write access is exposed. Attackers use it to:
- Modify package.json in popular open-source projects
- Inject backdoors into npm packages
- Compromise thousands of downstream users
Scenario 3: Data Exfiltration
With read access via an exposed token, attackers systematically:
- Download proprietary source code
- Extract secrets from other repositories
- Map organizational structure and security practices
- Sell information to competitors
Scenario 4: CI/CD Pipeline Manipulation
Attackers leverage the token to:
- Modify GitHub Actions workflows
- Inject cryptocurrency miners into build processes
- Exfiltrate environment variables containing additional secrets
- Pivot to cloud infrastructure credentials
The Fix: Implementing Secure Secrets Management
Core Security Improvements
The vulnerability fix requires implementing a comprehensive secrets management strategy:
1. Environment-Based Configuration
// SECURE: Load from environment with validation
import * as dotenv from 'dotenv';
dotenv.config();
function getGitHubToken(): string {
const token = process.env.GITHUB_TOKEN;
if (!token) {
throw new Error('GITHUB_TOKEN environment variable is required');
}
// Validate token format
if (!token.match(/^(ghp_|gho_|ghu_|ghs_|ghr_)[a-zA-Z0-9]{36,}$/)) {
throw new Error('Invalid GitHub token format');
}
return token;
}
const octokit = new Octokit({
auth: getGitHubToken()
});
2. Secrets Management Service Integration
// SECURE: Use dedicated secrets manager
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
async function getGitHubTokenFromVault(): Promise<string> {
const client = new SecretsManagerClient({ region: 'us-east-1' });
try {
const response = await client.send(
new GetSecretValueCommand({
SecretId: 'github/api-token'
})
);
return JSON.parse(response.SecretString).token;
} catch (error) {
// Log error without exposing token
console.error('Failed to retrieve GitHub token from secrets manager');
throw new Error('Authentication configuration error');
}
}
3. Secure Error Handling
// SECURE: Never log sensitive data
try {
const response = await octokit.rest.issues.list({
owner: 'example',
repo: 'project'
});
} catch (error) {
// Sanitize error messages
console.error('GitHub API request failed:', {
message: error.message,
status: error.status,
// Never include: auth headers, tokens, or request details
});
throw new Error('Failed to fetch issues');
}
4. Configuration File Security
# Set restrictive permissions on .env files
chmod 600 .env
# Add to .gitignore
echo ".env" >> .gitignore
echo "*.secret" >> .gitignore
echo "config/secrets.json" >> .gitignore
5. Token Rotation and Scoping
// SECURE: Use fine-grained tokens with minimal permissions
const octokit = new Octokit({
auth: getGitHubToken(),
// Use fine-grained tokens with specific repository access
// and limited permissions (e.g., read-only for issues)
});
// Implement token rotation
async function rotateTokenIfNeeded() {
const tokenAge = await getTokenAge();
const MAX_TOKEN_AGE = 90 * 24 * 60 * 60 * 1000; // 90 days
if (tokenAge > MAX_TOKEN_AGE) {
await rotateGitHubToken();
}
}
Package.json Security Configuration
{
"name": "secure-github-integration",
"version": "2.0.0",
"scripts": {
"prestart": "node scripts/validate-secrets.js",
"start": "node src/index.js",
"audit": "npm audit && node scripts/check-secrets.js"
},
"dependencies": {
"@octokit/rest": "^19.0.0",
"dotenv": "^16.0.0"
},
"devDependencies": {
"detect-secrets": "^1.0.0",
"git-secrets": "^1.3.0"
}
}
Prevention & Best Practices
1. Implement Pre-Commit Hooks
Use tools like git-secrets or detect-secrets to prevent accidental commits:
# Install git-secrets
brew install git-secrets
# Configure for repository
git secrets --install
git secrets --register-aws
git secrets --add 'ghp_[a-zA-Z0-9]{36,}'
git secrets --add 'gho_[a-zA-Z0-9]{36,}'
2. Use Environment Variable Validators
Create a validation script that runs before application startup:
// scripts/validate-secrets.js
const requiredSecrets = ['GITHUB_TOKEN'];
function validateEnvironment() {
const missing = requiredSecrets.filter(key => !process.env[key]);
if (missing.length > 0) {
console.error('Missing required environment variables:', missing);
process.exit(1);
}
// Check for suspicious patterns
Object.entries(process.env).forEach(([key, value]) => {
if (value && value.match(/^(ghp_|gho_)/)) {
console.warn(`Warning: GitHub token detected in ${key}`);
}
});
}
validateEnvironment();
3. Adopt Secrets Management Solutions
For Production:
- AWS Secrets Manager
- HashiCorp Vault
- Azure Key Vault
- Google Cloud Secret Manager
For Development:
- dotenv with .env.example templates
- direnv for directory-based environments
- docker-compose secrets
4. Implement Security Scanning
Add automated security scanning to your CI/CD pipeline:
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
- name: Run TruffleHog
uses: trufflesecurity/trufflehog@main
with:
path: ./
5. Follow the Principle of Least Privilege
When creating GitHub tokens:
- Use fine-grained personal access tokens instead of classic tokens
- Grant only the minimum required permissions
- Scope tokens to specific repositories
- Set expiration dates (maximum 90 days)
- Use separate tokens for different applications
6. Security Standards & References
This vulnerability maps to several security standards:
- CWE-798: Use of Hard-coded Credentials
- CWE-522: Insufficiently Protected Credentials
- OWASP Top 10 2021: A07:2021 – Identification and Authentication Failures
- NIST SP 800-53: IA-5 (Authenticator Management)
7. Monitoring & Incident Response
Implement monitoring to detect token compromise:
// Monitor for unusual API usage
async function monitorAPIUsage() {
const octokit = new Octokit({ auth: getGitHubToken() });
const rateLimit = await octokit.rest.rateLimit.get();
if (rateLimit.data.rate.remaining < rateLimit.data.rate.limit * 0.1) {
// Alert on unusual consumption
console.warn('Unusual API rate limit consumption detected');
// Trigger security review
}
}
If a token is compromised:
1. Immediately revoke the token in GitHub settings
2. Rotate all potentially affected credentials
3. Review audit logs for unauthorized access
4. Scan repositories for unauthorized changes
5. Notify security team and stakeholders
Conclusion
GitHub API token exposure represents a critical vulnerability that can lead to complete compromise of your development infrastructure, intellectual property theft, and supply chain attacks. The lack of proper secrets management isn't just a coding oversight—it's a fundamental security failure that puts entire organizations at risk.
The key takeaways from this vulnerability:
- Never hardcode credentials in source files or configuration
- Use dedicated secrets management solutions for production environments
- Implement automated scanning to detect secrets before they're committed
- Apply least privilege principles when creating API tokens
- Monitor and rotate credentials regularly
- Educate your team about secure secrets handling
Remember: security is not a feature you add later—it's a fundamental requirement from day one. By implementing proper secrets management, you protect not just your own systems, but the entire ecosystem that depends on your code.
Stay secure, and always treat your API tokens like the master keys they are.
Resources:
- GitHub Token Security Best Practices
- OWASP Secrets Management Cheat Sheet
- CWE-798: Use of Hard-coded Credentials