Skip to content

fix: refuse to stringify a remote form's file - #17314

Open
t-eckert wants to merge 1 commit into
sveltejs:mainfrom
t-eckert:fix/lazyfile-refuses-string-coercion-3.0.0
Open

t-eckert wants to merge 1 commit into
sveltejs:mainfrom
t-eckert:fix/lazyfile-refuses-string-coercion-3.0.0

Conversation

@t-eckert

@t-eckert t-eckert commented Oct 3, 2026 •

Copy link
Copy Markdown

closes #17313

A remote form's file is a LazyFile behind a Proxy that reports File.prototype. That gets it past FormData.append(name, blob, filename)'s type check without it being a real Blob, and the value is then coerced to a string: the request carries [object Object] under the right filename and content type, and nothing fails. FormData.set with a filename and new Blob([file]) lose the contents the same way.

This gives LazyFile a Symbol.toPrimitive that throws a TypeError naming the likely cause. All three silent paths fail by string coercion, so all three now fail loudly. Nothing else changes: the two-argument append(name, file) still streams lazily, and validators still see a File.

It closes the failure mode rather than the underlying mismatch. The structural options (buffering into a real File, or a distinct type with an explicit .blob()) are discussed in #17313, and this can land without choosing between them.

The test deserializes a binary form, then checks that string coercion and a three-argument append throw, and that a two-argument append still puts the contents on the wire. It fails without the change.


Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Ran the kit unit suite (pnpm test:unit: 77 files, 1054 passed, 110 skipped), oxfmt --check ., eslint on the changed files, and pnpm check in packages/kit, all passing. I did not run the integration suites locally.

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

`deserialize_binary_form` rebuilds each upload as a `LazyFile` wrapped in a Proxy
that reports `File.prototype`, so that validators accept it. That also gets the
value past the type check in `FormData.append(name, blob, filename)` without it
being a real `Blob`, and `FormData` then falls back to string coercion: the
request carries the fifteen bytes of `[object Object]` under the right filename
and content type, and nothing reports a failure.

Without the Proxy, `append` raises `TypeError: parameter 2 is not of type 'Blob'`.
So the Proxy does not cause a coercion bug so much as suppress an accurate error.
A `Symbol.toPrimitive` that throws puts that error back, with a message naming
the likely cause, and leaves everything else alone — the two-argument
`append(name, blob)` still streams, and validators still see a `File`.

Subclassing `File` was tried first and is worse. A subclass built with an empty
`super([])` that overrides `size` and `stream()` passes every type check and is
written to the wire empty, because the three-argument path reads the blob's
internal slots rather than calling `stream()`. An empty part is harder to notice
than `[object Object]`.

The test appends a deserialized file both ways: three arguments must throw, and
two arguments must still put the contents on the wire.

Claude-Session: https://claude.ai/code/session_01EssSzytrsfTbMhCguS3RmD
@pkg-svelte-dev

pkg-svelte-dev Bot commented Oct 3, 2026

Copy link
Copy Markdown

Install the latest version of @sveltejs/kit from 46c2292:

pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/46c2292772a2aa2ade6b510a105fc76500801184

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/kit/pr/17314

Note

This PR is from a fork. A maintainer must approve approve each commit before it can be built and installed.

@changeset-bot

changeset-bot Bot commented Oct 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 46c2292

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

This branch has not been deployed

No deployments
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.

Remote form file is silently replaced with [object Object] when appended to FormData with a filename

1 participant