CVE-2022-2589 (TypeScript, published 2022-08-01T15:15Z)
Description: value: Cross-site Scripting (XSS) - Reflected in GitHub repository beancount/fava prior to 1.22.3.
Severity: nan (CVSS v3 base score: 6.1)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product 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.
Repository: https://github.com/beancount/fava
Files changed:
frontend/src/charts/ScatterPlot.svelte
frontend/src/charts/Treemap.svelte
frontend/src/charts/bar.ts
frontend/src/charts/context.ts
frontend/src/charts/line.ts
frontend/src/charts/tooltip.ts
frontend/src/keyboard-shortcuts.ts
frontend/src/sidebar/index.ts
Commit message: create tooltip contents in a xss-safe way


Vulnerable code:
  import { positionedTooltip } from "./tooltip";
    return `${d.description}<em>${day(d.date)}</em>`;
  import { followingTooltip } from "./tooltip";
    return `${$ctx.amount(val, currency)} (${formatPercentage(
      val / rootValue
    )})<em>${d.data.account}</em>`;
  tooltipText: (c: FormatterContext, d: BarChartDatum, e: string) => string;
      let text = "";
          text += c.amount(a.value, a.currency);
          if (a.budget) {
            text += ` / ${c.amount(a.budget, a.currency)}`;
          }
          text += "<br>";
        text += `<em>${e}</em>`;
          text += `${c.amount(value, a.currency)}<br>`;
      text += `<em>${d.label}</em>`;
      return text;
import { conversion, operating_currency } from "../stores";
  [operating_currency, conversion],
  ([operating_currency_val, conversion_val]) => {
    if (
      !conversion_val ||
      ["at_cost", "at_value", "units"].includes(conversion_val) ||
      operating_currency_val.includes(conversion_val)
    ) {
      return operating_currency_val;
    }
    return [...operating_currency_val, conversion_val];
  }
  tooltipText: (c: FormatterContext, d: LineChartDatum) => string;
    tooltipText: (c, d) =>
      `${c.amount(d.value, d.name)}<em>${day(d.date)}</em>`,
    tooltipText(c, d) {
      return `1 ${base} = ${c.amount(d.value, quote)}<em>${day(d.date)}</em>`;
    },
  text: () => string
): { destroy: () => void; update: (t: () => string) => void } {
    t.innerHTML = getter();
    update(t: () => string): void {
) => [number, number, string] | undefined;
      t.innerHTML = content;
  tooltip.innerHTML = target.getAttribute("data-key") || "";
        span.innerHTML = `${errorCount_val}`;

Fixed code:
  import { domHelpers, positionedTooltip } from "./tooltip";
    return [domHelpers.t(d.description), domHelpers.em(day(d.date))];
  import { domHelpers, followingTooltip } from "./tooltip";
    return [
      domHelpers.t(
        `${$ctx.amount(val, currency)} (${formatPercentage(val / rootValue)})`
      ),
      domHelpers.em(d.data.account),
    ];
import type { TooltipContent } from "./tooltip";
import { domHelpers } from "./tooltip";
  tooltipText: (
    c: FormatterContext,
    d: BarChartDatum,
    e: string
  ) => TooltipContent;
      const content: TooltipContent = [];
          content.push(
            domHelpers.t(
              a.budget
                ? `${c.amount(a.value, a.currency)} / ${c.amount(
                    a.budget,
                    a.currency
                  )}`
                : c.amount(a.value, a.currency)
            )
          );
          content.push(domHelpers.br());
        content.push(domHelpers.em(e));
          content.push(domHelpers.t(`${c.amount(value, a.currency)}`));
          content.push(domHelpers.br());
      content.push(domHelpers.em(d.label));
      return content;
import { conversion, currencies, operating_currency } from "../stores";
  [operating_currency, currencies, conversion],
  ([operating_currency_val, currencies_val, conversion_val]) =>
    currencies_val.includes(conversion_val) &&
    !operating_currency_val.includes(conversion_val)
      ? [...operating_currency_val, conversion_val]
      : operating_currency_val
import type { TooltipContent } from "./tooltip";
import { domHelpers } from "./tooltip";

  tooltipText: (c: FormatterContext, d: LineChartDatum) => TooltipContent;
    tooltipText: (c, d) => [
      domHelpers.t(c.amount(d.value, d.name)),
      domHelpers.em(day(d.date)),
    ],
    tooltipText: (c, d) => [
      domHelpers.t(`1 ${base} = ${c.amount(d.value, quote)}`),
      domHelpers.em(day(d.date)),
    ],
/** Some small utilities to create tooltip contents. */
export const domHelpers = {
  br: () => document.createElement("br"),
  em: (content: string) => {
    const em = document.createElement("em");
    em.textContent = content;
    return em;
  },
  t: (text: string) => document.createTextNode(text),
};

export type TooltipContent = (HTMLElement | Text)[];

  text: () => TooltipContent
): { destroy: () => void; update: (t: () => TooltipContent) => void } {
    t.replaceChildren(...getter());
    update(t: () => TooltipContent): void {
) => [number, number, TooltipContent] | undefined;
      t.replaceChildren(...content);
  tooltip.textContent = target.getAttribute("data-key") ?? "";
        span.textContent = `${errorCount_val}`;