Astro Node Integration CVE-2026-41322
MEDIUMAttack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
ReconSend HTTP request with malformed if-match header
-
DeliveryServer processes as conditional request
-
ExploitFile found, emit file event
-
InstallSet immutable Cache-Control header
-
C2Emit PreconditionFailedError
-
ExecuteError handler overwrites status to 500
-
ImpactResponse sent with 500 status and cache headers
-
Step 8CDN caches 500 error
-
Step 9Legitimate requests receive cached error until expiration
Vulnerability Assessment
| Weakness |
CWE
OWASP Top 10:2025
A06:2025 - Insecure Design
ASVS 5.0
V3 - Web Frontend Security Browser cache control
|
| Exploitation Conditions | The vulnerability requires the following conditions: (1) Application must be deployed using @astrojs/node version 9.4.4 or earlier; (2) Application must serve immutable static assets from the _astro directory (default Astro configuration); (3) Attacker must have network access to the application (typically internet-facing); (4) CDN or origin caching must be configured to cache responses based on URL only, without segmenting cache keys by conditional headers such as if-match (this is a common misconfiguration but not universal). If the CDN already segments cache keys by the if-match header value, the poisoned response affects only subsequent requests with the same malformed header value, greatly reducing real-world impact. No user interaction is required. Exploitation does not require authentication. The attack is not limited to specific browser types or platforms. The vulnerability is exacerbated by the immutable static file naming scheme (content hashes in filenames), which is standard Astro practice and leads to one-year cache lifetimes that prolong the impact window. |
| Impact | This vulnerability presents a moderate real-world risk despite the 5.3 CVSS score. The CVSS vector (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L) correctly reflects network-accessible, unauthenticated, low-complexity exploitation with availability impact only. However, the severity in practice depends heavily on CDN configuration and deployment topology. The vulnerability only manifests as a persistent problem if the CDN caches conditional request headers as part of the cache key; the vendor's own testing environment mitigated this by including if-match in cache keys, preventing poisoning. The impact rating of 'Availability' is accurate: the attack causes asset 503/500 errors until cache expiration, degrading application functionality. The 31,536,000-second (one-year) cache lifetime is intentional for immutable hashed assets but becomes dangerous when paired with 500 errors. This is not a high-CVSS-but-low-risk issue; rather, it is a correctly-rated vulnerability whose real-world impact is deployment-dependent. Organizations using aggressive CDN caching without conditional-header-based cache segmentation face significantly higher risk. No EPSS score or exploitation metrics are provided in the input, so baseline CVSS is the primary signal; CISA KEV status is unknown. |
| Exploit ScenarioDerived | An attacker sends a single curl request with a malformed if-match header (e.g., 'if-match: xxx') to any static asset in the _astro directory of an Astro application using the vulnerable Node integration (e.g., curl -H 'if-match: xxx' https://example.com/_astro/style.abc123.css). The server incorrectly returns HTTP 500 Internal Server Error with Cache-Control: public, max-age=31536000, immutable headers. The CDN (e.g., CloudFront) caches this 500 error response. All subsequent legitimate requests to that asset-regardless of whether they include the if-match header-receive the cached 500 error instead of the CSS or JavaScript file, breaking the application's styling or functionality for all visitors sharing the same CDN edge node until the one-year cache expires. |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Apr 23, 2026
Cache poisoning in @astrojs/node versions 9.4.4 and earlier allows unauthenticated remote attackers to poison CDN caches by sending malformed if-match headers to static asset endpoints, causing the server to return 500 errors with immutable one-year cache directives instead of the correct 412 Precondition Failed response. This vulnerability affects all subsequent requests to poisoned assets until the cache expires, breaking application functionality for legitimate users. The vulnerability is not actively exploited in the wild, but proof-of-concept exploitation is straightforward and requires only a single crafted HTTP request.
RemediationAI
Update @astrojs/node to version 9.4.5 or later, and update Astro to the next patched release following 5.14.1. The fix modifies serve-static.ts to check the error object's status code before applying default 500 handling, allowing legitimate 412 Precondition Failed responses to pass through with correct status and no cache headers. Additionally, implement conditional-header-based cache key segmentation at the CDN level (e.g., include if-match in CloudFront cache key) to prevent cache poisoning even if the application returns erroneous cache headers. As a temporary workaround for unpatched deployments, disable or reduce the max-age of Cache-Control headers on error responses (though this requires application code modification), or configure CDN origin shield and request coalescing to minimize the window of exposure. The workaround trades performance for safety and should only be used as a bridge until patching is completed.
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 1 package in npm
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
The vulnerability exists in the Node.js integration layer of Astro, specifically in the serve-static.ts file that handles streaming responses for immutable static assets in the _astro directory. The root cause is improper error handling in the conditional request processing pipeline. The 'send' library correctly emits a PreconditionFailedError (412) when an if-match header fails ETag validation, but the Astro integration unconditionally converts all errors to 500 status codes after the file event has been emitted. Additionally, Cache-Control headers with immutable directives and one-year max-age are set during the 'headers' event before the error is handled, resulting in these cache headers being sent with the erroneous 500 response. This is a logic flaw in event sequencing where the immutable cache headers intended for successful responses are applied before error validation completes. CWE-525 identifies this as an improper synchronous validation of conditional expressions, where the precondition check result is not properly evaluated before applying cache directives.
Know when Kubernetes gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
Lifecycle Timeline
2DescriptionGitHub Advisory
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
astro@5.14.1@astrojs/node@9.4.4
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: chunkedThis 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: > > 1. Emits file (the file exists) → forwardError = true > 2. Emits headers → Cache-Control: public, max-age=31536000, immutable is set on res > 3. Emits error with a PreconditionFailedError (status 412) > > However, the error handler does not inspect the error's status code: > > `js > 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.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Information disclosure occurs when an application unintentionally exposes sensitive data that aids attackers in reconnaissance or directly compromises security.
Affected Products (CPE)GitHub Advisory
More in Kubernetes
View allA critical vulnerability in Kubernetes ingress-nginx controller allows unauthenticated attackers with pod network access
Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio
Kubernetes ingress-nginx contains a configuration injection vulnerability via the mirror-target and mirror-host Ingress
A security issue was discovered in ingress-nginx https://github.com/kubernetes/ingress-nginx where the `auth-url` Ingres
A security issue was discovered in ingress-nginx https://github.com/kubernetes/ingress-nginx where the `auth-tls-match-c
Kubernetes API server in all versions allow an attacker who is able to create a ClusterIP service and set the spec.exter
A security issue was discovered in Kubernetes where a user that can create pods on Windows nodes may be able to escalate
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. Rated critical severity (CVSS 9.9), this vulne
Unauthenticated remote attackers can trigger complete database overwrites, server-side file reads, and SSRF attacks agai
The Kubernetes integration in GitLab Enterprise Edition 11.x before 11.2.8, 11.3.x before 11.3.9, and 11.4.x before 11.4
Fluentd configuration injection in the kube-logging Logging operator before 6.6.0 allows a namespace-scoped user who can
Kyverno Kubernetes policy engine prior to 1.x has a privilege escalation vulnerability (CVSS 9.9) allowing policy bypass
GHSA-c57f-mm3j-27q9
NVD
CVE.org
EPSS
Vulnrichment
VulDB
Exploit-DB