Skip to content

fix: escape CRLF in multipart/form-data serialization - #605

Merged
stefan-guggisberg merged 1 commit into
mainfrom
fix/formdata-crlf-injection
Aug 6, 2026
Merged

stefan-guggisberg merged 1 commit into
mainfrom
fix/formdata-crlf-injection

Conversation

@stefan-guggisberg

Copy link
Copy Markdown
Contributor

Summary

Prompted by an audit checking whether this library is affected by the undici v6.28.0 security fixes. It is not@adobe/fetch does not use undici at runtime, and guessContentType never reflects a Blob .type into a header. The audit did, however, surface one spot matching the same CRLF-injection pattern, fixed here.

The multipart/form-data serializer (src/common/formData.js) interpolated the field name, filename and blob type into the request body without escaping. Because isBlob() is duck-typed (accepts any blob-like object with a string type), a value containing CR/LF could inject additional part headers or forged form fields into the multipart body — the same class as undici GHSA-m8rv-5g2x-5cg5, though lower severity here (multipart body-structure injection, not HTTP header injection — actual request headers remain guarded by Node's http.validateHeaderValue).

Changes

  • escapeName() — percent-encodes \r%0D, \n%0A, "%22 in the field name and filename, per the WHATWG multipart/form-data serialization algorithm.
  • sanitizeContentType() — strips CR/LF from the per-part Content-Type (a spec Blob normalizes its type, but the duck-typed check does not).
  • Regression test driving the serializer with CRLF-laden name/filename/blob-type; asserts no raw \r\n-prefixed injection reaches the body, values are escaped/stripped, declared length still matches, and no rogue boundary appears.

Testing

  • npx mocha test/common/formData.test.js — 3 passing
  • npx eslint on changed files — clean
  • Existing FormData suites unaffected

🤖 Generated with Claude Code

… type

The multipart/form-data serializer interpolated the field name, filename
and blob `type` into the request body without escaping. Because isBlob()
is duck-typed and accepts any blob-like object with a string `type`, a
value containing CR/LF could inject additional part headers or forged
form fields into the multipart body (same class as undici GHSA-m8rv-5g2x-5cg5).

Escape `\r`, `\n` and `"` in the field name and filename per the WHATWG
multipart/form-data serialization algorithm, and strip CR/LF from the
per-part Content-Type.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@stefan-guggisberg
stefan-guggisberg merged commit b6571db into main Aug 6, 2026
6 checks passed
@stefan-guggisberg
stefan-guggisberg deleted the fix/formdata-crlf-injection branch August 6, 2026 09:49
github-actions Bot pushed a commit that referenced this pull request Aug 6, 2026
## [4.3.1](v4.3.0...v4.3.1) (2026-08-06)

### Bug Fixes

* escape CRLF in multipart/form-data field name, filename and blob type ([#605](#605)) ([b6571db](b6571db))
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 4.3.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant