fix: restore array parsing for req.query repeated keys (#7147)#7181
fix: restore array parsing for req.query repeated keys (#7147)#7181jonchurch merged 2 commits intoexpressjs:4.xfrom
Conversation
krzysdz
left a comment
There was a problem hiding this comment.
Infinity completely removes protection from sending something like arr[999999]=x and allows creating large arrays by sending just 1 parameter with an index.
PR #7151 (to master) uses a value of 1000 (same as the parameter limit), which is a better idea, but it still relaxes the limit compared to qs 6.14.0.
) qs 6.14 enforces a default `arrayLimit` of 20, which caused the extended query parser to collapse query strings with more than 20 repeated values (e.g. `?ids=1&ids=2&...&ids=25`) into an object instead of an array. This regressed in 4.22.0 (with the bump to `qs@~6.14.1`) and broke existing consumers relying on the pre-6.14 behavior. Pass `arrayLimit: Infinity` to `qs.parse` in `parseExtendedQueryString` so repeated keys always produce arrays, matching the behavior of Express \<= 4.21.x. Added a regression test in `test/req.query.js` that sends 25 repeated values and asserts they round-trip as an array.
e8b025b to
c0b4eaa
Compare
|
Thanks @krzysdz — good catch. Updated to Also added a second regression test asserting that |
|
I pushed an update to drop refs to the specific issue (prefer commit history for that) and to drop the sparse array test which is not related to the regression. |
There was a problem hiding this comment.
For clarity, the specific root cause and knock on effect is:
qs's arrayLimit default of 20 was historically not enforced on repeated-key parsing.
qs 6.14.1 closed that gap, and Express 4.22.0 pulled the fix in via the qs@~6.14.1 bump (#6972) — so ?ids=1&ids=2&... past 20 values silently flips req.query.ids from array to object on default config.
This PR sets arrayLimit: 1000 in parseExtendedQueryString (matching parameterLimit) to restore the repeated-key behavior Express 4.x has historically supported.
Note on indexed-bracket notation: qs uses arrayLimit as a single knob across three parsing paths (repeated keys, empty-bracket arr[]=, and indexed-bracket arr[N]=). For indexed-bracket queries at indices 21..999, this PR widens the threshold past 4.21's effective limit of 20 — ?arr[500]=v will now parse as an array ["v"] rather than an object {"500":"v"}.
EDIT: see update and change suggestion #7181 (comment)
This is, to me, a surprising side effect. But I do think it is reasonable as my own experience expects that the repeated key form is more common. Open to input here though
|
it'd actually parse as an array where index 500 will be |
|
@ljharb this is how Im testing it: // express 4.21 baseline (qs 6.13.0, default arrayLimit:20)
qs.parse('arr[500]=v')
// { arr: { '500': 'v' } }
// Proposed fix (qs 6.14.2, arrayLimit:1000)
qs.parse('arr[500]=v', { arrayLimit: 1000 })
// { arr: [ 'v' ] }
|
|
ah right, you'd need the |
|
with |
|
Definitely. But if you treat it like an object with |
|
Right — that's the deciding factor. If the keyed access |
|
Closing — using |
There was a problem hiding this comment.
Reopened this, it is my preferred fix for the 4.x line, it is neat and tidy and fixes a regression in default settings that has been open too long.
PR restores the default extended req.query behavior 4.x had before, allowing up to 1000 array elements, by passing arrayLimit: 1000 when constructing the extended query parser.
There was a problem hiding this comment.
Objects suddenly becoming arrays should not be a big problem (arrays can be used as objects, JS being JS...).
The thing that concerns me is that someone may have relied on e.g. ?filter[1001]=abc being accessible as req.query.filter[1001]. While this has never worked for ?filter[5]=abc (req.query.filter would be ['abc']), I wouldn’t be surprised if someone has used hardcoded numbers > 20 as keys.
I think we can try releasing this as is and see if anyone reports new issues. With the allowSparse: true alternative, there would be a difference for indices <20 (small arrays suddenly are sparse and have empty elements) and surely someone would report this as a security issue (DoS).
EDIT: If this becomes an issue again, here's a small demonstration of the old behaviour and possible changes to arrayLimit and allowSparse: https://gist.github.com/krzysdz/35d7ce9962a08534226ba91d51530d7c#file-results-md
Closes #7147.
The bump to
qs@~6.14.1in 4.22.0 (#6972) pulled in qs's new defaultarrayLimitof 20, which causes the extended query parser to silently collapse repeated-key query strings with more than 20 values into an object (e.g.{ "0": "a", "1": "b", ... }) instead of producing an array. This broke consumers that had been passing larger batches of repeated parameters throughreq.query.ids(the issue reporter hits it with ~20 tenancy IDs).body-parseralready got an opt-out path for the extended URL-encoded middleware, but theparseExtendedQueryStringused byreq.queryfor the'extended'query parser still only passesallowPrototypes: trueand otherwise inherits qs defaults — so the regression is only visible onreq.query, which is what #7147 reports.This PR restores the pre-6.14 behavior by passing
arrayLimit: InfinityarrayLimit: 1000toqs.parseinsideparseExtendedQueryString. Repeated-key values of any length round-trip back to an array, matching Express <= 4.21.x.Test plan
test/req.query.jsthat sends 25 repeatedidsvalues and assertsreq.query.idsis an array of 25 strings. Verified the test fails on4.xwithout the fix and passes with it.test/req.query.jssuite: 11 passing.If it would help, I'm happy to open a matching PR against
5.x— the sameparseExtendedQueryStringlives there with the same defaults.