• Agents
  • Pricing
  • Blog
Log in
Get started

Security for apps built with AI. Paste a URL, get a report, fix what matters.

Product

  • How it works
  • What we find
  • Pricing
  • Agents
  • MCP Server
  • CLI
  • GitHub Action

Resources

  • Guides
  • Blog
  • Docs
  • OWASP Top 10
  • Glossary
  • FAQ

Security

  • Supabase Security
  • Next.js Security
  • Lovable Security
  • Cursor Security
  • Bolt Security

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • Imprint
© 2026 Flowpatrol. All rights reserved.
Home/OWASP Top 10/Web Top 10/A02: Cryptographic Failures
A02CWE-259CWE-327CWE-331

Plaintext passwords in the table
Cryptographic Failures

The bug where the data is technically stored — just not in a way anyone should have stored it.

Affects roughly 4.49% of tested apps.

Reference: Web Top 10 (2021) — A02·Last updated April 7, 2026·By Flowpatrol Team
Cryptographic Failures illustration

The model knows what a password column should look like. It also knows what bcrypt is. What it does not do, unless you ask, is wire the second thing into the first. So you end up with a users table that works — and a password column that reads like a phone book.

Cryptographic failures cover everything from storing passwords in plaintext to signing JWTs with 'secret' to leaving TLS off on an internal hop. The common thread is that something sensitive is sitting somewhere it should not be, in a form anyone who gets their hands on it can read. It is rarely about picking the wrong cipher — it is about not picking one at all.

What your AI actually built

You asked for user signup and login. The model gave you a clean auth flow: form, API route, database insert, session cookie. Every step looks right when you test it as yourself.

What you did not notice is that the password hit the database exactly as the user typed it. No hash, no salt, no pepper. The signing secret for your JWTs is the literal string 'secret' sitting in an env file that was committed last Tuesday.

Everywhere else, the pattern repeats. API keys stored in plain columns. Password reset tokens generated with Math.random. TLS terminated at the load balancer and then cheerfully proxied over HTTP inside the VPC. Each one looks fine on its own. Together they are a single compromised backup away from a headline.

How it gets exploited

An old database backup ends up on a misconfigured S3 bucket. The attacker does not need to exploit anything — they just list the bucket.

  1. 1
    Grab the dump
    They download users.sql. The password column is not hashed. Every credential is in the clear.
  2. 2
    Stuff the logins
    They replay the email and password pairs against Gmail, GitHub, and Stripe. Password reuse does the rest.
  3. 3
    Forge a session
    The JWT signing secret is 'secret'. They mint an admin token in 30 seconds using jwt.io.
  4. 4
    Walk in the front door
    The admin token unlocks the dashboard, the billing page, and the impersonation endpoint. No password was ever cracked.

The attacker has a live admin session, every user credential, and a linked identity on half a dozen other services — all from a file that was never supposed to be sensitive.

Vulnerable vs Fixed

Vulnerable — password stored as typed
// app/api/auth/signup/route.ts
export async function POST(req: Request) {
  const { email, password } = await req.json();

  const user = await db.user.create({
    data: {
      email,
      password, // goes in exactly as typed
    },
  });

  return Response.json({ id: user.id });
}
Fixed — hashed with a real KDF
// app/api/auth/signup/route.ts
import { hash } from '@node-rs/argon2';

export async function POST(req: Request) {
  const { email, password } = await req.json();

  const passwordHash = await hash(password, {
    memoryCost: 19456,
    timeCost: 2,
    parallelism: 1,
  });

  const user = await db.user.create({
    data: { email, passwordHash },
  });

  return Response.json({ id: user.id });
}

Two lines of setup and a rename. Argon2id is the current default — bcrypt is still fine if it is what you already have. The password the user typed never touches the database.

A real case

Adobe leaked 153 million password hints in plain text

The 2013 Adobe breach shipped a database where every password was encrypted with a single reversible key and every hint was plaintext — the textbook definition of cryptographic failure at scale.

Related reading

Glossary

Hard-Coded Credentials (API Key Exposure)JSON Web Token Security (JWT Misconfiguration)Sensitive Data Exposure (Information Disclosure)

What we find

exposed secrets

References

  • A02: Cryptographic Failures — official OWASP entry
  • OWASP Top 10 for Web Applications (2021) — full list
  • CWE-259 on cwe.mitre.org
  • CWE-327 on cwe.mitre.org
  • CWE-331 on cwe.mitre.org

Find every place your secrets are hiding in plain sight.

Flowpatrol walks your app the way an attacker would and flags every credential, token, and header that is not where it should be.

Try it free