Content
Original code:
const DB_PASSWORD = "admin123";
const JWT_SECRET = "mysecret";
The problem: Hardcoded credentials can leak through public repositories, shared files, or application logs. Anyone who obtains these values may gain unauthorized access to databases or forge authentication tokens.
The fix:
const DB_PASSWORD = process.env.DB_PASSWORD;
const JWT_SECRET = process.env.JWT_SECRET;
if (!DB_PASSWORD || !JWT_SECRET) {
throw new Error("Required security configuration is missing");
}
Sensitive values should be stored in environment variables or a dedicated secrets manager. Never commit real credentials to version control, and rotate any secrets that have already been exposed.
Bug 2: SQL Injection Through Unsafe Input
Original code:
const query =
`SELECT * FROM users WHERE email = '${req.body.email}'`;
The problem: An attacker could manipulate the email input to change the SQL query's meaning, potentially bypassing authentication or accessing unauthorized data.
The fix:
const result = await db.query(
"SELECT * FROM users WHERE email = $1",
[req.body.email]
);
Parameterized queries separate user input from SQL instructions. This is a fundamental defense against SQL injection. Input validation is also necessary, but it should not replace parameterized queries.
Bug 3: Weak Password Storage
Original code:
const passwordHash = password;
The problem: Storing passwords as plain text means anyone who obtains the database can immediately read users' passwords. Ordinary fast hashes, such as unsalted SHA-256, are also unsuitable for password storage.
The fix:
const argon2 = require("argon2");
const passwordHash = await argon2.hash(password);
Use a dedicated password-hashing algorithm such as Argon2id, with appropriate parameters. During login, verify the submitted password against the stored hash using the library's verification function. Never store or log plain-text passwords.
Bug 4: Missing Input Validation
Original code:
app.post("/register", async (req, res) => {
const user = await createUser(req.body);
res.json(user);
});
The problem: The application accepts arbitrary request data without checking whether required fields exist or contain valid values. This may lead to unexpected behavior, invalid records, or mass-assignment vulnerabilities.
The fix:
const { email, password } = req.body;
if (
typeof email !== "string" ||
email.length > 254 ||
typeof password !== "string" ||
password.length < 12 ||
password.length > 128
) {
return res.status(400).json({
error: "Invalid registration details"
});
}
Use a trusted validation library for more complete checks, including email format, password policy, and request size. Validate every field on the server, even when the frontend already performs validation.
Bug 5: Weak Authentication
Original code:
if (user.password === req.body.password) {
res.json({ message: "Login successful" });
}
The problem: This example compares plain-text passwords and does not establish a secure authenticated session. In a real application, such logic can expose credentials and leave protected resources without reliable identity verification.
The fix:
const valid = await argon2.verify(
user.passwordHash,
req.body.password
);
if (!valid) {
return res.status(401).json({
error: "Invalid email or password"
});
}
After verification, establish a secure session or issue a properly configured authentication token. Add rate limiting, multi-factor authentication where appropriate, and generic login errors to reduce account enumeration and brute-force risks.
Bug 6: Missing Authorization Checks
Original code:
app.get("/users/:id", authenticate, async (req, res) => {
const user = await getUserById(req.params.id);
res.json(user);
});
The problem: Authentication confirms who a user is, but it does not automatically grant permission to access every user's information. An attacker could change the ID in the URL and retrieve another user's data.
The fix:
app.get("/users/:id", authenticate, async (req, res) => {
if (req.params.id !== req.user.id) {
return res.sendStatus(403);
}
const user = await getUserById(req.user.id);
res.json(user);
});
In production, use consistent ID types and enforce ownership or role-based access policies on the server. Apply authorization checks to every sensitive operation, including reading, editing, and deleting resources.
Bug 7: Excessive User Permissions
Original code:
const user = await createUser({
...req.body
});
The problem: Copying the entire request body into a database operation may allow users to set protected fields, such as role: "admin", if the data model accepts them.
The fix:
const user = await createUser({
name: req.body.name,
email: req.body.email,
passwordHash: await argon2.hash(req.body.password)
});
Only accept explicitly permitted fields. Enforce least privilege across database accounts, API credentials, cloud permissions, and application roles. Ordinary users should never be able to grant themselves administrative access.
Bug 8: Insecure Session Configuration
Original code:
app.use(session({
secret: "session-secret",
cookie: { secure: false }
}));
The problem: A hardcoded session secret and cookies that are not restricted to HTTPS increase the risk of session compromise.
The fix:
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 30 * 60 * 1000
}
}));
Use a cryptographically strong session secret and HTTPS in production. Configure a suitable production session store, regenerate session identifiers after login, and invalidate sessions during logout. Applications using cookie-based authentication should also assess their CSRF protection requirements.
Bug 9: Exposing Sensitive Error Messages
Original code:
app.use((err, req, res, next) => {
res.status(500).send(err.stack);
});
The problem: Stack traces may reveal internal file paths, database details, and implementation information that attackers can use to plan further attacks.
The fix:
app.use((err, req, res, next) => {
console.error("Application error:", err);
res.status(500).json({
error: "An unexpected error occurred"
});
});
Use structured server-side logging with appropriate access controls. Avoid recording passwords, access tokens, session identifiers, or other sensitive information. Detailed debugging information should remain in protected development and monitoring systems.
Bug 10: Vulnerable or Outdated Dependencies
Original code:
{
"dependencies": {
"express": "*"
}
}
The problem: Unrestricted dependency versions can make builds unpredictable and increase exposure to known vulnerabilities. Even correctly pinned packages can become vulnerable as new security advisories emerge.
The fix:
{
"dependencies": {
"express": "^5.1.0"
}
}
This version is an illustrative example, not a claim that it is the latest or appropriate version for every project. Select a supported release compatible with your application, commit the lockfile, and review dependency changes.
Run regular security checks:
npm audit
npm audit fix
Review the audit results before applying updates, run automated tests afterward, and use trusted dependency monitoring tools. Do not assume that every reported issue is exploitable in your application or that every automatic fix is risk-free.