VYPR
Moderate severityNVD Advisory· Published Mar 20, 2025· Updated Apr 4, 2025

Cross-Site Scripting (XSS) in mudler/localai

CVE-2024-9900

Description

mudler/localai version v2.21.1 contains a Cross-Site Scripting (XSS) vulnerability in its search functionality. The vulnerability arises due to improper sanitization of user input, allowing the injection and execution of arbitrary JavaScript code. This can lead to the execution of malicious scripts in the context of the victim's browser, potentially compromising user sessions, stealing session cookies, redirecting users to malicious websites, or manipulating the DOM.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/mudler/LocalAIGo
< 2.22.02.22.0

Affected products

1

Patches

1
a1634b219a4e

fix: roll out bluemonday Sanitize more widely (#3794)

https://github.com/mudler/localaiDaveOct 12, 2024via ghsa
6 files changed · +37 29
  • core/http/elements/gallery.go+10 13 modified
    @@ -6,6 +6,7 @@ import (
     
     	"github.com/chasefleming/elem-go"
     	"github.com/chasefleming/elem-go/attrs"
    +	"github.com/microcosm-cc/bluemonday"
     	"github.com/mudler/LocalAI/core/gallery"
     	"github.com/mudler/LocalAI/core/p2p"
     	"github.com/mudler/LocalAI/core/services"
    @@ -41,7 +42,7 @@ func DoneProgress(galleryID, text string, showDelete bool) string {
     				"tabindex":  "-1",
     				"autofocus": "",
     			},
    -			elem.Text(text),
    +			elem.Text(bluemonday.StrictPolicy().Sanitize(text)),
     		),
     		elem.If(showDelete, deleteButton(galleryID, modelName), reInstallButton(galleryID)),
     	).Render()
    @@ -57,7 +58,7 @@ func ErrorProgress(err, galleryName string) string {
     				"tabindex":  "-1",
     				"autofocus": "",
     			},
    -			elem.Text("Error "+err),
    +			elem.Text("Error "+bluemonday.StrictPolicy().Sanitize(err)),
     		),
     		installButton(galleryName),
     	).Render()
    @@ -170,7 +171,7 @@ func P2PNodeBoxes(nodes []p2p.NodeData) string {
     						attrs.Props{
     							"class": "text-gray-200 font-semibold ml-2 mr-1",
     						},
    -						elem.Text(n.ID),
    +						elem.Text(bluemonday.StrictPolicy().Sanitize(n.ID)),
     					),
     					elem.Text("Status: "),
     					elem.If(
    @@ -227,7 +228,7 @@ func StartProgressBar(uid, progress, text string) string {
     				"tabindex":  "-1",
     				"autofocus": "",
     			},
    -			elem.Text(text),
    +			elem.Text(bluemonday.StrictPolicy().Sanitize(text)), //Perhaps overly defensive
     			elem.Div(attrs.Props{
     				"hx-get":     "/browse/job/progress/" + uid,
     				"hx-trigger": "every 600ms",
    @@ -249,9 +250,7 @@ func cardSpan(text, icon string) elem.Node {
     			"class": icon + " pr-2",
     		}),
     
    -		elem.Text(text),
    -
    -		//elem.Text(text),
    +		elem.Text(bluemonday.StrictPolicy().Sanitize(text)),
     	)
     }
     
    @@ -285,11 +284,9 @@ func searchableElement(text, icon string) elem.Node {
     				elem.I(attrs.Props{
     					"class": icon + " pr-2",
     				}),
    -				elem.Text(text),
    +				elem.Text(bluemonday.StrictPolicy().Sanitize(text)),
     			),
     		),
    -
    -		//elem.Text(text),
     	)
     }
     
    @@ -303,7 +300,7 @@ func link(text, url string) elem.Node {
     		elem.I(attrs.Props{
     			"class": "fas fa-link pr-2",
     		}),
    -		elem.Text(text),
    +		elem.Text(bluemonday.StrictPolicy().Sanitize(text)),
     	)
     }
     func installButton(galleryName string) elem.Node {
    @@ -387,13 +384,13 @@ func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, g
     				attrs.Props{
     					"class": "mb-2 text-xl font-bold leading-tight",
     				},
    -				elem.Text(m.Name),
    +				elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)),
     			),
     			elem.P(
     				attrs.Props{
     					"class": "mb-4 text-sm [&:not(:hover)]:truncate text-base",
     				},
    -				elem.Text(m.Description),
    +				elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)),
     			),
     		)
     	}
    
  • core/http/endpoints/openai/assistant.go+9 8 modified
    @@ -10,6 +10,7 @@ import (
     	"time"
     
     	"github.com/gofiber/fiber/v2"
    +	"github.com/microcosm-cc/bluemonday"
     	"github.com/mudler/LocalAI/core/config"
     	"github.com/mudler/LocalAI/core/schema"
     	"github.com/mudler/LocalAI/core/services"
    @@ -83,7 +84,7 @@ func CreateAssistantEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoad
     
     		if !modelExists(cl, ml, request.Model) {
     			log.Warn().Msgf("Model: %s was not found in list of models.", request.Model)
    -			return c.Status(fiber.StatusBadRequest).SendString("Model " + request.Model + " not found")
    +			return c.Status(fiber.StatusBadRequest).SendString(bluemonday.StrictPolicy().Sanitize(fmt.Sprintf("Model %q not found", request.Model)))
     		}
     
     		if request.Tools == nil {
    @@ -147,7 +148,7 @@ func ListAssistantsEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoade
     		// Convert string limit to integer
     		limit, err := strconv.Atoi(limitQuery)
     		if err != nil {
    -			return c.Status(http.StatusBadRequest).SendString(fmt.Sprintf("Invalid limit query value: %s", limitQuery))
    +			return c.Status(http.StatusBadRequest).SendString(bluemonday.StrictPolicy().Sanitize(fmt.Sprintf("Invalid limit query value: %s", limitQuery)))
     		}
     
     		// Sort assistants
    @@ -288,7 +289,7 @@ func GetAssistantEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoader,
     			}
     		}
     
    -		return c.Status(fiber.StatusNotFound).SendString(fmt.Sprintf("Unable to find assistant with id: %s", assistantID))
    +		return c.Status(fiber.StatusNotFound).SendString(bluemonday.StrictPolicy().Sanitize(fmt.Sprintf("Unable to find assistant with id: %s", assistantID)))
     	}
     }
     
    @@ -337,11 +338,11 @@ func CreateAssistantFileEndpoint(cl *config.BackendConfigLoader, ml *model.Model
     					}
     				}
     
    -				return c.Status(fiber.StatusNotFound).SendString(fmt.Sprintf("Unable to find file_id: %s", request.FileID))
    +				return c.Status(fiber.StatusNotFound).SendString(bluemonday.StrictPolicy().Sanitize(fmt.Sprintf("Unable to find file_id: %s", request.FileID)))
     			}
     		}
     
    -		return c.Status(fiber.StatusNotFound).SendString(fmt.Sprintf("Unable to find %q", assistantID))
    +		return c.Status(fiber.StatusNotFound).SendString(bluemonday.StrictPolicy().Sanitize(fmt.Sprintf("Unable to find %q", assistantID)))
     	}
     }
     
    @@ -442,7 +443,7 @@ func ModifyAssistantEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoad
     				return c.Status(fiber.StatusOK).JSON(newAssistant)
     			}
     		}
    -		return c.Status(fiber.StatusNotFound).SendString(fmt.Sprintf("Unable to find assistant with id: %s", assistantID))
    +		return c.Status(fiber.StatusNotFound).SendString(bluemonday.StrictPolicy().Sanitize(fmt.Sprintf("Unable to find assistant with id: %s", assistantID)))
     	}
     }
     
    @@ -513,9 +514,9 @@ func GetAssistantFileEndpoint(cl *config.BackendConfigLoader, ml *model.ModelLoa
     				if assistantFile.ID == fileId {
     					return c.Status(fiber.StatusOK).JSON(assistantFile)
     				}
    -				return c.Status(fiber.StatusNotFound).SendString(fmt.Sprintf("Unable to find assistant file with file_id: %s", fileId))
    +				return c.Status(fiber.StatusNotFound).SendString(bluemonday.StrictPolicy().Sanitize(fmt.Sprintf("Unable to find assistant file with file_id: %s", fileId)))
     			}
     		}
    -		return c.Status(fiber.StatusNotFound).SendString(fmt.Sprintf("Unable to find assistant file with assistant_id: %s", assistantID))
    +		return c.Status(fiber.StatusNotFound).SendString(bluemonday.StrictPolicy().Sanitize(fmt.Sprintf("Unable to find assistant file with assistant_id: %s", assistantID)))
     	}
     }
    
  • core/http/endpoints/openai/files.go+7 6 modified
    @@ -8,6 +8,7 @@ import (
     	"sync/atomic"
     	"time"
     
    +	"github.com/microcosm-cc/bluemonday"
     	"github.com/mudler/LocalAI/core/config"
     	"github.com/mudler/LocalAI/core/schema"
     
    @@ -49,7 +50,7 @@ func UploadFilesEndpoint(cm *config.BackendConfigLoader, appConfig *config.Appli
     
     		err = c.SaveFile(file, savePath)
     		if err != nil {
    -			return c.Status(fiber.StatusInternalServerError).SendString("Failed to save file: " + err.Error())
    +			return c.Status(fiber.StatusInternalServerError).SendString("Failed to save file: " + bluemonday.StrictPolicy().Sanitize(err.Error()))
     		}
     
     		f := schema.File{
    @@ -121,7 +122,7 @@ func GetFilesEndpoint(cm *config.BackendConfigLoader, appConfig *config.Applicat
     	return func(c *fiber.Ctx) error {
     		file, err := getFileFromRequest(c)
     		if err != nil {
    -			return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
    +			return c.Status(fiber.StatusInternalServerError).SendString(bluemonday.StrictPolicy().Sanitize(err.Error()))
     		}
     
     		return c.JSON(file)
    @@ -143,14 +144,14 @@ func DeleteFilesEndpoint(cm *config.BackendConfigLoader, appConfig *config.Appli
     	return func(c *fiber.Ctx) error {
     		file, err := getFileFromRequest(c)
     		if err != nil {
    -			return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
    +			return c.Status(fiber.StatusInternalServerError).SendString(bluemonday.StrictPolicy().Sanitize(err.Error()))
     		}
     
     		err = os.Remove(filepath.Join(appConfig.UploadDir, file.Filename))
     		if err != nil {
     			// If the file doesn't exist then we should just continue to remove it
     			if !errors.Is(err, os.ErrNotExist) {
    -				return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("Unable to delete file: %s, %v", file.Filename, err))
    +				return c.Status(fiber.StatusInternalServerError).SendString(bluemonday.StrictPolicy().Sanitize(fmt.Sprintf("Unable to delete file: %s, %v", file.Filename, err)))
     			}
     		}
     
    @@ -180,12 +181,12 @@ func GetFilesContentsEndpoint(cm *config.BackendConfigLoader, appConfig *config.
     	return func(c *fiber.Ctx) error {
     		file, err := getFileFromRequest(c)
     		if err != nil {
    -			return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
    +			return c.Status(fiber.StatusInternalServerError).SendString(bluemonday.StrictPolicy().Sanitize(err.Error()))
     		}
     
     		fileContents, err := os.ReadFile(filepath.Join(appConfig.UploadDir, file.Filename))
     		if err != nil {
    -			return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
    +			return c.Status(fiber.StatusInternalServerError).SendString(bluemonday.StrictPolicy().Sanitize(err.Error()))
     		}
     
     		return c.Send(fileContents)
    
  • core/http/middleware/auth.go+2 1 modified
    @@ -7,6 +7,7 @@ import (
     	"github.com/dave-gray101/v2keyauth"
    
     	"github.com/gofiber/fiber/v2"
    
     	"github.com/gofiber/fiber/v2/middleware/keyauth"
    
    +	"github.com/microcosm-cc/bluemonday"
    
     	"github.com/mudler/LocalAI/core/config"
    
     )
    
     
    
    @@ -38,7 +39,7 @@ func getApiKeyErrorHandler(applicationConfig *config.ApplicationConfig) fiber.Er
     			if applicationConfig.OpaqueErrors {
    
     				return ctx.SendStatus(403)
    
     			}
    
    -			return ctx.Status(403).SendString(err.Error())
    
    +			return ctx.Status(403).SendString(bluemonday.StrictPolicy().Sanitize(err.Error()))
    
     		}
    
     		if applicationConfig.OpaqueErrors {
    
     			return ctx.SendStatus(500)
    
    
  • core/http/routes/ui.go+2 1 modified
    @@ -6,6 +6,7 @@ import (
     	"sort"
     	"strings"
     
    +	"github.com/microcosm-cc/bluemonday"
     	"github.com/mudler/LocalAI/core/config"
     	"github.com/mudler/LocalAI/core/gallery"
     	"github.com/mudler/LocalAI/core/http/elements"
    @@ -171,7 +172,7 @@ func RegisterUIRoutes(app *fiber.App,
     				Search string `form:"search"`
     			}{}
     			if err := c.BodyParser(&form); err != nil {
    -				return c.Status(fiber.StatusBadRequest).SendString(err.Error())
    +				return c.Status(fiber.StatusBadRequest).SendString(bluemonday.StrictPolicy().Sanitize(err.Error()))
     			}
     
     			models, _ := gallery.AvailableGalleryModels(appConfig.Galleries, appConfig.ModelPath)
    
  • .github/ci/modelslist.go+7 0 modified
    @@ -6,6 +6,7 @@ import (
     	"io/ioutil"
     	"os"
     
    +	"github.com/microcosm-cc/bluemonday"
     	"gopkg.in/yaml.v3"
     )
     
    @@ -279,6 +280,12 @@ func main() {
     		return
     	}
     
    +	// Ensure that all arbitrary text content is sanitized before display
    +	for i, m := range models {
    +		models[i].Name = bluemonday.StrictPolicy().Sanitize(m.Name)
    +		models[i].Description = bluemonday.StrictPolicy().Sanitize(m.Description)
    +	}
    +
     	// render the template
     	data := struct {
     		Models          []*GalleryModel
    

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.