This document outlines the security features implemented in Pulse Server and best practices for deploying to production.
The server supports API Key-based authentication to protect endpoints from unauthorized access.
Enable API Key authentication by setting these environment variables:
# Enable API key validation
API_KEY_AUTH_ENABLED=true
# Comma-separated list of valid API keys
API_KEYS=pulse-dev-key-123,pulse-test-key-456,sk-demo-key-789Provide your API key in one of two ways:
Option 1: Request Header (Recommended)
curl -H "X-API-Key: pulse-dev-key-123" \
http://localhost:3000/api/enrichmentOption 2: Query Parameter
curl "http://localhost:3000/api/enrichment?api_key=pulse-dev-key-123"// With header
const response = await fetch("http://localhost:3000/api/enrichment", {
method: "POST",
headers: {
"X-API-Key": "pulse-dev-key-123",
"Content-Type": "application/json",
},
body: JSON.stringify({
owner: "user",
name: "repo",
scope: "repo",
task: "analyze",
}),
});
// With query parameter
const response = await fetch(
"http://localhost:3000/api/enrichment?api_key=pulse-dev-key-123",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
owner: "user",
name: "repo",
scope: "repo",
task: "analyze",
}),
}
);// Custom hook for authenticated API calls
import { useState } from "react";
export function useApi(apiKey) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const call = async (endpoint, options = {}) => {
setLoading(true);
setError(null);
try {
const response = await fetch(`http://localhost:3000/api${endpoint}`, {
...options,
headers: {
"X-API-Key": apiKey,
"Content-Type": "application/json",
...options.headers,
},
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP ${response.status}`);
}
return await response.json();
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
};
return { call, loading, error };
}
// Usage in component
function AnalyzeRepo() {
const { call, loading, error } = useApi("pulse-dev-key-123");
const handleAnalyze = async () => {
try {
const result = await call("/enrichment", {
method: "POST",
body: JSON.stringify({
owner: "torvalds",
name: "linux",
scope: "repo",
task: "analyze",
}),
});
console.log(result);
} catch (err) {
console.error(err);
}
};
return (
<button onClick={handleAnalyze} disabled={loading}>
{loading ? "Analyzing..." : "Analyze Repository"}
</button>
);
}Rate limiting is automatically applied to all API endpoints to prevent abuse.
- Unauthenticated (by IP): 100 requests per 15 minutes
- Authenticated (by API Key): 500 requests per 15 minutes
Rate limit information is included in response headers:
X-RateLimit-Limit: 100 # Maximum requests allowed
X-RateLimit-Remaining: 87 # Remaining requests in window
X-RateLimit-Reset: 2025-11-06... # When the window resets (ISO 8601)
{
"success": false,
"error": "Too many requests. Limit: 100 requests per 15 minutes.",
"retryAfter": 312
}The retryAfter value is in seconds. Wait this long before making another request.
{
"success": false,
"error": "Access denied. API key required. Provide via X-API-Key header or ?api_key=xxx query parameter."
}{
"success": false,
"error": "Invalid API key."
}All authentication and rate limiting events are logged:
WARN: API request rejected: Missing API key from 192.168.1.1
WARN: API request rejected: Invalid API key from 192.168.1.1
WARN: Rate limit exceeded for key:pulse-dev-key-123: 500/500 requests in 900000ms
DEBUG: API request authenticated with key: pulse-dev...
Set LOG_LEVEL=debug in .env.local to see all authentication details.
Always use HTTPS in production. Redirect HTTP to HTTPS:
// In app.js or server.js
if (config.isProduction) {
app.use((req, res, next) => {
if (req.header("x-forwarded-proto") !== "https") {
res.redirect(`https://${req.header("host")}${req.originalUrl}`);
}
next();
});
}- Never commit API keys to version control
- Use secure environment management:
- Docker: Secrets (Docker Swarm/Kubernetes)
- Cloud: AWS Secrets Manager, Azure Key Vault, Google Cloud Secret Manager
- On-premise: HashiCorp Vault, encrypted environment files
- Rotate API keys regularly (e.g., every 3-6 months)
- Create separate keys for each environment (dev, staging, prod)
- Monitor API key usage for suspicious activity
Restrict CORS to trusted domains:
# .env.local
CORS_ORIGIN=https://app.example.com,https://admin.example.comValidate all request parameters:
const schema = Joi.object({
owner: Joi.string().alphanum().min(1).max(50).required(),
name: Joi.string().alphanum().min(1).max(100).required(),
scope: Joi.string().valid("repo").required(),
task: Joi.string().valid("analyze", "summarize-issues").required(),
});Monitor for:
- Multiple failed API key attempts → Alert
- Sudden spike in rate limit hits → Alert
- Errors from unusual IP addresses → Alert
- Unusual request patterns → Investigate
For storing API keys in a database:
// Hash API keys before storing
import crypto from "crypto";
function hashApiKey(apiKey) {
return crypto.createHash("sha256").update(apiKey).digest("hex");
}
// Store hashed version in DB
db.apiKeys.insert({
hashedKey: hashApiKey("pulse-dev-key-123"),
name: "Development Key",
owner: "dev-team",
created: new Date(),
lastUsed: null,
active: true,
});
// During validation, hash incoming key and compare
const incomingHash = hashApiKey(req.header("X-API-Key"));
const valid = await db.apiKeys.findOne({
hashedKey: incomingHash,
active: true,
});For more advanced use cases, implement JWT tokens:
import jwt from "jsonwebtoken";
// Generate token (after API key validation)
const token = jwt.sign(
{ apiKey: "pulse-dev-key-123", scope: "admin" },
process.env.JWT_SECRET,
{ expiresIn: "24h" }
);
// Validate token in middleware
export const verifyJwt = (req, res, next) => {
const token = req.header("Authorization")?.replace("Bearer ", "");
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(403).json({ error: "Invalid token" });
}
};Before deploying to production:
- API_KEY_AUTH_ENABLED=true in production .env
- CORS_ORIGIN restricted to your domain(s)
- HTTPS enabled and HTTP redirects to HTTPS
- API keys stored securely (not in version control)
- Rate limiting tested and appropriate for your use case
- Error messages don't leak sensitive information
- Logging includes all auth failures
- Regular security audits scheduled
- API keys rotated regularly
- Monitoring and alerts configured
- Backup and disaster recovery plan in place
- Rate limits adjusted based on usage patterns
Test the API key system:
# ✅ Valid API key (should work)
curl -H "X-API-Key: pulse-dev-key-123" \
http://localhost:3000/api/enrichment
# ❌ Missing API key (should fail with 401)
curl http://localhost:3000/api/enrichment
# ❌ Invalid API key (should fail with 403)
curl -H "X-API-Key: invalid-key" \
http://localhost:3000/api/enrichmentSee other documentation:
QUICK_START.md- Getting started guideAPI_GUIDE.md- Complete API referencePURPOSE.md- Project vision and architecture