Skip to main content

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:

  1. Always use sql tagged templates for dynamic queries
  2. Never use .unsafe() unless absolutely necessary (and document why)
  3. Never concatenate user input into SQL strings
  4. 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:

  1. Always whitelist specific origins in production
  2. Never use origin: true in production
  3. Use environment variables for allowed origins
  4. 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:

  1. Validate type - Ensure data is expected type
  2. Validate format - Use regex or validators for formats
  3. Validate length - Prevent buffer overflows
  4. Sanitize - Remove dangerous characters
  5. 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:

  1. Never commit secrets to git
  2. Use environment variables for all secrets
  3. Validate secrets exist on startup
  4. Rotate secrets regularly
  5. 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:

  1. Always use requireAuth middleware for protected routes
  2. Verify user permissions before allowing actions
  3. Check resource ownership when applicable
  4. 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)

Resources