CVE-2026-53607
@apostrophecms/file pretty-URL Vulnerable to Unauthenticated SSRF via Host header
ApostropheCMS is an open-source Node.js content management system. In versions up to and including 4.30.0, when prettyUrls: true is enabled on @apostrophecms/file (a documented SEO feature for serving uploaded files at clean URLs), the public pretty-URL handler builds the upstream URL using the raw Host HTTP request header. That URL is then fetch'ed and the response body + headers are streamed straight back to the requester. Because Host is fully attacker-controlled, an unauthenticated remote attacker can pivot the apostrophe process to issue outbound HTTP requests against any host it can reach on the private network. The path component is constrained to /uploads/attachments/<cuid>-<slug>.<ext> (built from a local-DB lookup), which keeps the impact narrow: cross-instance data exfiltration is neutralized by cuid uniqueness, but blind-SSRF residuals remain (network-topology mapping via response-code / timing differences and verbose proxy/WAF 404 body disclosure). As of time of publication, no known patched versions exist.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
3.7
@apostrophecms/file pretty-URL Vulnerable to Unauthenticated SSRF via Host header
ApostropheCMS is an open-source Node.js content management system. In versions up to and including 4.30.0, when prettyUrls: true is enabled on @apostrophecms/file (a documented SEO feature for serving uploaded files at clean URLs), the public pretty-URL handler builds the upstream URL using the raw Host HTTP request header. That URL is then fetch'ed and the response body + headers are streamed straight back to the requester. Because Host is fully attacker-controlled, an unauthenticated remote attacker can pivot the apostrophe process to issue outbound HTTP requests against any host it can reach on the private network. The path component is constrained to /uploads/attachments/<cuid>-<slug>.<ext> (built from a local-DB lookup), which keeps the impact narrow: cross-instance data exfiltration is neutralized by cuid uniqueness, but blind-SSRF residuals remain (network-topology mapping via response-code / timing differences and verbose proxy/WAF 404 body disclosure). As of time of publication, no known patched versions exist.
- Record
CVE-2026-53607- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
EUVDEUVD-2026-36576
3.7
ApostropheCMS is an open-source Node.js content management system. In versions up to and including 4.30.0, when prettyUrls: true is enabled on @apostrophecms/file (a documented SEO feature for serving uploaded files at clean URLs), the public pretty-URL handler builds the upstream URL using the raw Host HTTP request header. That URL is then fetch'ed and the response body + headers are streamed straight back to the requester. Because Host is fully attacker-controlled, an unauthenticated remote attacker can pivot the apostrophe process to issue outbound HTTP requests against any host it can reach on the private network. The path component is constrained to /uploads/attachments/<cuid>-<slug>.<ext> (built from a local-DB lookup), which keeps the impact narrow: cross-instance data exfiltration is neutralized by cuid uniqueness, but blind-SSRF residuals remain (network-topology mapping via response-code / timing differences and verbose proxy/WAF 404 body disclosure). As of time of publication, no known patched versions exist.
- Record
EUVD-2026-36576- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-34pj-2622-jvxq
3.7
@apostrophecms/file pretty-URL Vulnerable to Unauthenticated SSRF via Host header
Summary
When prettyUrls: true is enabled on @apostrophecms/file (a documented SEO
feature for serving uploaded files at clean URLs), the public pretty-URL
handler builds the upstream URL using the raw Host HTTP request header:
proxyUrl = `${req.protocol}://${req.get('host')}${uglyUrl}`
That URL is then fetch'ed and the response body + headers are streamed
straight back to the requester. Because Host is fully attacker-controlled,
an unauthenticated remote attacker can pivot the apostrophe process to
issue outbound HTTP requests against any host it can reach on the private
network. The path component is constrained to
/uploads/attachments/<cuid>-<slug>.<ext> (built from a local-DB lookup),
which keeps the impact narrow: cross-instance data exfiltration is
neutralised by cuid uniqueness, but blind-SSRF residuals remain
(network-topology mapping via response-code / timing differences and
verbose proxy/WAF 404 body disclosure). Verified on [email protected]
(latest); no fixed release exists.
- Affected:
apostrophe <= 4.30.0when@apostrophecms/fileis configured withprettyUrls: trueand uploadfs is local (the default; S3/CDN deployments produce an absoluteuglyUrland are not affected).
Details
modules/@apostrophecms/file/index.js (excerpt; the public GET route
registered when prettyUrls: true):
if (!self.options.prettyUrls) return;
return {
get: {
async [`${self.options.prettyUrlDir}/*`](req, res) {
const matches = (req.params[0] || '').match(/^([^.]+)\.\w+$/);
if (!matches) return res.status(400).send('invalid');
const [ , slug ] = matches;
if (slug.includes('..') || slug.includes('/')) {
return res.status(403).send('forbidden');
}
const file = await self.find(req, {
slug: `${self.options.slugPrefix}${slug}`
}).toObject();
if (!file) return res.status(404).send('not found');
const uglyUrl = self.apos.attachment.url(file.attachment, { prettyUrl: false });
const proxyUrl = uglyUrl.startsWith('/')
? `${req.protocol}://${req.get('host')}${uglyUrl}` // <-- sink
: uglyUrl;
return await streamProxy(req, proxyUrl, { error: self.apos.util.error });
}
}
};
lib/stream-proxy.js (excerpt):
module.exports = async function(req, url, { error }) {
const res = req.res;
if (url.startsWith('/')) url = `${req.baseUrl}${url}`;
let response;
try { response = await fetch(url); } // <-- attacker-steered fetch
catch (e) { return send502(e); }
for (const header of ['content-type','etag','last-modified','content-disposition','cache-control']) {
const v = response.headers.get(header);
if (v != null) res.header(header, v);
}
res.status(response.status);
response.body.pipeTo(new WritableStream({ write(c){ res.write(c) }, close(){ res.end() }, ... }));
};
req.get('host') returns the unvalidated Host HTTP header from the request.
Express does not validate or restrict it, and apostrophe does not check the
constructed proxyUrl against an allowlist. The upstream's body and
content-type are forwarded verbatim — so any response the targeted host does
return at the constrained path will reach the attacker. In practice the path
constraint (/uploads/attachments/<cuid>-<slug>.<ext>) and cuid uniqueness
mean meaningful body exfiltration only occurs against verbose-404 / banner-
leaky proxies; against most internal services this degenerates to blind
SSRF (response-code + timing side channels).
Prerequisites are minimal: prettyUrls: true (a documented production SEO
option) + at least one file uploaded with a known slug. Slugs are publicly
enumerable in normal CMS use (file URLs appear in page content).
Distinct from the only published apostrophe SSRF advisory, GHSA-pr28-mf3q-qpg6 ("Authenticated SSRF in rich-text widget import via @apostrophecms/area validate-widget"), which is authenticated and lives in a completely…
- Record
GHSA-34pj-2622-jvxq- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
NVD
3.7
ApostropheCMS is an open-source Node.js content management system. In versions up to and including 4.30.0, when prettyUrls: true is enabled on @apostrophecms/file (a documented SEO feature for serving uploaded files at clean URLs), the public pretty-URL handler builds the upstream URL using the raw Host HTTP request header. That URL is then fetch'ed and the response body + headers are streamed straight back to the requester. Because Host is fully attacker-controlled, an unauthenticated remote attacker can pivot the apostrophe process to issue outbound HTTP requests against any host it can reach on the private network. The path component is constrained to /uploads/attachments/<cuid>-<slug>.<ext> (built from a local-DB lookup), which keeps the impact narrow: cross-instance data exfiltration is neutralized by cuid uniqueness, but blind-SSRF residuals remain (network-topology mapping via response-code / timing differences and verbose proxy/WAF 404 body disclosure). As of time of publication, no known patched versions exist.
- Record
CVE-2026-53607- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSV
3.7
@apostrophecms/file pretty-URL Vulnerable to Unauthenticated SSRF via Host header
ApostropheCMS is an open-source Node.js content management system. In versions up to and including 4.30.0, when prettyUrls: true is enabled on @apostrophecms/file (a documented SEO feature for serving uploaded files at clean URLs), the public pretty-URL handler builds the upstream URL using the raw Host HTTP request header. That URL is then fetch'ed and the response body + headers are streamed straight back to the requester. Because Host is fully attacker-controlled, an unauthenticated remote attacker can pivot the apostrophe process to issue outbound HTTP requests against any host it can reach on the private network. The path component is constrained to /uploads/attachments/<cuid>-<slug>.<ext> (built from a local-DB lookup), which keeps the impact narrow: cross-instance data exfiltration is neutralized by cuid uniqueness, but blind-SSRF residuals remain (network-topology mapping via response-code / timing differences and verbose proxy/WAF 404 body disclosure). As of time of publication, no known patched versions exist.
- Record
CVE-2026-53607- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVGHSA-34pj-2622-jvxq
3.7
@apostrophecms/file pretty-URL Vulnerable to Unauthenticated SSRF via Host header
Summary
When prettyUrls: true is enabled on @apostrophecms/file (a documented SEO
feature for serving uploaded files at clean URLs), the public pretty-URL
handler builds the upstream URL using the raw Host HTTP request header:
proxyUrl = `${req.protocol}://${req.get('host')}${uglyUrl}`
That URL is then fetch'ed and the response body + headers are streamed
straight back to the requester. Because Host is fully attacker-controlled,
an unauthenticated remote attacker can pivot the apostrophe process to
issue outbound HTTP requests against any host it can reach on the private
network. The path component is constrained to
/uploads/attachments/<cuid>-<slug>.<ext> (built from a local-DB lookup),
which keeps the impact narrow: cross-instance data exfiltration is
neutralised by cuid uniqueness, but blind-SSRF residuals remain
(network-topology mapping via response-code / timing differences and
verbose proxy/WAF 404 body disclosure). Verified on [email protected]
(latest); no fixed release exists.
- Affected:
apostrophe <= 4.30.0when@apostrophecms/fileis configured withprettyUrls: trueand uploadfs is local (the default; S3/CDN deployments produce an absoluteuglyUrland are not affected).
Details
modules/@apostrophecms/file/index.js (excerpt; the public GET route
registered when prettyUrls: true):
if (!self.options.prettyUrls) return;
return {
get: {
async [`${self.options.prettyUrlDir}/*`](req, res) {
const matches = (req.params[0] || '').match(/^([^.]+)\.\w+$/);
if (!matches) return res.status(400).send('invalid');
const [ , slug ] = matches;
if (slug.includes('..') || slug.includes('/')) {
return res.status(403).send('forbidden');
}
const file = await self.find(req, {
slug: `${self.options.slugPrefix}${slug}`
}).toObject();
if (!file) return res.status(404).send('not found');
const uglyUrl = self.apos.attachment.url(file.attachment, { prettyUrl: false });
const proxyUrl = uglyUrl.startsWith('/')
? `${req.protocol}://${req.get('host')}${uglyUrl}` // <-- sink
: uglyUrl;
return await streamProxy(req, proxyUrl, { error: self.apos.util.error });
}
}
};
lib/stream-proxy.js (excerpt):
module.exports = async function(req, url, { error }) {
const res = req.res;
if (url.startsWith('/')) url = `${req.baseUrl}${url}`;
let response;
try { response = await fetch(url); } // <-- attacker-steered fetch
catch (e) { return send502(e); }
for (const header of ['content-type','etag','last-modified','content-disposition','cache-control']) {
const v = response.headers.get(header);
if (v != null) res.header(header, v);
}
res.status(response.status);
response.body.pipeTo(new WritableStream({ write(c){ res.write(c) }, close(){ res.end() }, ... }));
};
req.get('host') returns the unvalidated Host HTTP header from the request.
Express does not validate or restrict it, and apostrophe does not check the
constructed proxyUrl against an allowlist. The upstream's body and
content-type are forwarded verbatim — so any response the targeted host does
return at the constrained path will reach the attacker. In practice the path
constraint (/uploads/attachments/<cuid>-<slug>.<ext>) and cuid uniqueness
mean meaningful body exfiltration only occurs against verbose-404 / banner-
leaky proxies; against most internal services this degenerates to blind
SSRF (response-code + timing side channels).
Prerequisites are minimal: prettyUrls: true (a documented production SEO
option) + at least one file uploaded with a known slug. Slugs are publicly
enumerable in normal CMS use (file URLs appear in page content).
Distinct from the only published apostrophe SSRF advisory, GHSA-pr28-mf3q-qpg6 ("Authenticated SSRF in rich-text widget import via @apostrophecms/area validate-widget"), which is authenticated and lives in a completely…
- Record
GHSA-34pj-2622-jvxq- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
apostrophenpm |
through 4.30.0 | 4.31.0 |
apostrophenpm |
before 4.31.0 | 4.31.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-53607 is a low vulnerability (CVSS 3.7) in Apostrophecms Apostrophe, published June 12, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.27%, the 19th percentile. Fixed in apostrophe 4.31.0.
References
github.com/apostrophecms/apostrophe/security/advisories/GHSA-34pj-2622-jvxq
nvd.nist.gov/vuln/detail/CVE-2026-53607
github.com/apostrophecms/apostrophe/pull/5464
github.com/apostrophecms/apostrophe/commit/5a88e9630cbbdde33154ef8abe7557ddf7be418b
github.com/advisories/GHSA-34pj-2622-jvxq
github.com/CVEProject/cvelistV5/tree/main/cves/2026/53xxx/CVE-2026-53607.json
github.com/apostrophecms/apostrophe
Left out here: the rest of 2 source descriptions. The JSON record has them.