VYPR
Moderate severityNVD Advisory· Published Apr 24, 2025· Updated Apr 24, 2025

Webapp DoS via malicious retrospective post in Playbooks

CVE-2025-41395

Description

Mattermost versions 10.4.x <= 10.4.2, 10.5.x <= 10.5.0, 9.11.x <= 9.11.10 fail to properly validate the props used by the RetrospectivePost custom post type in the Playbooks plugin, which allows an attacker to create a specially crafted post with maliciously crafted props and cause a denial of service (DoS) of the web app for all users.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/mattermost/mattermost-plugin-playbooksGo
>= 2.0.0
github.com/mattermost/mattermost/server/v8Go
< 8.0.0-20250218121836-2b5275d871368.0.0-20250218121836-2b5275d87136
github.com/mattermost/mattermost/server/v8Go
>= 10.4.0
github.com/mattermost/mattermost/server/v8Go
>= 10.5.0
github.com/mattermost/mattermost/server/v8Go
>= 9.11.0
github.com/mattermost/mattermost-plugin-playbooksGo
< 1.41.01.41.0

Affected products

1

Patches

2
2b5275d87136

chore: Update Playbooks plugin to v2.1.1 (#29996)

https://github.com/mattermost/mattermostChristopher SpellerFeb 18, 2025via ghsa
1 file changed · +1 1
  • server/Makefile+1 1 modified
    @@ -141,7 +141,7 @@ PLUGIN_PACKAGES += mattermost-plugin-gitlab-v1.9.1
     PLUGIN_PACKAGES += mattermost-plugin-jira-v4.2.0
     # We need to prepackage both versions of playbooks and install the correct one based on the server license. See MM-60025.
     PLUGIN_PACKAGES += mattermost-plugin-playbooks-v1.40.0
    -PLUGIN_PACKAGES += mattermost-plugin-playbooks-v2.0.1
    +PLUGIN_PACKAGES += mattermost-plugin-playbooks-v2.1.1
     PLUGIN_PACKAGES += mattermost-plugin-nps-v1.3.3
     PLUGIN_PACKAGES += mattermost-plugin-servicenow-v2.3.4
     PLUGIN_PACKAGES += mattermost-plugin-zoom-v1.8.0
    
4c823090e281

Validate props in retrospective post (aider assisted) (#1973)

2 files changed · +51 4
  • webapp/src/components/retrospective_post.tsx+12 2 modified
    @@ -10,6 +10,13 @@ import {Metric, MetricType} from 'src/types/playbook';
     
     import {RunMetricData} from 'src/types/playbook_run';
     
    +import {
    +    isArrayOf,
    +    isMetric,
    +    isMetricData,
    +    safeJSONParse,
    +} from 'src/utils';
    +
     import {ClockOutline, DollarSign, PoundSign} from './backstage/playbook_edit/styles';
     import {metricToString} from './backstage/playbook_edit/metrics/shared';
     
    @@ -33,8 +40,11 @@ export const RetrospectivePost = (props: Props) => {
     
         const mdText = (text: string) => messageHtmlToComponent(formatText(text, markdownOptions), true, messageHtmlToComponentOptions);
     
    -    const metricsConfigs: Array<Metric> = JSON.parse(props.post.props.metricsConfigs);
    -    const metricsData: Array<RunMetricData> = JSON.parse(props.post.props.metricsData);
    +    const parsedMetricsConfigs = safeJSONParse<unknown>(props.post.props?.metricsConfigs);
    +    const parsedMetricsData = safeJSONParse<unknown>(props.post.props?.metricsData);
    +
    +    const metricsConfigs: Array<Metric> = isArrayOf(parsedMetricsConfigs, isMetric) ? parsedMetricsConfigs : [];
    +    const metricsData: Array<RunMetricData> = isArrayOf(parsedMetricsData, isMetricData) ? parsedMetricsData : [];
     
         return (
             <>
    
  • webapp/src/utils.ts+39 2 modified
    @@ -1,7 +1,7 @@
     import {useState} from 'react';
     
    -import {PlaybookRun} from 'src/types/playbook_run';
    -import {Checklist} from 'src/types/playbook';
    +import {PlaybookRun, RunMetricData} from 'src/types/playbook_run';
    +import {Checklist, Metric} from 'src/types/playbook';
     
     let idCounter = 0;
     
    @@ -132,3 +132,40 @@ export function runCallsSlashCommand(command: string, channelId: string, teamId:
             },
         }, window.origin);
     }
    +
    +export function safeJSONParse<T>(value: unknown): T | null {
    +    if (!value || typeof value !== 'string') {
    +        return null;
    +    }
    +
    +    try {
    +        return JSON.parse(value) as T;
    +    } catch {
    +        return null;
    +    }
    +}
    +
    +export function isArrayOf<T>(value: unknown, typeGuard: (value: unknown) => value is T): value is T[] {
    +    return Array.isArray(value) && value.every(typeGuard);
    +}
    +
    +export function isMetric(value: unknown): value is Metric {
    +    if (!value || typeof value !== 'object') {
    +        return false;
    +    }
    +
    +    const metric = value as Metric;
    +    return typeof metric.id === 'string' &&
    +           typeof metric.title === 'string' &&
    +           typeof metric.type === 'string';
    +}
    +
    +export function isMetricData(value: unknown): value is RunMetricData {
    +    if (!value || typeof value !== 'object') {
    +        return false;
    +    }
    +
    +    const metricData = value as RunMetricData;
    +    return typeof metricData.metric_config_id === 'string' &&
    +           (typeof metricData.value === 'string' || typeof metricData.value === 'number');
    +}
    

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

5

News mentions

0

No linked articles in our index yet.