Summary
Requesting 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.
Sending an incorrect or malformed if-match header should always return a 412 error without any cache headers, which is not the current behavior.
Affected Versions
[email protected]@astrojs/[email protected]
Proof of Concept
Run the following command:
curl -s -o /dev/null -D - <host location>/_astro/_slug_.UTbyeVfw.css -H "if-match: xxx"
If 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.
A response similar to the following is expected from CloudFront:
HTTP/2 500
content-type: text/html
content-length: 166541
date: Thu, 09 Apr 2026 12:53:08 GMT
last-modified: Wed, 21 Jan 2026 13:40:08 GMT
etag: "a68349e96c2faf8861c330aeb548441a"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Error from cloudfront
via: 1.1 3591be88662e5675a9dc1cc4e0a9c392.cloudfront.net (CloudFront)
x-amz-cf-pop: ZRH55-P2
x-amz-cf-id: Rg--RIYCKcA55GZqZXdvu-VTvpxBFFVzV4LBIcKq5pB_hktcrhYbKg==
The 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:
❯ 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"
HTTP/1.1 500 Internal Server Error
Cache-Control: public, max-age=31536000, immutable
Accept-Ranges: bytes
Last-Modified: Tue, 07 Apr 2026 07:08:03 GMT
ETag: W/"560-19d66c50c38"
Content-Type: text/javascript; charset=utf-8
Date: Tue, 07 Apr 2026 08:23:54 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked
This 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.
Because 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.
Details
The findings were analyzed with an LLM, which identified the following file as the likely source: serve-static.ts
// Lines 129-153
let forwardError = false;
stream.on('error', (err) => {
if (forwardError) {
console.error(err.toString());
res.writeHead(500);
res.end('Internal server error');
return;
}
// File not found, forward to the SSR handler
ssr();
});
stream.on('headers', (_res: ServerResponse) => {
// assets in dist/_astro are hashed and should get the immutable header
if (normalizedPathname.startsWith(`/${app.manifest.assetsDir}/`)) {
// This is the "far future" cache header, used for static files whose name includes their digest hash.
// 1 year (31,536,000 seconds) is convention.
// Taken from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable
_res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}
});
stream.on('file', () => {
forwardError = true;
});
stream.pipe(res);
LLM analysis:
send handles conditional request headers such as If-Match internally. When a file is found but the precondition fails (ETag mismatch), send:
- Emits
file(the file exists) →forwardError = true - Emits
headers→Cache-Control: public, max-age=31536000, immutableis set onres - Emits
errorwith aPreconditionFailedError(status 412)
However, the error handler does not inspect the error's status code:
stream.on('error', (err) => {
if (forwardError) {
console.error(err.toString());
res.writeHead(500); // ← always 500, regardless of the actual error
res.end('Internal server error');
return;
}
ssr();
});
Because Cache-Control was already set during the headers event, the response is sent as:
HTTP/1.1 500 Internal Server Error
Cache-Control: public, max-age=31536000, immutable
Impact
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.
CVE-2026-41322 has a CVSS score of 5.3 (Medium). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (10.0.5); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-41322? CVE-2026-41322 is a medium-severity security vulnerability in @astrojs/node (npm), affecting versions < 10.0.5. It is fixed in 10.0.5.
- How severe is CVE-2026-41322? CVE-2026-41322 has a CVSS score of 5.3 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of @astrojs/node are affected by CVE-2026-41322? @astrojs/node (npm) versions < 10.0.5 is affected.
- Is there a fix for CVE-2026-41322? Yes. CVE-2026-41322 is fixed in 10.0.5. Upgrade to this version or later.
- Is CVE-2026-41322 exploitable, and should I be worried? Whether CVE-2026-41322 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-41322 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-41322? Upgrade
@astrojs/nodeto 10.0.5 or later.