I’ve seen too many projects suffer because security was an afterthought. Building protection directly into code from the start saves countless headaches later. Here’s what works based on years of hands-on experience:
Validating user input is like checking IDs at the door. Assume every piece of external data is hostile until proven otherwise. For web forms, I combine client-side validation for user experience with rigorous server-side checks:
# Flask input validation with WTForms
from flask_wtf import FlaskForm
from wtforms import StringField, validators
class RegistrationForm(FlaskForm):
email = StringField('Email', [
validators.Email(),
validators.Length(max=120)
])
username = StringField('Username', [
validators.Regexp(r'^\w+$', message="Alphanumeric only")
])
@app.route('/register', methods=['POST'])
def register():
form = RegistrationForm()
if form.validate():
# Safe to process
create_user(form.email.data, form.username.data)
Database interactions demand parameterization. I once patched a system bleeding data through SQL injection - the fix was simple:
// Java PreparedStatement example
String query = "SELECT * FROM accounts WHERE owner_id = ?";
try (PreparedStatement pstmt = connection.prepareStatement(query)) {
pstmt.setString(1, userSuppliedId);
ResultSet rs = pstmt.executeQuery();
}
Authentication needs multiple barriers. When implementing sessions, I always include:
// Express session hardening
const sessionConfig = {
secret: crypto.randomBytes(32).toString('hex'),
cookie: {
sameSite: 'lax', // Balances security and UX
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 30 * 60 * 1000 // 30 minute timeout
},
rolling: true, // Renew on activity
resave: false,
saveUninitialized: false
};
app.use(session(sessionConfig));
For XSS protection, encoding context matters. Angular automatically handles this, but when working with vanilla JS:
<!-- Manual encoding for different contexts -->
<script>
// HTML body context
function encodeHTML(input) {
return input.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
}
// Attribute context
function encodeAttr(input) {
return input.replace(/"/g, '"')
.replace(/'/g, ''');
}
</script>
Third-party dependencies hide landmines. My CI pipeline always includes:
# GitHub Actions security scanning
name: Security Audit
on: [push]
jobs:
dependency-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run npm audit
run: npm audit --production
- name: OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'MyApp'
Error handling requires careful balance. In production systems:
// C# custom error middleware
app.UseExceptionHandler(errorApp => {
errorApp.Run(async context => {
context.Response.StatusCode = 500;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Request processing error");
// Log detailed error internally
var exception = context.Features.Get<IExceptionHandlerFeature>();
logger.LogCritical(exception.Error, "Unhandled exception");
});
});
Security headers provide free armor. My standard Nginx configuration includes:
# Comprehensive header settings
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header Referrer-Policy "strict-origin-when-cross-origin";
File uploads require strict containment:
// PHP secure file handling
$allowedTypes = ['image/jpeg' => '.jpg', 'image/png' => '.png'];
$fileInfo = finfo_open(FILEINFO_MIME_TYPE);
$detectedType = finfo_file($fileInfo, $_FILES['upload']['tmp_name']);
if (!array_key_exists($detectedType, $allowedTypes)) {
die("Invalid file type");
}
$extension = $allowedTypes[$detectedType];
$safeName = bin2hex(random_bytes(16)) . $extension;
move_uploaded_file(
$_FILES['upload']['tmp_name'],
"/var/storage/" . $safeName // Outside web root
);
Configuration security often gets overlooked. My checklist includes:
- Environment variables for secrets (never in code)
- Principle of least privilege for database accounts
- Automatic credential rotation every 90 days
- Encryption both at rest (AES-256) and in transit (TLS 1.3)
- Disabling debug modes in production
I integrate security at every phase:
- Pre-commit hooks with static analysis (
bandit
for Python,ESLint
for JS) - CI pipeline with dependency scanning and SAST tools
- Staging environment with OWASP ZAP dynamic scans
- Production monitoring for abnormal patterns
Security isn’t about perfection - it’s about making attacks prohibitively expensive. Each layer adds work for adversaries. I’ve found teams that bake these practices into their daily workflow suffer fewer breaches and sleep better at night. The key is consistency: security isn’t a feature, it’s how you write code.