Skip to content

Commit 193f06f

Browse files
authored
fix(aws): add validateAwsRegion to all AWS route schemas to prevent SSRF (#4250)
* fix(aws): add validateAwsRegion to all AWS route schemas to prevent SSRF * fix(validation): add mx and eu-isoe prefixes to validateAwsRegion regex * test(validation): add mx-central-1, eu-isoe-west-1, and us-iso-west-1 region test cases * fix(aws): eliminate double validateAwsRegion call and fix regex alternation order - Replace double-call .refine() pattern with single-call + static message across all 61 AWS routes - Reorder regex alternation to put longer prefixes first (eu-isoe before eu, us-isob/us-iso/us-gov before us) for engine-agnostic correctness
1 parent 34cfc26 commit 193f06f

63 files changed

Lines changed: 450 additions & 63 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/app/api/tools/cloudwatch/describe-alarms/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ import { toError } from '@sim/utils/errors'
99
import { type NextRequest, NextResponse } from 'next/server'
1010
import { z } from 'zod'
1111
import { checkInternalAuth } from '@/lib/auth/hybrid'
12+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
1213
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1314

1415
const logger = createLogger('CloudWatchDescribeAlarms')
1516

1617
const DescribeAlarmsSchema = z.object({
17-
region: z.string().min(1, 'AWS region is required'),
18+
region: z
19+
.string()
20+
.min(1, 'AWS region is required')
21+
.refine((v) => validateAwsRegion(v).isValid, {
22+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
23+
}),
1824
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
1925
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
2026
alarmNamePrefix: z.string().optional(),

apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@ import { toError } from '@sim/utils/errors'
44
import { type NextRequest, NextResponse } from 'next/server'
55
import { z } from 'zod'
66
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
7+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
78
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
89
import { createCloudWatchLogsClient } from '@/app/api/tools/cloudwatch/utils'
910

1011
const logger = createLogger('CloudWatchDescribeLogGroups')
1112

1213
const DescribeLogGroupsSchema = z.object({
13-
region: z.string().min(1, 'AWS region is required'),
14+
region: z
15+
.string()
16+
.min(1, 'AWS region is required')
17+
.refine((v) => validateAwsRegion(v).isValid, {
18+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
19+
}),
1420
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
1521
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
1622
prefix: z.string().optional(),

apps/sim/app/api/tools/cloudwatch/describe-log-streams/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@ import { toError } from '@sim/utils/errors'
33
import { type NextRequest, NextResponse } from 'next/server'
44
import { z } from 'zod'
55
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
6+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
67
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
78
import { createCloudWatchLogsClient, describeLogStreams } from '@/app/api/tools/cloudwatch/utils'
89

910
const logger = createLogger('CloudWatchDescribeLogStreams')
1011

1112
const DescribeLogStreamsSchema = z.object({
12-
region: z.string().min(1, 'AWS region is required'),
13+
region: z
14+
.string()
15+
.min(1, 'AWS region is required')
16+
.refine((v) => validateAwsRegion(v).isValid, {
17+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
18+
}),
1319
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
1420
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
1521
logGroupName: z.string().min(1, 'Log group name is required'),

apps/sim/app/api/tools/cloudwatch/get-log-events/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@ import { toError } from '@sim/utils/errors'
33
import { type NextRequest, NextResponse } from 'next/server'
44
import { z } from 'zod'
55
import { checkInternalAuth } from '@/lib/auth/hybrid'
6+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
67
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
78
import { createCloudWatchLogsClient, getLogEvents } from '@/app/api/tools/cloudwatch/utils'
89

910
const logger = createLogger('CloudWatchGetLogEvents')
1011

1112
const GetLogEventsSchema = z.object({
12-
region: z.string().min(1, 'AWS region is required'),
13+
region: z
14+
.string()
15+
.min(1, 'AWS region is required')
16+
.refine((v) => validateAwsRegion(v).isValid, {
17+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
18+
}),
1319
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
1420
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
1521
logGroupName: z.string().min(1, 'Log group name is required'),

apps/sim/app/api/tools/cloudwatch/get-metric-statistics/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ import { toError } from '@sim/utils/errors'
44
import { type NextRequest, NextResponse } from 'next/server'
55
import { z } from 'zod'
66
import { checkInternalAuth } from '@/lib/auth/hybrid'
7+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
78
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
89

910
const logger = createLogger('CloudWatchGetMetricStatistics')
1011

1112
const GetMetricStatisticsSchema = z.object({
12-
region: z.string().min(1, 'AWS region is required'),
13+
region: z
14+
.string()
15+
.min(1, 'AWS region is required')
16+
.refine((v) => validateAwsRegion(v).isValid, {
17+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
18+
}),
1319
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
1420
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
1521
namespace: z.string().min(1, 'Namespace is required'),

apps/sim/app/api/tools/cloudwatch/list-metrics/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ import { toError } from '@sim/utils/errors'
44
import { type NextRequest, NextResponse } from 'next/server'
55
import { z } from 'zod'
66
import { checkInternalAuth } from '@/lib/auth/hybrid'
7+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
78
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
89

910
const logger = createLogger('CloudWatchListMetrics')
1011

1112
const ListMetricsSchema = z.object({
12-
region: z.string().min(1, 'AWS region is required'),
13+
region: z
14+
.string()
15+
.min(1, 'AWS region is required')
16+
.refine((v) => validateAwsRegion(v).isValid, {
17+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
18+
}),
1319
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
1420
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
1521
namespace: z.string().optional(),

apps/sim/app/api/tools/cloudwatch/put-metric-data/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { toError } from '@sim/utils/errors'
88
import { type NextRequest, NextResponse } from 'next/server'
99
import { z } from 'zod'
1010
import { checkInternalAuth } from '@/lib/auth/hybrid'
11+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
1112
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1213

1314
const logger = createLogger('CloudWatchPutMetricData')
@@ -43,7 +44,12 @@ const VALID_UNITS = [
4344
] as const
4445

4546
const PutMetricDataSchema = z.object({
46-
region: z.string().min(1, 'AWS region is required'),
47+
region: z
48+
.string()
49+
.min(1, 'AWS region is required')
50+
.refine((v) => validateAwsRegion(v).isValid, {
51+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
52+
}),
4753
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
4854
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
4955
namespace: z.string().min(1, 'Namespace is required'),

apps/sim/app/api/tools/cloudwatch/query-logs/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@ import { toError } from '@sim/utils/errors'
44
import { type NextRequest, NextResponse } from 'next/server'
55
import { z } from 'zod'
66
import { checkInternalAuth } from '@/lib/auth/hybrid'
7+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
78
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
89
import { createCloudWatchLogsClient, pollQueryResults } from '@/app/api/tools/cloudwatch/utils'
910

1011
const logger = createLogger('CloudWatchQueryLogs')
1112

1213
const QueryLogsSchema = z.object({
13-
region: z.string().min(1, 'AWS region is required'),
14+
region: z
15+
.string()
16+
.min(1, 'AWS region is required')
17+
.refine((v) => validateAwsRegion(v).isValid, {
18+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
19+
}),
1420
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
1521
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
1622
logGroupNames: z.array(z.string().min(1)).min(1, 'At least one log group name is required'),

apps/sim/app/api/tools/dynamodb/delete/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@ import { toError } from '@sim/utils/errors'
33
import { type NextRequest, NextResponse } from 'next/server'
44
import { z } from 'zod'
55
import { checkInternalAuth } from '@/lib/auth/hybrid'
6+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
67
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
78
import { createDynamoDBClient, deleteItem } from '@/app/api/tools/dynamodb/utils'
89

910
const logger = createLogger('DynamoDBDeleteAPI')
1011

1112
const DeleteSchema = z.object({
12-
region: z.string().min(1, 'AWS region is required'),
13+
region: z
14+
.string()
15+
.min(1, 'AWS region is required')
16+
.refine((v) => validateAwsRegion(v).isValid, {
17+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
18+
}),
1319
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
1420
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
1521
tableName: z.string().min(1, 'Table name is required'),

apps/sim/app/api/tools/dynamodb/get/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@ import { toError } from '@sim/utils/errors'
33
import { type NextRequest, NextResponse } from 'next/server'
44
import { z } from 'zod'
55
import { checkInternalAuth } from '@/lib/auth/hybrid'
6+
import { validateAwsRegion } from '@/lib/core/security/input-validation'
67
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
78
import { createDynamoDBClient, getItem } from '@/app/api/tools/dynamodb/utils'
89

910
const logger = createLogger('DynamoDBGetAPI')
1011

1112
const GetSchema = z.object({
12-
region: z.string().min(1, 'AWS region is required'),
13+
region: z
14+
.string()
15+
.min(1, 'AWS region is required')
16+
.refine((v) => validateAwsRegion(v).isValid, {
17+
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
18+
}),
1319
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
1420
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
1521
tableName: z.string().min(1, 'Table name is required'),

0 commit comments

Comments
 (0)