fix(mattermost): validate JSON payload fields + normalize callbackPath

- parseSlashCommandPayload JSON branch now validates required fields
  (token, team_id, channel_id, user_id, command) like the form-encoded
  branch, preventing runtime exceptions on malformed JSON payloads
- normalizeCallbackPath() ensures leading '/' to prevent malformed URLs
  like 'http://host:portapi/...' when callbackPath lacks a leading slash
- Applied in resolveSlashCommandConfig and resolveCallbackUrl

Addresses Codex review round 3 (P2 items).
This commit is contained in:
Echo 2026-02-15 01:36:43 -05:00 committed by Muhammed Mukhthar CM
parent a59b27f2e6
commit f0a0766d99
1 changed files with 38 additions and 3 deletions

View File

@ -301,7 +301,32 @@ export function parseSlashCommandPayload(
try {
if (contentType?.includes("application/json")) {
return JSON.parse(body) as MattermostSlashCommandPayload;
const parsed = JSON.parse(body) as Record<string, unknown>;
// Validate required fields (same checks as the form-encoded branch)
const token = typeof parsed.token === "string" ? parsed.token : "";
const teamId = typeof parsed.team_id === "string" ? parsed.team_id : "";
const channelId = typeof parsed.channel_id === "string" ? parsed.channel_id : "";
const userId = typeof parsed.user_id === "string" ? parsed.user_id : "";
const command = typeof parsed.command === "string" ? parsed.command : "";
if (!token || !teamId || !channelId || !userId || !command) {
return null;
}
return {
token,
team_id: teamId,
team_domain: typeof parsed.team_domain === "string" ? parsed.team_domain : undefined,
channel_id: channelId,
channel_name: typeof parsed.channel_name === "string" ? parsed.channel_name : undefined,
user_id: userId,
user_name: typeof parsed.user_name === "string" ? parsed.user_name : undefined,
command,
text: typeof parsed.text === "string" ? parsed.text : "",
trigger_id: typeof parsed.trigger_id === "string" ? parsed.trigger_id : undefined,
response_url: typeof parsed.response_url === "string" ? parsed.response_url : undefined,
};
}
// Default: application/x-www-form-urlencoded
@ -349,13 +374,23 @@ export function resolveCommandText(trigger: string, text: string): string {
const DEFAULT_CALLBACK_PATH = "/api/channels/mattermost/command";
/**
* Ensure the callback path starts with a leading `/` to prevent
* malformed URLs like `http://host:portapi/...`.
*/
function normalizeCallbackPath(path: string): string {
const trimmed = path.trim();
if (!trimmed) return DEFAULT_CALLBACK_PATH;
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
}
export function resolveSlashCommandConfig(
raw?: Partial<MattermostSlashCommandConfig>,
): MattermostSlashCommandConfig {
return {
native: raw?.native ?? "auto",
nativeSkills: raw?.nativeSkills ?? "auto",
callbackPath: raw?.callbackPath?.trim() || DEFAULT_CALLBACK_PATH,
callbackPath: normalizeCallbackPath(raw?.callbackPath ?? DEFAULT_CALLBACK_PATH),
callbackUrl: raw?.callbackUrl?.trim() || undefined,
};
}
@ -383,6 +418,6 @@ export function resolveCallbackUrl(params: {
return params.config.callbackUrl;
}
const host = params.gatewayHost || "localhost";
const path = params.config.callbackPath;
const path = normalizeCallbackPath(params.config.callbackPath);
return `http://${host}:${params.gatewayPort}${path}`;
}