Skip to content

Commit bfac28c

Browse files
committed
fix(gateway): check only header names against the token rule
A query parameter name can hold any character once percent-encoded (RFC 3986), and the AgentCore API only limits it to 1-64 characters, so names such as api:key and auth[token] are valid. Apply the HTTP token rule only when the effective location is HEADER, including an unset location, and keep the length checks for query names.
1 parent e731168 commit bfac28c

2 files changed

Lines changed: 41 additions & 17 deletions

File tree

‎src/projectSchemas/gateway.test.ts‎

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,16 @@ describe("gateway custom validation", () => {
276276
},
277277
{ parameterName: "X-Pets-Key" },
278278
],
279+
[
280+
"a query parameter name with a colon",
281+
githubPassthrough,
282+
{ location: "QUERY_PARAMETER", parameterName: "api:key" },
283+
],
284+
[
285+
"a query parameter name with brackets",
286+
githubPassthrough,
287+
{ location: "QUERY_PARAMETER", parameterName: "auth[token]" },
288+
],
279289
])("accepts %s", (_case, target, placement) => {
280290
expect(
281291
targetIssues({ ...target, outboundAuth: { ...apiKeyAuth, ...placement } }),
@@ -295,16 +305,22 @@ describe("gateway custom validation", () => {
295305
"Too big: expected string to have <=64 characters",
296306
],
297307
[
298-
"a parameterName with a colon",
299-
{ parameterName: "Authorization: " },
308+
"a query parameter name over 64 characters",
309+
{ location: "QUERY_PARAMETER", parameterName: "x".repeat(65) },
310+
["outboundAuth", "parameterName"],
311+
"Too big: expected string to have <=64 characters",
312+
],
313+
[
314+
"a HEADER parameterName with a colon",
315+
{ location: "HEADER", parameterName: "Authorization: " },
300316
["outboundAuth", "parameterName"],
301-
"parameterName must be a header or query parameter name, with no spaces or colons.",
317+
"A HEADER API key's parameterName must be an HTTP header name, with no spaces or colons.",
302318
],
303319
[
304-
"a parameterName with spaces",
320+
"a parameterName with spaces when location defaults to HEADER",
305321
{ parameterName: "x api key" },
306322
["outboundAuth", "parameterName"],
307-
"parameterName must be a header or query parameter name, with no spaces or colons.",
323+
"A HEADER API key's parameterName must be an HTTP header name, with no spaces or colons.",
308324
],
309325
[
310326
"an empty API key prefix",

‎src/projectSchemas/gateway.ts‎

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ export const OutboundAuthTypeSchema = z.enum([
4848
"JWT_PASSTHROUGH",
4949
]);
5050
export type OutboundAuthType = z.infer<typeof OutboundAuthTypeSchema>;
51-
// An HTTP token (RFC 9110 tchar), also safe as a query parameter name. Rejects spaces and
52-
// colons, so "Authorization: " fails validation instead of the CloudFormation deploy.
53-
const API_KEY_PARAMETER_NAME_PATTERN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
51+
// An HTTP header name (RFC 9110 token). Rejects spaces and colons, so "Authorization: " fails
52+
// validation instead of the CloudFormation deploy. Query parameter names have no such rule: any
53+
// character can be percent-encoded (RFC 3986), so they're only length-checked.
54+
const HTTP_HEADER_NAME_PATTERN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
5455
export const OutboundAuthSchema = z
5556
.object({
5657
type: OutboundAuthTypeSchema.default("NONE"),
@@ -59,15 +60,7 @@ export const OutboundAuthSchema = z
5960
service: z.string().min(1).max(64).optional(),
6061
region: z.string().min(1).max(32).optional(),
6162
location: z.enum(["HEADER", "QUERY_PARAMETER"]).optional(),
62-
parameterName: z
63-
.string()
64-
.min(1)
65-
.max(64)
66-
.regex(
67-
API_KEY_PARAMETER_NAME_PATTERN,
68-
"parameterName must be a header or query parameter name, with no spaces or colons.",
69-
)
70-
.optional(),
63+
parameterName: z.string().min(1).max(64).optional(),
7164
prefix: z.string().min(1).max(64).optional(),
7265
})
7366
.strict();
@@ -465,6 +458,21 @@ export const AgentCoreGatewayTargetSchema = z
465458
path: ["outboundAuth", "parameterName"],
466459
});
467460
}
461+
// location defaults to HEADER, so an unset location is checked as a header name.
462+
const parameterName = data.outboundAuth?.parameterName;
463+
if (
464+
data.outboundAuth?.type === "API_KEY" &&
465+
data.outboundAuth.location !== "QUERY_PARAMETER" &&
466+
parameterName !== undefined &&
467+
!HTTP_HEADER_NAME_PATTERN.test(parameterName)
468+
) {
469+
ctx.addIssue({
470+
code: "custom",
471+
message:
472+
"A HEADER API key's parameterName must be an HTTP header name, with no spaces or colons.",
473+
path: ["outboundAuth", "parameterName"],
474+
});
475+
}
468476
}
469477
});
470478
export type AgentCoreGatewayTarget = z.infer<typeof AgentCoreGatewayTargetSchema>;

0 commit comments

Comments
 (0)