redis: reject file-backed Blob arguments instead of sending the empty string
What does this PR do?
RedisClient accepts Blob for any KeyLike parameter (key or value). The argument converter read the Blob's bytes via shared_view(), which is empty for a file- or S3-backed store. So:
await redis.set("tpl", Bun.file("page.html"));
sent SET "tpl" "" on the wire, resolved "OK", and silently dropped the file contents. The same applied to:
- a
Bun.file()as a key (became the empty key"") - a single-part
new Blob([Bun.file(p)])(shares the file store) - a nonexistent path (no
ENOENT, just"")
Wire-verified on stock 1.4.0-canary and release-asan main; deterministic.
Cause
js_valkey_functions.rs::from_js calls BlobOrStringOrBuffer::from_js_maybe_file(global, value, false). The false skips the file-blob guard in from_js_maybe_file_maybe_async, so a file-backed Blob falls through to Self::Blob(blob.dupe()) and slice() returns its empty shared_view(). (The allow_file parameter there is named the opposite of its effect; left alone to keep the diff small.)
Fix
After conversion, check needs_to_read_file() || is_s3() on the Blob arm and throw ERR_INVALID_ARG_TYPE with "pass await blob.bytes() instead", matching ServerWebSocket.send and the MySQL parameter binder. All ~50 prototype methods and .send() route through this one helper, so the check covers every argument position.
Not changed
A Uint8Array over a detached ArrayBuffer still serializes as "". Per spec a detached view has byteLength === 0, and Node treats it as empty in comparable APIs, so sending zero bytes is arguably correct; happy to reject it too if preferred.
How did you verify your code works?
Added two dockerless tests to test/js/valkey/unit/buffer-operations.test.ts that stand up a minimal RESP3 recorder:
- in-memory / sliced Blobs reach the wire with their bytes intact (regression guard)
Bun.file()as value, as key, wrapped, after.text(), nonexistent path, and viagetBuffer/appendall throw and produce no wire frame
USE_SYSTEM_BUN=1 bun test buffer-operations.test.ts -t "Blob argument" # 1 pass, 1 fail
bun bd test buffer-operations.test.ts -t "Blob argument" # 2 pass
[review] gate passed · iteration 0 · 2 files touched
fails on main (without fix)
ASAN without fix: 1 failed, 9 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/valkey/unit/buffer-operations.test.ts
bun test v1.4.0 (23619b79a)
test/js/valkey/unit/buffer-operations.test.ts:
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is running: dial unix /var/run/docker.sock: connect: no such file or directory
Redis is not enabled, skipping tests
(skip) Valkey: Buffer Operations > getBuffer returns binary data as Uint8Array
(skip) Valkey: Buffer Operations > getBuffer for non-existent key returns null
(skip) Valkey: Buffer Operations > Really long buffer
(skip) Valkey: Buffer Operations > Buffer with no bytes
(skip) Valkey: Buffer Operations > Buffer with null bytes
(skip) Valkey: Buffer Operations > concurrent getBuffer against large blob
(skip) Valkey: Buffer Operations > set and getBuffer with ArrayBufferView key
(skip) Valkey: Buffer Operations > set and getBuffer with ArrayBuffer key
(skip) Valkey: Buffer Operations > set and getBuffer with Blob key
(pass) Valkey: Blob argument serialization > in-memory Blob arguments reach th
... (truncated)
release without fix: 1 failed, 9 skipped
bun test v1.4.0-canary.1 (1498d7b77)
test/js/valkey/unit/buffer-operations.test.ts:
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is running: dial unix /var/run/docker.sock: connect: no such file or directory
Redis is not enabled, skipping tests
(skip) Valkey: Buffer Operations > getBuffer returns binary data as Uint8Array
(skip) Valkey: Buffer Operations > getBuffer for non-existent key returns null
(skip) Valkey: Buffer Operations > Really long buffer
(skip) Valkey: Buffer Operations > Buffer with no bytes
(skip) Valkey: Buffer Operations > Buffer with null bytes
(skip) Valkey: Buffer Operations > concurrent getBuffer against large blob
(skip) Valkey: Buffer Operations > set and getBuffer with ArrayBufferView key
(skip) Valkey: Buffer Operations > set and getBuffer with ArrayBuffer key
(skip) Valkey: Buffer Operations > set and getBuffer with Blob key
(pass) Valkey: Blob argument serialization > in-memory Blob arguments reach the wire with their bytes intact [1.76ms]
209 | const expected = expect.objectContaining({
210 | code: "ERR_INVALID_ARG_TYPE",
211 | message: expect.s
... (truncated)
passes on PR (with fix)
ASAN with fix: 9 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/valkey/unit/buffer-operations.test.ts
bun test v1.4.0 (23619b79a)
test/js/valkey/unit/buffer-operations.test.ts:
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is running: dial unix /var/run/docker.sock: connect: no such file or directory
Redis is not enabled, skipping tests
(skip) Valkey: Buffer Operations > getBuffer returns binary data as Uint8Array
(skip) Valkey: Buffer Operations > getBuffer for non-existent key returns null
(skip) Valkey: Buffer Operations > Really long buffer
(skip) Valkey: Buffer Operations > Buffer with no bytes
(skip) Valkey: Buffer Operations > Buffer with null bytes
(skip) Valkey: Buffer Operations > concurrent getBuffer against large blob
(skip) Valkey: Buffer Operations > set and getBuffer with ArrayBufferView key
(skip) Valkey: Buffer Operations > set and getBuffer with ArrayBuffer key
(skip) Valkey: Buffer Operations > set and getBuffer with Blob key
(pass) Valkey: Blob argument serialization > in-memory Blob arguments reach th
... (truncated)
release with fix: 9 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 665ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/124] cxx obj/unified/UnifiedSource-src_jsc_bindings-0.cpp.o
[2/124] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 238 extern-C blocks audited
[2/124] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)
[1m[92m Compiling[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
[1m[92m Compiling[0m lol_html v2.7.2 (/workspace/bun/vendor/lolhtml)
[1m[92m Compiling[0m subtle v2.6.1
[1m[92m Compiling[0m arrayvec v0.7.6
[1m[92m Compiling[0m base64 v0.22.1
[1m[92m Compiling[0m constant_time_eq v0.4.2
[1m[92m Compiling[0m generic-array v0.14.7
[1m[92m Compiling[0m arrayref v0.3.9
[1m[92m Compiling[0m byteorder v1.5.0
[1m[92m Compiling[0m blake2b_simd v1.0.4
[1m[92m Compiling[0m crypto-common v0.1.7
[1m[92m Compiling[0m block-buffer v0.10.4
[1m[92m Compiling[0m inout v0.1.4
[1m
... (truncated)
diff hotspot
src/runtime/valkey_jsc/js_valkey_functions.rs | 11 ++-
test/js/valkey/unit/buffer-operations.test.ts | 130 ++++++++++++++++++++++++++
2 files changed, 140 insertions(+), 1 deletion(-)
gate history · 1 passed · 0 rejected · iteration 0
evidence per changed file
file reads edits tests
src/runtime/valkey_jsc/js_valkey_functions.rs 4 3 0
test/js/valkey/unit/buffer-operations.test.ts 1 3 0