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.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/mattermost/mattermost-plugin-playbooksGo | >= 2.0.0 | — |
github.com/mattermost/mattermost/server/v8Go | < 8.0.0-20250218121836-2b5275d87136 | 8.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.0 | 1.41.0 |
Affected products
1- Range: 10.4.0
Patches
22b5275d87136chore: Update Playbooks plugin to v2.1.1 (#29996)
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
4c823090e281Validate 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- github.com/advisories/GHSA-3g36-gf7c-75qwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-41395ghsaADVISORY
- github.com/mattermost/mattermost-plugin-playbooks/commit/4c823090e281cb9c0d5c17ee2e5db275117540d1ghsaWEB
- github.com/mattermost/mattermost/commit/2b5275d87136f07e016c8eca09a2f004b31afc8aghsaWEB
- mattermost.com/security-updatesghsaWEB
News mentions
0No linked articles in our index yet.