Skip to main content
interlace
Plugin: knex-securityRules

no-hardcoded-credentials

Disallow literal database passwords in knex connection configuration, including credentials embedded in a connection URL.

CWE: CWE-798 OWASP: A07:2021 – Identification and Authentication Failures

Detects a database password written as a literal — as a config property, or embedded in a connection URL. This rule is part of eslint-plugin-knex-security.

💼 This rule is set to error in the strict config.

Quick Summary

AspectDetails
CWE ReferenceCWE-798 (Use of Hard-coded Credentials)
SeverityCritical (CVSS 9.8)
Auto-Fix❌ No auto-fix available
CategorySecurity

Why this matters

A password in source is a password in git history, in every fork and clone, in every CI log that prints the file, and in every layer of the built image.

That is what separates it from most findings: it does not stop being true when you fix it. Deleting the line in a follow-up commit changes nothing — the secret is still one git log -p away for anyone who has ever had read access. A real fix means rotating the credential and rewriting history, which is expensive enough that in practice it does not happen. The only cheap moment is before the line is committed, which is where this rule sits.

❌ Incorrect

// ❌ literal password
knex({ client: 'pg', connection: { host, user, password: 'hunter2' } });

// ❌ the same secret, hidden in a URL
knex({ client: 'pg', connection: 'postgres://app:s3cret@db.internal/app' });

✅ Correct

// ✅ read from the environment
knex({
  client: 'pg',
  connection: { host, user, password: process.env.DB_PASSWORD },
});

What this rule deliberately does not report

  • A connection URL with no credentials in it. postgres://localhost:5432/app and postgres://app@db.internal/app are safe to commit. Only the user:pass@ userinfo form is a finding.
  • An empty password. password: '' is the "no password" sentinel for local trust-auth setups. Reporting it teaches people the rule cries wolf.
  • A runtime value — process.env.DB_PASSWORD (the fix), a value read from a config object this file cannot see, a template literal with a hole in the password. A literal bound to a constant, a literal fallback (process.env.DB_PASSWORD || 'root') and a literal exported from a relative module are not runtime values, and are reported.
  • A login or signup form. { user, password } and { password, confirm } are not connection configs. The credential cannot be its own evidence that an object connects to a database — the object has to name somewhere to connect to (host, port, database, connectionString) before its password counts. Without that rule, every app with a login form and a database reports.
  • A file that never imports knex. The driver import is the gate that keeps this rule inside its own plugin; generic secret scanning belongs to a dedicated tool.

When Not To Use It

There is no configuration in which committing a database password is correct, so this rule has no options.

If a specific line is genuinely a throwaway — a docker-compose fixture, an integration test against an ephemeral container — disable it there with a reason rather than switching the rule off:

// eslint-disable-next-line knex-security/no-hardcoded-credentials -- ephemeral test container
knex({ client: 'pg', connection: { host, user, password: 'hunter2' } });

How a connection config is read (2026-10 review)

  • Config files. A knexfile — { client: 'pg', connection: … } — never imports knex, and is read as knex's.

  • Another library's options are not a connection. An object handed to another package — new https.Agent({ host, rejectUnauthorized: false }), axios.create({ … }) — is that package's to judge, even in a file that imports the driver.

  • Values are followed, not skipped. A ternary or || / ?? arm, a constant bound earlier and a value exported from a relative module are read; one written elsewhere is reported at the property that uses it.

  • A DSN bound to a constant and handed to a driver call, or written as a template whose static head already carries user:password@, is reported.

Test files are skipped.

Further Reading

Use this rule

npm install -D oxlint-plugin-knex-security
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-knex-security"],  "rules": {    "knex-security/no-hardcoded-credentials": "error"  }}
vite.config.ts
import { defineConfig } from 'vite-plus';export default defineConfig({  lint: {    jsPlugins: ['oxlint-plugin-knex-security'],    rules: {      'knex-security/no-hardcoded-credentials': 'error',    },  },});

oxlint-plugin-knex-security re-exports eslint-plugin-knex-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.

Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.