{"id":"EUVD-2026-25580","revision":null,"records":[{"revision":"bb1499905d104ea5ac61492e1c9f5e6a","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-41322","aliases":[],"title":"@astrojs/node: Cache Poisoning due to incorrect error handling when if-match header is malformed","description":"@astrojs/node allows Astro to deploy your SSR site to Node targets. Prior to 10.0.5, requesting a static js/css resources from _astro path with an incorrect/malformed if-match header returns a 500 error with a one year cache lifetime instead of 412 in some cases. This has the effect that all subsequent requests to that file, regardless of if-match header will be served a 5xx error instead of the file until the cache expires. This vulnerability is fixed in 10.0.5.","state":0,"publishedAt":"2026-04-24T17:08:13.08+00:00","modifiedAt":"2026-04-25T01:48:27.606+00:00","affected":[{"ecosystem":"Vendor product","name":"astro","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"withastro\",\n                    \"product\": \"astro\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 10.0.5\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","score":5.3,"severity":"MEDIUM"}],"references":["https://github.com/withastro/astro/security/advisories/GHSA-c57f-mm3j-27q9"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-41322","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-41322","identifiers":["CVE-2026-41322"]},"retained":false,"historicalAffected":[]},{"revision":"5006699163e04d8eb694f1b00a4a9cad","observedAt":"2026-10-28T05:08:04.122+00:00","statement":{"source":"euvd","id":"EUVD-2026-25580","aliases":["CVE-2026-41322","GHSA-c57f-mm3j-27q9"],"title":"@astrojs/node allows Astro to deploy your SSR site to Node targets. Prior to 10.0.5, requesting a static js/css resources from _astro path with an incorrect/malformed if-match header returns a 500 error with a one year cache lifetime instea…","description":"@astrojs/node allows Astro to deploy your SSR site to Node targets. Prior to 10.0.5, requesting a static js/css resources from _astro path with an incorrect/malformed if-match header returns a 500 error with a one year cache lifetime instead of 412 in some cases. This has the effect that all subsequent requests to that file, regardless of if-match header will be served a 5xx error instead of the file until the cache expires. This vulnerability is fixed in 10.0.5.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","score":5.3,"severity":null}],"references":["https://github.com/withastro/astro/security/advisories/GHSA-c57f-mm3j-27q9"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-25580","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Apr 24, 2026, 5:08:13 PM","dateUpdated":"Apr 25, 2026, 1:48:27 AM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-25580","identifiers":["EUVD-2026-25580","CVE-2026-41322","GHSA-c57f-mm3j-27q9"]},"retained":false,"historicalAffected":[]},{"revision":"320f94eb6a4241559b42eb833b0005c7","observedAt":"2026-10-27T10:10:11.623+00:00","statement":{"source":"nvd","id":"CVE-2026-41322","aliases":[],"title":"@astrojs/node allows Astro to deploy your SSR site to Node targets. Prior to 10.0.5, requesting a static js/css resources from _astro path with an incorrect/malformed if-match header returns a 500 error with a one year cache lifetime instea…","description":"@astrojs/node allows Astro to deploy your SSR site to Node targets. Prior to 10.0.5, requesting a static js/css resources from _astro path with an incorrect/malformed if-match header returns a 500 error with a one year cache lifetime instead of 412 in some cases. This has the effect that all subsequent requests to that file, regardless of if-match header will be served a 5xx error instead of the file until the cache expires. This vulnerability is fixed in 10.0.5.","state":0,"publishedAt":"2026-04-24T18:16:28.923+00:00","modifiedAt":"2026-10-26T21:10:00.17+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","score":5.3,"severity":"MEDIUM"}],"references":["https://github.com/withastro/astro/security/advisories/GHSA-c57f-mm3j-27q9"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-41322","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-525"],"productCriteria":[],"sourceDates":{"published":"2026-04-24T18:16:28.923","lastModified":"2026-10-26T21:10:00.170"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-41322","identifiers":["CVE-2026-41322"]},"retained":false,"historicalAffected":[]},{"revision":"b237ed5b981c40bca20b0160aa5c7b92","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-c57f-mm3j-27q9","aliases":["CVE-2026-41322"],"title":"Astro: Cache Poisoning due to incorrect error handling when if-match header is malformed ","description":"### Summary\nRequesting a static JS/CSS resource from the `_astro` path with an incorrect or malformed `if-match` header returns a `500` error with a one-year cache lifetime instead of `412` in some cases. As a result, all subsequent requests to that file — regardless of the `if-match` header — will be served a 5xx error instead of the file until the cache expires.\n\nSending an incorrect or malformed `if-match` header should always return a `412` error without any cache headers, which is not the current behavior.\n\n### Affected Versions\n- `astro@5.14.1`\n- `@astrojs/node@9.4.4`\n\n### Proof of Concept\n\nRun the following command:\n\n```\ncurl -s -o /dev/null -D - <host location>/_astro/_slug_.UTbyeVfw.css -H \"if-match: xxx\"\n```\n\nIf a 5xx error is not returned, inspect the resources via the browser's web inspector and select another CSS/JS file to request until a 5xx error is returned. The behavior generally defaults to a 5xx response. Note that all static files are immutable, so the cache must be purged or disabled to reproduce reliably.\n\nA response similar to the following is expected from CloudFront:\n\n```\nHTTP/2 500 \ncontent-type: text/html\ncontent-length: 166541\ndate: Thu, 09 Apr 2026 12:53:08 GMT\nlast-modified: Wed, 21 Jan 2026 13:40:08 GMT\netag: \"a68349e96c2faf8861c330aeb548441a\"\nx-amz-server-side-encryption: AES256\naccept-ranges: bytes\nserver: AmazonS3\nx-cache: Error from cloudfront\nvia: 1.1 3591be88662e5675a9dc1cc4e0a9c392.cloudfront.net (CloudFront)\nx-amz-cf-pop: ZRH55-P2\nx-amz-cf-id: Rg--RIYCKcA55GZqZXdvu-VTvpxBFFVzV4LBIcKq5pB_hktcrhYbKg==\n```\n\nThe above is not the real server output but the AWS error response triggered when the pods return a 5xx. Below is the output of the same `curl` command issued directly against a pod in Kubernetes:\n\n```\n❯ curl -s -o /dev/null -D - -H \"Host: tagesanzeiger.ch\" 127.0.0.1:3333/_astro/InstallPrompt.astro_astro_type_script_index_0_lang.C0M4llHG.js -H \"if-match: xxx\"\n\nHTTP/1.1 500 Internal Server Error\nCache-Control: public, max-age=31536000, immutable\nAccept-Ranges: bytes\nLast-Modified: Tue, 07 Apr 2026 07:08:03 GMT\nETag: W/\"560-19d66c50c38\"\nContent-Type: text/javascript; charset=utf-8\nDate: Tue, 07 Apr 2026 08:23:54 GMT\nConnection: keep-alive\nKeep-Alive: timeout=5\nTransfer-Encoding: chunked\n```\n\nThis demonstrates that the pod itself returns a `5xx` error instead of `412`. In addition, the response includes a `Cache-Control: public, max-age=31536000, immutable` header.\n\nBecause the testing setup configures `if-match` as part of the cache key, the exploit no longer affects the production application. Prior to that change, the CDN Point of Presence would become cache-poisoned, and any client visiting the affected pages without cached files through the same PoP would receive broken pages. This was reproduced by creating test URLs and visiting them in a browser only after triggering the exploit. The exploited resources returned `5xx` errors instead of the original CSS/JS content, breaking the application.\n\n### Details\nThe findings were analyzed with an LLM, which identified the following file as the likely source: [serve-static.ts](https://github.com/withastro/astro/blob/main/packages/integrations/node/src/serve-static.ts)\n\n```js\n// Lines 129-153\n\nlet forwardError = false;\n\nstream.on('error', (err) => {\n    if (forwardError) {\n        console.error(err.toString());\n        res.writeHead(500);\n        res.end('Internal server error');\n        return;\n    }\n    // File not found, forward to the SSR handler\n    ssr();\n});\nstream.on('headers', (_res: ServerResponse) => {\n    // assets in dist/_astro are hashed and should get the immutable header\n    if (normalizedPathname.startsWith(`/${app.manifest.assetsDir}/`)) {\n        // This is the \"far future\" cache header, used for static files whose name includes their digest hash.\n        // 1 year (31,536,000 seconds) is convention.\n        // Taken from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable\n        _res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');\n    }\n});\nstream.on('file', () => {\n    forwardError = true;\n});\nstream.pipe(res);\n```\n\nLLM analysis:\n\n> `send` handles conditional request headers such as `If-Match` internally. When a file is found but the precondition fails (ETag mismatch), `send`:\n>\n> 1. Emits `file` (the file exists) → `forwardError = true`\n> 2. Emits `headers` → `Cache-Control: public, max-age=31536000, immutable` is set on `res`\n> 3. Emits `error` with a `PreconditionFailedError` (status 412)\n>\n> However, the error handler does not inspect the error's status code:\n>\n> ```js\n> stream.on('error', (err) => {\n>     if (forwardError) {\n>         console.error(err.toString());\n>         res.writeHead(500);   // ← always 500, regardless of the actual error\n>         res.end('Internal server error');\n>         return;\n>     }\n>     ssr();\n> });\n> ```\n>\n> Because `Cache-Control` was already set during the `headers` event, the response is sent as:\n>\n> ```\n> HTTP/1.1 500 Internal Server Error\n> Cache-Control: public, max-age=31536000, immutable\n> ```\n\n\n### Impact\n**Cache Poisoning** — An attacker can force edge servers to cache an error page instead of the actual content, rendering one or more assets unavailable to legitimate users until the cache expires.","state":0,"publishedAt":"2026-04-23T14:36:03+00:00","modifiedAt":"2026-05-05T16:05:45.4658+00:00","affected":[{"ecosystem":"npm","name":"@astrojs/node","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"10.0.5"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/withastro/astro/security/advisories/GHSA-c57f-mm3j-27q9","https://nvd.nist.gov/vuln/detail/CVE-2026-41322","https://github.com/withastro/astro"],"sourceUrl":"https://github.com/advisories/GHSA-c57f-mm3j-27q9","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-c57f-mm3j-27q9","identifiers":["GHSA-c57f-mm3j-27q9","CVE-2026-41322"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-30T03:49:20.9459112+00:00","complete":true,"records":1764,"lastAttemptAt":"2026-10-30T03:49:20.9459112+00:00","lastSuccessAt":"2026-10-30T03:49:21.0956195+00:00","error":null,"lastVerifiedAt":"2026-10-30T03:49:21.0956209+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-7e2c4e7288f2c464ad8c61f3bb6017374714faedcc781283ac06a706ee1a820b-31f63acb545dc1dc83bfd29d704f05b1f9bcf49861d98da742d3d09d4feba239","complete":true,"records":416818,"lastAttemptAt":"2026-10-30T03:48:58.3782708+00:00","lastSuccessAt":"2026-10-30T03:48:58.3604559+00:00","error":null,"lastVerifiedAt":"2026-10-30T03:48:58.3782708+00:00","snapshotPublishedAt":"2026-10-30T03:26:48+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-88fe0b1aa80645bf9d69475adeb3cab6","complete":false,"records":13518,"lastAttemptAt":"2026-10-30T04:01:31.5435945+00:00","lastSuccessAt":"2026-10-30T04:01:31.539+00:00","error":"15 identifier lookups failed; latest failures include HTTP 403. Other identifiers were checked independently.","lastVerifiedAt":"2026-10-30T04:01:31.539+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13136,"expectedIdentifiers":13601,"failedIdentifiers":15},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-30T03:49:20.9459112+00:00","complete":true,"records":1094,"lastAttemptAt":"2026-10-30T03:49:20.9459112+00:00","lastSuccessAt":"2026-10-30T03:49:21.0964525+00:00","error":null,"lastVerifiedAt":"2026-10-30T03:49:21.0964527+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-dff131a3390246408db17133f034dc7f","complete":true,"records":13541,"lastAttemptAt":"2026-10-30T03:49:22.1579892+00:00","lastSuccessAt":"2026-10-30T03:49:22.155+00:00","error":null,"lastVerifiedAt":"2026-10-30T03:49:22.155+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13601,"expectedIdentifiers":13601,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-ed3d9cf8b8585536fcb9b5cdf9f82e66117dc54e56de651034444f578191d1cf","complete":true,"records":9806,"lastAttemptAt":"2026-10-30T03:49:17.9057093+00:00","lastSuccessAt":"2026-10-30T03:49:17.6553909+00:00","error":null,"lastVerifiedAt":"2026-10-30T03:49:17.9057093+00:00","snapshotPublishedAt":"2026-10-30T03:03:06+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-2de9ccc5290f060c7aa63f89c25dd76f655a64ba1aba7c05c97fa3bee4f09899","complete":true,"records":1967,"lastAttemptAt":"2026-10-30T03:48:58.4335698+00:00","lastSuccessAt":"2026-10-28T20:55:34.2982937+00:00","error":null,"lastVerifiedAt":"2026-10-30T03:48:58.4335698+00:00","snapshotPublishedAt":"2026-10-28T20:03:08+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-3d73c87158b66efc3d2abd577048eff6d8962b9b8404ac23159c8b20cd3852a7","complete":true,"records":3014,"lastAttemptAt":"2026-10-30T03:49:20.9154714+00:00","lastSuccessAt":"2026-10-30T03:49:20.7978659+00:00","error":null,"lastVerifiedAt":"2026-10-30T03:49:20.9154714+00:00","snapshotPublishedAt":"2026-10-30T03:03:06+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-ec5b13e78c39945726a720832da7660d2774969f16e2a34f99460c135811abd5","complete":true,"records":230948,"lastAttemptAt":"2026-10-30T03:48:58.6037653+00:00","lastSuccessAt":"2026-10-29T22:50:35.0693267+00:00","error":null,"lastVerifiedAt":"2026-10-30T03:48:58.6037653+00:00","snapshotPublishedAt":"2026-10-29T22:33:13+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}