From eca91232bf0890a0358ec363ceeb87035d03a041 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 30 Dec 2025 14:55:08 -0800 Subject: [PATCH] improvement(tools): added input validation to jira service management routes (#2642) --- apps/sim/app/api/tools/jsm/approvals/route.ts | 39 ++++++++++++++++--- apps/sim/app/api/tools/jsm/comment/route.ts | 12 ++++++ apps/sim/app/api/tools/jsm/comments/route.ts | 12 ++++++ apps/sim/app/api/tools/jsm/customers/route.ts | 12 ++++++ .../app/api/tools/jsm/organization/route.ts | 28 +++++++++++++ .../app/api/tools/jsm/organizations/route.ts | 12 ++++++ .../app/api/tools/jsm/participants/route.ts | 23 +++++++++++ apps/sim/app/api/tools/jsm/queues/route.ts | 12 ++++++ apps/sim/app/api/tools/jsm/request/route.ts | 25 ++++++++++++ apps/sim/app/api/tools/jsm/requests/route.ts | 14 +++++++ .../app/api/tools/jsm/requesttypes/route.ts | 12 ++++++ .../app/api/tools/jsm/servicedesks/route.ts | 7 ++++ apps/sim/app/api/tools/jsm/sla/route.ts | 12 ++++++ .../sim/app/api/tools/jsm/transition/route.ts | 21 ++++++++++ .../app/api/tools/jsm/transitions/route.ts | 12 ++++++ 15 files changed, 247 insertions(+), 6 deletions(-) diff --git a/apps/sim/app/api/tools/jsm/approvals/route.ts b/apps/sim/app/api/tools/jsm/approvals/route.ts index 280cd4f2b..896e5bab9 100644 --- a/apps/sim/app/api/tools/jsm/approvals/route.ts +++ b/apps/sim/app/api/tools/jsm/approvals/route.ts @@ -1,11 +1,20 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { + validateAlphanumericId, + validateEnum, + validateJiraCloudId, + validateJiraIssueKey, +} from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' const logger = createLogger('JsmApprovalsAPI') +const VALID_ACTIONS = ['get', 'answer'] as const +const VALID_DECISIONS = ['approve', 'decline'] as const + export async function POST(request: Request) { try { const body = await request.json() @@ -41,7 +50,23 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Action is required' }, { status: 400 }) } + const actionValidation = validateEnum(action, VALID_ACTIONS, 'action') + if (!actionValidation.isValid) { + return NextResponse.json({ error: actionValidation.error }, { status: 400 }) + } + const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey') + if (!issueIdOrKeyValidation.isValid) { + return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) if (action === 'get') { @@ -91,12 +116,14 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Approval ID is required' }, { status: 400 }) } - if (!decision || !['approve', 'decline'].includes(decision)) { - logger.error('Invalid or missing decision in request') - return NextResponse.json( - { error: 'Decision is required and must be "approve" or "decline"' }, - { status: 400 } - ) + const approvalIdValidation = validateAlphanumericId(approvalId, 'approvalId') + if (!approvalIdValidation.isValid) { + return NextResponse.json({ error: approvalIdValidation.error }, { status: 400 }) + } + + const decisionValidation = validateEnum(decision, VALID_DECISIONS, 'decision') + if (!decisionValidation.isValid) { + return NextResponse.json({ error: decisionValidation.error }, { status: 400 }) } const url = `${baseUrl}/request/${issueIdOrKey}/approval/${approvalId}` diff --git a/apps/sim/app/api/tools/jsm/comment/route.ts b/apps/sim/app/api/tools/jsm/comment/route.ts index 1fa11e223..b93b90ecc 100644 --- a/apps/sim/app/api/tools/jsm/comment/route.ts +++ b/apps/sim/app/api/tools/jsm/comment/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -38,6 +39,17 @@ export async function POST(request: Request) { } const cloudId = providedCloudId || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey') + if (!issueIdOrKeyValidation.isValid) { + return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const url = `${baseUrl}/request/${issueIdOrKey}/comment` diff --git a/apps/sim/app/api/tools/jsm/comments/route.ts b/apps/sim/app/api/tools/jsm/comments/route.ts index 06cd79023..429857f7c 100644 --- a/apps/sim/app/api/tools/jsm/comments/route.ts +++ b/apps/sim/app/api/tools/jsm/comments/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -36,6 +37,17 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey') + if (!issueIdOrKeyValidation.isValid) { + return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const params = new URLSearchParams() diff --git a/apps/sim/app/api/tools/jsm/customers/route.ts b/apps/sim/app/api/tools/jsm/customers/route.ts index 857f4c3b5..de735337d 100644 --- a/apps/sim/app/api/tools/jsm/customers/route.ts +++ b/apps/sim/app/api/tools/jsm/customers/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -36,6 +37,17 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId') + if (!serviceDeskIdValidation.isValid) { + return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const parsedEmails = emails diff --git a/apps/sim/app/api/tools/jsm/organization/route.ts b/apps/sim/app/api/tools/jsm/organization/route.ts index a3a3cc7c5..168a76be8 100644 --- a/apps/sim/app/api/tools/jsm/organization/route.ts +++ b/apps/sim/app/api/tools/jsm/organization/route.ts @@ -1,11 +1,18 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { + validateAlphanumericId, + validateEnum, + validateJiraCloudId, +} from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' const logger = createLogger('JsmOrganizationAPI') +const VALID_ACTIONS = ['create', 'add_to_service_desk'] as const + export async function POST(request: Request) { try { const body = await request.json() @@ -34,7 +41,18 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Action is required' }, { status: 400 }) } + const actionValidation = validateEnum(action, VALID_ACTIONS, 'action') + if (!actionValidation.isValid) { + return NextResponse.json({ error: actionValidation.error }, { status: 400 }) + } + const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) if (action === 'create') { @@ -90,6 +108,16 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Organization ID is required' }, { status: 400 }) } + const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId') + if (!serviceDeskIdValidation.isValid) { + return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 }) + } + + const organizationIdValidation = validateAlphanumericId(organizationId, 'organizationId') + if (!organizationIdValidation.isValid) { + return NextResponse.json({ error: organizationIdValidation.error }, { status: 400 }) + } + const url = `${baseUrl}/servicedesk/${serviceDeskId}/organization` logger.info('Adding organization to service desk:', { serviceDeskId, organizationId }) diff --git a/apps/sim/app/api/tools/jsm/organizations/route.ts b/apps/sim/app/api/tools/jsm/organizations/route.ts index 7fc53d6a6..b6b0f0400 100644 --- a/apps/sim/app/api/tools/jsm/organizations/route.ts +++ b/apps/sim/app/api/tools/jsm/organizations/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -27,6 +28,17 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId') + if (!serviceDeskIdValidation.isValid) { + return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const params = new URLSearchParams() diff --git a/apps/sim/app/api/tools/jsm/participants/route.ts b/apps/sim/app/api/tools/jsm/participants/route.ts index 2e5e6de9b..649edd29b 100644 --- a/apps/sim/app/api/tools/jsm/participants/route.ts +++ b/apps/sim/app/api/tools/jsm/participants/route.ts @@ -1,11 +1,18 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { + validateEnum, + validateJiraCloudId, + validateJiraIssueKey, +} from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' const logger = createLogger('JsmParticipantsAPI') +const VALID_ACTIONS = ['get', 'add'] as const + export async function POST(request: Request) { try { const body = await request.json() @@ -40,7 +47,23 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Action is required' }, { status: 400 }) } + const actionValidation = validateEnum(action, VALID_ACTIONS, 'action') + if (!actionValidation.isValid) { + return NextResponse.json({ error: actionValidation.error }, { status: 400 }) + } + const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey') + if (!issueIdOrKeyValidation.isValid) { + return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) if (action === 'get') { diff --git a/apps/sim/app/api/tools/jsm/queues/route.ts b/apps/sim/app/api/tools/jsm/queues/route.ts index c69bea140..691de5d9c 100644 --- a/apps/sim/app/api/tools/jsm/queues/route.ts +++ b/apps/sim/app/api/tools/jsm/queues/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -35,6 +36,17 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId') + if (!serviceDeskIdValidation.isValid) { + return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const params = new URLSearchParams() diff --git a/apps/sim/app/api/tools/jsm/request/route.ts b/apps/sim/app/api/tools/jsm/request/route.ts index 972fa478f..86f18b5be 100644 --- a/apps/sim/app/api/tools/jsm/request/route.ts +++ b/apps/sim/app/api/tools/jsm/request/route.ts @@ -1,5 +1,10 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { + validateAlphanumericId, + validateJiraCloudId, + validateJiraIssueKey, +} from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -33,11 +38,26 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const isCreateOperation = serviceDeskId && requestTypeId && summary if (isCreateOperation) { + const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId') + if (!serviceDeskIdValidation.isValid) { + return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 }) + } + + const requestTypeIdValidation = validateAlphanumericId(requestTypeId, 'requestTypeId') + if (!requestTypeIdValidation.isValid) { + return NextResponse.json({ error: requestTypeIdValidation.error }, { status: 400 }) + } const url = `${baseUrl}/request` logger.info('Creating request at:', url) @@ -95,6 +115,11 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 }) } + const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey') + if (!issueIdOrKeyValidation.isValid) { + return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 }) + } + const url = `${baseUrl}/request/${issueIdOrKey}` logger.info('Fetching request from:', url) diff --git a/apps/sim/app/api/tools/jsm/requests/route.ts b/apps/sim/app/api/tools/jsm/requests/route.ts index fd854257c..e12ea98fe 100644 --- a/apps/sim/app/api/tools/jsm/requests/route.ts +++ b/apps/sim/app/api/tools/jsm/requests/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -32,6 +33,19 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + if (serviceDeskId) { + const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId') + if (!serviceDeskIdValidation.isValid) { + return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 }) + } + } + const baseUrl = getJsmApiBaseUrl(cloudId) const params = new URLSearchParams() diff --git a/apps/sim/app/api/tools/jsm/requesttypes/route.ts b/apps/sim/app/api/tools/jsm/requesttypes/route.ts index e10cbfb9e..d7e9bdb28 100644 --- a/apps/sim/app/api/tools/jsm/requesttypes/route.ts +++ b/apps/sim/app/api/tools/jsm/requesttypes/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -27,6 +28,17 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId') + if (!serviceDeskIdValidation.isValid) { + return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const params = new URLSearchParams() diff --git a/apps/sim/app/api/tools/jsm/servicedesks/route.ts b/apps/sim/app/api/tools/jsm/servicedesks/route.ts index 4fa01aded..bab91d5bb 100644 --- a/apps/sim/app/api/tools/jsm/servicedesks/route.ts +++ b/apps/sim/app/api/tools/jsm/servicedesks/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateJiraCloudId } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -22,6 +23,12 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const params = new URLSearchParams() diff --git a/apps/sim/app/api/tools/jsm/sla/route.ts b/apps/sim/app/api/tools/jsm/sla/route.ts index be02d053e..ac826afcf 100644 --- a/apps/sim/app/api/tools/jsm/sla/route.ts +++ b/apps/sim/app/api/tools/jsm/sla/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -27,6 +28,17 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey') + if (!issueIdOrKeyValidation.isValid) { + return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const params = new URLSearchParams() diff --git a/apps/sim/app/api/tools/jsm/transition/route.ts b/apps/sim/app/api/tools/jsm/transition/route.ts index 1f128450c..5d17a7ca4 100644 --- a/apps/sim/app/api/tools/jsm/transition/route.ts +++ b/apps/sim/app/api/tools/jsm/transition/route.ts @@ -1,5 +1,10 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { + validateAlphanumericId, + validateJiraCloudId, + validateJiraIssueKey, +} from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -38,6 +43,22 @@ export async function POST(request: Request) { } const cloudId = providedCloudId || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey') + if (!issueIdOrKeyValidation.isValid) { + return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 }) + } + + const transitionIdValidation = validateAlphanumericId(transitionId, 'transitionId') + if (!transitionIdValidation.isValid) { + return NextResponse.json({ error: transitionIdValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const url = `${baseUrl}/request/${issueIdOrKey}/transition` diff --git a/apps/sim/app/api/tools/jsm/transitions/route.ts b/apps/sim/app/api/tools/jsm/transitions/route.ts index dc381bc87..a9242177b 100644 --- a/apps/sim/app/api/tools/jsm/transitions/route.ts +++ b/apps/sim/app/api/tools/jsm/transitions/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' +import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' export const dynamic = 'force-dynamic' @@ -27,6 +28,17 @@ export async function POST(request: Request) { } const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) + + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') + if (!cloudIdValidation.isValid) { + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) + } + + const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey') + if (!issueIdOrKeyValidation.isValid) { + return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 }) + } + const baseUrl = getJsmApiBaseUrl(cloudId) const url = `${baseUrl}/request/${issueIdOrKey}/transition`