VYPR
High severity7.3NVD Advisory· Published Feb 21, 2025· Updated Apr 15, 2026

CVE-2025-27109

CVE-2025-27109

Description

solid-js is a declarative, efficient, and flexible JavaScript library for building user interfaces. In affected versions Inserts/JSX expressions inside illegal inlined JSX fragments lacked escaping, allowing user input to be rendered as HTML when put directly inside JSX fragments. This issue has been addressed in version 1.9.4 and all users are advised to upgrade. There are no known workarounds for this vulnerability.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
solid-jsnpm
< 1.9.41.9.4

Patches

2
b93956f28ed7

fix escaping in resolution done outside of DOM Expressions

https://github.com/solidjs/solidRyan CarniatoJan 7, 2025via ghsa
2 files changed · +65 5
  • .changeset/clever-months-cover.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +"solid-js": patch
    +---
    +
    +fix escaping in resolution done outside of DOM Expressions
    
  • packages/solid/src/server/rendering.ts+60 5 modified
    @@ -29,6 +29,61 @@ export type ComponentProps<T extends ValidComponent> = T extends Component<infer
       ? JSX.IntrinsicElements[T]
       : Record<string, unknown>;
     
    +// these methods are duplicates from solid-js/web
    +// we need a better solution for this in the future
    +function escape(s: any, attr?: boolean) {
    +  const t = typeof s;
    +  if (t !== "string") {
    +    if (!attr && t === "function") return escape(s());
    +    if (!attr && Array.isArray(s)) {
    +      for (let i = 0; i < s.length; i++) s[i] = escape(s[i]);
    +      return s;
    +    }
    +    if (attr && t === "boolean") return String(s);
    +    return s;
    +  }
    +  const delim = attr ? '"' : "<";
    +  const escDelim = attr ? "&quot;" : "&lt;";
    +  let iDelim = s.indexOf(delim);
    +  let iAmp = s.indexOf("&");
    +
    +  if (iDelim < 0 && iAmp < 0) return s;
    +
    +  let left = 0,
    +    out = "";
    +
    +  while (iDelim >= 0 && iAmp >= 0) {
    +    if (iDelim < iAmp) {
    +      if (left < iDelim) out += s.substring(left, iDelim);
    +      out += escDelim;
    +      left = iDelim + 1;
    +      iDelim = s.indexOf(delim, left);
    +    } else {
    +      if (left < iAmp) out += s.substring(left, iAmp);
    +      out += "&amp;";
    +      left = iAmp + 1;
    +      iAmp = s.indexOf("&", left);
    +    }
    +  }
    +
    +  if (iDelim >= 0) {
    +    do {
    +      if (left < iDelim) out += s.substring(left, iDelim);
    +      out += escDelim;
    +      left = iDelim + 1;
    +      iDelim = s.indexOf(delim, left);
    +    } while (iDelim >= 0);
    +  } else
    +    while (iAmp >= 0) {
    +      if (left < iAmp) out += s.substring(left, iAmp);
    +      out += "&amp;";
    +      left = iAmp + 1;
    +      iAmp = s.indexOf("&", left);
    +    }
    +
    +  return left < s.length ? out + s.substring(left) : out;
    +}
    +
     function resolveSSRNode(node: any): string {
       const t = typeof node;
       if (t === "string") return node;
    @@ -304,7 +359,7 @@ export function ErrorBoundary(props: {
       });
       if (error) return displayFallback();
       sync = false;
    -  return { t: `<!--!$e${id}-->${resolveSSRNode(res)}<!--!$/e${id}-->` };
    +  return { t: `<!--!$e${id}-->${resolveSSRNode(escape(res))}<!--!$/e${id}-->` };
     }
     
     // Suspense Context
    @@ -376,9 +431,9 @@ export function createResource<T, S>(
     ): ResourceReturn<T> | ResourceReturn<T | undefined> {
       
       if (typeof fetcher !== "function") {
    -    source = true as ResourceSource<S>;
    -    fetcher = source as ResourceFetcher<S, T>;
         options = (fetcher || {}) as ResourceOptions<T> | ResourceOptions<undefined>;
    +    fetcher = source as ResourceFetcher<S, T>;
    +    source = true as ResourceSource<S>;
       }
     
       const contexts = new Set<SuspenseContextType>();
    @@ -585,7 +640,7 @@ export function Suspense(props: { fallback?: string; children: string }) {
           completed: () => {
             const res = runSuspense();
             if (suspenseComplete(value)) {
    -          done!(resolveSSRNode(res));
    +          done!(resolveSSRNode(escape(res)));
             }
           }
         });
    @@ -623,7 +678,7 @@ export function Suspense(props: { fallback?: string; children: string }) {
         if (ctx.async) {
           setHydrateContext({ ...ctx, count: 0, id: ctx.id + "0F", noHydrate: true });
           const res = {
    -        t: `<template id="pl-${id}"></template>${resolveSSRNode(props.fallback)}<!--pl-${id}-->`
    +        t: `<template id="pl-${id}"></template>${resolveSSRNode(escape(props.fallback))}<!--pl-${id}-->`
           };
           setHydrateContext(ctx);
           return res;
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.