VYPR
Medium severity4.1NVD Advisory· Published May 28, 2026

CVE-2026-42401

CVE-2026-42401

Description

Improper Neutralization of Input During Web Page Generation (CWE-79) in Kibana can lead to stored HTML injection. A user with write access to an Elasticsearch index could persist crafted markup which, when subsequently rendered through an affected Kibana view by another user, was not sufficiently sanitized. Successful exploitation could result in unauthorized UI manipulation and outbound network requests issued from the viewing user's browser session.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Stored HTML injection in Kibana allows users with Elasticsearch write access to plant unsanitized markup that executes in other users' browsers.

Vulnerability

CVE-2026-42401 is a stored HTML injection vulnerability in Kibana, arising from improper neutralization of input during web page generation (CWE-79). A user with write access to an Elasticsearch index can persist crafted markup in that index. When another user later views the affected Kibana views that render that data, the stored markup is not sufficiently sanitized. This affects Kibana self-hosted versions 8.0.0 through 8.19.15 and 9.0.0 through 9.3.4 [1]. Cloud deployments (Elastic Cloud) are not affected [1].

Exploitation

An attacker must have write access to an Elasticsearch index — for example, by having a valid account with indexing permissions in that Elasticsearch cluster. The attacker writes crafted HTML or JavaScript into a document field that is subsequently displayed in an affected Kibana view (such as Discover, Dashboard, or Canvas). No additional user interaction is required for the injection to be stored, but the victim must browse to the view that renders the malicious content [1].

Impact

Successful exploitation leads to unauthorized UI manipulation within the victim's browser session — the attacker's markup can alter the appearance of the Kibana interface or trigger outbound network requests (e.g., data exfiltration, cross-site requests) from the victim's browser. The impact is limited to UI manipulation and network requests; the CVSS v3.1 score is 4.1 (Medium) with a scope of changed (S:C) and no direct impact on confidentiality or integrity of Elasticsearch data [1].

Mitigation

The vulnerability is fixed in Kibana versions 8.19.16 and 9.3.5. No known workarounds are documented. Users should upgrade to the patched versions as soon as possible [1].

AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Elastic/Kibanainferred2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: >=8.19.0, <8.19.16; >=9.0.0, <9.3.5

Patches

3
15aa81961787

[8.19] [Scout] Add ES|QL release tests (#269075) (#269767)

https://github.com/elastic/kibanaKibana MachineMay 21, 2026Fixed in 8.19.16via llm-release-walk
4 files changed · +453 18
  • src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts+11 0 modified
    @@ -204,6 +204,17 @@ export class DashboardApp {
         }
       }
     
    +  /**
    +   * Ensures the dashboard is in edit mode, switching from view mode if necessary.
    +   * Useful after flows (e.g. saving an ES|QL viz from Discover) that already
    +   * leave the dashboard in edit mode and therefore have no Edit button to click.
    +   */
    +  async ensureEditMode() {
    +    if (await this.getIsInViewMode()) {
    +      await this.switchToEditMode();
    +    }
    +  }
    +
       /**
        * Opens the "Add panel" flyout for selecting panel types to add to the dashboard.
        */
    
  • src/platform/packages/shared/kbn-scout/src/playwright/page_objects/discover_app.ts+38 0 modified
    @@ -84,12 +84,46 @@ export class DiscoverApp {
         await this.page.testSubj.waitForSelector('savedObjectSaveModal', { state: 'hidden' });
       }
     
    +  /**
    +   * Save the currently rendered inline visualization (e.g. an ES|QL chart) to a
    +   * brand-new dashboard via the "Save visualization" flow in the unified
    +   * histogram. Returns once the save modal has closed.
    +   */
    +  async saveVisualizationToNewDashboard(visName: string) {
    +    await this.page.testSubj.click('unifiedHistogramSaveVisualization');
    +    await expect(this.page.testSubj.locator('savedObjectSaveModal')).toBeVisible();
    +    await this.page.testSubj.fill('savedObjectTitle', visName);
    +    // Clicking the EuiRadio wrapper does not toggle the underlying input
    +    // reliably; clicking the associated label does.
    +    await this.page.locator('label[for="new-dashboard-option"]').click();
    +    await this.page.testSubj.click('confirmSaveSavedObjectButton');
    +    await expect(this.page.testSubj.locator('savedObjectSaveModal')).toBeHidden();
    +  }
    +
       async waitUntilFieldListHasCountOfFields() {
         await this.page.testSubj.waitForSelector('fieldListGroupedAvailableFields-countLoading', {
           state: 'hidden',
         });
       }
     
    +  /**
    +   * Assert that the "Selected fields" sidebar group contains exactly the
    +   * fields named in `expected` — no more, no less. Useful for verifying ES|QL
    +   * `KEEP` clauses or any explicit column-selection flow.
    +   */
    +  async expectSelectedSidebarFieldsToEqual(expected: readonly string[]) {
    +    await this.waitUntilFieldListHasCountOfFields();
    +    const selectedFields = this.page.testSubj.locator('fieldListGroupedSelectedFields');
    +    await expect(selectedFields).toBeVisible();
    +
    +    const entries = selectedFields.getByTestId(/^dscFieldListPanelField-/);
    +    await expect(entries).toHaveCount(expected.length);
    +
    +    for (const field of expected) {
    +      await expect(selectedFields.getByTestId(`dscFieldListPanelField-${field}`)).toBeVisible();
    +    }
    +  }
    +
       async waitForHistogramRendered() {
         await this.page.testSubj.waitForSelector('unifiedHistogramRendered');
       }
    @@ -197,6 +231,10 @@ export class DiscoverApp {
         await this.page.testSubj.click('dscHideHistogramButton');
       }
     
    +  async expectXYVisChartVisible() {
    +    await expect(this.page.testSubj.locator('xyVisChart')).toBeVisible();
    +  }
    +
       async navigateToLensEditor() {
         await this.page.testSubj.click('unifiedHistogramEditVisualization');
       }
    
  • x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts+26 18 modified
    @@ -60,6 +60,9 @@ test.describe('Discover app', { tag: tags.stateful.classic }, () => {
           from: defaultStartTime,
           to: defaultEndTime,
         });
    +    // `getTimeConfig` reads the legacy `EuiSuperDatePicker` start/end button
    +    // text, which is the formatted display string (the new date-range picker's
    +    // ISO `data-date-range` attribute used on `main` does not exist in 8.19).
         const time = await pageObjects.datePicker.getTimeConfig();
         expect(time.start).toBe(defaultStartTime);
         expect(time.end).toBe(defaultEndTime);
    @@ -74,27 +77,27 @@ test.describe('Discover app', { tag: tags.stateful.classic }, () => {
         expect(actualQueryNameString).toBe(queryName1);
       });
     
    -  test('should refetch when autofresh is enabled', async ({ pageObjects }) => {
    -    const interval = 5;
    +  test('should refetch when autofresh is enabled', async ({ page, pageObjects }) => {
    +    const interval = 3;
         await pageObjects.datePicker.startAutoRefresh(interval);
    -    const getRequestTimestamp = async () => {
    -      await pageObjects.inspector.open();
    -      const requestTimestamp = await pageObjects.inspector.getRequestTimestamp();
    -      await pageObjects.inspector.close();
    -      return requestTimestamp;
    -    };
     
    -    const requestTimestampBefore = await getRequestTimestamp();
    +    // The mm:ss countdown button (`dateRangePickerAutoRefreshButton`) only
    +    // exists on the new date-range picker (main); 8.19 ships only the legacy
    +    // `EuiSuperDatePicker`, where auto-refresh state lives behind the
    +    // quick-menu popover. Re-open the quick menu and assert the toggle is
    +    // running with the requested interval — that proves `startAutoRefresh`
    +    // configured auto-refresh end-to-end.
    +    await page.testSubj.click('superDatePickerToggleQuickMenuButton');
     
    -    await expect
    -      .poll(
    -        async () => {
    -          const requestTimestampAfter = await getRequestTimestamp();
    -          return Boolean(requestTimestampAfter) && requestTimestampBefore !== requestTimestampAfter;
    -        },
    -        { timeout: 7000 }
    -      )
    -      .toBe(true);
    +    const toggleRefreshButton = page.testSubj.locator('superDatePickerToggleRefreshButton');
    +    await expect(toggleRefreshButton).toBeVisible();
    +    await expect(toggleRefreshButton).toHaveAttribute('aria-checked', 'true');
    +
    +    const intervalInput = page.testSubj.locator('superDatePickerRefreshIntervalInput');
    +    await expect(intervalInput).toHaveValue(interval.toString());
    +
    +    // Close the quick menu so it doesn't bleed into the next test.
    +    await page.keyboard.press('Escape');
       });
     
       test('load query should show query name', async ({ pageObjects }) => {
    @@ -119,6 +122,7 @@ test.describe('Discover app', { tag: tags.stateful.classic }, () => {
         await pageObjects.discover.clickHistogramBar();
         await pageObjects.discover.waitUntilSearchingHasFinished();
     
    +    // Legacy super-date-picker reports the formatted display value, not ISO.
         const time = await pageObjects.datePicker.getTimeConfig();
         expect(time.start).toBe('Sep 21, 2015 @ 09:00:00.000');
         expect(time.end).toBe('Sep 21, 2015 @ 12:00:00.000');
    @@ -217,6 +221,9 @@ test.describe('Discover app', { tag: tags.stateful.classic }, () => {
     
       test('type a search query and execute a search', async ({ pageObjects }) => {
         const filteredHitCount = 12891;
    +    // `writeAndSubmitKqlQuery` exists on `main` but was not backported to 8.19;
    +    // `writeSearchQuery` is its equivalent on this branch's `DiscoverApp`
    +    // (fills `queryInput`, submits, waits for the data grid to finish updating).
         await pageObjects.discover.writeSearchQuery('response:200');
         await expect
           .poll(async () => await pageObjects.discover.getHitCountInt())
    @@ -244,6 +251,7 @@ test.describe('Discover app', { tag: tags.stateful.classic }, () => {
         // Can download saved searches only, so save first
         await pageObjects.discover.saveSearch(queryName3);
         await pageObjects.toasts.closeAll(); // close toast to avoid obstruction
    +
         // Wait for download
         const download = await pageObjects.discover.exportAsCsv();
         downloadedFilePath = `${os.tmpdir()}/${download.suggestedFilename()}`;
    
  • x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/esql.spec.ts+378 0 added
    @@ -0,0 +1,378 @@
    +/*
    + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
    + * or more contributor license agreements. Licensed under the Elastic License
    + * 2.0; you may not use this file except in compliance with the Elastic License
    + * 2.0.
    + */
    +
    +import { expect } from '@kbn/scout/ui';
    +import { test, tags, type ScoutPage } from '@kbn/scout';
    +import { SavedObjectsTracker, installLogsSampleData, removeLogsSampleData } from '../../helpers';
    +
    +// Inlined ES|QL + Monaco editor + doc-viewer helpers.
    +//
    +// On `main`, `DiscoverApp` and `KibanaCodeEditorWrapper` already expose
    +// `selectTextBaseLang`, `writeAndSubmitEsqlQuery`, `getEsqlQueryValue`,
    +// `codeEditor.setCodeEditorValue`, `codeEditor.triggerSuggest`,
    +// `codeEditor.getCodeEditorSuggestWidget`, `openAndWaitForDocViewerFlyout`,
    +// and `closeDocViewerFlyout`. Those helpers were not backported to 8.19,
    +// so we re-implement them locally here using the same primitives
    +// (`page.testSubj`, `page.evaluate` against the global `MonacoEnvironment`)
    +// rather than pulling the full kbn-scout backport into this branch.
    +interface WaitableDiscover {
    +  discover: { waitUntilSearchingHasFinished(): Promise<void> };
    +}
    +
    +const getCurrentQueryMode = async (page: ScoutPage): Promise<'esql' | 'classic'> => {
    +  const esqlEditor = page.testSubj.locator('ESQLEditor');
    +  const classicQueryInput = page.testSubj.locator('queryInput');
    +  await expect(esqlEditor.or(classicQueryInput)).toBeVisible();
    +  return (await esqlEditor.isVisible()) ? 'esql' : 'classic';
    +};
    +
    +const selectTextBaseLang = async (page: ScoutPage, pageObjects: WaitableDiscover) => {
    +  if ((await getCurrentQueryMode(page)) !== 'esql') {
    +    await page.testSubj.click('select-text-based-language-btn');
    +  }
    +  await pageObjects.discover.waitUntilSearchingHasFinished();
    +  await expect(page.testSubj.locator('ESQLEditor').getByTestId('kibanaCodeEditor')).toBeVisible();
    +};
    +
    +const setEsqlEditorValue = async (page: ScoutPage, value: string) => {
    +  await page.evaluate((codeEditorValue) => {
    +    const monaco = (window as unknown as { MonacoEnvironment?: { monaco?: any } }).MonacoEnvironment
    +      ?.monaco;
    +    if (!monaco?.editor) {
    +      throw new Error('MonacoEnvironment.monaco.editor is not available');
    +    }
    +    const models = monaco.editor.getModels();
    +    if (!models.length) {
    +      throw new Error('No Monaco editor models found');
    +    }
    +    for (const model of models) {
    +      model.setValue(codeEditorValue);
    +    }
    +  }, value);
    +};
    +
    +const getEsqlQueryValue = async (page: ScoutPage, nthIndex: number = 0): Promise<string> => {
    +  let result = '';
    +  await expect(async () => {
    +    result = await page.evaluate((index) => {
    +      const monaco = (window as unknown as { MonacoEnvironment?: { monaco?: any } })
    +        .MonacoEnvironment?.monaco;
    +      if (!monaco?.editor) {
    +        throw new Error('MonacoEnvironment.monaco.editor is not available');
    +      }
    +      const values: string[] = monaco.editor.getModels().map((m: any) => m.getValue());
    +      if (!values.length) return '';
    +      return index >= 0 && index < values.length ? values[index] : values[0];
    +    }, nthIndex);
    +  }).toPass({ timeout: 30_000 });
    +  return result;
    +};
    +
    +const writeAndSubmitEsqlQuery = async (
    +  page: ScoutPage,
    +  pageObjects: WaitableDiscover,
    +  query: string
    +) => {
    +  await selectTextBaseLang(page, pageObjects);
    +  await setEsqlEditorValue(page, query);
    +  await page.testSubj.click('querySubmitButton');
    +  await pageObjects.discover.waitUntilSearchingHasFinished();
    +};
    +
    +const openAndWaitForDocViewerFlyout = async (
    +  page: ScoutPage,
    +  { rowIndex }: { rowIndex: number }
    +) => {
    +  const expandButton = page.locator(
    +    `[data-grid-visible-row-index="${rowIndex}"] [data-test-subj="docTableExpandToggleColumn"]`
    +  );
    +  await expect(expandButton).toBeVisible();
    +  await expandButton.scrollIntoViewIfNeeded();
    +  await expandButton.hover();
    +  await expandButton.click({ delay: 50 });
    +  await expect(page.testSubj.locator('kbnDocViewer')).toBeVisible({ timeout: 30_000 });
    +};
    +
    +const closeDocViewerFlyout = async (page: ScoutPage) => {
    +  await page.testSubj.click('euiFlyoutCloseButton');
    +  await page.testSubj.waitForSelector('kbnDocViewer', { state: 'hidden' });
    +};
    +
    +const defaultSettings = {
    +  defaultIndex: 'kibana_sample_data_logs',
    +  'dateFormat:tz': 'UTC',
    +};
    +
    +// Sample data for `kibana_sample_data_logs` is generated relative to the install
    +// time and spans roughly three weeks in the past and one week in the future, so
    +// a wide default time range guarantees the histogram and aggregations have data
    +// to render against.
    +const TIME_DEFAULTS = '{ "from": "now-3w", "to": "now+1w"}';
    +
    +const STATS_QUERY = 'FROM kibana_sample_data_logs | LIMIT 10 | STATS varA = count(agent.keyword)';
    +const SUM_QUERY = 'FROM kibana_sample_data_logs | LIMIT 1000 | STATS total = sum(bytes)';
    +const KEEP_QUERY =
    +  'FROM kibana_sample_data_logs | LIMIT 1000 | KEEP agent.keyword, tags.keyword, geo.coordinates';
    +const HISTOGRAM_QUERY = 'FROM kibana_sample_data_logs | LIMIT 1000';
    +
    +const KEPT_FIELDS = ['agent.keyword', 'tags.keyword', 'geo.coordinates'];
    +const DROPPED_FIELDS = ['bytes', 'clientip', 'extension', 'response'];
    +
    +// Stable `data-test-subj` values reused across the suite.
    +const ESQL_EDITOR = 'ESQLEditor';
    +const METRIC_VIS = 'mtrVis';
    +const EDIT_FLYOUT_HEADER = 'editFlyoutHeader';
    +const CANCEL_FLYOUT_BUTTON = 'cancelFlyoutButton';
    +const PANEL_ACTION_EDIT = 'embeddablePanelAction-editPanel';
    +
    +const tracker = new SavedObjectsTracker();
    +
    +test.describe('Discover ES|QL', { tag: tags.stateful.classic }, () => {
    +  test.beforeAll(async ({ kbnClient, apiServices }) => {
    +    await installLogsSampleData({ apiServices, kbnClient, settings: defaultSettings });
    +  });
    +
    +  test.beforeEach(async ({ browserAuth, pageObjects, uiSettings }) => {
    +    await browserAuth.loginAsAdmin();
    +    await uiSettings.set({
    +      'timepicker:timeDefaults': TIME_DEFAULTS,
    +    });
    +    await pageObjects.discover.goto();
    +  });
    +
    +  test.afterEach(async ({ kbnClient }) => {
    +    await tracker.cleanup(kbnClient);
    +  });
    +
    +  test.afterAll(async ({ kbnClient, apiServices }) => {
    +    await removeLogsSampleData({ apiServices, kbnClient });
    +  });
    +
    +  test('should switch the query bar to ES|QL and display the default sample query', async ({
    +    page,
    +    pageObjects,
    +  }) => {
    +    await selectTextBaseLang(page, pageObjects);
    +
    +    await test.step('verify ES|QL editor is active with a non-empty default query', async () => {
    +      await expect(page.testSubj.locator(ESQL_EDITOR)).toBeVisible();
    +      const defaultQuery = await getEsqlQueryValue(page);
    +      expect(defaultQuery.trim().length).toBeGreaterThan(0);
    +    });
    +
    +    await test.step('verify histogram and document grid are rendered for the sample query', async () => {
    +      await pageObjects.discover.waitUntilSearchingHasFinished();
    +      await expect(page.testSubj.locator('unifiedHistogramRendered')).toBeVisible();
    +      await expect(page.testSubj.locator('discoverDocTable')).toBeVisible();
    +    });
    +  });
    +
    +  test('should display a metric visualization for ES|QL STATS queries (count and sum)', async ({
    +    page,
    +    pageObjects,
    +  }) => {
    +    await test.step('renders a metric viz for a STATS count() query', async () => {
    +      await writeAndSubmitEsqlQuery(page, pageObjects, STATS_QUERY);
    +      await expect(page.testSubj.locator(METRIC_VIS)).toBeVisible();
    +    });
    +
    +    await test.step('renders a metric viz for a STATS sum() query', async () => {
    +      await writeAndSubmitEsqlQuery(page, pageObjects, SUM_QUERY);
    +      await expect(page.testSubj.locator(METRIC_VIS)).toBeVisible();
    +    });
    +  });
    +
    +  test('should open the inline edit visualization flyout for an ES|QL chart', async ({
    +    page,
    +    pageObjects,
    +  }) => {
    +    await writeAndSubmitEsqlQuery(page, pageObjects, STATS_QUERY);
    +
    +    await test.step('open the edit visualization flyout', async () => {
    +      await page.testSubj.click('unifiedHistogramEditFlyoutVisualization');
    +      await expect(page.testSubj.locator(EDIT_FLYOUT_HEADER)).toBeVisible();
    +    });
    +
    +    await test.step('verify the inline configuration panel is interactive', async () => {
    +      await expect(page.testSubj.locator('inlineEditingFlyoutLabel')).toBeVisible();
    +    });
    +
    +    await test.step('close the flyout', async () => {
    +      await page.testSubj.click(CANCEL_FLYOUT_BUTTON);
    +      await expect(page.testSubj.locator(EDIT_FLYOUT_HEADER)).toBeHidden();
    +    });
    +  });
    +
    +  test('should save an ES|QL visualization to a new dashboard from Discover', async ({
    +    page,
    +    pageObjects,
    +  }) => {
    +    const visName = 'ES|QL Stats Vis - New Dashboard';
    +    tracker.track({ type: 'lens', title: visName });
    +    tracker.track({ type: 'dashboard' });
    +
    +    await writeAndSubmitEsqlQuery(page, pageObjects, STATS_QUERY);
    +
    +    await test.step('save the ES|QL visualization to a new dashboard', async () => {
    +      await pageObjects.discover.saveVisualizationToNewDashboard(visName);
    +    });
    +
    +    await test.step('verify Kibana navigated to the new dashboard with the panel', async () => {
    +      await pageObjects.dashboard.waitForRenderComplete();
    +      expect(await pageObjects.dashboard.getPanelCount()).toBe(1);
    +    });
    +  });
    +
    +  test('should edit, explore in Discover, and copy an ES|QL panel from a dashboard', async ({
    +    page,
    +    pageObjects,
    +  }) => {
    +    const dashboardName = 'ES|QL Panel Actions Dashboard';
    +    const visName = 'ES|QL Panel Actions Vis';
    +    tracker.track({ type: 'lens', title: visName });
    +    tracker.track({ type: 'dashboard', title: dashboardName });
    +
    +    await test.step('save an ES|QL chart to a new dashboard', async () => {
    +      await writeAndSubmitEsqlQuery(page, pageObjects, STATS_QUERY);
    +      await pageObjects.discover.saveVisualizationToNewDashboard(visName);
    +      await pageObjects.dashboard.waitForRenderComplete();
    +      await pageObjects.dashboard.saveDashboard(dashboardName);
    +    });
    +
    +    await test.step('edit the panel inline from the dashboard', async () => {
    +      // Saving an ES|QL viz from Discover lands the dashboard already in edit mode,
    +      // so only switch when the Edit button is actually present (view mode).
    +      await pageObjects.dashboard.ensureEditMode();
    +      await pageObjects.dashboard.clickPanelAction(PANEL_ACTION_EDIT, visName);
    +      await expect(page.testSubj.locator(EDIT_FLYOUT_HEADER)).toBeVisible();
    +      await page.testSubj.click(CANCEL_FLYOUT_BUTTON);
    +      await expect(page.testSubj.locator(EDIT_FLYOUT_HEADER)).toBeHidden();
    +    });
    +
    +    await test.step('explore the panel in Discover and verify the ES|QL query is preserved', async () => {
    +      const newPagePromise = page.context().waitForEvent('page');
    +      await pageObjects.dashboard.clickPanelAction(
    +        'embeddablePanelAction-ACTION_OPEN_IN_DISCOVER',
    +        visName
    +      );
    +      const discoverPage = await newPagePromise;
    +      await discoverPage.waitForLoadState();
    +      await expect(discoverPage.getByTestId(ESQL_EDITOR)).toContainText('kibana_sample_data_logs');
    +      await discoverPage.close();
    +    });
    +
    +    await test.step('copy the panel to a new dashboard', async () => {
    +      await pageObjects.dashboard.clickPanelAction(
    +        'embeddablePanelAction-copyToDashboard',
    +        visName
    +      );
    +      const copyModal = page.locator('[role="dialog"][aria-labelledby="copyToDashboardTitle"]');
    +      await expect(copyModal).toBeVisible();
    +      await page.locator('label[for="new-dashboard-option"]').click();
    +      await expect(page.testSubj.locator('confirmCopyToButton')).toBeEnabled();
    +      await page.testSubj.click('confirmCopyToButton');
    +
    +      await pageObjects.dashboard.waitForRenderComplete();
    +      expect(await pageObjects.dashboard.getPanelCount()).toBe(1);
    +    });
    +  });
    +
    +  test('should restrict sidebar fields and grid columns to KEEP-listed fields', async ({
    +    page,
    +    pageObjects,
    +  }) => {
    +    await writeAndSubmitEsqlQuery(page, pageObjects, KEEP_QUERY);
    +
    +    await test.step('verify only KEEP-listed fields are present in the sidebar', async () => {
    +      await pageObjects.discover.expectSelectedSidebarFieldsToEqual(KEPT_FIELDS);
    +    });
    +
    +    await test.step('verify the data grid only renders KEEP-listed columns', async () => {
    +      const headerText = await pageObjects.discover.getDocHeader();
    +      for (const field of KEPT_FIELDS) {
    +        expect(headerText).toContain(field);
    +      }
    +      for (const field of DROPPED_FIELDS) {
    +        expect(headerText).not.toContain(field);
    +      }
    +    });
    +  });
    +
    +  test('should embed a saved ES|QL Discover session on a dashboard and interact with its table', async ({
    +    page,
    +    pageObjects,
    +  }) => {
    +    const savedSearchName = 'ES|QL Saved Search';
    +    tracker.track({ type: 'search', title: savedSearchName });
    +
    +    await test.step('write a KEEP ES|QL query and save the Discover session', async () => {
    +      await writeAndSubmitEsqlQuery(page, pageObjects, KEEP_QUERY);
    +      await pageObjects.discover.expectSelectedSidebarFieldsToEqual(KEPT_FIELDS);
    +      await pageObjects.discover.saveSearch(savedSearchName);
    +    });
    +
    +    await test.step('add the saved ES|QL session as a panel on a new dashboard', async () => {
    +      await pageObjects.dashboard.openNewDashboard();
    +      await pageObjects.dashboard.addPanelFromLibrary(savedSearchName);
    +      await pageObjects.dashboard.waitForRenderComplete();
    +      expect(await pageObjects.dashboard.getPanelCount()).toBe(1);
    +    });
    +
    +    await test.step('verify the embedded panel renders the KEEP-listed columns', async () => {
    +      // `addPanelFromLibrary` strips spaces/dashes when waiting for the heading.
    +      const panelHeading = page.testSubj.locator(
    +        `embeddablePanelHeading-${savedSearchName.replace(/[- ]/g, '')}`
    +      );
    +      await expect(panelHeading).toBeVisible();
    +
    +      const headerText = await pageObjects.discover.getDocHeader();
    +      for (const field of KEPT_FIELDS) {
    +        expect(headerText).toContain(field);
    +      }
    +    });
    +
    +    await test.step('interact with the embedded table by opening the first-row doc viewer', async () => {
    +      // Expanding a row proves the embedded saved-search grid is fully
    +      // interactive end-to-end: rows are rendered, the expand action
    +      // surfaces, and the document-viewer flyout opens for the row.
    +      await openAndWaitForDocViewerFlyout(page, { rowIndex: 0 });
    +      await closeDocViewerFlyout(page);
    +    });
    +  });
    +
    +  test('should save an ES|QL bar histogram to a dashboard and edit it inline', async ({
    +    page,
    +    pageObjects,
    +  }) => {
    +    const dashboardName = 'ES|QL Histogram Dashboard';
    +    const visName = 'ES|QL Histogram Vis';
    +    tracker.track({ type: 'lens', title: visName });
    +    tracker.track({ type: 'dashboard', title: dashboardName });
    +
    +    await writeAndSubmitEsqlQuery(page, pageObjects, HISTOGRAM_QUERY);
    +
    +    await test.step('verify a histogram (xy chart) is rendered for the ES|QL query', async () => {
    +      await pageObjects.discover.expectXYVisChartVisible();
    +    });
    +
    +    await test.step('save the histogram to a new dashboard', async () => {
    +      await pageObjects.discover.saveVisualizationToNewDashboard(visName);
    +      await pageObjects.dashboard.waitForRenderComplete();
    +      await pageObjects.dashboard.saveDashboard(dashboardName);
    +    });
    +
    +    await test.step('edit the saved panel inline from the dashboard', async () => {
    +      // Saving an ES|QL viz from Discover lands the dashboard already in edit mode,
    +      // so only switch when the Edit button is actually present.
    +      await pageObjects.dashboard.ensureEditMode();
    +      await pageObjects.dashboard.clickPanelAction(PANEL_ACTION_EDIT, visName);
    +      await expect(page.testSubj.locator(EDIT_FLYOUT_HEADER)).toBeVisible();
    +      await page.testSubj.click(CANCEL_FLYOUT_BUTTON);
    +      await expect(page.testSubj.locator(EDIT_FLYOUT_HEADER)).toBeHidden();
    +    });
    +  });
    +});
    
209c12d77d1b

[Scout] Update test config manifests (#270528)

https://github.com/elastic/kibanaKibana MachineMay 23, 2026Fixed in 8.19.16via release-tag
1 file changed · +130 18
  • x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/.meta/ui/standard.json+130 18 modified
    @@ -1,5 +1,5 @@
     {
    -  "sha1": "50b5dfba62ac64690de376d8bc22cd52d75b4e93",
    +  "sha1": "d17dc6f8487721a630d816876f7496bd8d02b0cc",
       "tests": [
         {
           "id": "709f30ae5fb788f-05a1663c9e84f0e",
    @@ -221,7 +221,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 71,
    +        "line": 74,
             "column": 7
           }
         },
    @@ -235,7 +235,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 77,
    +        "line": 80,
             "column": 7
           }
         },
    @@ -249,7 +249,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 100,
    +        "line": 103,
             "column": 7
           }
         },
    @@ -263,7 +263,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 108,
    +        "line": 111,
             "column": 7
           }
         },
    @@ -277,7 +277,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 112,
    +        "line": 115,
             "column": 7
           }
         },
    @@ -291,7 +291,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 118,
    +        "line": 121,
             "column": 7
           }
         },
    @@ -305,7 +305,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 137,
    +        "line": 141,
             "column": 7
           }
         },
    @@ -319,7 +319,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 144,
    +        "line": 148,
             "column": 7
           }
         },
    @@ -333,7 +333,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 152,
    +        "line": 156,
             "column": 7
           }
         },
    @@ -347,7 +347,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 161,
    +        "line": 165,
             "column": 7
           }
         },
    @@ -361,7 +361,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 177,
    +        "line": 181,
             "column": 7
           }
         },
    @@ -375,7 +375,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 192,
    +        "line": 196,
             "column": 7
           }
         },
    @@ -389,7 +389,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 202,
    +        "line": 206,
             "column": 7
           }
         },
    @@ -403,7 +403,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 218,
    +        "line": 222,
             "column": 7
           }
         },
    @@ -417,7 +417,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 226,
    +        "line": 233,
             "column": 7
           }
         },
    @@ -431,7 +431,7 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 233,
    +        "line": 240,
             "column": 7
           }
         },
    @@ -445,7 +445,119 @@
           ],
           "location": {
             "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/discovery.spec.ts",
    -        "line": 243,
    +        "line": 250,
    +        "column": 7
    +      }
    +    },
    +    {
    +      "id": "7b807c730155cc2-79d3cf13237ee2d",
    +      "title": "Discover ES|QL should switch the query bar to ES|QL and display the default sample query",
    +      "expectedStatus": "passed",
    +      "tags": [
    +        "@local-stateful-classic",
    +        "@cloud-stateful-classic"
    +      ],
    +      "location": {
    +        "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/esql.spec.ts",
    +        "line": 155,
    +        "column": 7
    +      }
    +    },
    +    {
    +      "id": "7b807c730155cc2-75f10f82cef9fbf",
    +      "title": "Discover ES|QL should display a metric visualization for ES|QL STATS queries (count and sum)",
    +      "expectedStatus": "passed",
    +      "tags": [
    +        "@local-stateful-classic",
    +        "@cloud-stateful-classic"
    +      ],
    +      "location": {
    +        "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/esql.spec.ts",
    +        "line": 174,
    +        "column": 7
    +      }
    +    },
    +    {
    +      "id": "7b807c730155cc2-1081a7397d42c88",
    +      "title": "Discover ES|QL should open the inline edit visualization flyout for an ES|QL chart",
    +      "expectedStatus": "passed",
    +      "tags": [
    +        "@local-stateful-classic",
    +        "@cloud-stateful-classic"
    +      ],
    +      "location": {
    +        "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/esql.spec.ts",
    +        "line": 189,
    +        "column": 7
    +      }
    +    },
    +    {
    +      "id": "7b807c730155cc2-4b67968e425b300",
    +      "title": "Discover ES|QL should save an ES|QL visualization to a new dashboard from Discover",
    +      "expectedStatus": "passed",
    +      "tags": [
    +        "@local-stateful-classic",
    +        "@cloud-stateful-classic"
    +      ],
    +      "location": {
    +        "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/esql.spec.ts",
    +        "line": 210,
    +        "column": 7
    +      }
    +    },
    +    {
    +      "id": "7b807c730155cc2-dc0e7a2eb6e2df6",
    +      "title": "Discover ES|QL should edit, explore in Discover, and copy an ES|QL panel from a dashboard",
    +      "expectedStatus": "passed",
    +      "tags": [
    +        "@local-stateful-classic",
    +        "@cloud-stateful-classic"
    +      ],
    +      "location": {
    +        "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/esql.spec.ts",
    +        "line": 230,
    +        "column": 7
    +      }
    +    },
    +    {
    +      "id": "7b807c730155cc2-f21c2c6ffe20444",
    +      "title": "Discover ES|QL should restrict sidebar fields and grid columns to KEEP-listed fields",
    +      "expectedStatus": "passed",
    +      "tags": [
    +        "@local-stateful-classic",
    +        "@cloud-stateful-classic"
    +      ],
    +      "location": {
    +        "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/esql.spec.ts",
    +        "line": 284,
    +        "column": 7
    +      }
    +    },
    +    {
    +      "id": "7b807c730155cc2-8bbbfff9121d488",
    +      "title": "Discover ES|QL should embed a saved ES|QL Discover session on a dashboard and interact with its table",
    +      "expectedStatus": "passed",
    +      "tags": [
    +        "@local-stateful-classic",
    +        "@cloud-stateful-classic"
    +      ],
    +      "location": {
    +        "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/esql.spec.ts",
    +        "line": 305,
    +        "column": 7
    +      }
    +    },
    +    {
    +      "id": "7b807c730155cc2-ab47176f22b71bd",
    +      "title": "Discover ES|QL should save an ES|QL bar histogram to a dashboard and edit it inline",
    +      "expectedStatus": "passed",
    +      "tags": [
    +        "@local-stateful-classic",
    +        "@cloud-stateful-classic"
    +      ],
    +      "location": {
    +        "file": "x-pack/platform/packages/private/kbn-scout-release-testing/test/scout/ui/tests/discover/esql.spec.ts",
    +        "line": 347,
             "column": 7
           }
         },
    
db396449a69d

[9.3] [Scout] Don't create test track if no load candidates are identified (#270584) (#270599)

https://github.com/elastic/kibanaKibana MachineMay 22, 2026Fixed in 9.3.5via release-tag
3 files changed · +29 4
  • src/platform/packages/shared/kbn-scout/src/cli/create_test_tracks.ts+15 3 modified
    @@ -549,19 +549,31 @@ export const createTestTracks: Command<void> = {
                 : [...new Set(loads.map((load) => load.config.server.configSet))];
     
             // Each server config set gets its own track
    -        return configSets.map((configSet) => {
    +        return configSets.flatMap((configSet): TestTrack[] => {
               log.info(
                 `Building test track for test target '${target.tag}' with server config set '${configSet}'`
               );
    +
    +          const enabledLoads = loads.filter(
    +            (load) => load.enabled && load.config.server.configSet === configSet
    +          );
    +
    +          if (enabledLoads.length === 0) {
    +            log.warning(
    +              `No enabled test loads found for test target '${target.tag}' and server config set '${configSet}'`
    +            );
    +            return [];
    +          }
    +
               const track = buildTrack(
                 Math.max(minimumRuntime, runtimeTarget),
                 estimatedLaneSetupDuration,
                 target,
    -            loads.filter((load) => load.enabled && load.config.server.configSet === configSet),
    +            enabledLoads,
                 log
               );
               track.metadata.server = { configSet };
    -          return track;
    +          return [track];
             });
           })
           .toArray();
    
  • src/platform/packages/shared/kbn-scout/src/execution/test_track.test.ts+10 0 modified
    @@ -19,6 +19,16 @@ describe('TestTrack', () => {
         expect(track.leastLoadedOpenLane).toBe(undefined);
       });
     
    +  it('should produce valid stats for an empty track specification', () => {
    +    const track = new TestTrack({ runtimeTarget: 10 });
    +    const spec = track.specification;
    +
    +    expect(spec.stats.lane.count).toBe(0);
    +    expect(spec.stats.lane.saturationPercent).toBe(0);
    +    expect(spec.stats.combinedRuntime.target).toBe(0);
    +    expect(spec.lanes).toEqual([]);
    +  });
    +
       it('closes the lane when one load fills it entirely', () => {
         const track = new TestTrack({ runtimeTarget: 10 });
     
    
  • src/platform/packages/shared/kbn-scout/src/execution/test_track.ts+4 1 modified
    @@ -236,7 +236,10 @@ export class TestTrack {
           stats: {
             lane: {
               count: this.laneCount,
    -          saturationPercent: parseFloat(((expectedRuntime / provisionedRuntime) * 100).toFixed(2)),
    +          saturationPercent:
    +            provisionedRuntime !== 0
    +              ? parseFloat(((expectedRuntime / provisionedRuntime) * 100).toFixed(2))
    +              : 0,
               longestEstimate: longestLaneEstimate,
               shortestEstimate: shortestLaneEstimate,
             },
    

Vulnerability mechanics

No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.

References

1

News mentions

0

No linked articles in our index yet.