Skip to content

[pull] main from TryGhost:main#1168

Merged
pull[bot] merged 4 commits into
code:mainfrom
TryGhost:main
May 23, 2026
Merged

[pull] main from TryGhost:main#1168
pull[bot] merged 4 commits into
code:mainfrom
TryGhost:main

Conversation

@pull

@pull pull Bot commented May 23, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

renovate Bot and others added 4 commits May 23, 2026 00:54
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [qs](https://redirect.github.com/ljharb/qs) | [`6.14.2` →
`6.15.2`](https://renovatebot.com/diffs/npm/qs/6.14.2/6.15.2) |
![age](https://developer.mend.io/api/mc/badges/age/npm/qs/6.15.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/qs/6.14.2/6.15.2?slim=true)
|

---

### qs has a remotely triggerable DoS: qs.stringify crashes with
TypeError on null/undefined entries in comma-format arrays when
encodeValuesOnly is set
[CVE-2026-8723](https://nvd.nist.gov/vuln/detail/CVE-2026-8723) /
[GHSA-q8mj-m7cp-5q26](https://redirect.github.com/advisories/GHSA-q8mj-m7cp-5q26)

<details>
<summary>More information</summary>

#### Details
##### Summary

`qs.stringify` throws `TypeError` when called with `arrayFormat:
'comma'` and `encodeValuesOnly: true` on an array containing `null` or
`undefined`. The throw is synchronous and not handled by any of qs's
null-related options (`skipNulls`, `strictNullHandling`).

##### Details

In the comma + `encodeValuesOnly` branch, `lib/stringify.js:145` mapped
the array through the raw encoder before joining:

```js
obj = utils.maybeMap(obj, encoder);
```

`utils.encode` (`lib/utils.js:195`) reads `str.length` with no null
guard, so a `null` or `undefined` element throws `TypeError`.
`skipNulls` and `strictNullHandling` are both checked in the per-element
loop below this line and never get a chance to run.

Same class of bug as the filter-array path fixed in 0c180a4. The
vulnerable shape of the comma + `encodeValuesOnly` branch was introduced
in 4c4b23d ("encode comma values more consistently", PR #&#8203;463,
2023-01-19), first released in v6.11.1.

##### PoC

```js
const qs = require('qs');

qs.stringify({ a: [null, 'b'] },      { arrayFormat: 'comma', encodeValuesOnly: true });
qs.stringify({ a: [undefined, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true });
qs.stringify({ a: [null] },           { arrayFormat: 'comma', encodeValuesOnly: true });
// TypeError: Cannot read properties of null (reading 'length')
//     at encode (lib/utils.js:195:13)
//     at Object.maybeMap (lib/utils.js:322:37)
//     at stringify (lib/stringify.js:145:25)
```

##### Fix

`lib/stringify.js:145`, applied in 21f80b3 on `main`:

```diff
- obj = utils.maybeMap(obj, encoder);
+ obj = utils.maybeMap(obj, function (v) {
+     return v == null ? v : encoder(v);
+ });
```

`null` and `undefined` now pass through `maybeMap` unchanged and reach
the `join(',')` step as-is. For `{ a: [null, 'b'] }` this produces
`a=,b`, matching the non-`encodeValuesOnly` comma path (which already
joins before encoding and produces `a=%2Cb` for the same input).
Single-element `[null]` arrays still collapse via the existing
`obj.join(',') || null` and remain subject to `skipNulls` /
`strictNullHandling` in the main loop.

##### Affected versions

`>=6.11.1 <=6.15.1`

The vulnerable code shape was introduced in 4c4b23d and first shipped in
v6.11.1. Earlier versions — including all of 6.7.x, 6.8.x, 6.9.x,
6.10.x, and 6.11.0 — implemented the comma + `encodeValuesOnly` path
differently (joining before encoding) and are not affected. Empirically
verified across released versions.

##### Impact

Application code that calls `qs.stringify` with both `arrayFormat:
'comma'` and `encodeValuesOnly: true` (both non-default) on input that
may contain a `null` or `undefined` array element will throw
synchronously instead of producing a query string. In a typical Node.js
HTTP framework (Express, Fastify, Koa, hapi) the sync throw is caught by
the framework's error boundary and the affected request returns a 500;
the worker process does not exit and subsequent requests are unaffected.
The "kills the worker process" framing applies only to call sites
outside a request-handler error boundary (background jobs, startup
paths, stream pipelines) or to deployments with framework error handling
explicitly disabled.

The vulnerable input is a `null` or `undefined` entry inside an array;
this is reachable from JSON request bodies or from application code
constructing arrays from user input, but not from standard HTML form
submissions (which produce strings or omitted fields, not literal
`null`).

#### Severity
- CVSS Score: 6.3 / 10 (Medium)
- Vector String:
`CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N`

#### References
-
[https://github.com/ljharb/qs/security/advisories/GHSA-q8mj-m7cp-5q26](https://redirect.github.com/ljharb/qs/security/advisories/GHSA-q8mj-m7cp-5q26)
-
[https://nvd.nist.gov/vuln/detail/CVE-2026-8723](https://nvd.nist.gov/vuln/detail/CVE-2026-8723)
-
[https://github.com/ljharb/qs/commit/21f80b33e5c8b3f7eba1034fff0da4a4a37a1d41](https://redirect.github.com/ljharb/qs/commit/21f80b33e5c8b3f7eba1034fff0da4a4a37a1d41)
-
[https://github.com/advisories/GHSA-q8mj-m7cp-5q26](https://redirect.github.com/advisories/GHSA-q8mj-m7cp-5q26)

This data is provided by the [GitHub Advisory
Database](https://redirect.github.com/advisories/GHSA-q8mj-m7cp-5q26)
([CC-BY
4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Release Notes

<details>
<summary>ljharb/qs (qs)</summary>

###
[`v6.15.2`](https://redirect.github.com/ljharb/qs/blob/HEAD/CHANGELOG.md#6152)

[Compare
Source](https://redirect.github.com/ljharb/qs/compare/v6.15.1...v6.15.2)

- \[Fix] `stringify`: skip null/undefined entries in `arrayFormat:
'comma'` + `encodeValuesOnly` instead of crashing in `encoder`
- \[Fix] `stringify`: use configured `delimiter` after `charsetSentinel`
([#&#8203;555](https://redirect.github.com/ljharb/qs/issues/555))
- \[Fix] `stringify`: apply `formatter` to encoded key under
`strictNullHandling`
([#&#8203;554](https://redirect.github.com/ljharb/qs/issues/554))
- \[Fix] `stringify`: skip null/undefined filter-array entries instead
of crashing in `encoder`
([#&#8203;551](https://redirect.github.com/ljharb/qs/issues/551))
- \[Fix] `parse`: handle nested bracket groups and add regression tests
([#&#8203;530](https://redirect.github.com/ljharb/qs/issues/530))
- \[readme] fix grammar
([#&#8203;550](https://redirect.github.com/ljharb/qs/issues/550))
- \[Dev Deps] update `@ljharb/eslint-config`
- \[Tests] add regression tests for keys containing percent-encoded
bracket text

###
[`v6.15.1`](https://redirect.github.com/ljharb/qs/blob/HEAD/CHANGELOG.md#6151)

[Compare
Source](https://redirect.github.com/ljharb/qs/compare/v6.15.0...v6.15.1)

- \[Fix] `parse`: `parameterLimit: Infinity` with `throwOnLimitExceeded:
true` silently drops all parameters
- \[Deps] update `@ljharb/eslint-config`
- \[Dev Deps] update `@ljharb/eslint-config`, `iconv-lite`
- \[Tests] increase coverage

###
[`v6.15.0`](https://redirect.github.com/ljharb/qs/blob/HEAD/CHANGELOG.md#6150)

[Compare
Source](https://redirect.github.com/ljharb/qs/compare/v6.14.2...v6.15.0)

- \[New] `parse`: add `strictMerge` option to wrap object/primitive
conflicts in an array
([#&#8203;425](https://redirect.github.com/ljharb/qs/issues/425),
[#&#8203;122](https://redirect.github.com/ljharb/qs/issues/122))
- \[Fix] `duplicates` option should not apply to bracket notation keys
([#&#8203;514](https://redirect.github.com/ljharb/qs/issues/514))

</details>

---

### Configuration

📅 **Schedule**: (in timezone Etc/UTC)

- Branch creation
  - ""
- Automerge
  - Only on Sunday and Saturday (`* * * * 0,6`)
  - Between 12:00 AM and 12:59 PM, only on Monday (`* 0-12 * * 1`)
- Between 10:00 PM and 11:59 PM, Monday through Friday (`* 22-23 * *
1-5`)
- Between 12:00 AM and 04:59 AM, Tuesday through Saturday (`* 0-4 * *
2-6`)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/TryGhost/Ghost).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTQuMCIsInVwZGF0ZWRJblZlciI6IjQzLjE5NC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
no ref
- skips saving/uploading the production Docker image artifact when CI
can push/pull from GHCR
- keeps the artifact transfer path for external fork and cross-repo PR
runs where GHCR push is unavailable
## Summary
- skips the E2E report merge job when the E2E matrix succeeds
- keeps report generation for failed E2E runs, where blob reports are
uploaded and useful for debugging
no ref
- changes the main browser E2E matrix from 8 shards to 10 shards
- leaves analytics E2E at 2 shards
- keeps `TEST_WORKERS_COUNT=1` unchanged
@pull pull Bot locked and limited conversation to collaborators May 23, 2026
@pull pull Bot added the ⤵️ pull label May 23, 2026
@pull pull Bot merged commit ac1e702 into code:main May 23, 2026
1 check passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant