FormData: send only basename, not full path, as Bun.file() multipart filename
Repro
import { mkdirSync, writeFileSync } from "node:fs";
mkdirSync("/tmp/secret-dir", { recursive: true });
writeFileSync("/tmp/secret-dir/report.pdf", "%PDF");
const fd = new FormData();
fd.append("upload", Bun.file("/tmp/secret-dir/report.pdf"));
console.log((await new Request("http://x/", { method: "POST", body: fd }).text())
.split("\r\n").find(l => l.startsWith("Content-Disposition")));
Before:
Content-Disposition: form-data; name="upload"; filename="/tmp/secret-dir/report.pdf"
After (and in browsers):
Content-Disposition: form-data; name="upload"; filename="report.pdf"
Cause
FormData.append/set default the entry filename from Blob__getFileNameString when no explicit filename is given (JSDOMFormData.cpp:311/474). For a Bun.file(path) blob that returned the full host path, which was emitted verbatim in the multipart Content-Disposition header. The documented upload shape (fd.append("f", Bun.file(p)); fetch(url, { method: "POST", body: fd })) therefore disclosed the client's directory layout, usernames, and temp paths to the server.
A browser File reports only the basename (File API: "the name of the file without path information"), and Node's fs.openAsBlob part serializes filename="blob"; neither leaks a path.
Fix
Blob__getFileNameString returns an assigned name as given, and otherwise basename(store_path) (a Bun.file() path or S3 key), computed on the raw path bytes. Its only callers are the FormData.append/set default-filename paths.
For that to be a clean rule, get_name_string() no longer caches the derived path into blob.name. Before, the first .name read wrote the full path into name, which made an assigned name and a derived one indistinguishable. The JS name getter is already cached by JSC on the wrapper (cache: true), so nothing is recomputed on repeated reads from JS.
Unchanged:
Bun.file(path).namestill returns the full path.- An explicit
fd.append(name, blob, filename)argument is still used verbatim. - An assigned name is used verbatim:
new File([..], "reports/q3.pdf"),new File([Bun.file(p)], "reports/q3.pdf"),bunFile.name = "renamed.pdf". - A
Fileread back out of a FormData (fd.get()) and appended again still serializes the basename.
Verification
bun bd test test/js/web/html/FormData.test.ts
150 pass / 0 fail. The new test fails on the current release (emits the full path) and passes with this change. Also green: FormData-multipart-serialization, form-data-set-append, fetch-file-upload, FormData-file-error-leak, structured-clone-blob-file, structured-clone-fastpath, fetch/blob, fetch/body, util/inspect.
[review] gate passed · iteration 8 · 2 files touched
fails on main (without fix)
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/html/FormData.test.ts
bun test v1.4.1 (adc354d99)
test/js/web/html/FormData.test.ts:
(pass) FormData > should be able to append a string [3.22ms]
(pass) FormData > should be able to append a Blob [8.24ms]
(pass) FormData > should be able to set a Blob [5.72ms]
(pass) FormData > should be able to set a string [2.64ms]
(pass) FormData > should get filename from file [3.83ms]
(pass) FormData > should use the correct filenames [4.82ms]
(pass) FormData > should parse multipart/form-data (simple) with Response [12.99ms]
(pass) FormData > should roundtrip multipart/form-data (simple) with Response [28.39ms]
(pass) FormData > should parse multipart/form-data (simple) with Request [2.32ms]
(pass) FormData > should roundtrip multipart/form-data (simple) with Request [5.00ms]
(pass) FormData > should parse multipart/form-data (simple with trailing CRLF) with Response [1.55ms]
(pass) FormData > should roundtrip multipart/form-data (simple with trailing CRLF) with Response [3.71ms]
(pass) FormData > should parse multipart/form-data (si
... (truncated)
release without fix: all passed
bun test v1.4.1-canary.1 (aacd9512f)
test/js/web/html/FormData.test.ts:
(pass) FormData > should be able to append a string [0.06ms]
(pass) FormData > should be able to append a Blob [0.12ms]
(pass) FormData > should be able to set a Blob [0.06ms]
(pass) FormData > should be able to set a string [0.03ms]
(pass) FormData > should get filename from file [0.04ms]
(pass) FormData > should use the correct filenames [0.05ms]
(pass) FormData > should parse multipart/form-data (simple) with Response [0.27ms]
(pass) FormData > should roundtrip multipart/form-data (simple) with Response [0.55ms]
(pass) FormData > should parse multipart/form-data (simple) with Request [0.03ms]
(pass) FormData > should roundtrip multipart/form-data (simple) with Request [0.03ms]
(pass) FormData > should parse multipart/form-data (simple with trailing CRLF) with Response [0.02ms]
(pass) FormData > should roundtrip multipart/form-data (simple with trailing CRLF) with Response [0.02ms]
(pass) FormData > should parse multipart/form-data (simple with trailing CRLF) with Request [0.01ms]
(pass) FormData > should roundtrip multipart/form-data (simple with trailing CRLF) with Request [0.01ms]
(pass) F
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/html/FormData.test.ts
bun test v1.4.1 (adc354d99)
test/js/web/html/FormData.test.ts:
(pass) FormData > should be able to append a string [3.30ms]
(pass) FormData > should be able to append a Blob [7.96ms]
(pass) FormData > should be able to set a Blob [5.63ms]
(pass) FormData > should be able to set a string [2.63ms]
(pass) FormData > should get filename from file [3.31ms]
(pass) FormData > should use the correct filenames [4.75ms]
(pass) FormData > should parse multipart/form-data (simple) with Response [13.37ms]
(pass) FormData > should roundtrip multipart/form-data (simple) with Response [28.73ms]
(pass) FormData > should parse multipart/form-data (simple) with Request [2.33ms]
(pass) FormData > should roundtrip multipart/form-data (simple) with Request [5.53ms]
(pass) FormData > should parse multipart/form-data (simple with trailing CRLF) with Response [1.61ms]
(pass) FormData > should roundtrip multipart/form-data (simple with trailing CRLF) with Response [3.46ms]
(pass) FormData > should parse multipart/form-data (si
... (truncated)
release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 582ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/6] gen generated_host_exports.rs
generated_host_exports.rs: 116 exports (host=5, lazy=10, generic=101, rust=0); 242 extern-C blocks audited
[1/6] cargo bun_runtime → libbun_runtime.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 bun_errno v0.0.0 (/workspace/bun/src/errno)
[1m[92m Compiling[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
[1m[92m Compiling[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
[1m[92m Compiling[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
[1m[92m Compiling[0m bun_base64 v0.0.0 (/workspace/bun/src/base64)
[1m[92m Compiling[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
[1m[92m Compiling[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
[1m[92m Compiling[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
[1m[92m Compiling[0m bun_pic
... (truncated)
diff hotspot
src/runtime/webcore/Blob.rs | 29 ++++++++++---------
test/js/web/html/FormData.test.ts | 60 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 75 insertions(+), 14 deletions(-)
gate history · 5 passed · 2 rejected · iteration 8
evidence per changed file
file reads edits tests
src/runtime/webcore/Blob.rs 14 11 0
test/js/web/html/FormData.test.ts 2 9 0