Skip to main content

Token or secret column stored in plain text

A token, API key or secret column is written with the raw value. Anyone who can read the table (a leaked backup, a read-only SQL injection, an over-broad analytics replica) can replay the token or key directly.

What Vulkro detects​

Rule schema-plaintext-secret-column, default severity Medium, risk category Data protection.

Vulkro finds the column in the database schema and every place the code writes it. The rule fires when no write site hashes or encrypts the value. Columns that only look like secrets, such as a key's last four digits or a public prefix, are ignored.

Non-compliant code​

// The raw reset token is stored, so a database read can replay it
exports.forgotPassword = async (req, res) => {
const token = crypto.randomBytes(32).toString('hex');
await User.updateOne(
{ email: req.body.email },
{ resetToken: token, resetTokenExpiresAt: Date.now() + 3600000 }
);
await sendResetMail(req.body.email, token);
res.sendStatus(204);
};

Compliant code​

// Only a SHA-256 digest of the reset token is stored
exports.forgotPassword = async (req, res) => {
const token = crypto.randomBytes(32).toString('hex');
const tokenDigest = crypto.createHash('sha256').update(token).digest('hex');
await User.updateOne(
{ email: req.body.email },
{ resetToken: tokenDigest, resetTokenExpiresAt: Date.now() + 3600000 }
);
await sendResetMail(req.body.email, token);
res.sendStatus(204);
};

How to fix​

  • Tokens you issue and only need to check later (session, refresh, reset, verification, API tokens): store a SHA-256 or HMAC digest and compare digests. Show the raw value to the user once.
  • Values you must use later (a third-party API key or OAuth token): encrypt at rest with a key kept outside the database, such as a KMS key or an application-level envelope key.
  • Rotate existing values after the change.

References​

See also: Database schema discovery, Secrets.