Nov 10, 2026·10 min read·4 visits
Kubeflow Pipelines versions prior to 2.15.0 are vulnerable to reflected XSS (CVE-2026-88986) in the '/pipeline/k8s/pod' and '/pipeline/k8s/pod/events' endpoints due to unescaped query parameters in error responses. Upgrading to version 2.15.0 resolves this issue by enforcing strict DNS-1123 naming validation and suppressing raw backend errors.
A reflected Cross-Site Scripting (XSS) vulnerability exists in the Kubeflow Pipelines frontend server prior to version 2.15.0. Specifically, the Express route handlers for '/pipeline/k8s/pod' and '/pipeline/k8s/pod/events' reflect unvalidated 'podname' and 'podnamespace' query parameters, along with backend Kubernetes API error messages, directly into HTML-capable HTTP error responses. An unauthenticated attacker can exploit this flaw by convincing an authenticated user to visit a crafted URL, resulting in arbitrary JavaScript execution within the context of the Kubeflow Pipelines frontend origin.
Kubeflow Pipelines is a platform for building and deploying portable, scalable machine learning (ML) workflows based on Docker containers within Kubernetes environments. The platform includes a web-based user interface (kfp-frontend) built on Node.js and Express, which acts as an intermediary between the user's browser and the underlying Kubernetes API and pipeline backend services. This frontend server exposes several internal proxy and helper endpoints that allow users to inspect the execution state, logs, and Kubernetes events of individual pods corresponding to pipeline steps.
In Kubeflow Pipelines versions prior to 2.15.0, the frontend server endpoints /pipeline/k8s/pod and /pipeline/k8s/pod/events contain a reflected Cross-Site Scripting (XSS) vulnerability, tracked as CVE-2026-88986 (GHSA-g3qx-frcg-j979) and classified under CWE-79 (Improper Neutralization of Input During Web Page Generation). When processing client requests to retrieve pod details or associated cluster events, the backend helper routines in frontend/server/k8s-helper.ts take user-supplied query parameters—specifically podname and podnamespace—and pass them to the Kubernetes client API.
When the Kubernetes API call fails (for example, when querying a non-existent pod or an invalid namespace format), the error handling logic constructs a diagnostic message containing the raw, unvalidated podname and podnamespace values alongside the backend error string. The Express application then returns this string directly in the HTTP 500 response body via res.status(500).send(err.message).
Because Express defaults to a Content-Type of text/html when sending string responses without an explicit MIME type override, the victim's web browser interprets the error response as an HTML document. Consequently, any HTML markup or JavaScript payloads injected into the query parameters are parsed and executed within the security context of the Kubeflow frontend origin.
The root cause of CVE-2026-88986 is the combination of missing input validation on Kubernetes resource identifiers, unsafe string interpolation in error catch blocks, and reliance on Express's default text/html content-type assignment in res.send().
When an HTTP GET request reaches /pipeline/k8s/pod or /pipeline/k8s/pod/events, the Express route handlers extract podname and podnamespace from req.query. Prior to version 2.15.0, the server did not verify whether these strings conformed to Kubernetes resource naming conventions (such as RFC 1123 DNS label standards, which restrict names to lowercase alphanumeric characters and hyphens). Instead, arbitrary strings containing HTML control characters (<, >, ", ') were passed directly to getPod(podName, podNamespace) or listPodEvents(podName, podNamespace) inside frontend/server/k8s-helper.ts.
Inside frontend/server/k8s-helper.ts, the functions invoke k8sV1Client.readNamespacedPod(podName, podNamespace) or k8sV1Client.listNamespacedEvent(...). When passed a malformed namespace or a non-existent pod name containing an XSS payload, the Kubernetes API server rejects the request and throws an exception. The catch (error) block intercepts this exception, calls parseError(error) to extract the downstream message, and formats a composite string:
const userMessage = \Could not get pod ${podName} in namespace ${podNamespace}: ${message}`;`
This userMessage is returned to the caller in frontend/server/app.ts (or modular route handlers), which executes res.status(500).send(err.message). In the Express framework, passing a String argument to res.send() automatically sets the HTTP response Content-Type header to text/html; charset=utf-8 unless another type was previously set via res.type() or res.set(). Because no output encoding (such as HTML entity encoding) is applied to podName, podNamespace, or message, the browser renders the reflected string as active HTML.
The vulnerability was remediated in commit fedc25e3f7251933d4a37b964bc3e8176beea28e (Pull Request #12295). The patch addresses the flaw across multiple layers: introducing strict allowlist validation for Kubernetes resource identifiers, removing reflected backend error details, and hardening adjacent handlers for artifacts and TensorBoard integration.
frontend/server/utils.ts)The patch introduces a new validation helper function, isAllowedResourceName, which enforces that resource strings strictly match the Kubernetes DNS-1123 label specification (between 1 and 63 characters, consisting solely of lowercase alphanumeric characters or internal hyphens):
// Added in frontend/server/utils.ts
export function isAllowedResourceName(name: string): boolean {
return name.length > 0 && name.length <= 63 && /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(name);
}Because this regular expression forbids <, >, ", ', /, and all uppercase or special characters, any string passing this check cannot break out of text contexts or introduce HTML tags.
frontend/server/k8s-helper.ts)In frontend/server/k8s-helper.ts, the error handling blocks within getPod, getConfigMap, and listPodEvents were modified to validate inputs before reflecting them and to strip raw Kubernetes error details (message and additionalInfo) from client-facing responses:
// Before Patch (Vulnerable)
export async function getPod(
podName: string,
podNamespace: string,
): Promise<[V1Pod | undefined, K8sError | undefined]> {
try {
const { body } = await k8sV1Client.readNamespacedPod(podName, podNamespace);
return [body, undefined];
} catch (error) {
const { message, additionalInfo } = await parseError(error);
// FLAW: Unvalidated podName, podNamespace, and message interpolated directly
const userMessage = `Could not get pod ${podName} in namespace ${podNamespace}: ${message}`;
return [undefined, { message: userMessage, additionalInfo }];
}
}
// After Patch (Remediated)
export async function getPod(
podName: string,
podNamespace: string,
): Promise<[V1Pod | undefined, K8sError | undefined]> {
try {
const { body } = await k8sV1Client.readNamespacedPod(podName, podNamespace);
return [body, undefined];
} catch (error) {
let userMessage = `Could not get pod ${podName} in namespace ${podNamespace}`;
// FIX: Validate both parameters against strict DNS-1123 regex
if (!isAllowedResourceName(podName) || !isAllowedResourceName(podNamespace)) {
userMessage = `Invalid resource name`;
}
// FIX: Downstream error message and additionalInfo are omitted
return [undefined, { message: userMessage }];
}
}Identical validation logic was applied to listPodEvents(podName, podNamespace) and getConfigMap(configMapName, configMapNamespace) within the same file.
The commit also audited other request handlers for similar input reflection and verbose error exposure patterns:
// frontend/server/handlers/artifacts.ts
if (!isAllowedResourceName(bucket)) {
res.status(500).send('Invalid bucket name');
return;
}
// frontend/server/handlers/tensorboard.ts
if (typeof namespace !== 'string' || !isAllowedResourceName(namespace as string)) {
res.status(400).send('invalid namespace');
return;
}Additionally, in the Go backend (backend/src/apiserver/server/pipeline_upload_server.go), detailed internal error strings returned during pipeline creation and upload failures were replaced with static messages (errors.New("Failed to create a pipeline") and errors.New("Failed to create a pipeline version")), while preserving full diagnostic logging server-side via glog.Errorf.
The introduction of isAllowedResourceName effectively blocks HTML/JavaScript injection via podname, podnamespace, configMapName, bucket, and namespace parameters by restricting the character set to [a-z0-9-]. Furthermore, removing ${message} from the error string eliminates the risk of secondary XSS or sensitive information disclosure originating from upstream Kubernetes API server error responses. However, the application still uses res.status(500).send(...) without explicitly setting Content-Type: text/plain or application/json. While the strict character allowlist prevents XSS in the patched routes, setting an explicit text/plain content type alongside X-Content-Type-Options: nosniff would provide stronger defense-in-depth against future regressions.
Exploiting CVE-2026-88986 requires no authentication on the part of the attacker to construct the payload, though user interaction (UI:R) is required. Specifically, a user who satisfies the Kubeflow deployment's authentication and route-access requirements must navigate to an attacker-supplied link.
GET request to a top-level URL (/pipeline/k8s/pod or /pipeline/k8s/pod/events), standard browser SameSite=Lax cookie policies do not block session cookies when a user clicks a link from an external site, email, or chat client.In the regression unit tests introduced in frontend/server/app.test.ts within commit fedc25e3f7251933d4a37b964bc3e8176beea28e, the following paths demonstrate the attack vector:
GET /pipeline/k8s/pod?podname=test-pod-name&podnamespace=test-namespace%7d%7dt93g1%3Cscript%3Ealert(1)%3C%2fscript%3Ej66h HTTP/1.1
Host: kubeflow.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8As well as the events endpoint variant:
GET /pipeline/k8s/pod/events?podname=test-pod-name&podnamespace=test-namespace%7d%7dt93g1%3Cscript%3Ealert(1)%3C%2fscript%3Ej66h HTTP/1.1
Host: kubeflow.example.com<script>alert(1)</script> payload inside the podnamespace (or podname) parameter.test-namespace}}t93g1<script>alert(1)</script>j66h and queries the Kubernetes API.2.15.0, the server responds with HTTP 500 and an HTML body:Could not get pod test-pod-name in namespace test-namespace}}t93g1<script>alert(1)</script>j66h: ...<script> tag and executes the JavaScript payload under the Kubeflow frontend's origin. In patched versions (>= 2.15.0), the server detects that podnamespace fails isAllowedResourceName() and returns the static text Invalid resource name.CVE-2026-88986 is rated as Medium severity with a CVSS v3.1 base score of 6.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N).
XHR) or Fetch requests to any Kubeflow Pipelines API endpoint accessible to the victim. In a machine learning platform context, an attacker can programmatically trigger pipeline runs, modify experiment configurations, upload malicious pipeline definitions (which subsequently execute container workloads on the Kubernetes cluster), or read sensitive pipeline artifacts.localStorage, or sessionStorage, as well as query backend APIs for pipeline parameters, environment variables, or artifact contents. Whether session cookies can be directly exfiltrated via document.cookie depends on whether the deployment's authentication layer sets the HttpOnly attribute on session cookies.S:C): While the vulnerability resides in the server-side Node.js response generation logic, the impact manifests within the client's web browser, crossing a security boundary from the web server to the client execution environment.A:N): As noted in the official advisory, the flaw does not directly provide server-side code execution on the kfp-frontend Node.js container or disrupt service availability.As of November 2026, the FIRST Exploit Prediction Scoring System (EPSS) assigns CVE-2026-88986 a score of 0.00276 (0.28% probability of exploitation within 30 days, placing it in the 20.17th percentile). It is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog, and no weaponized exploitation in the wild has been reported.
Organizations running Kubeflow Pipelines should apply the official patch or implement compensating controls at the ingress layer.
Upgrade Kubeflow Pipelines to version 2.15.0 or later. Version 2.15.0 incorporates commit fedc25e3f7251933d4a37b964bc3e8176beea28e, which enforces strict input validation on podname, podnamespace, configMapName, bucket, and namespace parameters and removes verbose error reflection.
> [!NOTE]
> Ensure that both the kfp-frontend container image and the kfp-apiserver image are updated to version 2.15.0 to benefit from both the frontend XSS protections and backend verbose error suppression.
If an immediate upgrade is not feasible, administrators can block exploitation attempts at the Kubernetes Ingress controller, Envoy/Istio gateway, or external WAF by enforcing parameter validation on /pipeline/k8s/pod and /pipeline/k8s/pod/events:
podname and podnamespace query parameters to match the regular expression ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ with a maximum length of 63 characters./pipeline/k8s/* containing raw or URL-encoded HTML special characters (<, >, ", ', %3C, %3E, %22, %27).Configure the reverse proxy or ingress gateway fronting kfp-frontend to inject defensive HTTP response headers:
Content-Security-Policy (CSP): Enforce a strict CSP header such as Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; to prevent the execution of inline <script> tags and event handlers injected via reflected parameters.X-Content-Type-Options: Set X-Content-Type-Options: nosniff to prevent browsers from MIME-sniffing responses away from declared content types.HttpOnly, Secure, and SameSite=Strict (or Lax) attributes on all session cookies.CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Kubeflow Pipelines Kubeflow | < 2.15.0 | 2.15.0 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | CWE-79: Cross-Site Scripting (Reflected) |
| Affected Component | frontend/server/k8s-helper.ts (/pipeline/k8s/pod & /pipeline/k8s/pod/events) |
| Attack Vector | Network (Crafted URL via HTTP GET parameters podname and podnamespace) |
| CVSS v3.1 Score | 6.1 Medium (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N) |
| EPSS Score | 0.28% (20.17th Percentile) |
| Impact | Arbitrary JavaScript execution in the victim's browser under the frontend origin |
| Exploit Status | PoC in unit tests (No known active exploitation) |
| Patched Version | Kubeflow Pipelines 2.15.0 |
The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVE-2026-88983 is an authenticated Server-Side Request Forgery (SSRF) vulnerability affecting the Kubeflow Pipelines API server in versions prior to 2.17.0. When an authenticated user creates a pipeline or pipeline version using a remote specification URI, the backend server retrieves the document via Go's default HTTP client without validating the target scheme, hostname, port, or IP address. Because the API server reflects remote connection errors and HTTP status codes in its response, an attacker with pipeline creation permissions can exploit the server as a status-code and port-scanning oracle against Kubernetes cluster-internal services, private networks, and cloud metadata endpoints.
CVE-2026-88985 is a SQL injection vulnerability (CWE-89) in the Kubeflow Pipelines (KFP) API server prior to version 2.17.0. The flaw stems from the untrusted deserialization of base64-encoded JSON pagination tokens (page_token) and the direct concatenation of metric names during run list sorting. Authenticated users with permissions to invoke REST or gRPC list endpoints can inject arbitrary SQL syntax into structural query identifiers and JOIN predicates, enabling unauthorized cross-tenant read access to all database records accessible by the API server.
CVE-2026-88984 (GHSA-ghgh-57qv-h687) is an incorrect authorization vulnerability affecting Kubeflow Pipelines (KFP) prior to version 2.16.1 in multi-user deployments. The multi-user API server treats the empty Kubernetes namespace associated with shared or global pipelines as an unconditional authorization exception across all API verbs. Consequently, any authenticated user can modify, overwrite, or delete shared pipeline definitions and pipeline versions without possessing the required Kubernetes RBAC permissions, compromising the integrity and availability of global machine learning workflows.
Prior to version 2.31.0, the Telepresence Traffic Manager exposed an unauthenticated gRPC API on cluster-internal port 8081 that lacked caller identity verification and Kubernetes RBAC authorization checks. An attacker operating any pod with network access to the Traffic Manager can register a session in an arbitrary managed namespace, establish service intercepts, redirect live production traffic, and exfiltrate the complete container environment of victim workloads, including mounted secrets and service account tokens.
CVE-2026-63198 (GHSA-8cfq-jgf7-8m27) is an incorrect authorization vulnerability (CWE-863) in traQ, an open-source messaging platform developed in Go by Digital Creators Club traP. The flaw resides in the OAuth 2.0 authorization endpoint handler (`router/oauth2/authorization_endpoint.go`). When processing silent authorization requests (`prompt=none`) with an omitted or empty `scope` parameter, the application evaluates prior user consent against the empty requested scope set prior to applying default scope fallback logic. Because an empty set vacuously satisfies the subset check against any existing token, consent verification succeeds if the user has ever authorized the client for any single scope. Subsequently, the token issuance logic defaults the empty scope parameter to all scopes registered to the client (`req.ValidScopes`), allowing a client application with partial consent to silently escalate privileges without user interaction.
CVE-2026-63475 (GHSA-96wc-2m84-2cj7) is an uncontrolled recursion and infinite loop vulnerability in isomorphic-git prior to version 1.38.6. When processing Git protocol responses from a malicious or man-in-the-middle (MitM) server, isomorphic-git writes server-advertised symbolic reference (symref) capabilities directly to the local repository filesystem without validating for circular dependencies. Because the reference resolution logic in GitRefManager.resolve() lacks both cycle detection and a default recursion depth limit, any subsequent operation that resolves the circular references enters a non-terminating asynchronous recursion loop. Because the malicious references are persisted on disk, this condition induces a persistent denial of service (DoS) against the affected repository.