Comprehensive security implementation guide for production deployment.
This integration implements multiple layers of security to protect sensitive credentials and ensure secure communication between Salesforce and GitHub.
┌─────────────────────────────────────────────────────────────┐
│ Security Layers │
├─────────────────────────────────────────────────────────────┤
│ 1. Protected Custom Metadata (Salesforce Platform) │
│ 2. JWT Token (10-minute expiry, RSA-SHA256) │
│ 3. Installation Token (1-hour expiry, scoped permissions) │
│ 4. Named Credentials (Endpoint management, SSL/TLS) │
│ 5. HMAC-SHA256 Webhook Verification (Payload integrity) │
│ 6. Permission Sets (Access control) │
└─────────────────────────────────────────────────────────────┘
Implementation:
<!-- GitHub_App_Settings__mdt -->
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<visibility>Protected</visibility>
</CustomObject>Benefits:
- ✅ Not accessible through standard API calls
- ✅ Not visible in UI to standard users
- ✅ Only accessible through Apex code
- ✅ Requires explicit permission set assignment
- ✅ Audit trail through Setup Audit Trail
Access Control:
// Only code with proper permissions can access
GitHub_App_Settings__mdt settings = [
SELECT App_Id__c, Installation_Id__c, Private_Key_Base64__c
FROM GitHub_App_Settings__mdt
WHERE DeveloperName = 'salesforce_gforce_devhub'
LIMIT 1
];Token Characteristics:
- ⏱️ Expiry: 10 minutes maximum (GitHub limit)
- 🔐 Algorithm: RSA-SHA256
- 🎯 Scope: Bound to specific GitHub App ID
- 🔄 Usage: Single-use, generated fresh for each request
Implementation:
private static String generateJWT(String appId, String privateKeyBase64) {
Map<String, Object> header = new Map<String, Object>{
'alg' => 'RS256',
'typ' => 'JWT'
};
Long issuedAt = DateTime.now().getTime() / 1000;
Long expiresAt = issuedAt + 600; // 10 minutes
Map<String, Object> payload = new Map<String, Object>{
'iat' => issuedAt,
'exp' => expiresAt,
'iss' => appId
};
// Sign with private key
return signJWT(header, payload, privateKeyBase64);
}Security Considerations:
- 🚫 Never log or expose JWT tokens
- 🚫 Don't cache JWT tokens (generate fresh each time)
- ✅ Validate expiration on every use
- ✅ Use strong RSA keys (2048 or 4096 bits)
Token Characteristics:
- ⏱️ Expiry: 1 hour
- 🎯 Scope: Repository-specific, limited permissions
- 🔄 Refresh: Automatic with new JWT
- 📝 Permissions: Only what's explicitly granted (Actions: write, Contents: read)
Caching Strategy (Recommended):
public static String getInstallationToken() {
// Check cache first (50-minute TTL for safety)
String cachedToken = (String)Cache.Org.get('github_installation_token');
if (cachedToken != null) {
return cachedToken;
}
// Generate new token
String jwt = generateJWT();
String installationToken = exchangeJWTForToken(jwt);
// Cache for 50 minutes (10-minute buffer before expiry)
Cache.Org.put('github_installation_token', installationToken, 3000);
return installationToken;
}Benefits:
- ✅ Reduces API calls to GitHub
- ✅ Improves performance
- ✅ Stays within rate limits
- ✅ Automatic expiry handling
Configuration:
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
<endpoint>https://api.github.com</endpoint>
<label>GitHub API</label>
<principalType>Anonymous</principalType>
<protocol>Password</protocol>
</NamedCredential>Security Benefits:
- ✅ No hardcoded URLs in code
- ✅ Centralized endpoint management
- ✅ Automatic SSL/TLS enforcement
- ✅ Automatic CORS handling
- ✅ Remote Site Settings managed automatically
Usage:
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:GitHub_API/repos/owner/repo/actions/workflows');
req.setMethod('GET');
req.setHeader('Authorization', 'Bearer ' + installationToken);HMAC-SHA256 Implementation:
private static Boolean verifySignature(String payload, String signature, String secret) {
// GitHub sends: X-Hub-Signature-256: sha256=<hex_digest>
String expectedSignature = 'sha256=' +
EncodingUtil.convertToHex(
Crypto.generateMac('hmacSHA256',
Blob.valueOf(payload),
Blob.valueOf(secret)
)
);
return signature == expectedSignature;
}Security Flow:
GitHub Webhook → Salesforce
↓
Extract X-Hub-Signature-256
↓
Calculate HMAC-SHA256(payload, secret)
↓
Compare signatures
↓
Match? → Process | Reject → Return 401
Protection Against:
- ✅ Man-in-the-middle attacks
- ✅ Replay attacks
- ✅ Payload tampering
- ✅ Unauthorized webhook sources
Principle of Least Privilege:
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<label>GitHub Integration Admin</label>
<hasActivationRequired>false</hasActivationRequired>
<!-- Custom Metadata Type Access -->
<customMetadataTypeAccesses>
<enabled>true</enabled>
<name>GitHub_App_Settings__mdt</name>
</customMetadataTypeAccesses>
<!-- Apex Class Access -->
<classAccesses>
<apexClass>GitHubAppAuthService</apexClass>
<enabled>true</enabled>
</classAccesses>
</PermissionSet>Best Practices:
- ✅ Assign only to users who need to trigger workflows
- ✅ Regular review of permission set assignments
- ✅ Remove access when no longer needed
- ✅ Use profiles + permission sets for granular control
Why This Approach?
This integration uses Protected Custom Metadata to store the GitHub App private key instead of Salesforce External Credentials or Certificate/Key Pairs. Here's why:
Technical Limitation: Salesforce External Credentials with certificate authentication require:
- X.509 certificate format (
.crtor.cer) - Complete certificate chain
- Certificate metadata (issuer, subject, validity period)
GitHub App Private Keys:
- Provided in PKCS#1 or PKCS#8 format (
.pem) - Standalone RSA private keys (no certificate wrapper)
- No issuer or certificate chain
- Self-signed by GitHub for your app only
The Problem:
# GitHub provides this:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----
# Salesforce requires this:
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKL... # X.509 certificate
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0... # With certificate context
-----END PRIVATE KEY-----Why Conversion Fails:
- No Certificate Authority: GitHub keys aren't issued by a CA
- Self-Signed Limitations: Converting to self-signed cert loses GitHub's signature
- Format Mismatch: Salesforce expects PKCS#12 or JKS, not raw PEM
- Metadata Missing: No subject, issuer, or validity period in GitHub keys
Attempted Workarounds (All Failed):
# Attempt 1: Convert to PKCS#8
openssl pkcs8 -topk8 -inform PEM -outform PEM \
-in github-key.pem -out pkcs8.pem -nocrypt
# Result: Still not X.509 certificate
# Attempt 2: Create self-signed certificate
openssl req -new -x509 -key github-key.pem -out cert.pem -days 365
# Result: Changes key signature, GitHub rejects it
# Attempt 3: Create PKCS#12 bundle
openssl pkcs12 -export -out keystore.p12 \
-inkey github-key.pem -in cert.pem
# Result: Salesforce import fails (missing certificate chain)Pros:
- ✅ Built-in Salesforce feature
- ✅ Protected visibility restricts access
- ✅ No additional infrastructure needed
- ✅ Easy to deploy and manage
- ✅ Audit trail through Setup Audit Trail
Cons:
⚠️ No field-level encryption (Custom Metadata limitation)⚠️ Visible to system administrators⚠️ Included in change sets and metadata backups
For enterprise production environments, consider using external secret managers:
Architecture:
Salesforce → Named Credential → AWS API Gateway → Secrets Manager
Implementation:
public class SecureCredentialService {
public static String getPrivateKey() {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:AWS_Secrets_Manager/github-private-key');
req.setMethod('GET');
Http http = new Http();
HttpResponse res = http.send(req);
Map<String, Object> secret = (Map<String, Object>) JSON.deserializeUntyped(
res.getBody()
);
return (String) secret.get('privateKey');
}
}Architecture:
Salesforce → Named Credential → Vault API → Encrypted Secret
Benefits:
- ✅ Secrets never stored in Salesforce
- ✅ Centralized secret rotation
- ✅ Additional audit logging
- ✅ Encryption at rest and in transit
- ✅ Fine-grained access policies
Note: Custom Metadata Types don't support Platform Encryption. Alternative approach:
// Store encrypted credentials in Custom Object
public class EncryptedCredentialService {
public static String getPrivateKey() {
Encrypted_Credential__c cred = [
SELECT Private_Key_Encrypted__c
FROM Encrypted_Credential__c
WHERE Name = 'GitHub_App'
LIMIT 1
];
return cred.Private_Key_Encrypted__c; // Automatically decrypted
}
}| Credential | Rotation Frequency | Priority |
|---|---|---|
| Private Key | Every 90 days | High |
| Webhook Secret | Every 180 days | Medium |
| JWT Token | Auto (10 min) | N/A |
| Installation Token | Auto (1 hour) | N/A |
Step 1: Generate New Key
# In GitHub App settings
GitHub App → Private keys → Generate a private keyStep 2: Convert to Base64
cat new-private-key.pem | grep -v "BEGIN\|END" | tr -d '\n' > new_key_base64.txtStep 3: Update Salesforce
Setup → Custom Metadata Types → GitHub App Settings → Edit Record
→ Update Private_Key_Base64__c field
Step 4: Test
// Test connection with new key
GitHubActionsService.listWorkflows('owner', 'repo');Step 5: Revoke Old Key
GitHub App → Private keys → Revoke old key
Step 1: Generate New Secret
openssl rand -hex 32Step 2: Update Both Systems Simultaneously
1. Update GitHub App webhook secret
2. Update Salesforce Custom Metadata record
3. Test webhook delivery
Monitor:
- Custom Metadata record changes
- Permission set assignments
- Apex class modifications
Access:
Setup → Security → View Setup Audit Trail
Filter by: "GitHub"
Enable for integration users:
sf apex log set --level DEBUG --apex-code DEBUGBest Practices:
- 🚫 Don't log full JWT or installation tokens
- ✅ Log token generation success/failure
- ✅ Log API call results (without sensitive data)
- ✅ Log webhook verification results
Track (if Event Monitoring enabled):
- API Usage
- Login events
- Report exports
- Custom Metadata access
Check regularly:
GitHub App → Advanced → Recent Deliveries
- Webhook delivery success rate
- Response codes
- Failed deliveries
Immediate Actions:
⚠️ Revoke key in GitHub App settings- 🔄 Generate new private key
- 📝 Update Salesforce Custom Metadata
- ✅ Test new key works
- 📊 Review audit logs for unauthorized access
- 📧 Notify security team
Immediate Actions:
⚠️ Generate new webhook secret- 🔄 Update GitHub App and Salesforce simultaneously
- ✅ Test webhook delivery
- 📊 Review recent webhook deliveries for anomalies
Investigation Steps:
- 📊 Review Setup Audit Trail
- 📊 Review Debug Logs
- 📊 Check permission set assignments
- 🔍 Identify affected users
- 🚫 Remove unauthorized access
- 🔄 Rotate all credentials
- 📧 Document incident
- Protected Custom Metadata visibility set
- Private key stored in base64 format
- Strong webhook secret generated (32+ characters)
- Named Credentials configured
- Permission set created with minimal access
- Debug logs reviewed (no sensitive data)
- JWT generation tested
- Installation token exchange tested
- Webhook signature verification tested
- Permission set access tested
- Error handling tested (invalid credentials)
- Rate limiting tested
- All credentials rotated for production
- External secret management evaluated
- Monitoring configured
- Audit trail enabled
- Incident response plan documented
- Key rotation schedule established
- Backup admin assigned permission set
- Security review completed
- Quarterly private key rotation
- Semi-annual webhook secret rotation
- Monthly audit trail review
- Quarterly permission set review
- Annual security assessment
Data Stored:
- GitHub App ID (not personal data)
- Installation ID (not personal data)
- Private key (system credential)
- Webhook secret (system credential)
Recommendations:
- Document data flows in privacy policy
- Include in data processing agreements
- Regular access reviews
Control Implementations:
- ✅ Access control (permission sets)
- ✅ Encryption in transit (HTTPS/TLS)
- ✅ Audit logging (Setup Audit Trail)
- ✅ Credential rotation (documented process)
- ✅ Least privilege (scoped permissions)
Financial Services (PCI DSS):
- Strong cryptography (RSA-2048/4096, SHA-256)
- Key rotation policies
- Access control and logging
Healthcare (HIPAA):
- Audit controls
- Access management
- Transmission security
- OWASP API Security Top 10
- GitHub Security Best Practices
- Salesforce Security Guide
- JWT Best Practices
- HMAC Best Practices