Skip to content

Commit acd300d

Browse files
authored
fix(gateway): add missing validation for custom JWT claim values (#644)
matchValueString and matchValueStringList items lacked regex validation, allowing characters (e.g. / :) that the API rejects at deploy time. Also blocks 'client_id' as a reserved custom claim name (server-side business rule) and fixes stale mcp.json references in comments. Validation constraints sourced from the ClaimMatchValueType and CustomClaimValidationType API reference documentation. - matchValueString: added regex [A-Za-z0-9_.-]+ and max(255) - matchValueStringList items: same regex and max(255) per item - inboundTokenClaimName: added max(255) and reserved name blocklist - Updated stale mcp.json comment references to agentcore.json
1 parent e1e2bbf commit acd300d

4 files changed

Lines changed: 27 additions & 8 deletions

File tree

src/cli/operations/fetch-access/fetch-gateway-token.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ function resolveClientId(
206206
return envClientId;
207207
}
208208

209-
// Tier 3: allowedClients[0] from mcp.json (fallback)
209+
// Tier 3: allowedClients[0] from agentcore.json (fallback)
210210
if (jwtConfig.allowedClients && jwtConfig.allowedClients.length > 0) {
211211
return jwtConfig.allowedClients[0];
212212
}

src/cli/primitives/PolicyEnginePrimitive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export class PolicyEnginePrimitive extends BasePrimitive<AddPolicyEngineOptions,
143143
}
144144

145145
/**
146-
* Attach a policy engine to the specified gateways in mcp.json.
146+
* Attach a policy engine to the specified gateways in agentcore.json.
147147
*/
148148
async attachToGateways(engineName: string, gatewayNames: string[], mode: 'LOG_ONLY' | 'ENFORCE'): Promise<void> {
149149
if (gatewayNames.length === 0) return;

src/cli/tui/screens/mcp/__tests__/finishJwtConfig.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ describe('finishJwtConfig data mapping', () => {
5858
},
5959
],
6060
};
61-
// Schema accepts this structurally (both fields are optional at schema level)
62-
// but the TUI should map STRING_ARRAY to matchValueStringList, not matchValueString
61+
// 'admin,dev' contains a comma which violates the API-documented
62+
// pattern [A-Za-z0-9_.-]+ — schema now correctly rejects this
6363
const result = CustomJwtAuthorizerConfigSchema.safeParse(config);
64-
expect(result.success).toBe(true);
64+
expect(result.success).toBe(false);
6565
});
6666
});

src/schema/schemas/mcp.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,28 @@ const OidcDiscoveryUrlSchema = z
4646

4747
// ── Custom Claims Schemas (matches CFN CustomClaimValidationType) ──
4848

49+
// API-documented patterns (from ClaimMatchValueType and CustomClaimValidationType)
50+
const MATCH_VALUE_PATTERN = /^[A-Za-z0-9_.-]+$/;
51+
const CLAIM_NAME_PATTERN = /^[A-Za-z0-9_.:-]+$/;
52+
// Server-side reserved claim names (not regex-documented; API rejects these at deploy time)
53+
const RESERVED_CLAIM_NAMES = ['client_id'];
54+
4955
export const ClaimMatchOperatorSchema = z.enum(['EQUALS', 'CONTAINS', 'CONTAINS_ANY']);
5056
export type ClaimMatchOperator = z.infer<typeof ClaimMatchOperatorSchema>;
5157

5258
export const ClaimMatchValueSchema = z
5359
.object({
54-
matchValueString: z.string().min(1).optional(),
55-
matchValueStringList: z.array(z.string().min(1)).min(1).max(255).optional(),
60+
matchValueString: z
61+
.string()
62+
.min(1)
63+
.max(255)
64+
.regex(MATCH_VALUE_PATTERN, 'Match value must match [A-Za-z0-9_.-]+')
65+
.optional(),
66+
matchValueStringList: z
67+
.array(z.string().min(1).max(255).regex(MATCH_VALUE_PATTERN, 'Each match value must match [A-Za-z0-9_.-]+'))
68+
.min(1)
69+
.max(255)
70+
.optional(),
5671
})
5772
.refine(data => data.matchValueString !== undefined || data.matchValueStringList !== undefined, {
5873
message: 'Either matchValueString or matchValueStringList must be provided',
@@ -70,7 +85,11 @@ export const CustomClaimValidationSchema = z
7085
inboundTokenClaimName: z
7186
.string()
7287
.min(1)
73-
.regex(/^[A-Za-z0-9_.\-:]+$/, 'Claim name must match [A-Za-z0-9_.-:]+'),
88+
.max(255)
89+
.regex(CLAIM_NAME_PATTERN, 'Claim name must match [A-Za-z0-9_.-:]+')
90+
.refine(name => !RESERVED_CLAIM_NAMES.includes(name), {
91+
message: `Claim name cannot be a reserved name (${RESERVED_CLAIM_NAMES.join(', ')})`,
92+
}),
7493
inboundTokenClaimValueType: InboundTokenClaimValueTypeSchema,
7594
authorizingClaimMatchValue: z.object({
7695
claimMatchOperator: ClaimMatchOperatorSchema,

0 commit comments

Comments
 (0)