Compare commits

...

2 Commits

Author SHA1 Message Date
Claude Bot
20e820ac54 chore: remove redundant sql.end() calls with await using
The `await using` syntax automatically disposes resources at scope end,
making explicit `await sql.end()` calls unnecessary.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 15:38:26 +00:00
Claude Bot
1307f7a26d fix(sql): correct byte order in MySQL caching_sha2_password auth
The caching_sha2_password scramble function was concatenating bytes
in the wrong order when computing SHA256(SHA256(SHA256(password)) + nonce).

The MySQL protocol requires SHA256(digest2 || nonce), but the code was
computing SHA256(nonce || digest2), causing "Access denied" errors for
users on MySQL 8.0+ with caching_sha2_password authentication.

Fixes #26195

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 15:33:00 +00:00
2 changed files with 51 additions and 3 deletions

View File

@@ -50,10 +50,10 @@ pub const caching_sha2_password = struct {
bun.sha.SHA256.hash(&digest1, &digest2, jsc.VirtualMachine.get().rareData().boringEngine());
// SHA256(SHA256(SHA256(password)) + nonce)
const combined = try bun.default_allocator.alloc(u8, nonce.len + digest2.len);
const combined = try bun.default_allocator.alloc(u8, digest2.len + nonce.len);
defer bun.default_allocator.free(combined);
@memcpy(combined[0..nonce.len], nonce);
@memcpy(combined[nonce.len..], &digest2);
@memcpy(combined[0..digest2.len], &digest2);
@memcpy(combined[digest2.len..], nonce);
bun.sha.SHA256.hash(combined, &digest3, jsc.VirtualMachine.get().rareData().boringEngine());
// XOR(SHA256(password), digest3)

View File

@@ -0,0 +1,48 @@
// https://github.com/oven-sh/bun/issues/26195
// MySQL 8.0 caching_sha2_password authentication fails due to incorrect byte order
// in the SHA256 hash calculation.
import { SQL } from "bun";
import { expect, test } from "bun:test";
import { describeWithContainer } from "harness";
describeWithContainer(
"mysql_plain",
{
image: "mysql_plain",
env: {},
args: [],
concurrent: true,
},
container => {
// mysql_plain uses MySQL 8.4 with caching_sha2_password as default auth plugin
const getUrl = () => `mysql://root@${container.host}:${container.port}/bun_sql_test`;
test("should connect using caching_sha2_password (default in MySQL 8.0+)", async () => {
await using sql = new SQL({
url: getUrl(),
max: 1,
});
const result = await sql`SELECT 1 as x`;
expect(result).toEqual([{ x: 1 }]);
});
test("should connect with password using caching_sha2_password", async () => {
// First create a user with caching_sha2_password and a password
{
await using sql = new SQL({
url: getUrl(),
max: 1,
});
await sql`DROP USER IF EXISTS testuser26195@'%';`.simple();
await sql`CREATE USER testuser26195@'%' IDENTIFIED WITH caching_sha2_password BY 'testpass123';
GRANT ALL PRIVILEGES ON bun_sql_test.* TO testuser26195@'%';
FLUSH PRIVILEGES;`.simple();
}
// Now connect with the new user using caching_sha2_password
await using sql = new SQL(`mysql://testuser26195:testpass123@${container.host}:${container.port}/bun_sql_test`);
const result = await sql`SELECT 1 as x`;
expect(result).toEqual([{ x: 1 }]);
});
},
);