VYPR
Low severity3.5OSV Advisory· Published Jan 10, 2026· Updated Apr 29, 2026

CVE-2026-0824

CVE-2026-0824

Description

A security flaw has been discovered in questdb ui up to 1.11.9. Impacted is an unknown function of the component Web Console. The manipulation results in cross site scripting. The attack can be executed remotely. The exploit has been released to the public and may be used for attacks. Upgrading to version 1.1.10 is recommended to address this issue. The patch is identified as b42fd9f18476d844ae181a10a249e003dafb823d. You should upgrade the affected component. The vendor confirmed early that the fix "is going to be released as a part of QuestDB 9.3.0" as well.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@questdb/web-consolenpm
< 1.1.101.1.10

Affected products

1

Patches

1
b42fd9f18476

fix XSS security vulnerability (#519)

https://github.com/questdb/uiVlad IlyushchenkoJan 8, 2026via ghsa
4 files changed · +41 38
  • e2e/tests/console/aiAssistant.spec.js+9 5 modified
    @@ -1274,17 +1274,21 @@ describe("ai assistant", () => {
       })
     
       describe("explain schema", () => {
    -    beforeEach(() => {
    -      cy.loadConsoleWithAuth(false, getOpenAIConfiguredSettings())
    +    before(() => {
    +      cy.loadConsoleWithAuth()
           cy.typeQuery(
             "CREATE TABLE IF NOT EXISTS test_trades (symbol SYMBOL, price DOUBLE, ts TIMESTAMP) TIMESTAMP(ts) PARTITION BY DAY WAL;",
           )
           cy.clickRunQuery()
    +      cy.refreshSchema()
    +    })
    +    after(() => {
    +      cy.loadConsoleWithAuth()
    +      cy.dropTable("test_trades")
         })
     
    -    afterEach(() => {
    -      cy.typeQuery("DROP TABLE IF EXISTS test_trades;")
    -      cy.clickRunQuery()
    +    beforeEach(() => {
    +      cy.loadConsoleWithAuth(false, getOpenAIConfiguredSettings())
         })
     
         it("should show processing status and display valid schema explanation", () => {
    
  • src/js/console/grid.js+18 31 modified
    @@ -33,17 +33,6 @@ const hashString = (str) => {
       return new Uint32Array([hash])[0].toString(36)
     }
     
    -const escapeHtml = (text) => {
    -  const map = {
    -    "<": "&lt;",
    -    ">": "&gt;",
    -    '"': "&quot;",
    -    "'": "&#039;",
    -  }
    -
    -  return text.replace(/[<>"']/g, (m) => map[m])
    -}
    -
     export function grid(rootElement, _paginationFn, id) {
       const defaults = {
         gridID: "qdb-grid",
    @@ -963,26 +952,26 @@ export function grid(rootElement, _paginationFn, id) {
           const hType = document.createElement("span")
           addClass(hType, "qg-header-type")
           if (c.type !== "ARRAY") {
    -        hType.innerHTML = c.type.toLowerCase()
    +        hType.textContent = c.type.toLowerCase()
           } else if (c.dim > 2) {
    -        hType.innerHTML =
    +        hType.textContent =
               c.type.toUpperCase() +
               "(" +
               c.elemType.toUpperCase() +
               "," +
               c.dim +
               ")"
           } else {
    -        let html = c.elemType.toLowerCase() + "[]"
    +        let typeText = c.elemType.toLowerCase() + "[]"
             if (c.dim > 1) {
    -          html += "[]"
    +          typeText += "[]"
             }
    -        hType.innerHTML = html
    +        hType.textContent = typeText
           }
     
           const hName = document.createElement("span")
           addClass(hName, "qg-header-name")
    -      hName.innerHTML = c.name
    +      hName.textContent = c.name
     
           const hysteresis = document.createElement("div")
           addClass(hysteresis, "qg-col-resize-hysteresis")
    @@ -1106,14 +1095,12 @@ export function grid(rootElement, _paginationFn, id) {
       }
     
       function getArrayString(cellData) {
    -    return escapeHtml(
    -      JSON.stringify(cellData, (_, val) => {
    -        if (Number.isInteger(val)) {
    -          return val.toString() + ".0"
    -        }
    -        return val
    -      }).replace(/"/g, ""),
    -    )
    +    return JSON.stringify(cellData, (_, val) => {
    +      if (Number.isInteger(val)) {
    +        return val.toString() + ".0"
    +      }
    +      return val
    +    }).replace(/"/g, "")
       }
     
       function getDisplayedCellValue(column, cellData, columnWidth = null) {
    @@ -1126,10 +1113,10 @@ export function grid(rootElement, _paginationFn, id) {
         if (!isArray) {
           if (containsPrecision) {
             return Number.isInteger(cellData)
    -          ? escapeHtml(cellData.toString() + ".0")
    -          : escapeHtml(cellData.toString())
    +          ? cellData.toString() + ".0"
    +          : cellData.toString()
           }
    -      return escapeHtml(cellData.toString())
    +      return cellData.toString()
         }
     
         const arrayString = getArrayString(cellData)
    @@ -1166,15 +1153,15 @@ export function grid(rootElement, _paginationFn, id) {
         if (cellData !== null) {
           const layoutEntry = getLayoutEntry()
           const columnWidth = layoutEntry.deviants[column.name] ?? null
    -      cell.innerHTML = getDisplayedCellValue(column, cellData, columnWidth)
    +      cell.textContent = getDisplayedCellValue(column, cellData, columnWidth)
     
           cell.classList.remove("qg-null")
     
           if (column.type === "ARRAY") {
             cell.classList.add("qg-arr")
           }
         } else {
    -      cell.innerHTML = "null"
    +      cell.textContent = "null"
           cell.classList.add("qg-null")
         }
       }
    @@ -1769,7 +1756,7 @@ export function grid(rootElement, _paginationFn, id) {
           }
           addClass(focusedCell, "qg-c-active-pulse")
     
    -      let valueToCopy = focusedCell.innerHTML
    +      let valueToCopy = focusedCell.textContent
     
           if (focusedCell.classList.contains("qg-arr")) {
             const rowIndex = focusedCell.parentElement.rowIndex
    
  • src/js/console/quick-vis.ts+3 2 modified
    @@ -38,6 +38,7 @@ import eChartsMacarons from "./utils/macarons"
     import { arrayEquals } from "./array-equals"
     import { eventBus } from "../../modules/EventBus"
     import { EventType } from "../../modules/EventBus/types"
    +import { escapeHtml } from "../../utils/escapeHtml"
     import * as QuestDB from "../../utils/questdb"
     import { AnyIfEmpty } from "react-redux"
     import { request } from "http"
    @@ -290,12 +291,12 @@ export function quickVis(
         const x = []
         const columns = data.columns
         for (let i = 0; i < columns.length; i++) {
    -      x[i] = { text: columns[i].name, value: columns[i].name }
    +      x[i] = { text: escapeHtml(columns[i].name), value: columns[i].name }
         }
         xAxisPicker.setData(x)
         yAxisPicker.setData(x)
     
    -    yAxisPicker.set(x.slice(1).map((item) => item.text))
    +    yAxisPicker.set(x.slice(1).map((item) => item.value))
     
         // stash query text so that we can use this later to server for chart column values
         query = data.query
    
  • src/utils/escapeHtml.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export const escapeHtml = (text: string): string => {
    +  const map: Record<string, string> = {
    +    "&": "&amp;",
    +    "<": "&lt;",
    +    ">": "&gt;",
    +    '"': "&quot;",
    +    "'": "&#039;",
    +  }
    +
    +  return text.replace(/[&<>"']/g, (m) => map[m])
    +}
    

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

10

News mentions

0

No linked articles in our index yet.