This document outlines the security measures, practices, and considerations for gh-notif.
gh-notif handles sensitive data including GitHub authentication tokens and user notifications. Security is implemented at multiple layers:
- Authentication Security: Secure OAuth2 flow and token management
- Data Protection: Encrypted storage and secure transmission
- Input Validation: Comprehensive input sanitization
- Supply Chain Security: Dependency scanning and verification
- Runtime Security: Secure defaults and minimal privileges
gh-notif uses GitHub's OAuth2 device flow for secure authentication:
// Secure device flow implementation
func (a *Authenticator) DeviceFlow() error {
// 1. Request device code
deviceCode, err := a.requestDeviceCode()
if err != nil {
return err
}
// 2. Display user code and open browser
fmt.Printf("Please visit: %s\n", deviceCode.VerificationURI)
fmt.Printf("Enter code: %s\n", deviceCode.UserCode)
// 3. Poll for token with exponential backoff
token, err := a.pollForToken(deviceCode)
if err != nil {
return err
}
// 4. Securely store token
return a.storeToken(token)
}Tokens are stored securely using platform-specific credential managers:
- Uses Windows Credential Manager
- Tokens encrypted with user's Windows credentials
- Accessible only to the current user
- Uses macOS Keychain
- Tokens encrypted with user's keychain password
- Integrated with system security
- Uses Secret Service API (GNOME Keyring, KDE Wallet)
- Fallback to encrypted file storage
- File permissions restricted to user only
// Platform-specific secure storage
type SecureStorage interface {
Store(key, value string) error
Retrieve(key string) (string, error)
Delete(key string) error
}
// Implementation selection based on platform
func NewSecureStorage() SecureStorage {
switch runtime.GOOS {
case "windows":
return &WindowsCredentialManager{}
case "darwin":
return &MacOSKeychain{}
case "linux":
return &LinuxSecretService{}
default:
return &EncryptedFileStorage{}
}
}All tokens are validated before use:
func (c *Client) validateToken(token string) error {
// Check token format
if !isValidTokenFormat(token) {
return ErrInvalidTokenFormat
}
// Verify token with GitHub API
user, err := c.getCurrentUser(token)
if err != nil {
return fmt.Errorf("token validation failed: %w", err)
}
// Check required scopes
if !hasRequiredScopes(user.Scopes) {
return ErrInsufficientScopes
}
return nil
}Sensitive data is encrypted when stored locally:
// AES-256-GCM encryption for local data
func encryptData(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
ciphertext := gcm.Seal(nonce, nonce, data, nil)
return ciphertext, nil
}All API communications use TLS 1.2+:
// Secure HTTP client configuration
func newSecureHTTPClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: false,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
},
},
Timeout: 30 * time.Second,
}
}For enhanced security, certificate pinning is implemented:
// Certificate pinning for GitHub API
var githubCertFingerprints = []string{
"sha256:fingerprint1",
"sha256:fingerprint2",
}
func verifyGitHubCertificate(cert *x509.Certificate) error {
fingerprint := sha256.Sum256(cert.Raw)
fingerprintStr := "sha256:" + hex.EncodeToString(fingerprint[:])
for _, expected := range githubCertFingerprints {
if fingerprintStr == expected {
return nil
}
}
return ErrCertificatePinningFailed
}All user inputs are validated and sanitized:
// Comprehensive input validation
func validateFilter(filter string) error {
// Check length limits
if len(filter) > maxFilterLength {
return ErrFilterTooLong
}
// Validate syntax
if !isValidFilterSyntax(filter) {
return ErrInvalidFilterSyntax
}
// Check for injection attempts
if containsSQLInjection(filter) {
return ErrSQLInjectionAttempt
}
if containsXSS(filter) {
return ErrXSSAttempt
}
return nil
}
// SQL injection detection
func containsSQLInjection(input string) bool {
sqlPatterns := []string{
`(?i)(union|select|insert|update|delete|drop|create|alter|exec|execute)`,
`(?i)(script|javascript|vbscript|onload|onerror)`,
`['"]\s*;\s*--`,
`['"]\s*or\s+['"]\w+['"]\s*=\s*['"]\w+['"]`,
}
for _, pattern := range sqlPatterns {
if matched, _ := regexp.MatchString(pattern, input); matched {
return true
}
}
return false
}All external commands are properly escaped:
// Safe command execution
func executeCommand(command string, args ...string) error {
// Validate command is in allowlist
if !isAllowedCommand(command) {
return ErrCommandNotAllowed
}
// Escape all arguments
escapedArgs := make([]string, len(args))
for i, arg := range args {
escapedArgs[i] = shellescape.Quote(arg)
}
cmd := exec.Command(command, escapedArgs...)
return cmd.Run()
}All dependencies are regularly scanned for vulnerabilities:
# Vulnerability scanning
govulncheck ./...
# Dependency auditing
go list -m all | nancy sleuth
# License compliance
go-licenses check ./...Builds are secured with:
- Reproducible Builds: Deterministic build process
- Signed Releases: All releases are signed with GPG
- SBOM Generation: Software Bill of Materials for transparency
- Container Scanning: Docker images scanned for vulnerabilities
# Secure build pipeline
- name: Generate SBOM
run: |
syft packages . -o spdx-json > sbom.json
- name: Sign release
run: |
gpg --detach-sign --armor gh-notif
- name: Scan container
run: |
trivy image ghcr.io/user/gh-notif:latestThe application uses secure defaults:
// Secure configuration defaults
var defaultConfig = Config{
API: APIConfig{
Timeout: 30 * time.Second,
RetryCount: 3,
UserAgent: "gh-notif/1.0.0",
},
Auth: AuthConfig{
TokenStorage: "auto", // Use most secure available
Scopes: []string{"notifications", "repo:status"},
},
Security: SecurityConfig{
ValidateSSL: true,
MinTLSVersion: "1.2",
AllowInsecure: false,
CertPinning: true,
},
}The application runs with minimal privileges:
- No root/administrator privileges required
- Minimal file system access
- Network access only to GitHub API
- No unnecessary system calls
Memory safety measures:
// Secure memory handling
func secureZeroMemory(data []byte) {
for i := range data {
data[i] = 0
}
runtime.GC()
}
// Secure string handling
type SecureString struct {
data []byte
}
func (s *SecureString) String() string {
return string(s.data)
}
func (s *SecureString) Clear() {
secureZeroMemory(s.data)
}Security-relevant events are logged:
// Security audit logging
func auditLog(event string, details map[string]interface{}) {
logEntry := map[string]interface{}{
"timestamp": time.Now().UTC(),
"event": event,
"details": details,
"user": getCurrentUser(),
"ip": getClientIP(),
}
logger.Info("security_audit", logEntry)
}
// Usage examples
auditLog("auth_success", map[string]interface{}{
"method": "oauth2_device_flow",
})
auditLog("auth_failure", map[string]interface{}{
"method": "token_validation",
"error": "invalid_token",
})Basic anomaly detection for security events:
// Rate limiting for API calls
type RateLimiter struct {
requests map[string][]time.Time
mutex sync.RWMutex
}
func (rl *RateLimiter) Allow(key string) bool {
rl.mutex.Lock()
defer rl.mutex.Unlock()
now := time.Now()
requests := rl.requests[key]
// Remove old requests
var recent []time.Time
for _, req := range requests {
if now.Sub(req) < time.Hour {
recent = append(recent, req)
}
}
// Check rate limit
if len(recent) >= maxRequestsPerHour {
auditLog("rate_limit_exceeded", map[string]interface{}{
"key": key,
"requests": len(recent),
})
return false
}
recent = append(recent, now)
rl.requests[key] = recent
return true
}- Detection: Automated monitoring and user reports
- Assessment: Severity classification and impact analysis
- Containment: Immediate measures to limit damage
- Eradication: Remove the threat and vulnerabilities
- Recovery: Restore normal operations
- Lessons Learned: Post-incident review and improvements
Security vulnerabilities should be reported to:
- Email: [email protected]
- GitHub Security Advisories
- Coordinated disclosure timeline: 90 days
In case of security incidents:
-
Immediate Actions:
- Revoke compromised tokens
- Disable affected features
- Notify users if necessary
-
Communication:
- Internal team notification
- User communication plan
- Public disclosure timeline
-
Recovery:
- Deploy security patches
- Monitor for continued threats
- Update security measures
-
Token Management:
- Use tokens with minimal required scopes
- Regularly rotate tokens
- Never share tokens
-
System Security:
- Keep gh-notif updated
- Use secure operating systems
- Enable system firewalls
-
Network Security:
- Use trusted networks
- Avoid public Wi-Fi for sensitive operations
- Consider VPN usage
-
Code Security:
- Follow secure coding practices
- Regular security reviews
- Use static analysis tools
-
Dependency Management:
- Regular dependency updates
- Vulnerability scanning
- License compliance
-
Testing:
- Security test cases
- Penetration testing
- Code coverage for security functions