Skip to content

Conversation

@AndriiSherman
Copy link
Member

No description provided.

AlexBlokh and others added 30 commits November 8, 2025 10:25
Sukairo-02 and others added 28 commits December 10, 2025 12:44
- fixed: #2856
- fixed: #2630
- works, added a test just in case for #3713
* updated tests

* +

* updated tests

* [drizzle-kit] updated tests

* [drizzle-kit] added tests

* [drizzle-kit] added tests

* [drizzle-kit] updated test

* added tests

* updated pull tests

* +

* [integration-tests] updated tests

* corrected issue links

* [drizzle kit] added test

* [drizzle-kit] added test

* [drizzle-kit] added tests

* [feat-orm]: index logic

Added ability to pass table column to index, uniqueIndex, using in outer const. Without using 3rd param in table

* added tests

* updated tests

* [drizzle-kit] added tests

* [fix+update]: mysql use/force/ignore index update + pg asc/nullsFirst index update

Mysql:
- Added ability to handle unique constraints in use/force/ignore indexes in select queries
- Added tests

Pg:
- in drizzle-kit/drizzle.ts file updated logic regarding nullsFirst and asc/desc creation
pg docs: https://www.postgresql.org/docs/current/sql-createindex.html

* [update]: changelog

* [drizzle-kit] added tests

* [psql]: fixed broken tests in kit

* [mysql]: fixed some an issue in kit

* [sqlite]: isssue + fk introspect bugs fixed

* [mssql]: index bug fix in kit

* [cockroach]: fixed tests in kit

* [sqlite]: fk introspect fix

* [psql]: kit pk tests

* [update]: changelog for drizzle-kit

* [mssql]: kit fix

* [drizzle-kit] added tests

* [mssql-kit]: up to v2

* [kit]: psql + cockroach enum tests

* [drizzle-kit] added tests

* updated comments in tests

* [update]: kit tests update

* [update]: ignored ts error for deploy

* [update-casing]: removed casing from Name class

* [update]: isNull wrapped in ()

* [updates]: tests + bug fixes

- Fixed "buildIndex" method in mysql for `use/force/ignore` indexes
- Updated some integration tests

* [update]: fixed int:sqlite tests

* [update]: singlestore int test

* [update]: singlestore test

* [update]: changelog for kit + up of package.json versions of orm and kit

* [update]: changelog + test

---------

Co-authored-by: OleksiiKH0240 <[email protected]>
Co-authored-by: Alex Blokh <[email protected]>
* feat: Add duckdb for studio

* fix: Ensure consistent oid comparisons by converting to number

* refactor: Remove beta warning message from Drizzle Studio command
* updated tests

* +

* updated tests

* [drizzle-kit] updated tests

* [drizzle-kit] added tests

* [drizzle-kit] added tests

* [drizzle-kit] updated test

* added tests

* updated pull tests

* +

* [integration-tests] updated tests

* corrected issue links

* [drizzle kit] added test

* [drizzle-kit] added test

* [drizzle-kit] added tests

* [feat-orm]: index logic

Added ability to pass table column to index, uniqueIndex, using in outer const. Without using 3rd param in table

* added tests

* updated tests

* [drizzle-kit] added tests

* [fix+update]: mysql use/force/ignore index update + pg asc/nullsFirst index update

Mysql:
- Added ability to handle unique constraints in use/force/ignore indexes in select queries
- Added tests

Pg:
- in drizzle-kit/drizzle.ts file updated logic regarding nullsFirst and asc/desc creation
pg docs: https://www.postgresql.org/docs/current/sql-createindex.html

* [update]: changelog

* [drizzle-kit] added tests

* [psql]: fixed broken tests in kit

* [mysql]: fixed some an issue in kit

* [sqlite]: isssue + fk introspect bugs fixed

* [mssql]: index bug fix in kit

* [cockroach]: fixed tests in kit

* [sqlite]: fk introspect fix

* [psql]: kit pk tests

* [update]: changelog for drizzle-kit

* [mssql]: kit fix

* [drizzle-kit] added tests

* [mssql-kit]: up to v2

* [kit]: psql + cockroach enum tests

* [drizzle-kit] added tests

* updated comments in tests

* [update]: kit tests update

* [update]: ignored ts error for deploy

* [update-casing]: removed casing from Name class

* [update]: isNull wrapped in ()

* [updates]: tests + bug fixes

- Fixed "buildIndex" method in mysql for `use/force/ignore` indexes
- Updated some integration tests

* [update]: fixed int:sqlite tests

* [update]: singlestore int test

* [update]: singlestore test

* [update]: changelog for kit + up of package.json versions of orm and kit

* [update]: changelog + test

* [drizzle-kit] added tests

* [drizzle-kit] added test

* updated test

* [update-kit]: psql test

* [update-kit]: fixed tests

Added:
- handling on column alters to serial in pg
- suggestions for diff in mysql

* [update-kit]: deprecated 'strict' flag

* [update-kit]: test

* [update-kit]: test

* [update-kit]: mssql test

* [update-kit]: fix

* [update-kit]: added explain to every dialect

* added tests

* fixed prompt for column conflicts; added tests

* [update-kit]: sqlite up fixes

- Added tests on up (like pg and mysql had)
- fixed some minor fixes

* [fix-kit]: errors in mocks

* [fix-kit]: mssql fixes

* [fix-kit]: tests fix to deploy

* [fix-kit]

* [fix-kit]

* [fix-kit]

* [update]: versions + changelog for sqlite

* [update]: comment

* [update-kit]: fixed mssql + new tests

* [fix-kit]: --explain handling

* Update release notes

---------

Co-authored-by: OleksiiKH0240 <[email protected]>
Co-authored-by: Alex Blokh <[email protected]>
Co-authored-by: AndriiSherman <[email protected]>
* updated tests

* +

* updated tests

* [drizzle-kit] updated tests

* [drizzle-kit] added tests

* [drizzle-kit] added tests

* [drizzle-kit] updated test

* added tests

* updated pull tests

* +

* [integration-tests] updated tests

* corrected issue links

* [drizzle kit] added test

* [drizzle-kit] added test

* [drizzle-kit] added tests

* [feat-orm]: index logic

Added ability to pass table column to index, uniqueIndex, using in outer const. Without using 3rd param in table

* added tests

* updated tests

* [drizzle-kit] added tests

* [fix+update]: mysql use/force/ignore index update + pg asc/nullsFirst index update

Mysql:
- Added ability to handle unique constraints in use/force/ignore indexes in select queries
- Added tests

Pg:
- in drizzle-kit/drizzle.ts file updated logic regarding nullsFirst and asc/desc creation
pg docs: https://www.postgresql.org/docs/current/sql-createindex.html

* [update]: changelog

* [drizzle-kit] added tests

* [psql]: fixed broken tests in kit

* [mysql]: fixed some an issue in kit

* [sqlite]: isssue + fk introspect bugs fixed

* [mssql]: index bug fix in kit

* [cockroach]: fixed tests in kit

* [sqlite]: fk introspect fix

* [psql]: kit pk tests

* [update]: changelog for drizzle-kit

* [mssql]: kit fix

* [drizzle-kit] added tests

* [mssql-kit]: up to v2

* [kit]: psql + cockroach enum tests

* [drizzle-kit] added tests

* updated comments in tests

* [update]: kit tests update

* [update]: ignored ts error for deploy

* [update-casing]: removed casing from Name class

* [update]: isNull wrapped in ()

* [updates]: tests + bug fixes

- Fixed "buildIndex" method in mysql for `use/force/ignore` indexes
- Updated some integration tests

* [update]: fixed int:sqlite tests

* [update]: singlestore int test

* [update]: singlestore test

* [update]: changelog for kit + up of package.json versions of orm and kit

* [update]: changelog + test

* [drizzle-kit] added tests

* [drizzle-kit] added test

* updated test

* [update-kit]: psql test

* [update-kit]: fixed tests

Added:
- handling on column alters to serial in pg
- suggestions for diff in mysql

* [update-kit]: deprecated 'strict' flag

* [update-kit]: test

* [update-kit]: test

* [update-kit]: mssql test

* [update-kit]: fix

* [update-kit]: added explain to every dialect

* added tests

* fixed prompt for column conflicts; added tests

* [update-kit]: sqlite up fixes

- Added tests on up (like pg and mysql had)
- fixed some minor fixes

* [fix-kit]: errors in mocks

* [fix-kit]: mssql fixes

* [fix-kit]: tests fix to deploy

* [fix-kit]

* [fix-kit]

* [fix-kit]

* [update]: versions + changelog for sqlite

* [update]: comment

* [update-kit]: fixed mssql + new tests

* [fix-kit]: --explain handling

* Update release notes

* [sqlite-kit-fix]: introspect + explain

* [update-kit]: fixed two issues

* [fix-kit]: deepStrictEqual + fixed broken tests

* [update-kit]: fixed issues

* [update-kit]: added custom type for sqlite introspect + up all packages

* [fix-kit]: defaults for text

* added tests

* [fix-kit]: fixed + postponed tests

* [fix-kit]: sqlite def test

---------

Co-authored-by: OleksiiKH0240 <[email protected]>
Co-authored-by: Alex Blokh <[email protected]>
Co-authored-by: AndriiSherman <[email protected]>
}

const explainMessage = explain('mysql', groupedStatements, false, []);
if (explainMessage) console.log(explainMessage);

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This logs sensitive data returned by
an access to password
as clear text.
This logs sensitive data returned by
an access to password
as clear text.

Copilot Autofix

AI 14 days ago

To fix the problem, the code should avoid including actual password values in any logged or rendered output. Instead, it should either (a) omit password details entirely, or (b) replace them with a non‑sensitive indicator (e.g., just say that the password changed, or mask values). This preserves the usefulness of the “explain” output (it still informs the user that a password was modified) while preventing disclosure of sensitive data.

The minimal, behavior‑preserving approach is to modify psqlExplain in drizzle-kit/src/cli/views.ts so that the alter_role branch no longer appends d.password.from and d.password.to to cause. We can keep a password‑change line but without the values, for example: │ password: [REDACTED] -> [REDACTED]\n. No other parts of the code need to be aware of this change; the signature of psqlExplain and explain stays the same and all other fields (like canLogin, superuser, etc.) are untouched.

Concretely:

  • In views.ts, inside psqlExplain, at the if (d.password) line (around line 249), replace the interpolation of d.password.from and d.password.to with a static text indicating that the password changed, without printing actual values.
  • No changes are required in drizzle-kit/src/cli/commands/generate-mysql.ts because that file only calls explain and doesn’t itself handle the password string contents.
  • No new imports or helper methods are required.

Suggested changeset 1
drizzle-kit/src/cli/views.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-kit/src/cli/views.ts b/drizzle-kit/src/cli/views.ts
--- a/drizzle-kit/src/cli/views.ts
+++ b/drizzle-kit/src/cli/views.ts
@@ -246,7 +246,7 @@
 		if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
 		if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
 		if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
-		if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
+		if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;
 		if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
 		if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
 		if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
EOF
@@ -246,7 +246,7 @@
if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;
if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
Copilot is powered by AI and may make mistakes. Always verify output.
);

const explainMessage = explain('mysql', groupedStatements, false, []);
if (explainMessage) console.log(explainMessage);

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This logs sensitive data returned by
an access to password
as clear text.
This logs sensitive data returned by
an access to password
as clear text.

Copilot Autofix

AI 14 days ago

General fix: prevent clear‑text password values from being logged. Either avoid mentioning password changes at all or redact the actual values while still indicating that the password changed.

Best minimal fix: adjust psqlExplain in drizzle-kit/src/cli/views.ts so that it no longer embeds d.password.from and d.password.to into the cause string. Instead, log only that the password was changed, without exposing the values. All other logging (for non‑password fields) remains unchanged. No behavior changes are needed elsewhere; generate-postgres.ts can continue to log explainMessage because it will no longer contain raw password values.

Concrete change:

  • In psqlExplain, in the if (st.type === 'alter_role') block, replace:
    if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
    with a redacted, non‑sensitive message such as:
    if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;
    or even simpler:
    if (d.password) cause += `│ password: [changed]\n`;

This change is limited to views.ts; no imports or additional methods are required.

Suggested changeset 1
drizzle-kit/src/cli/views.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-kit/src/cli/views.ts b/drizzle-kit/src/cli/views.ts
--- a/drizzle-kit/src/cli/views.ts
+++ b/drizzle-kit/src/cli/views.ts
@@ -246,7 +246,7 @@
 		if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
 		if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
 		if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
-		if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
+		if (d.password) cause += `│ password: [changed]\n`;
 		if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
 		if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
 		if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
EOF
@@ -246,7 +246,7 @@
if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
if (d.password) cause += `│ password: [changed]\n`;
if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
Copilot is powered by AI and may make mistakes. Always verify output.
const hints = await suggestions(db, jsonStatements);
const explainMessage = explain('cockroach', groupedStatements, explainFlag, hints);

if (explainMessage) console.log(explainMessage);

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This logs sensitive data returned by
an access to password
as clear text.
This logs sensitive data returned by
an access to password
as clear text.

Copilot Autofix

AI 14 days ago

In general, the fix is to prevent raw password values from ever being included in the explanatory text that ultimately gets logged. We still want to show that a password changed, but not what it changed from/to. That means adjusting the psqlExplain function in drizzle-kit/src/cli/views.ts so that the password case logs only a generic note (e.g., “password has changed”) or redacted placeholders instead of the actual values, while leaving the rest of the explanation logic intact. Once the password values are no longer present in cause, the explain function can safely pass its result back to push-cockroach.ts, and the existing console.log(explainMessage) on line 98 there remains acceptable.

Concretely, in views.ts around lines 237–253, we will replace the line:

if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;

with a non-sensitive, non-tainted message such as:

if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;

or even just │ password: [changed]\n. This change does not affect control flow or any other functionality; it only alters the human-readable explain text. No additional imports or dependencies are required. The push-cockroach.ts file does not need code changes, because once psqlExplain stops embedding passwords, explainMessage will no longer contain sensitive data.

Suggested changeset 1
drizzle-kit/src/cli/views.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-kit/src/cli/views.ts b/drizzle-kit/src/cli/views.ts
--- a/drizzle-kit/src/cli/views.ts
+++ b/drizzle-kit/src/cli/views.ts
@@ -246,7 +246,7 @@
 		if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
 		if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
 		if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
-		if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
+		if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;
 		if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
 		if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
 		if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
EOF
@@ -246,7 +246,7 @@
if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;
if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
Copilot is powered by AI and may make mistakes. Always verify output.
const hints = await suggestions(db, jsonStatements, ddl2);

const explainMessage = explain('mssql', groupedStatements, explainFlag, hints);
if (explainMessage) console.log(explainMessage);

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This logs sensitive data returned by
an access to password
as clear text.
This logs sensitive data returned by
an access to password
as clear text.

Copilot Autofix

AI 14 days ago

To fix the problem, we must ensure that sensitive password values are never included in log/explain messages. Instead of interpolating the actual from/to password values into the cause string, we can either omit the password detail entirely or replace it with a generic description (e.g., “password changed”). This preserves the usefulness of the explain output for schema diffing while preventing clear‑text password disclosure.

The single best minimal change is in drizzle-kit/src/cli/views.ts inside psqlExplain, in the if (st.type === 'alter_role') block. We should replace the line:

if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;

with a redacted, non-sensitive message such as:

if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;

or even more minimal:

if (d.password) cause += `│ password: [changed]\n`;

Either avoids logging actual passwords; using a “[changed]” marker gives users a clear signal that there was a password modification without revealing values. No additional imports or helpers are required, and no behavior elsewhere needs to change. The logging in push-mssql.ts can remain as-is because it will now never include real password content.

Suggested changeset 1
drizzle-kit/src/cli/views.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-kit/src/cli/views.ts b/drizzle-kit/src/cli/views.ts
--- a/drizzle-kit/src/cli/views.ts
+++ b/drizzle-kit/src/cli/views.ts
@@ -246,7 +246,7 @@
 		if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
 		if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
 		if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
-		if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
+		if (d.password) cause += `│ password: [changed]\n`;
 		if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
 		if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
 		if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
EOF
@@ -246,7 +246,7 @@
if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
if (d.password) cause += `│ password: [changed]\n`;
if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
Copilot is powered by AI and may make mistakes. Always verify output.
const hints = await suggestions(db, filteredStatements, ddl2);
const explainMessage = explain('mysql', groupedStatements, explainFlag, hints);

if (explainMessage) console.log(explainMessage);

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This logs sensitive data returned by
an access to password
as clear text.
This logs sensitive data returned by
an access to password
as clear text.

Copilot Autofix

AI 14 days ago

To fix the problem, we need to ensure that password values are not written to logs. The simplest approach is to redact or avoid including the actual password values in the human‑readable explanation, while still indicating that a password change occurred. This keeps functionality (explaining schema changes) but removes leakage of secret data.

Concretely, in drizzle-kit/src/cli/views.ts within psqlExplain, we should change the line that currently appends the raw password diff:

if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;

to a redacted form such as:

if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;

or, if we want to preserve directional info without secrets:

if (d.password) cause += `│ password: [changed]\n`;

Either is acceptable from a security standpoint; the former makes it visually consistent with other diff lines while not revealing the secret. No additional imports or helpers are needed; this is a straightforward string change in views.ts. The sink in push-mysql.ts (console.log(explainMessage)) can remain as is because it will no longer receive any raw password data from the explainer.

No code changes are needed in drizzle-kit/src/cli/commands/push-mysql.ts itself for this particular issue; it can safely log the explanation as long as the explanation no longer includes secrets.


Suggested changeset 1
drizzle-kit/src/cli/views.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-kit/src/cli/views.ts b/drizzle-kit/src/cli/views.ts
--- a/drizzle-kit/src/cli/views.ts
+++ b/drizzle-kit/src/cli/views.ts
@@ -246,7 +246,7 @@
 		if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
 		if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
 		if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
-		if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
+		if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;
 		if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
 		if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
 		if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
EOF
@@ -246,7 +246,7 @@
if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;
if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
Copilot is powered by AI and may make mistakes. Always verify output.
} = await ddlDiffDry(ddl1, ddl2, 'push');

if (afterFileSqlStatements.length > 0) {
console.log(explain('mysql', groupedStatements, true, []));

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High test

This logs sensitive data returned by
an access to password
as clear text.
This logs sensitive data returned by
an access to password
as clear text.

Copilot Autofix

AI 14 days ago

To fix the issue, we should ensure that any sensitive values, specifically passwords, are not logged in clear text. In this case, the problem is in psqlExplain where the alter_role branch appends d.password.from and d.password.to into the cause string. The best fix is to change this log line to avoid including the actual values and instead log a generic message like password: [REDACTED] -> [REDACTED] or simply password changed. This preserves the usefulness of the explanation (that a password changed) without exposing the underlying secret.

Concretely, in drizzle-kit/src/cli/views.ts, within psqlExplain, we should replace the line:

if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;

with a redacted version, for example:

if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;

or, if we want even less risk of leakage from formatting, just:

if (d.password) cause += `│ password changed\n`;

No other changes are required in the data flow: the explain function can continue to return explanatory strings, and the test in drizzle-kit/tests/postgres/mocks.ts can keep logging them. We don’t need any new imports or helper functions; this is a simple string change.

Suggested changeset 1
drizzle-kit/src/cli/views.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-kit/src/cli/views.ts b/drizzle-kit/src/cli/views.ts
--- a/drizzle-kit/src/cli/views.ts
+++ b/drizzle-kit/src/cli/views.ts
@@ -246,7 +246,7 @@
 		if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
 		if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
 		if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
-		if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
+		if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;
 		if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
 		if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
 		if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
EOF
@@ -246,7 +246,7 @@
if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
if (d.password) cause += `│ password: [REDACTED] -> [REDACTED]\n`;
if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
);

const explainMessage = explain('singlestore', groupedStatements, false, []);
if (explainMessage) console.log(explainMessage);

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High test

This logs sensitive data returned by
an access to password
as clear text.
This logs sensitive data returned by
an access to password
as clear text.

Copilot Autofix

AI 14 days ago

General approach: avoid including actual password values in any logged message. Where we need to show that a password changed, we can log a generic message or a masked value instead of the real password. The safest minimal fix is to alter the formatting in psqlExplain so that, instead of interpolating d.password.from and d.password.to, we log a neutral message like password: [REDACTED] or just password changed. This preserves the indication that a password-related change occurred without exposing credentials. After that, diffPush can still log explainMessage unchanged because the sensitive content is no longer present.

Concretely:

  • In drizzle-kit/src/cli/views.ts, in psqlExplain, replace the line:

    if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;

    with a redacted or generic message, for example:

    if (d.password) cause += `│ password: [REDACTED]\n`;

    or

    if (d.password) cause += `│ password: (value changed)\n`;

    This ensures no clear-text passwords flow into cause, and thus into explain’s return value.

  • No changes are needed in drizzle-kit/tests/singlestore/mocks.ts other than relying on this safer behavior, because the sink (logging explainMessage) can remain as is when the tainted data source has been neutralized.

No additional imports or helper methods are needed; this is a simple string change.


Suggested changeset 1
drizzle-kit/src/cli/views.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-kit/src/cli/views.ts b/drizzle-kit/src/cli/views.ts
--- a/drizzle-kit/src/cli/views.ts
+++ b/drizzle-kit/src/cli/views.ts
@@ -246,7 +246,7 @@
 		if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
 		if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
 		if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
-		if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
+		if (d.password) cause += `│ password: (value changed)\n`;
 		if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
 		if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
 		if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
EOF
@@ -246,7 +246,7 @@
if (d.createDb) cause += `│ createDb: ${d.createDb.from} -> ${d.createDb.to}\n`;
if (d.createRole) cause += `│ createRole: ${d.createRole.from} -> ${d.createRole.to}\n`;
if (d.inherit) cause += `│ inherit: ${d.inherit.from} -> ${d.inherit.to}\n`;
if (d.password) cause += `│ password: ${d.password.from} -> ${d.password.to}\n`;
if (d.password) cause += `│ password: (value changed)\n`;
if (d.replication) cause += `│ replication: ${d.replication.from} -> ${d.replication.to}\n`;
if (d.superuser) cause += `│ superuser: ${d.superuser.from} -> ${d.superuser.to}\n`;
if (d.validUntil) cause += `│ validUntil: ${d.validUntil.from} -> ${d.validUntil.to}\n`;
Copilot is powered by AI and may make mistakes. Always verify output.
tablesConfig[key]!.primaryKey.push(column);
}
if (parseJson) {
row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);

Check warning

Code scanning / CodeQL

Prototype-polluting assignment Medium

This assignment may alter Object.prototype if a malicious '__proto__' string is injected from
library input
.

Copilot Autofix

AI 14 days ago

General approach: Prevent untrusted or unexpected property names like __proto__, constructor, and prototype from being used as keys in assignments to row. This can be done by either (a) creating a prototype-less object and copying into it, or (b) guarding against these special keys before assigning to row[...]. Option (b) is less invasive and preserves the existing object identity and structure.

Concrete fix here: In mapRelationalRow, before any use of selectionItem.key to read/assign row[selectionItem.key], compute a boolean isDangerousKey for that key (e.g., key === '__proto__' || key === 'constructor' || key === 'prototype'). If the key is dangerous, skip processing that selection item entirely. This prevents any path from causing writes to row.__proto__ (or similar), which would modify Object.prototype. We should apply this check once per iteration of the loop, at the top of the for (const selectionItem of buildQueryResultSelection) loop, so that it applies both to the JSON parse/recursive branch (selectionItem.selection) and to the decoding/assignment branch (the else part). No new imports are needed; the logic is local.

Concretely:

  • In drizzle-orm/src/relations.ts, inside mapRelationalRow, right after entering the for (const selectionItem of buildQueryResultSelection) loop, add a guard like:

    const key = selectionItem.key;
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
        continue;
    }
  • Replace subsequent uses of selectionItem.key in that function body with key (optional but clearer), or just keep using selectionItem.key if preferred. The important part is that we short-circuit processing when the key is dangerous, before any row[selectionItem.key] = ... assignment or JSON.parse(row[selectionItem.key] as string) happens.

This keeps all existing functionality for legitimate keys, adds a negligible performance cost, and closes the prototype-pollution vector.

Suggested changeset 1
drizzle-orm/src/relations.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-orm/src/relations.ts b/drizzle-orm/src/relations.ts
--- a/drizzle-orm/src/relations.ts
+++ b/drizzle-orm/src/relations.ts
@@ -741,21 +741,27 @@
 	path?: string,
 ): Record<string, unknown> {
 	for (const selectionItem of buildQueryResultSelection) {
+		const key = selectionItem.key;
+		// Guard against prototype pollution via special property names
+		if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
+			continue;
+		}
+
 		if (selectionItem.selection) {
-			const currentPath = `${path ? `${path}.` : ''}${selectionItem.key}`;
+			const currentPath = `${path ? `${path}.` : ''}${key}`;
 
-			if (row[selectionItem.key] === null) continue;
+			if (row[key] === null) continue;
 
 			if (parseJson) {
-				row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
-				if (row[selectionItem.key] === null) continue;
+				row[key] = JSON.parse(row[key] as string);
+				if (row[key] === null) continue;
 			}
-			if (parseJsonIfString && typeof row[selectionItem.key] === 'string') {
-				row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
+			if (parseJsonIfString && typeof row[key] === 'string') {
+				row[key] = JSON.parse(row[key] as string);
 			}
 
 			if (selectionItem.isArray) {
-				for (const item of (row[selectionItem.key] as Array<Record<string, unknown>>)) {
+				for (const item of (row[key] as Array<Record<string, unknown>>)) {
 					mapRelationalRow(
 						item,
 						selectionItem.selection!,
@@ -770,7 +768,7 @@
 			}
 
 			mapRelationalRow(
-				row[selectionItem.key] as Record<string, unknown>,
+				row[key] as Record<string, unknown>,
 				selectionItem.selection!,
 				mapColumnValue,
 				false,
@@ -782,7 +780,7 @@
 		}
 
 		const field = selectionItem.field!;
-		const value = mapColumnValue(row[selectionItem.key]);
+		const value = mapColumnValue(row[key]);
 		if (value === null) continue;
 
 		let decoder;
@@ -798,7 +796,7 @@
 			decoder = field.getSQL().decoder;
 		}
 
-		row[selectionItem.key] = 'mapFromJsonValue' in decoder
+		row[key] = 'mapFromJsonValue' in decoder
 			? (<(value: unknown) => unknown> decoder.mapFromJsonValue)(value)
 			: decoder.mapFromDriverValue(value);
 	}
EOF
@@ -741,21 +741,27 @@
path?: string,
): Record<string, unknown> {
for (const selectionItem of buildQueryResultSelection) {
const key = selectionItem.key;
// Guard against prototype pollution via special property names
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}

if (selectionItem.selection) {
const currentPath = `${path ? `${path}.` : ''}${selectionItem.key}`;
const currentPath = `${path ? `${path}.` : ''}${key}`;

if (row[selectionItem.key] === null) continue;
if (row[key] === null) continue;

if (parseJson) {
row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
if (row[selectionItem.key] === null) continue;
row[key] = JSON.parse(row[key] as string);
if (row[key] === null) continue;
}
if (parseJsonIfString && typeof row[selectionItem.key] === 'string') {
row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
if (parseJsonIfString && typeof row[key] === 'string') {
row[key] = JSON.parse(row[key] as string);
}

if (selectionItem.isArray) {
for (const item of (row[selectionItem.key] as Array<Record<string, unknown>>)) {
for (const item of (row[key] as Array<Record<string, unknown>>)) {
mapRelationalRow(
item,
selectionItem.selection!,
@@ -770,7 +768,7 @@
}

mapRelationalRow(
row[selectionItem.key] as Record<string, unknown>,
row[key] as Record<string, unknown>,
selectionItem.selection!,
mapColumnValue,
false,
@@ -782,7 +780,7 @@
}

const field = selectionItem.field!;
const value = mapColumnValue(row[selectionItem.key]);
const value = mapColumnValue(row[key]);
if (value === null) continue;

let decoder;
@@ -798,7 +796,7 @@
decoder = field.getSQL().decoder;
}

row[selectionItem.key] = 'mapFromJsonValue' in decoder
row[key] = 'mapFromJsonValue' in decoder
? (<(value: unknown) => unknown> decoder.mapFromJsonValue)(value)
: decoder.mapFromDriverValue(value);
}
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
if (row[selectionItem.key] === null) continue;
}
if (parseJsonIfString && typeof row[selectionItem.key] === 'string') {
row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);

Check warning

Code scanning / CodeQL

Prototype-polluting assignment Medium

This assignment may alter Object.prototype if a malicious '__proto__' string is injected from
library input
.

Copilot Autofix

AI 14 days ago

In general, to fix prototype-polluting assignments you must ensure that untrusted strings are not used as direct property names on plain objects with prototypes, unless they are validated or normalized. Common strategies are: (1) validate/reject dangerous keys like "__proto__", "constructor", "prototype", or (2) use safe key spaces (e.g., always prefix keys), or (3) use a prototype-less object or a Map for dynamic keys.

For this specific function, the minimal and safest fix without changing behavior is to skip any selectionItem whose key is a dangerous prototype-polluting name. That ensures we never execute row[selectionItem.key] = ... when selectionItem.key is "__proto__", "constructor", or "prototype". The right place to add this guard is at the top of the for (const selectionItem of buildQueryResultSelection) loop in mapRelationalRow, before any access to row[selectionItem.key]. That way, both the JSON-parsing assignments (lines 750 and 754) and the later decoder assignments (line 801) are protected, and we avoid touching Object.prototype even if a malicious key sneaks in via buildQueryResultSelection.

Concretely, in drizzle-orm/src/relations.ts, inside mapRelationalRow, add a small conditional block right after entering the loop:

for (const selectionItem of buildQueryResultSelection) {
    const key = selectionItem.key;
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
        continue;
    }
    // existing logic...
}

Then replace further uses of selectionItem.key inside the loop with the local key variable. This keeps behavior unchanged for all normal keys, but ensures that no prototype-polluting names are ever used for reads or writes on row.

Suggested changeset 1
drizzle-orm/src/relations.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-orm/src/relations.ts b/drizzle-orm/src/relations.ts
--- a/drizzle-orm/src/relations.ts
+++ b/drizzle-orm/src/relations.ts
@@ -741,21 +741,28 @@
 	path?: string,
 ): Record<string, unknown> {
 	for (const selectionItem of buildQueryResultSelection) {
+		const key = selectionItem.key;
+
+		// Prevent prototype pollution by skipping dangerous keys
+		if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
+			continue;
+		}
+
 		if (selectionItem.selection) {
-			const currentPath = `${path ? `${path}.` : ''}${selectionItem.key}`;
+			const currentPath = `${path ? `${path}.` : ''}${key}`;
 
-			if (row[selectionItem.key] === null) continue;
+			if (row[key] === null) continue;
 
 			if (parseJson) {
-				row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
-				if (row[selectionItem.key] === null) continue;
+				row[key] = JSON.parse(row[key] as string);
+				if (row[key] === null) continue;
 			}
-			if (parseJsonIfString && typeof row[selectionItem.key] === 'string') {
-				row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
+			if (parseJsonIfString && typeof row[key] === 'string') {
+				row[key] = JSON.parse(row[key] as string);
 			}
 
 			if (selectionItem.isArray) {
-				for (const item of (row[selectionItem.key] as Array<Record<string, unknown>>)) {
+				for (const item of (row[key] as Array<Record<string, unknown>>)) {
 					mapRelationalRow(
 						item,
 						selectionItem.selection!,
@@ -770,7 +769,7 @@
 			}
 
 			mapRelationalRow(
-				row[selectionItem.key] as Record<string, unknown>,
+				row[key] as Record<string, unknown>,
 				selectionItem.selection!,
 				mapColumnValue,
 				false,
@@ -782,7 +781,7 @@
 		}
 
 		const field = selectionItem.field!;
-		const value = mapColumnValue(row[selectionItem.key]);
+		const value = mapColumnValue(row[key]);
 		if (value === null) continue;
 
 		let decoder;
@@ -798,7 +797,7 @@
 			decoder = field.getSQL().decoder;
 		}
 
-		row[selectionItem.key] = 'mapFromJsonValue' in decoder
+		row[key] = 'mapFromJsonValue' in decoder
 			? (<(value: unknown) => unknown> decoder.mapFromJsonValue)(value)
 			: decoder.mapFromDriverValue(value);
 	}
EOF
@@ -741,21 +741,28 @@
path?: string,
): Record<string, unknown> {
for (const selectionItem of buildQueryResultSelection) {
const key = selectionItem.key;

// Prevent prototype pollution by skipping dangerous keys
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}

if (selectionItem.selection) {
const currentPath = `${path ? `${path}.` : ''}${selectionItem.key}`;
const currentPath = `${path ? `${path}.` : ''}${key}`;

if (row[selectionItem.key] === null) continue;
if (row[key] === null) continue;

if (parseJson) {
row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
if (row[selectionItem.key] === null) continue;
row[key] = JSON.parse(row[key] as string);
if (row[key] === null) continue;
}
if (parseJsonIfString && typeof row[selectionItem.key] === 'string') {
row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
if (parseJsonIfString && typeof row[key] === 'string') {
row[key] = JSON.parse(row[key] as string);
}

if (selectionItem.isArray) {
for (const item of (row[selectionItem.key] as Array<Record<string, unknown>>)) {
for (const item of (row[key] as Array<Record<string, unknown>>)) {
mapRelationalRow(
item,
selectionItem.selection!,
@@ -770,7 +769,7 @@
}

mapRelationalRow(
row[selectionItem.key] as Record<string, unknown>,
row[key] as Record<string, unknown>,
selectionItem.selection!,
mapColumnValue,
false,
@@ -782,7 +781,7 @@
}

const field = selectionItem.field!;
const value = mapColumnValue(row[selectionItem.key]);
const value = mapColumnValue(row[key]);
if (value === null) continue;

let decoder;
@@ -798,7 +797,7 @@
decoder = field.getSQL().decoder;
}

row[selectionItem.key] = 'mapFromJsonValue' in decoder
row[key] = 'mapFromJsonValue' in decoder
? (<(value: unknown) => unknown> decoder.mapFromJsonValue)(value)
: decoder.mapFromDriverValue(value);
}
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
}

row[selectionItem.key] = 'mapFromJsonValue' in decoder

Check warning

Code scanning / CodeQL

Prototype-polluting assignment Medium

This assignment may alter Object.prototype if a malicious '__proto__' string is injected from
library input
.

Copilot Autofix

AI 14 days ago

In general, the problem is that we assign to row[selectionItem.key] using an unvalidated dynamic key on a plain object. To fix prototype-polluting assignments, we must prevent dangerous keys (__proto__, prototype, constructor) from being used on regular objects, or use a data structure that does not participate in the normal prototype chain (e.g., Map or Object.create(null)). Here, changing row to a Map or to a prototype-less object would be invasive and could affect callers, so the least-disruptive fix is to guard against those special keys before performing any row[selectionItem.key] reads/writes.

The best minimal fix is to add a small helper that checks whether a key is safe for assignment to a normal object, and then use it to 1) skip processing of dangerous keys and 2) ensure that any nested objects we recurse into are plain objects, not Object.prototype. Concretely:

  • Introduce a helper inside mapRelationalRow (or just above it) such as isSafeObjectKey(key: string): boolean that returns false for "__proto__", "prototype", or "constructor".
  • At the top of the main for (const selectionItem of buildQueryResultSelection) loop, compute const key = selectionItem.key; and if !isSafeObjectKey(key), continue;.
  • Replace all uses of selectionItem.key to use the local key constant to ensure we only ever index row with validated keys.
  • Before recursing into nested rows (mapRelationalRow(row[key] as Record<string, unknown>, ...) or looping over arrays of nested rows), ensure that the value is an object and not null and not an array where an object is expected; but we must not alter behavior beyond what is needed, so we only add safe guards where we need to avoid prototype-pollution paths.
  • We do not need any new imports: the helper is a simple, local function.

This fix preserves existing behavior for all valid keys and only discards processing for the rare case where a key matches one of the dangerous names. That is typically acceptable because such names are unlikely to be valid column aliases or relation keys.

Suggested changeset 1
drizzle-orm/src/relations.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/drizzle-orm/src/relations.ts b/drizzle-orm/src/relations.ts
--- a/drizzle-orm/src/relations.ts
+++ b/drizzle-orm/src/relations.ts
@@ -740,22 +740,33 @@
 	parseJsonIfString: boolean = false,
 	path?: string,
 ): Record<string, unknown> {
+	const isSafeObjectKey = (key: string): boolean => {
+		return key !== '__proto__' && key !== 'prototype' && key !== 'constructor';
+	};
+
 	for (const selectionItem of buildQueryResultSelection) {
+		const key = selectionItem.key;
+
+		// Prevent prototype-polluting assignments on plain objects.
+		if (!isSafeObjectKey(key)) {
+			continue;
+		}
+
 		if (selectionItem.selection) {
-			const currentPath = `${path ? `${path}.` : ''}${selectionItem.key}`;
+			const currentPath = `${path ? `${path}.` : ''}${key}`;
 
-			if (row[selectionItem.key] === null) continue;
+			if (row[key] === null) continue;
 
 			if (parseJson) {
-				row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
-				if (row[selectionItem.key] === null) continue;
+				row[key] = JSON.parse(row[key] as string);
+				if (row[key] === null) continue;
 			}
-			if (parseJsonIfString && typeof row[selectionItem.key] === 'string') {
-				row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
+			if (parseJsonIfString && typeof row[key] === 'string') {
+				row[key] = JSON.parse(row[key] as string);
 			}
 
 			if (selectionItem.isArray) {
-				for (const item of (row[selectionItem.key] as Array<Record<string, unknown>>)) {
+				for (const item of (row[key] as Array<Record<string, unknown>>)) {
 					mapRelationalRow(
 						item,
 						selectionItem.selection!,
@@ -770,7 +772,7 @@
 			}
 
 			mapRelationalRow(
-				row[selectionItem.key] as Record<string, unknown>,
+				row[key] as Record<string, unknown>,
 				selectionItem.selection!,
 				mapColumnValue,
 				false,
@@ -782,7 +784,7 @@
 		}
 
 		const field = selectionItem.field!;
-		const value = mapColumnValue(row[selectionItem.key]);
+		const value = mapColumnValue(row[key]);
 		if (value === null) continue;
 
 		let decoder;
@@ -798,7 +800,7 @@
 			decoder = field.getSQL().decoder;
 		}
 
-		row[selectionItem.key] = 'mapFromJsonValue' in decoder
+		row[key] = 'mapFromJsonValue' in decoder
 			? (<(value: unknown) => unknown> decoder.mapFromJsonValue)(value)
 			: decoder.mapFromDriverValue(value);
 	}
EOF
@@ -740,22 +740,33 @@
parseJsonIfString: boolean = false,
path?: string,
): Record<string, unknown> {
const isSafeObjectKey = (key: string): boolean => {
return key !== '__proto__' && key !== 'prototype' && key !== 'constructor';
};

for (const selectionItem of buildQueryResultSelection) {
const key = selectionItem.key;

// Prevent prototype-polluting assignments on plain objects.
if (!isSafeObjectKey(key)) {
continue;
}

if (selectionItem.selection) {
const currentPath = `${path ? `${path}.` : ''}${selectionItem.key}`;
const currentPath = `${path ? `${path}.` : ''}${key}`;

if (row[selectionItem.key] === null) continue;
if (row[key] === null) continue;

if (parseJson) {
row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
if (row[selectionItem.key] === null) continue;
row[key] = JSON.parse(row[key] as string);
if (row[key] === null) continue;
}
if (parseJsonIfString && typeof row[selectionItem.key] === 'string') {
row[selectionItem.key] = JSON.parse(row[selectionItem.key] as string);
if (parseJsonIfString && typeof row[key] === 'string') {
row[key] = JSON.parse(row[key] as string);
}

if (selectionItem.isArray) {
for (const item of (row[selectionItem.key] as Array<Record<string, unknown>>)) {
for (const item of (row[key] as Array<Record<string, unknown>>)) {
mapRelationalRow(
item,
selectionItem.selection!,
@@ -770,7 +772,7 @@
}

mapRelationalRow(
row[selectionItem.key] as Record<string, unknown>,
row[key] as Record<string, unknown>,
selectionItem.selection!,
mapColumnValue,
false,
@@ -782,7 +784,7 @@
}

const field = selectionItem.field!;
const value = mapColumnValue(row[selectionItem.key]);
const value = mapColumnValue(row[key]);
if (value === null) continue;

let decoder;
@@ -798,7 +800,7 @@
decoder = field.getSQL().decoder;
}

row[selectionItem.key] = 'mapFromJsonValue' in decoder
row[key] = 'mapFromJsonValue' in decoder
? (<(value: unknown) => unknown> decoder.mapFromJsonValue)(value)
: decoder.mapFromDriverValue(value);
}
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
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.

8 participants