Before deploying Deltasync to production, ensure all items are completed:
- Environment Secrets: All secrets in
.env.example(no defaults in code) - JWT Secret: Set
JWT_SECRETto a cryptographically random value (min 16 chars)export JWT_SECRET=$(openssl rand -base64 32)
- S3 Credentials: Use real AWS IAM credentials, NOT defaults like
devaccesskey - API Keys: Generated keys are 256-bit, stored as SHA-256 hashes
- Database Passwords: Strong passwords for PostgreSQL connections
- HTTPS Enabled: All endpoints behind TLS 1.3+ reverse proxy
- No
.envin Git: Ensure.envis in.gitignore(use.env.localor secrets manager)
- Security Headers: Verify HSTS, CSP, X-Frame-Options headers enabled
- Rate Limiting: Configured and tested under load
- CORS: Restricted to known origins
- Input Validation: All endpoints validated with Zod schemas
- Error Messages: Don't leak internal details in error responses
- Logging: Secrets not logged (never log tokens, keys, hashes)
- Database: Use encrypted connections (PostgreSQL SSL mode=require)
- S3 Permissions: Bucket policy restricts to required operations only
- IP Whitelisting: Restrict API access by IP if applicable
- API Key Rotation: Implement key expiration and rotation process
- Audit Logging: Track file access, API key usage, permission changes
- Backup Strategy: Automated database backups with encryption
- Monitoring: Set up alerts for failed logins, rate limit breaches
- DDoS Protection: Use WAF or Cloudflare in front of API
Never store secrets in .env committed to Git:
# BAD ❌
.env (tracked in git) with JWT_SECRET=my-secret
# GOOD ✅
.env.example (safe to track, no secrets)
.env.local (not tracked, has real secrets)Use a secrets manager in production:
-
AWS Secrets Manager
aws secretsmanager create-secret \ --name deltasync/jwt-secret \ --secret-string "$(openssl rand -base64 32)" -
HashiCorp Vault
vault kv put secret/deltasync/prod \ jwt_secret="$(openssl rand -base64 32)" \ s3_access_key_id="..." \ s3_secret_access_key="..."
-
GitHub Secrets (for CI/CD)
- name: Deploy env: JWT_SECRET: ${{ secrets.JWT_SECRET }} S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
API Key Format & Generation:
// Generated keys are 256-bit, prefixed with 'dks_'
// Format: dks_<base64url-44-chars>
// Example: dks_KR-gKzAM_wN8...
import { generateApiKey, hashApiKey } from './server/api-key';
const key = generateApiKey(); // User sees this once
const hash = hashApiKey(key); // Store this in databaseKey Rotation Process:
- User generates new API key via dashboard
- Both old and new keys work for 30 days (grace period)
- User must manually revoke old key
- After 30 days, old key stops working
Invalidate Keys on User Logout:
// When user logs out
await db.update(apiKeys)
.set({ revokedAt: new Date() })
.where(eq(apiKeys.userId, userId))
.run();Configuration:
- Algorithm: HS256 (HMAC-SHA256)
- Expiry: 30 days
- Secret: From
JWT_SECRETenvironment variable - Signing: Immediate on login, never transmitted
Session Invalidation:
// Manually revoke all sessions for a user
await logout(userId); // Deletes refresh tokens
// After password change, invalidate old sessions
await invalidateAllSessions(userId);Hashing:
- Algorithm: bcryptjs with 12 salt rounds
- Never store plaintext passwords
- Never log passwords
Requirements:
- Minimum 8 characters
- No character complexity requirement (NIST 2017 guidelines)
- Prevent common passwords with zxcvbn library (optional)
import { hashPassword } from './server/auth';
const hash = await hashPassword(password);
// Store hash in database, never the plaintextMUST use TLS 1.3:
# Nginx configuration
ssl_protocols TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;Certificate Management:
- Use Let's Encrypt for free, auto-renewing certificates
- Or: AWS Certificate Manager for AWS ALBs
- Certificate pinning for CLI (optional, for maximum security)
All responses include:
X-Content-Type-Options: nosniff # Prevent MIME sniffing
X-Frame-Options: DENY # Prevent clickjacking
X-XSS-Protection: 1; mode=block # Legacy XSS protection
Strict-Transport-Security: max-age=31536000 # Enforce HTTPS (1 year)
Content-Security-Policy: ... # Control resource loading
CSP Policy:
default-src 'self'
script-src 'self'
style-src 'self' 'unsafe-inline' # Required for Tailwind
img-src 'self' data: https:
font-src 'self' data:
connect-src 'self'
frame-ancestors 'none'
base-uri 'self'
form-action 'self'
Whitelist specific origins:
const allowedOrigins = [
'https://app.example.com',
'https://api.example.com',
];
export function corsMiddleware(origin: string): boolean {
return allowedOrigins.includes(origin);
}Not safe:
// ❌ NEVER allow all origins
'Access-Control-Allow-Origin': '*'
// ❌ NEVER wildcard subdomains
'Access-Control-Allow-Origin': 'https://*.example.com'Enable SSL connections:
# In connection string
postgresql://user:pass@host:5432/db?sslmode=requireStrong password policy:
-- Require strong passwords
ALTER ROLE deltasync WITH PASSWORD 'super-strong-32-char-password-here';Row-Level Security (Optional):
-- Only users can see their own files
CREATE POLICY user_isolation ON files
USING (user_id = current_user_id());Database:
- AWS RDS: Enable encryption at launch
- PostgreSQL: Use pgcrypto or transparent disk encryption (LUKS)
S3 Blocks:
- Enable S3 server-side encryption (SSE-S3 or SSE-KMS)
Automated backups:
# Daily backup to S3
0 2 * * * pg_dump deltasync | gzip | aws s3 cp - s3://backups/db-$(date +%Y%m%d).sql.gzTest recovery:
# Monthly: Restore backup to test environment
aws s3 cp s3://backups/db-latest.sql.gz - | gunzip | psql deltasyncPrevent path traversal:
import { z } from 'zod';
const pathSchema = z.string()
.refine(path => {
// Reject unsafe patterns
if (path.includes('..')) return false;
if (path.includes('./')) return false;
if (path.startsWith('/')) return false;
// Reject null bytes and control characters
if (/[\x00-\x1f]/.test(path)) return false;
return true;
});Validate cryptographic hashes:
const sha256Regex = /^[a-f0-9]{64}$/;
const chunkSchema = z.object({
strongHash: z.string().regex(sha256Regex, 'Invalid SHA-256 hash'),
length: z.number().positive().max(MAX_CHUNK_SIZE),
weakHash: z.number().optional(),
});const MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB
const MAX_REQUEST_SIZE = 10 * 1024 * 1024; // 10MB
// Validate in upload handler
if (fileSize > MAX_FILE_SIZE) {
throw new Error('File exceeds 500MB limit');
}import { rateLimit } from './server/rate-limiter';
// Apply limits per endpoint
export async function negotiateHandler(req, res) {
await rateLimit(userId, 'negotiate', { max: 100, windowMs: 60000 });
// ... handler logic
}Limits:
/negotiate: 100 per minute/upload: 1000 per hour/download: Unlimited/files: 1000 per hour
// For unauthenticated endpoints
const ipLimit = await rateLimit(clientIP, 'global', {
max: 1000,
windowMs: 3600000, // 1 hour
});Never log secrets:
// ❌ BAD
console.log('User logged in with token:', token);
// ✅ GOOD
console.log('User logged in', { userId, timestamp: new Date() });Include correlation IDs:
const correlationId = crypto.randomUUID();
logger.info(
{ correlationId, userId, action: 'file-upload' },
'File uploaded successfully'
);Track sensitive operations:
// Log file access
await auditLog.create({
userId,
action: 'FILE_DOWNLOADED',
resourceId: fileId,
timestamp: new Date(),
ipAddress: req.ip,
});
// Log permission changes
await auditLog.create({
userId,
action: 'API_KEY_CREATED',
resourceId: apiKeyId,
details: { prefix: extractKeyPrefix(apiKey) },
});Alert on suspicious activity:
// Alert if user fails login 5 times in 5 minutes
if (failedAttempts > 5) {
await sendSecurityAlert(userId, 'Multiple failed login attempts');
// Lock account temporarily
}
// Alert if API key used from new IP
if (newIpDetected) {
await sendVerificationEmail(userId, ipAddress);
}Use minimal base images:
# ✅ Use distroless or alpine
FROM node:22-alpine
# ❌ Avoid
FROM ubuntu:latestRun as non-root:
RUN useradd -m deltasync
USER deltasyncUse Docker secrets or environment variables:
# ❌ Bad: Hardcode in Dockerfile
ENV JWT_SECRET=mysecret
# ✅ Good: Inject at runtime
docker run -e JWT_SECRET=$(aws secretsmanager get-secret-value ...) ...# deployment.yaml
apiVersion: v1
kind: Pod
metadata:
name: deltasync-api
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsReadOnlyRootFilesystem: true
containers:
- name: api
image: deltasync:latest
env:
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: deltasync-secrets
key: jwt_secret
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: trueIf you suspect a security breach:
-
Immediate Actions:
- Revoke all API keys
- Rotate JWT_SECRET
- Invalidate all sessions
-
Investigation:
- Check audit logs for suspicious activity
- Review access logs for unauthorized IPs
- Audit S3 bucket for unexpected files
-
Communication:
- Notify affected users
- Document timeline of events
- File incident report
If user passwords are compromised:
- Force password reset for all users
- Invalidate all sessions
- Require MFA re-enrollment
- Monitor for unauthorized access
- OWASP Top 10
- NIST Cybersecurity Framework
- JWT Best Practices
- API Security Checklist
- AWS Security Best Practices
For security concerns or to report vulnerabilities:
- Email: security@example.com
- Please do NOT open public GitHub issues for security vulnerabilities