This document describes the security measures implemented in GitHub Wrapped.
GitHub Wrapped is designed with security as a priority. All sensitive operations happen server-side, and multiple layers of protection prevent abuse.
Location: Environment variable GITHUB_TOKEN
Access: Server-side only (never sent to client)
Scope: No scopes required (public data only)
Security measures:
- Environment Variable: Token stored in
.env.local, never committed - Server-Side Only: Used only in API routes, never in client code
- No Logging: Token never logged or included in error messages
- No Exposure: GitHub errors are sanitized before returning to client
- Rotate tokens periodically (every 90 days)
- Use fine-grained PATs when available
- Never commit tokens to version control
- Use different tokens for dev/staging/production
All responses include the following security headers (configured in next.config.ts):
Value: SAMEORIGIN
Purpose: Prevents clickjacking attacks by disallowing iframe embedding
Value: nosniff
Purpose: Prevents MIME type sniffing, reduces drive-by download attacks
Value: 1; mode=block
Purpose: Enables browser's XSS filter (legacy, but still useful)
Value: strict-origin-when-cross-origin
Purpose: Controls referrer information sent with requests
Value: max-age=63072000; includeSubDomains; preload
Purpose: Forces HTTPS connections
Duration: 2 years
default-src 'self'
script-src 'self' 'unsafe-eval' 'unsafe-inline' # Required by Next.js
style-src 'self' 'unsafe-inline' # Required by Tailwind
img-src 'self' data: https://avatars.githubusercontent.com https://media.giphy.com
font-src 'self' data:
connect-src 'self' https://api.github.com
frame-ancestors 'self'
base-uri 'self'
form-action 'self'
Value: camera=(), microphone=(), geolocation=(), interest-cohort=()
Purpose: Disables unnecessary browser features, opts out of FLoC
// Location: src/lib/rateLimit.ts
Configuration:
- Window: 60 seconds
- Max requests: 30 per IP
- Algorithm: Sliding window- Denial of Service (DoS): Limits request volume
- Brute Force: Prevents rapid enumeration
- Resource Exhaustion: Protects backend services
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 25
X-RateLimit-Reset: 1704067200
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again in 45 seconds.",
"retryAfterSeconds": 45,
"requestId": "req_m5k7j9x2"
}
}// GitHub username rules enforced:
// - 1-39 characters
// - Alphanumeric characters only (plus single hyphens)
// - Cannot start or end with hyphen
// - No consecutive hyphens
const GITHUB_USERNAME_REGEX = /^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/;// Year must be:
// - Integer
// - Between 2008 (GitHub launch) and current year
// - Defaults to current year if not provided{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid GitHub username. Usernames can only contain alphanumeric characters and single hyphens.",
"requestId": "req_m5k7j9x2"
}
}React automatically escapes values before rendering, preventing XSS:
// Safe - React escapes the value
<p>{user.bio}</p>
// We NEVER use dangerouslySetInnerHTML with user dataCSP headers prevent inline script execution from untrusted sources.
// Stack traces are NEVER exposed in production
// Generic messages for internal errors
// Specific messages only for known error types
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again later.",
"requestId": "req_m5k7j9x2"
}
}| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR |
400 | Invalid input |
USER_NOT_FOUND |
404 | GitHub user doesn't exist |
RATE_LIMIT_EXCEEDED |
429 | Too many requests |
GITHUB_API_ERROR |
502 | GitHub API failed |
NETWORK_ERROR |
503 | Network connectivity issue |
TIMEOUT_ERROR |
504 | Request timed out |
INTERNAL_ERROR |
500 | Unexpected server error |
Browser → /api/wrapped/[username] → GitHub API
↑ ↑
Your server Authenticated with token
The browser never calls api.github.com directly. All requests go through your API endpoint, which:
- Validates input
- Applies rate limiting
- Adds authentication
- Caches responses
- Sanitizes errors
// Only OPTIONS requests return CORS headers
// API is designed for same-origin use only
export async function OPTIONS() {
return new NextResponse(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
},
});
}npm auditRun regularly to check for vulnerabilities in dependencies.
| Package | Purpose | Security Notes |
|---|---|---|
| next | Framework | Regular security updates |
| zod | Validation | Type-safe validation |
| html-to-image | Screenshot | Client-side only |
-
GITHUB_TOKENis set in production environment -
.env.localis in.gitignore - Source maps are disabled (
productionBrowserSourceMaps: false) - Security headers are applied (check with securityheaders.com)
- Rate limiting is working (test with rapid requests)
- Error messages don't expose internals
- HTTPS is enforced
- Dependencies are up to date
If you discover a security vulnerability, please email the maintainer directly instead of opening a public issue.
| Vulnerability | Mitigation |
|---|---|
| A01 Broken Access Control | N/A - public data only |
| A02 Cryptographic Failures | HTTPS enforced, no sensitive data stored |
| A03 Injection | Zod validation, parameterized requests |
| A04 Insecure Design | Server-side rendering, no direct API exposure |
| A05 Security Misconfiguration | Security headers, strict CSP |
| A06 Vulnerable Components | Regular dependency updates |
| A07 Auth Failures | N/A - no user authentication |
| A08 Software/Data Integrity | NPM lockfile, no arbitrary code execution |
| A09 Logging Failures | Request IDs for tracing |
| A10 SSRF | Input validation, no URL parameters |