Severity Assessment
CVSS Assessment
| Metric |
v3.1 |
v4.0 |
| Score |
7.6 / 10.0 |
8.6 / 10.0 |
| Severity |
High |
High |
| Vector |
CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L |
CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N |
| Calculator |
CVSS v3.1 Calculator |
CVSS v4.0 Calculator |
Threat Model Alignment
Classification: security-specific
OpenClaw’s trust model treats Mattermost slash callbacks as a protected webhook surface: the docs require an explicit HTTPS callbackUrl, state that non-HTTPS derived callbacks must fail closed, and authenticate callback requests with Mattermost-issued command tokens on a plugin route. A cleartext fallback callback therefore is not an intended public capability or harmless operator convenience; it weakens the transport and token-auth boundary that guards command execution. That makes the finding security-specific rather than a generic hardening request.
Impact
When Mattermost native slash commands are enabled without an explicit HTTPS callback URL, OpenClaw auto-registers a plain-HTTP callback and then treats any captured slash-command token as valid for every registered command. An on-path attacker can replay that token to inject arbitrary /oc_* or native skill commands as a spoofed Mattermost sender.
Affected Component
Files: extensions/mattermost/src/mattermost/monitor-slash.ts:151-180, extensions/mattermost/src/mattermost/slash-commands.ts:540-572, extensions/mattermost/src/mattermost/slash-state.ts:94-117, extensions/mattermost/src/mattermost/slash-http.ts:243-255
// slash-commands.ts
export function resolveCallbackUrl(params: {...}): string {
if (params.config.callbackUrl) {
return params.config.callbackUrl;
}
...
return `http://${host}:${params.gatewayPort}${path}`;
}
// monitor-slash.ts
const slashCallbackUrl = resolveCallbackUrl({
config: slashConfig,
gatewayPort: slashGatewayPort,
gatewayHost: params.cfg.gateway?.customBindHost ?? undefined,
});
// slash-state.ts
const tokenSet = new Set(commandTokens);
const handler = createSlashCommandHttpHandler({ ..., commandTokens: tokenSet, ... });
// slash-http.ts
if (commandTokens.size === 0 || !commandTokens.has(payload.token)) {
sendJsonResponse(res, 401, { text: "Unauthorized: invalid command token." });
return;
}
const trigger = payload.command.replace(/^\//, "").trim();
const commandText = resolveCommandText(trigger, payload.text, triggerMap);
Technical Reproduction
- Configure Mattermost native slash commands, set
gateway.customBindHost to a host that the Mattermost server can reach, and omit channels.mattermost.commands.callbackUrl.
- Start OpenClaw.
registerMattermostMonitorSlashCommands() derives and registers a callback like http://gateway.example.com:18789/api/channels/mattermost/command.
- Invoke any legitimate slash command once from Mattermost while observing the callback traffic on the network path between Mattermost and OpenClaw.
- Capture the POST body token, then replay a forged request directly to
/api/channels/mattermost/command, for example:
token=<captured-token>&team_id=T1&channel_id=C1&user_id=<allowlisted-user>&user_name=owner&command=/oc_model&text=gpt-5.4
- Observe that
createSlashCommandHttpHandler() accepts the request because it only checks whether payload.token is in the per-account token set, then executes the attacker-controlled command, text, channel_id, and user_id values.
Demonstrated Impact
resolveCallbackUrl() emits http://... whenever operators rely on the default derived callback, so the reusable Mattermost command token is transported in cleartext. activateSlashCommands() then discards the trigger-to-token mapping and keeps only a Set<string> of valid tokens, and createSlashCommandHttpHandler() authorizes any callback whose token is present in that set. Because the callback route is intentionally registered as auth: "plugin", the attacker does not need gateway bearer auth once a single token is observed. A passive or active network attacker can therefore capture one slash-command callback, then forge arbitrary /oc_* or native skill command payloads under any sender/channel values that satisfy Mattermost channel policy.
Environment
Tested against 87eeab703465eb42481946b130b9122c3e0fb587 on current main, with the bundled Mattermost plugin, channels.mattermost.commands.native=true, a reachable non-loopback gateway.customBindHost, and no explicit channels.mattermost.commands.callbackUrl override.
Remediation Advice
Fail closed when the derived Mattermost slash-command callback is not HTTPS, and preserve per-command token bindings so each callback token only authorizes the trigger that Mattermost issued it for.
Severity Assessment
CVSS Assessment
Threat Model Alignment
Classification:
security-specificOpenClaw’s trust model treats Mattermost slash callbacks as a protected webhook surface: the docs require an explicit HTTPS
callbackUrl, state that non-HTTPS derived callbacks must fail closed, and authenticate callback requests with Mattermost-issued command tokens on apluginroute. A cleartext fallback callback therefore is not an intended public capability or harmless operator convenience; it weakens the transport and token-auth boundary that guards command execution. That makes the finding security-specific rather than a generic hardening request.Impact
When Mattermost native slash commands are enabled without an explicit HTTPS callback URL, OpenClaw auto-registers a plain-HTTP callback and then treats any captured slash-command token as valid for every registered command. An on-path attacker can replay that token to inject arbitrary
/oc_*or native skill commands as a spoofed Mattermost sender.Affected Component
Files:
extensions/mattermost/src/mattermost/monitor-slash.ts:151-180,extensions/mattermost/src/mattermost/slash-commands.ts:540-572,extensions/mattermost/src/mattermost/slash-state.ts:94-117,extensions/mattermost/src/mattermost/slash-http.ts:243-255Technical Reproduction
gateway.customBindHostto a host that the Mattermost server can reach, and omitchannels.mattermost.commands.callbackUrl.registerMattermostMonitorSlashCommands()derives and registers a callback likehttp://gateway.example.com:18789/api/channels/mattermost/command./api/channels/mattermost/command, for example:createSlashCommandHttpHandler()accepts the request because it only checks whetherpayload.tokenis in the per-account token set, then executes the attacker-controlledcommand,text,channel_id, anduser_idvalues.Demonstrated Impact
resolveCallbackUrl()emitshttp://...whenever operators rely on the default derived callback, so the reusable Mattermost command token is transported in cleartext.activateSlashCommands()then discards the trigger-to-token mapping and keeps only aSet<string>of valid tokens, andcreateSlashCommandHttpHandler()authorizes any callback whose token is present in that set. Because the callback route is intentionally registered asauth: "plugin", the attacker does not need gateway bearer auth once a single token is observed. A passive or active network attacker can therefore capture one slash-command callback, then forge arbitrary/oc_*or native skill command payloads under any sender/channel values that satisfy Mattermost channel policy.Environment
Tested against
87eeab703465eb42481946b130b9122c3e0fb587on currentmain, with the bundled Mattermost plugin,channels.mattermost.commands.native=true, a reachable non-loopbackgateway.customBindHost, and no explicitchannels.mattermost.commands.callbackUrloverride.Remediation Advice
Fail closed when the derived Mattermost slash-command callback is not HTTPS, and preserve per-command token bindings so each callback token only authorizes the trigger that Mattermost issued it for.