Fix double-encoding of escaped string variables in multipart uploads#9839
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes incorrect double-encoding of escaped GraphQL string variables during multipart upload JSON rewrites by enabling a zero-copy “verbatim” write path (no re-escaping) and updating multipart rewrite codepaths to use it safely.
Changes:
- Updated
HotChocolate.Text.Json.JsonWriter.WriteStringValue(..., skipEscaping: true)to automatically add surrounding quotes when callers pass unquoted, pre-escaped UTF-8 content (e.g.,Utf8JsonReader.ValueSpan). - Adjusted Fusion multipart variable rewriting (
FileEntryBuilder) to passthrough escaped string values verbatim and to decode property names once for both writing and file-map path building. - Updated ASP.NET Core multipart middleware to decode escaped string variables before writing via
System.Text.Json(which lacks askipEscapingequivalent), and added regression tests across layers.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/HotChocolate/Json/test/Json.Tests/JsonWriterTests.cs | Adds coverage for quote-framing behavior when writing pre-escaped UTF-8 string content with skipEscaping: true (minimized + indented). |
| src/HotChocolate/Json/src/Json/JsonWriter.WriteValues.String.cs | Implements conditional quote-framing in the raw write path to prevent double-encoding and invalid JSON. |
| src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Transport/Http/FileEntryBuilderTests.cs | Adds regression tests for escaped string values and unicode-escaped property names in Fusion multipart variable rewriting. |
| src/HotChocolate/Fusion/src/Fusion.Execution/Transport/Http/FileEntryBuilder.cs | Writes string values using skipEscaping: true passthrough and avoids double-decoding property names. |
| src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpMultipartMiddlewareTests.cs | Adds multipart upload tests ensuring escaped/mixed-escape string variables reach resolvers intact. |
| src/HotChocolate/AspNetCore/test/AspNetCore.Tests.Utilities/UploadQuery.cs | Adds resolver used by new multipart upload tests (UploadWithText). |
| src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpMultipartMiddleware.cs | Decodes escaped string values using Utf8JsonReader.CopyString into a pooled buffer before writing with Utf8JsonWriter. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This was referenced Jun 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Escaped string variables in
graphql-multipart-request-specuploads were being double-encoded when the request JSON was rewritten to inject file references. Any escape sequence (\",\\,\n,\uXXXX, ...) in a string variable was doubled, so resolvers (and forwarded subgraph requests) received corrupted values.This fixes it across every affected path.
Root cause
While rewriting the variables/operations JSON, already-escaped bytes from
Utf8JsonReader.ValueSpanwere written back through a writer that escapes again, doubling every escape sequence.Fix
HotChocolate.Text.Json.JsonWriter(WriteStringValue(ReadOnlySpan<byte>, skipEscaping: true)): the raw-write path now adds the surrounding quotes automatically when the value is not already wrapped in them. Detection is a single leading-byte check inWriteStringValueRaw(sound because a JSON string's content can never start with a raw", a literal quote is always backslash-escaped), threaded into both the minimized and indented writers. This lets callers passUtf8JsonReader.ValueSpanstraight through as a zero-copy, zero-allocation verbatim copy, noGetString()and no re-escape.FileEntryBuilder: the string-value branch is now a singleWriteStringValue(reader.ValueSpan, skipEscaping: true)passthrough (no buffer, no decode), and property names decode once and reuse for both the writer and the file-map path. Fixes double-encoding of escaped string values and escaped property names.InMemorySourceSchemaClient: itsStripFileMarkersvalue path already usedskipEscaping: truewith an unquoted span and was emitting invalid JSON; the writer change corrects it automatically.HttpMultipartMiddleware: escaped string variables are decoded viaUtf8JsonReader.CopyStringinto a pooled buffer (cleared after use, since variable values may carry secrets) and written once, keeping the unescaped fast path.System.Text.Json'sUtf8JsonWriterhas noskipEscaping, so decode-then-write is required there.Tests
JsonWriterframing: bare content gets framed, already-quoted literals stay single-framed, empty maps to"", plus the indented path.FileEntryBuilder: escaped value and unicode-escaped property name round-trip single-encoded.HttpMultipartMiddleware: escaped and mixed-escape (",\, newline, unicode) string variables reach the resolver intact.skipEscapingcall sites were audited and are unaffected.Out of scope (follow-ups)
InMemorySourceSchemaClient(WritePropertyNamehas noskipEscaping) and the multi-segmentValueSpancase (value split acrossReadOnlySequencesegments) remain. Both are narrow and pre-existing.Supersedes #9810. Original fix by @BickelLukas (Consolidate-Software); this branch carries that work plus the Fusion and
JsonWriterfollow-ups.