Fastify's Missing End Anchor in "subtypeNameReg" Allows Malformed Content-Types to Pass Validation
Description
Fastify incorrectly accepts malformed Content-Type headers containing trailing characters after the subtype token, in violation of RFC 9110 §8.3.1(https://httpwg.org/specs/rfc9110.html#field.content-type). For example, a request sent with Content-Type: application/json garbage passes validation and is processed normally, rather than being rejected with 415 Unsupported Media Type.
When regex-based content-type parsers are in use (a documented Fastify feature), the malformed value is matched against registered parsers using the full string including the trailing garbage. This means a request with an invalid content-type may be routed to and processed by a parser it should never have reached.
Impact: An attacker can send requests with RFC-invalid Content-Type headers that bypass validity checks, reach content-type parser matching, and be processed by the server. Requests that should be rejected at the validation stage are instead handled as if the content-type were valid.
Workarounds: Deploy a WAF rule to protect against this
Fix:
The fix is available starting with v5.8.1.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
fastifynpm | >= 5.7.2, < 5.8.1 | 5.8.1 |
Affected products
1Patches
167f6c9b32cb3Merge commit from fork
2 files changed · +47 −3
lib/content-type.js+9 −3 modified@@ -11,7 +11,9 @@ const keyValuePairsReg = /([\w!#$%&'*+.^`|~-]+)=([^;]*)/gm /** * typeNameReg is used to validate that the first part of the media-type - * does not use disallowed characters. + * does not use disallowed characters. Types must consist solely of + * characters that match the specified character class. It must terminate + * with a matching character. * * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators * @type {RegExp} @@ -20,12 +22,16 @@ const typeNameReg = /^[\w!#$%&'*+.^`|~-]+$/ /** * subtypeNameReg is used to validate that the second part of the media-type - * does not use disallowed characters. + * does not use disallowed characters. Subtypes must consist solely of + * characters that match the specified character class, and optionally + * terminated with any amount of whitespace characters. Without the terminating + * anchor (`$`), the regular expression will match the leading portion of a + * string instead of the whole string. * * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators * @type {RegExp} */ -const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*/ +const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*$/ /** * ContentType parses and represents the value of the content-type header.
test/content-type.test.js+38 −0 modified@@ -74,6 +74,44 @@ describe('ContentType class', () => { found = new ContentType('foo/π; param=1') t.assert.equal(found.isEmpty, true) t.assert.equal(found.isValid, false) + + found = new ContentType('application/json<script>alert(1)</script>') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json/extra/slashes') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json(garbage)') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json@evil') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json\x00garbage') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + }) + + test('subtype with multiple fields validates as incorrect', (t) => { + let found = new ContentType('application/json whatever') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + + found = new ContentType('application/ json whatever') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + + found = new ContentType('application/json whatever; foo=bar') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + + found = new ContentType('application/ json whatever; foo=bar') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) }) test('returns a plain media type instance', (t) => {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-573f-x89g-hqp9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-3419ghsaADVISORY
- cna.openjsf.org/security-advisories.htmlghsaWEB
- github.com/fastify/fastify/commit/67f6c9b32cb3623d3c9470cc17ed830dd2f083d7ghsaWEB
- github.com/fastify/fastify/security/advisories/GHSA-573f-x89g-hqp9ghsaWEB
- httpwg.org/specs/rfc9110.htmlghsaWEB
- www.cve.org/CVERecordghsaWEB
News mentions
0No linked articles in our index yet.