Secure Coding Guidelines
These guidelines must be followed for all code changes. Violations will block code reviews and deployments.
🔴 CRITICAL: SQL Injection Prevention
❌ NEVER DO THIS
// ❌ VULNERABLE: String concatenation
const query = `SELECT * FROM users WHERE email = '${email}'`;
const result = await sql.unsafe(query);
// ❌ VULNERABLE: Template literals in SQL
const whereClause = `name = '${userInput}' AND status = '${status}'`;
const result = await sql.unsafe(`SELECT * FROM table WHERE ${whereClause}`);
✅ ALWAYS DO THIS
// ✅ SAFE: Parameterized queries
const result = await sql`
SELECT * FROM users
WHERE email = ${email}
`;
// ✅ SAFE: Multiple conditions with sql.join()
const conditions = [
sql`name = ${userInput}`,
sql`status = ${status}`
];
const result = await sql`
SELECT * FROM table
WHERE ${sql.join(conditions, sql` AND `)}
`;
Rules:
- Always use
sqltagged templates for dynamic queries - Never use
.unsafe()unless absolutely necessary (and document why) - Never concatenate user input into SQL strings
- Validate input before using in queries
🔴 CRITICAL: CORS Configuration
❌ NEVER DO THIS
// ❌ VULNERABLE: Allows all origins
app.use(cors({
origin: true, // Allows everything!
credentials: true
}));
✅ ALWAYS DO THIS
// ✅ SAFE: Whitelist specific origins
const allowedOrigins = [
'https://reformer.la',
'https://dash.reformer.la',
/\.webflow\.io$/
];
app.use(cors({
origin: function(origin, callback) {
if (!origin) return callback(null, true); // Allow no-origin (mobile apps)
const isAllowed = allowedOrigins.some(allowed => {
if (typeof allowed === 'string') return origin === allowed;
if (allowed instanceof RegExp) return allowed.test(origin);
return false;
});
if (isAllowed) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
}));
Rules:
- Always whitelist specific origins in production
- Never use
origin: truein production - Use environment variables for allowed origins
- Log blocked CORS requests for monitoring
Input Validation
Always Validate User Input
// ✅ GOOD: Validate before use
function validateEmail(email) {
if (!email || typeof email !== 'string') {
throw new Error('Email is required');
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new Error('Invalid email format');
}
return email.trim().toLowerCase();
}
// Use validation
const email = validateEmail(req.body.email);
Validation Rules:
- Validate type - Ensure data is expected type
- Validate format - Use regex or validators for formats
- Validate length - Prevent buffer overflows
- Sanitize - Remove dangerous characters
- Whitelist - Only allow known good values when possible
Error Handling
❌ NEVER DO THIS
// ❌ BAD: Exposes internal details
catch (error) {
res.status(500).json({
error: error.message, // Exposes stack traces, paths, etc.
stack: error.stack
});
}
✅ ALWAYS DO THIS
// ✅ GOOD: Hide internal details in production
catch (error) {
logger.error('Operation failed', {
error: error.message,
stack: error.stack,
userId: req.user?.id
});
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: error.message
});
}
Secrets Management
❌ NEVER DO THIS
// ❌ BAD: Hardcoded secrets
const API_KEY = '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';
// ❌ BAD: Committed to git
const config = {
database: 'postgresql://user:password@host/db'
};
✅ ALWAYS DO THIS
// ✅ GOOD: Environment variables
const API_KEY = process.env.STRIPE_SECRET_KEY;
if (!API_KEY) {
throw new Error('STRIPE_SECRET_KEY not configured');
}
// ✅ GOOD: Never commit secrets
// Use .env.local for local development
// Use Render environment variables for production
Rules:
- Never commit secrets to git
- Use environment variables for all secrets
- Validate secrets exist on startup
- Rotate secrets regularly
- Use different secrets for dev/staging/production
Authentication & Authorization
Always Verify Authentication
// ✅ GOOD: Use middleware
app.get('/api/protected', requireAuth, async (req, res) => {
// req.user is guaranteed to exist
const userId = req.user.id;
// ...
});
// ✅ GOOD: Check authorization
if (req.user.id !== resource.ownerId && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
Rules:
- Always use
requireAuthmiddleware for protected routes - Verify user permissions before allowing actions
- Check resource ownership when applicable
- Never trust client-side authorization alone
Logging Security
❌ NEVER DO THIS
// ❌ BAD: Logs sensitive data
console.log('User signed in:', {
email: user.email,
password: user.password, // NEVER LOG PASSWORDS
apiKey: user.apiKey
});
✅ ALWAYS DO THIS
// ✅ GOOD: Log safely
logger.info('User signed in', {
userId: user.id,
email: user.email, // OK - not sensitive
// Never log: passwords, tokens, API keys, credit cards
});
Rate Limiting
Always Implement Rate Limiting
// ✅ GOOD: Rate limit sensitive endpoints
import { strictRateLimiter } from './middleware/rate-limiter.js';
app.post('/api/auth/login', strictRateLimiter, async (req, res) => {
// Login attempts limited to 5 per minute
});
Security Checklist
Before submitting code for review, verify:
- No SQL injection vulnerabilities (all queries parameterized)
- CORS properly configured (whitelist, not allow-all)
- All user input validated and sanitized
- No secrets in code (use environment variables)
- Error messages don't expose sensitive information
- Authentication required for protected routes
- Authorization checks in place
- Rate limiting on sensitive endpoints
- No sensitive data in logs
- Dependencies are up to date (no known vulnerabilities)