Password Stored or Compared in Plain Text
Passwords are saved or checked as plain text.
Why it matters
If the database is ever copied, dumped by a bug, or read through one of the other issues in this scan, every user's real password is exposed, including the ones they reuse for email and banking. Hashing means a leak gives the attacker nothing usable. Comparing plain text on login also means the password had to be stored plain in the first place.
What it looks like
This is the shape of code that triggers the rule. AI tools produce it because it works, and nothing tells them it is unsafe.
app.post('/signup', async (req, res) => {
await User.create({ email: req.body.email, password: req.body.password });
res.json({ ok: true });
});The smallest fix
import bcrypt from 'bcrypt';
// Signup: store a hash, never the password
const passwordHash = await bcrypt.hash(req.body.password, 12);
await User.create({ email, passwordHash });
// Login: compare with the library, never with ===
const user = await User.findOne({ email });
const ok = user && (await bcrypt.compare(req.body.password, user.passwordHash));
if (!ok) return res.status(401).json({ error: 'Invalid email or password' });The better fix
If you have a few more minutes, this is the approach that holds up as the app grows.
// Let an auth provider own passwords entirely: Supabase Auth, Clerk,
// Auth0 or NextAuth. Your database then never contains a password column.Let your AI tool fix it
When the scanner finds this in your project, it fills in the file and line for you. This is the prompt it gives you to paste into Claude Code, Cursor or whatever you use.
How to check the fix worked
1. Create an account, then look at the row in the database: the password column must be a hash, not the typed value. 2. Log in with the right password: success. With a wrong one: 401 and the same message as an unknown email.