| sidebar_position | 9 |
|---|
Secure by default, not by heroism.
Avoid accidental vulnerabilities. Security should be built into processes, not bolted on afterward.
✅ DO:
- Use secret managers (AWS Secrets Manager, 1Password, Vault)
- Store secrets outside version control
- Rotate secrets regularly
- Use different secrets per environment
- Audit secret access
❌ DON'T:
- Commit
.envfiles - Hardcode API keys or passwords
- Share secrets via Slack or email
- Use production secrets in development
- Log secrets (even accidentally)
Example (Node.js with AWS Secrets Manager):
import {
SecretsManagerClient,
GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
const client = new SecretsManagerClient({ region: "us-east-1" });
async function getSecret(secretName: string): Promise<string> {
const command = new GetSecretValueCommand({ SecretId: secretName });
const response = await client.send(command);
return response.SecretString!;
}
const dbPassword = await getSecret("prod/database/password");Sources:
Prevent accidental commits:
# Install git-secrets
brew install git-secrets
# Initialize in repo
git secrets --install
git secrets --register-aws
# Scan history
git secrets --scan-historyUse GitHub secret scanning:
:::tip Pin real workflow refs with Ratchet This example uses readable refs for clarity. In a real repository, run Pin workflow refs with Ratchet on your workflow files and commit the rewritten SHA pins instead of leaving floating refs in place. :::
# .github/workflows/security.yml
name: Security Scan
on: [push]
jobs:
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: trufflesecurity/trufflehog@mainSources:
✅ DO:
- Use OAuth 2.0 / OpenID Connect for authentication
- Use JWT for stateless auth (with short expiration)
- Implement refresh tokens
- Hash passwords with bcrypt or Argon2
- Enforce MFA for admin accounts
❌ DON'T:
- Roll your own authentication
- Store passwords in plaintext
- Use MD5 or SHA-1 for passwords
- Set JWT expiration > 1 hour
- Use session IDs in URLs
Example (JWT Authentication):
import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";
async function login(email: string, password: string) {
const user = await findUserByEmail(email);
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
throw new UnauthorizedError("Invalid credentials");
}
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET!,
{ expiresIn: "1h" },
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: "30d" },
);
return { token, refreshToken };
}Sources:
Implement Role-Based Access Control:
enum Role {
Admin = "admin",
Editor = "editor",
Viewer = "viewer",
}
const permissions = {
[Role.Admin]: ["read", "write", "delete"],
[Role.Editor]: ["read", "write"],
[Role.Viewer]: ["read"],
};
function authorize(user: User, action: string) {
const userPermissions = permissions[user.role];
if (!userPermissions.includes(action)) {
throw new ForbiddenError("Insufficient permissions");
}
}
// Usage in route handler
app.delete("/users/:id", async (req, res) => {
authorize(req.user, "delete");
await deleteUser(req.params.id);
res.status(204).send();
});Sources:
| Level | Examples | Requirements |
|---|---|---|
| Public | Marketing content, docs | No restrictions |
| Internal | Code, designs, metrics | Require authentication |
| Confidential | Customer data, financials | Encrypt at rest, audit access |
| Restricted | PII, PHI, payment data | Encrypt in transit & at rest, limited access, audit all access |
✅ DO:
- Encrypt PII at rest and in transit
- Minimize data collection (only what's needed)
- Anonymize data in non-prod environments
- Log access to sensitive data
- Implement data retention policies
❌ DON'T:
- Store credit card numbers (use tokenization)
- Log PII or sensitive data
- Use production data in development
- Grant broad access to sensitive data
Example (Encryption at Rest):
import crypto from "crypto";
const algorithm = "aes-256-gcm";
const key = Buffer.from(process.env.ENCRYPTION_KEY!, "hex");
function encrypt(text: string): string {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encrypted = Buffer.concat([
cipher.update(text, "utf8"),
cipher.final(),
]);
const authTag = cipher.getAuthTag();
return Buffer.concat([iv, authTag, encrypted]).toString("base64");
}
function decrypt(encryptedText: string): string {
const buffer = Buffer.from(encryptedText, "base64");
const iv = buffer.slice(0, 16);
const authTag = buffer.slice(16, 32);
const encrypted = buffer.slice(32);
const decipher = crypto.createDecipheriv(algorithm, key, iv);
decipher.setAuthTag(authTag);
return decipher.update(encrypted) + decipher.final("utf8");
}Sources:
| Risk | Mitigation |
|---|---|
| Broken Access Control | Implement RBAC, validate permissions server-side |
| Cryptographic Failures | Use TLS, encrypt sensitive data, avoid weak algorithms |
| Injection | Use parameterized queries, input validation |
| Insecure Design | Security requirements in design phase, threat modeling |
| Security Misconfiguration | Secure defaults, disable unused features, update regularly |
| Vulnerable Components | Scan dependencies, keep libraries updated |
| Authentication Failures | MFA, secure session management, rate limiting |
| Software/Data Integrity | Code signing, SRI for CDN assets, verify updates |
| Logging Failures | Log security events, monitor for anomalies |
| SSRF | Validate URLs, use allowlists, network segmentation |
Sources:
✅ DO - Use Parameterized Queries:
// Safe - parameterized query
const users = await db.query("SELECT * FROM users WHERE email = $1", [email]);❌ DON'T - String Concatenation:
// Vulnerable to SQL injection
const users = await db.query(`SELECT * FROM users WHERE email = '${email}'`);Sources:
✅ DO - Escape Output:
import escapeHtml from "escape-html";
// Safe - escaped output
res.send(`<div>Welcome, ${escapeHtml(user.name)}</div>`);❌ DON'T - Unescaped User Input:
// Vulnerable to XSS
res.send(`<div>Welcome, ${user.name}</div>`);Use Content Security Policy:
app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",
);
next();
});Sources:
# .github/workflows/security.yml
name: Dependency Scan
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm audit --audit-level=moderate
- uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}✅ DO:
- Run
npm auditoryarn auditin CI - Use tools like Snyk, Dependabot, or Renovate
- Review and fix high/critical vulnerabilities within 7 days
- Update dependencies regularly
❌ DON'T:
- Ignore vulnerability warnings
- Use deprecated packages
- Pin dependencies without updates
- Skip security patches
Sources:
Always validate and sanitize input:
import Joi from "joi";
const userSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).required(),
age: Joi.number().integer().min(0).max(120),
});
function validateUser(data: unknown) {
const { error, value } = userSchema.validate(data);
if (error) {
throw new ValidationError(error.message);
}
return value;
}Sources:
Prevent abuse with rate limiting:
import rateLimit from "express-rate-limit";
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: "Too many login attempts, please try again later",
});
app.post("/login", loginLimiter, async (req, res) => {
// Handle login
});Sources:
Key requirements:
- Right to Access: Users can request their data
- Right to Erasure: Users can delete their data
- Data Portability: Export data in machine-readable format
- Consent: Explicit consent for data collection
- Breach Notification: Report breaches within 72 hours
Implementation Example:
// Export user data
app.get("/users/:id/export", async (req, res) => {
authorize(req.user, "read");
const userData = await exportUserData(req.params.id);
res.json(userData);
});
// Delete user data
app.delete("/users/:id", async (req, res) => {
authorize(req.user, "delete");
await anonymizeUserData(req.params.id);
res.status(204).send();
});Sources:
Log security-relevant events:
function auditLog(event: string, user: User, details: object) {
logger.info("Security audit event", {
event,
userId: user.id,
email: user.email,
timestamp: new Date().toISOString(),
...details,
});
}
// Usage
auditLog("user.login", user, { ipAddress: req.ip });
auditLog("user.password_changed", user, {});
auditLog("admin.user_deleted", admin, { deletedUserId: userId });Sources: