fix(core): escape backslashes in Content-Disposition quoted filenames - #47
Conversation
The quoted-string escaper in generateContentDisposition escaped " but not \, so a filename ending in a backslash produced an escaped closing quote, letting the remainder of the filename be smuggled into additional header parameters. Escape both characters, and teach getFilenameFromContentDisposition to unescape any quoted-pair so round-tripping stays consistent.
@standardserver/bun
@standardserver/core
@standardserver/deno
@standardserver/fastify
@standardserver/fetch
@standardserver/node
@standardserver/peer
@standardserver/shared
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Pull request overview
This PR hardens Content-Disposition filename handling by correctly escaping backslashes in generated quoted filenames and by unescaping any quoted-pair sequences when parsing filename="...", preventing quoted-string breakouts and enabling correct round-trips for backslash-containing filenames.
Changes:
- Escape both
"and\ingenerateContentDisposition()per quoted-string rules. - Update
getFilenameFromContentDisposition()to parse and decode quoted-pairs (e.g.,\\→\). - Add tests for backslash escaping, trailing-backslash safety, injection payloads, and backslash round-trips.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/core/src/utils.ts | Updates Content-Disposition generation/parsing to escape \ and decode quoted-pairs. |
| packages/core/src/utils.test.ts | Adds coverage for backslash-related edge cases and parsing round-trips. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| const encodedFilenameMatch = contentDisposition.match(/filename="((?:\\"|[^"])*)"/i) | ||
| const encodedFilenameMatch = contentDisposition.match(/filename="((?:\\.|[^"\\])*)"/i) |
The quoted-string escaper in
generateContentDispositionescaped"but not\, so a filename ending in a backslash turned the closing quote into an escaped quote and let the rest of an attacker-controlled filename be smuggled into extraContent-Dispositionparameters. Both characters are now escaped per the RFC 9110 §5.6.4 quoted-string rules, andgetFilenameFromContentDispositionunescapes any quoted-pair so backslash filenames round-trip correctly.Fixes
filename="..."values are now always valid quoted-strings — a trailing\or\"; injected=xpayload can no longer break out of the quotes.\\(and any other quoted-pair), so generate → parse round-trips filenames containing backslashes.Impact
Low severity: CR/LF were already filtered and the disposition type is hardcoded, so this permitted parameter injection only — and in the reachable flow the attacker already controls the whole filename. Fixed as a correctness bug.
Testing
type:checkand ESLint clean.