A ledger that was built wrong
Watch it happen
Mallory gets a copy of the ledger. Passwords and ID numbers are in plain ink, and no column says who owns each row.
Mallory walks in. Before any teller serves anyone, someone designed the ledger: its design decides what can ever be protected.
In bank terms and app terms
| In the bank | In your app |
|---|---|
Are you exposed?
Your schema has columns named password, secret, api_key or access_token with no hash or encrypted version. You store email, phone, address or ID numbers in plain text, or card details as ordinary strings. A multi-tenant app has orders, invoices or users tables with no tenant_id (or user_id) column.
Hash passwords with a slow algorithm (bcrypt or argon2), encrypt other secrets at rest with keys kept outside the database, and never store raw tokens you can avoid keeping. Add an owner or tenant column (user_id, tenant_id) to every table that holds someone’s data, and filter on it. Keep roles in their own table rather than an isAdmin column on users, so a stray update can’t promote someone.
What it looks like in code
// BAD: isAdmin on User model
model User {
id String @id @default(uuid())
email String @unique
isAdmin Boolean @default(false) // Mass assignment target!
}
// GOOD: Separate Role model
model User {
id String @id @default(uuid())
email String @unique
roles Role[]
}
model Role {
id String @id @default(uuid())
name String // "admin", "user", "moderator"
userId String
user User @relation(fields: [userId], references: [id])
}
Ask your AI tool to fix it
Paste this into Cursor, Claude, Lovable, Bolt or whatever you build with.
Review my Prisma or Drizzle schema. List every column that holds a password, secret, API key, token, or personal data (email, phone, address, SSN) and recommend whether to hash or encrypt it. Add a user_id or tenant_id column to every table that stores user-owned data, with a foreign key, and move role or isAdmin fields into a separate roles table. Show the migration.Where this comes from
The facts, figures and fixes on this page were checked against these sources on 25 Oct 2026. They are linked so you can check for yourself.
- Password Storage Cheat SheetOWASPHash passwords with a slow algorithm; Argon2id preferred, bcrypt for legacy
- Cryptographic Storage Cheat SheetOWASPKeep encryption keys apart from the data; best protection is not storing it
- Row Level SecuritySupabaseOwner column on each row lets policies enforce per-user access
- Custom Claims & Role-based Access Control (RBAC)SupabaseRoles in a separate table that client roles cannot write
- Drizzle schemaDrizzle ORMTables, columns and foreign keys are declared in the schema
- API1:2023 Broken Object Level AuthorizationOWASPOwnership checks need a way to tie each record to a user
Spot a mistake or something out of date? Report a correction · How these guides are made and checked
The free, open-source scanner checks for most of the problems in these guides.