Skip to content

fix(security): avoid password leaks on query logs - #5559

Merged
OlivierCavadenti merged 5 commits into
knex:masterfrom
castarco:fix-leakable-passwords
Jul 3, 2023
Merged

OlivierCavadenti merged 5 commits into
knex:masterfrom
castarco:fix-leakable-passwords

Conversation

@castarco

@castarco castarco commented May 1, 2023 •

Copy link
Copy Markdown
Contributor

Motivation:

We detected that under certain conditions, passwords could be leaked through logs, when executing code such as:

const query = knex('myTable').select('*');
logger.debug({ query }) // passwords are leaked here

In our case, the logger was Pino, this problem doesn't necessarily happen with other loggers (as they might use different
serialisation methods).

Fixes:

  • password fields are not leaked anymore. Fixes DB passwords can be accidentally leaked through logs #5560 .
  • accepts configuration objects where the password field exists but it is non-enumerable (this might happen when lib users are trying to prevent the same previous problem, but at a higher level).

Changes:

  • marks the password field as non-enumerable when possible, to avoid potential leaks through logs.
  • adapts tests
  • add new regression test

Signed-off-by: Andres Correa Casablanca <castarco@coderspirit.xyz>
@castarco
castarco marked this pull request as ready for review May 1, 2023 12:12
@castarco

castarco commented May 8, 2023 •

Copy link
Copy Markdown
Contributor Author

Hi @tgriesser & @kibertoad , I'm sorry for adding direct mentions, but I have the feeling that this PR fixes a serious issue, and it can be easy that it gets lost among the dozens of automated PRs produced by bots.

@OlivierCavadenti OlivierCavadenti self-assigned this May 15, 2023
Comment thread lib/client.js Outdated
castarco added 3 commits May 15, 2023 15:44
Signed-off-by: Andres Correa Casablanca <castarco@coderspirit.xyz>
check for object to be defined before accessing property

Signed-off-by: Andres Correa Casablanca <castarco@coderspirit.xyz>
Signed-off-by: Andres Correa Casablanca <castarco@coderspirit.xyz>
@coveralls

coveralls commented May 15, 2023 •

Copy link
Copy Markdown

Coverage Status

Coverage: 92.847% (+0.01%) from 92.836% when pulling 6196921 on castarco:fix-leakable-passwords into b6d04f7 on knex:master.

@OlivierCavadenti

Copy link
Copy Markdown
Collaborator

@castarco Thanks for the PR by the way, the error is on the "master" branch and not in your PR.

@castarco

Copy link
Copy Markdown
Contributor Author

Thanks @OlivierCavadenti 😃 , I was investigating now what I might have broken. It will save me some time!

Comment thread lib/util/security.js Outdated
Comment thread test/integration2/query/misc/additional.spec.js
Signed-off-by: Andres Correa Casablanca <castarco@coderspirit.xyz>
@castarco

Copy link
Copy Markdown
Contributor Author

Any news on whether this will be approved/rejected/merged? From our side it's not urgent, because we are using yarn patch in our project, but this PR fixes a vulnerability that can affect other people...

@Eomm

Eomm commented Jun 22, 2023

Copy link
Copy Markdown

@OlivierCavadenti any update on this?

@OlivierCavadenti
OlivierCavadenti merged commit f6ea812 into knex:master Jul 3, 2023
@OlivierCavadenti

Copy link
Copy Markdown
Collaborator

@Eomm sorry for the delay.
I will release a new version as soon as possible (this week), but I need time to also check documentation.
Stay tuned :D

@daniellockyer

daniellockyer commented Jul 11, 2023 •

Copy link
Copy Markdown
Contributor

Is it possible that this has broken auth in 2.5.0? We've started to get auth issues for the 2.4.0 -> 2.5.0 Knex bump PR in Ghost: https://github.com/TryGhost/Ghost/actions/runs/5505546016/jobs/10033054604?pr=17254#step:11:283

  Error: Access denied for user 'root'@'172.17.0.1' (using password: NO)
      at /home/runner/work/Ghost/Ghost/node_modules/knex-migrator/lib/database.js:176:39
      at Packet.asError (/home/runner/work/Ghost/Ghost/node_modules/mysql2/lib/packets/packet.js:728:17)
      at ClientHandshake.execute (/home/runner/work/Ghost/Ghost/node_modules/mysql2/lib/commands/command.js:29:26)
      at Connection.handlePacket (/home/runner/work/Ghost/Ghost/node_modules/mysql2/lib/connection.js:489:32)
      at PacketParser.onPacket (/home/runner/work/Ghost/Ghost/node_modules/mysql2/lib/connection.js:94:12)
      at PacketParser.executeStart (/home/runner/work/Ghost/Ghost/node_modules/mysql2/lib/packet_parser.js:75:16)
      at Socket.<anonymous> (/home/runner/work/Ghost/Ghost/node_modules/mysql2/lib/connection.js:101:25)
      at Socket.emit (node:events:390:28)
      at addChunk (node:internal/streams/readable:315:12)
      at readableAddChunk (node:internal/streams/readable:289:9)
      at Socket.Readable.push (node:internal/streams/readable:228:10)
      at TCP.onStreamRead (node:internal/stream_base_commons:199:23)

@OlivierCavadenti

Copy link
Copy Markdown
Collaborator

Hi, can you check this @castarco ? #5629

@castarco

Copy link
Copy Markdown
Contributor Author

I'll check. It could be related.

@OlivierCavadenti

Copy link
Copy Markdown
Collaborator

I'll check. It could be related.

Thanks !

@elszczepano

Copy link
Copy Markdown

Is it possible that this has broken auth in 2.5.0? We've started to get auth issues for the 2.4.0 -> 2.5.0

Yes, it looks exactly the same as in my case 😄

@castarco

castarco commented Jul 13, 2023 •

Copy link
Copy Markdown
Contributor Author

I didn't had time this past night to fix it as I was low on energy, but I was walking through the code and I already have some idea of how to fix it. Hopefully today I'll be able to post a patch.

Again, sorry for the disturbance.

@OlivierCavadenti

Copy link
Copy Markdown
Collaborator

I didn't had time this past night to fix it as I was low on energy, but I was walking through the code and I already have some idea of how to fix it. Hopefully today I'll be able to post a patch.

Again, sorry for the disturbance.

No problem, we all deal with our personal life, do as you can

@idandagan1

idandagan1 commented Jul 23, 2023 •

Copy link
Copy Markdown

Hi,
I used to pass the db connection password using the package "config" with custom variables from outside the app.

But after upgrading to v2.5.0, it fails with the error:

Cannot redefine property: password
TypeError: Cannot redefine property: password
    at Function.defineProperty (<anonymous>)
    at setHiddenProperty 

So what's your suggestion for injecting the password?

This is my knexfile.ts :

import * as process from 'process';

require('ts-node/register');

import config from 'config';

export interface KnexConfig {
    // knex fields
}

const originalConfig: KnexConfig = config.get('postgres');

// Create a new object with the modified password value
const postgres: KnexConfig = {
    ...originalConfig,
    connection: {
        ...originalConfig.connection,
        password: process.env.PG_PASSWORD,
    },
};

export default postgres;

Does it makes sense?

@Fryuni

Fryuni commented Jul 27, 2023

Copy link
Copy Markdown

Just as a note, this broke our integrations tests since we now cannot reuse the same object with the configurations between scenarios.

We have to clone the object for each instantiation of knex otherwise we get SCRAM-SERVER-FIRST-MESSAGE: client password must be a string from all but the first instantiation.

Easy fix on our side, but just registering here so other might know.

myndzi added a commit to myndzi/knex that referenced this pull request Nov 15, 2025
knex#5559 adjusted makeKnex to store the
`password` property as non-enumerable. As a consequence, tape's `t.deepEqual`
was failing when comparing the rendered config with the expectation.

In the "parse connection string" test, I added an extra expectation to
ensure the password was indeed correctly extracted from the connection string.

In the other tests, I just removed the password property from the expectation,
since they aren't testing the password specifically and we're already covered
on that front.
kibertoad pushed a commit that referenced this pull request Dec 8, 2025
* Update test to match both old and new error descriptions

I'm assuming this test used to pass, but the error message I get from
the version of oracle using the documented setup includes "or closed".
Change to a regex that matches either.

* MSSQL: Drop "test" table before each run

For some reason, the "afterEach" hook is not enough to leave the
database clean of this table which is used in the "column default
handling" tests. Ensure it's removed before running the tests.

* General: Drop extra integration test tables in cleanup

In Oracle, dropping the "accounts" table was failing because it was
referenced by the "test_foreign_table_two" table (which is created
in the schema/misc tests). Ensure it is dropped so that accounts
can be dropped as expected.

I also identified "check_test" as another table being created by
raw tests (and not the tableCreatorHelper code) and added it to
the list of tables to clean up.

* MySQL: ensure query contents are unique for timeout tests

The last timeout test, which abandons the connection, was leaving
the sleep query running. Since both "mysql" and "mysql2" drivers
are connecting to the same database for integration testing, when
both were executed in the same test run, some "mysql2" tests were
seeing these hanging queries and failing, when they were actually
started by a different test against a different database client.

I added comments to the queries to ensure that they are unique
per-test and per-driver, so that the timeout tests are only
asserting against their own queries.

* Use a proper port number for connection string parse assertions

`parse-connection.js`'s `tryParse` uses `new URL` to parse URLs.
Even though the "port" property of a URL instance is a string,
the string is of a non-zero-prefixed integer. These tests were
failing since they asserted that "0000" parsed to "0000", but
in actuality they parsed to "0". Rather than adjust the test
to expect "0", I changed the port to something that would
result in the same thing as the input; that way, we're not
testing the behavior of the URL constructor's parsing, but
just that what _we_ care about is true (the port supplied
in the connection string becomes the value in the object)

* Fix assertions for non-enumerable password property of connection config

#5559 adjusted makeKnex to store the
`password` property as non-enumerable. As a consequence, tape's `t.deepEqual`
was failing when comparing the rendered config with the expectation.

In the "parse connection string" test, I added an extra expectation to
ensure the password was indeed correctly extracted from the connection string.

In the other tests, I just removed the password property from the expectation,
since they aren't testing the password specifically and we're already covered
on that front.

* Exclude `better-sqlite3` from the db-setup tests too

`sqlite3` was already excluded, because it succeeds always. I assume
`better-sqlite3` was added later but not excluded from this test.
louispotok pushed a commit to louispotok/knex that referenced this pull request Dec 12, 2025
* Update test to match both old and new error descriptions

I'm assuming this test used to pass, but the error message I get from
the version of oracle using the documented setup includes "or closed".
Change to a regex that matches either.

* MSSQL: Drop "test" table before each run

For some reason, the "afterEach" hook is not enough to leave the
database clean of this table which is used in the "column default
handling" tests. Ensure it's removed before running the tests.

* General: Drop extra integration test tables in cleanup

In Oracle, dropping the "accounts" table was failing because it was
referenced by the "test_foreign_table_two" table (which is created
in the schema/misc tests). Ensure it is dropped so that accounts
can be dropped as expected.

I also identified "check_test" as another table being created by
raw tests (and not the tableCreatorHelper code) and added it to
the list of tables to clean up.

* MySQL: ensure query contents are unique for timeout tests

The last timeout test, which abandons the connection, was leaving
the sleep query running. Since both "mysql" and "mysql2" drivers
are connecting to the same database for integration testing, when
both were executed in the same test run, some "mysql2" tests were
seeing these hanging queries and failing, when they were actually
started by a different test against a different database client.

I added comments to the queries to ensure that they are unique
per-test and per-driver, so that the timeout tests are only
asserting against their own queries.

* Use a proper port number for connection string parse assertions

`parse-connection.js`'s `tryParse` uses `new URL` to parse URLs.
Even though the "port" property of a URL instance is a string,
the string is of a non-zero-prefixed integer. These tests were
failing since they asserted that "0000" parsed to "0000", but
in actuality they parsed to "0". Rather than adjust the test
to expect "0", I changed the port to something that would
result in the same thing as the input; that way, we're not
testing the behavior of the URL constructor's parsing, but
just that what _we_ care about is true (the port supplied
in the connection string becomes the value in the object)

* Fix assertions for non-enumerable password property of connection config

knex#5559 adjusted makeKnex to store the
`password` property as non-enumerable. As a consequence, tape's `t.deepEqual`
was failing when comparing the rendered config with the expectation.

In the "parse connection string" test, I added an extra expectation to
ensure the password was indeed correctly extracted from the connection string.

In the other tests, I just removed the password property from the expectation,
since they aren't testing the password specifically and we're already covered
on that front.

* Exclude `better-sqlite3` from the db-setup tests too

`sqlite3` was already excluded, because it succeeds always. I assume
`better-sqlite3` was added later but not excluded from this test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DB passwords can be accidentally leaked through logs

8 participants