-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathheader-utils.ts
More file actions
133 lines (116 loc) · 4.1 KB
/
Copy pathheader-utils.ts
File metadata and controls
133 lines (116 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import {
HEADER_ALLOWLIST_PREFIX as HEADER_ALLOWLIST_PREFIX_FROM_SCHEMA,
MAX_HEADER_ALLOWLIST_SIZE as MAX_HEADER_ALLOWLIST_SIZE_FROM_SCHEMA,
getHeaderRejectionReason,
} from '../../../schema/schemas/agent-env';
export const HEADER_ALLOWLIST_PREFIX = HEADER_ALLOWLIST_PREFIX_FROM_SCHEMA;
export const MAX_HEADER_ALLOWLIST_SIZE = MAX_HEADER_ALLOWLIST_SIZE_FROM_SCHEMA;
/**
* Normalize a header name according to AgentCore Runtime rules:
* - "Authorization" (case-insensitive) -> "Authorization"
* - Headers already starting with the AgentCore custom prefix
* (case-insensitive) -> canonical prefix + original suffix
* - Otherwise -> the input is returned unchanged. The allowlist now accepts any
* non-restricted HTTP header name (alphanumerics, hyphens, underscores), so
* we no longer auto-prepend the AgentCore custom prefix.
*/
export function normalizeHeaderName(input: string): string {
if (input.toLowerCase() === 'authorization') {
return 'Authorization';
}
if (input.toLowerCase().startsWith(HEADER_ALLOWLIST_PREFIX.toLowerCase())) {
return `${HEADER_ALLOWLIST_PREFIX}${input.slice(HEADER_ALLOWLIST_PREFIX.length)}`;
}
return input;
}
/**
* Parse a comma-separated string of header names, normalize each, and deduplicate
* (case-insensitive; first occurrence wins).
*/
export function parseAndNormalizeHeaders(input: string): string[] {
const headers = input
.split(',')
.map(s => s.trim())
.filter(Boolean)
.map(normalizeHeaderName);
const seen = new Set<string>();
const result: string[] = [];
for (const h of headers) {
const key = h.toLowerCase();
if (!seen.has(key)) {
seen.add(key);
result.push(h);
}
}
return result;
}
/**
* Validate a comma-separated list of header names for the allowlist.
* Empty/whitespace input is considered valid (field is optional).
*/
export function validateHeaderAllowlist(value: string): { success: boolean; error?: string } {
const trimmed = value.trim();
if (trimmed === '') {
return { success: true };
}
const rawNames = trimmed
.split(',')
.map(s => s.trim())
.filter(Boolean);
// Validate each header name against the allowlist rules (regex + restricted
// names + reserved prefixes).
for (const name of rawNames) {
const rejection = getHeaderRejectionReason(normalizeHeaderName(name));
if (rejection) {
return { success: false, error: rejection };
}
}
// Detect duplicates (case-insensitive, after normalization).
const headers = parseAndNormalizeHeaders(value);
const seen = new Set<string>();
for (const raw of rawNames) {
const key = normalizeHeaderName(raw).toLowerCase();
if (seen.has(key)) {
return {
success: false,
error: `Duplicate header (case-insensitive): "${raw}".`,
};
}
seen.add(key);
}
if (headers.length > MAX_HEADER_ALLOWLIST_SIZE) {
return {
success: false,
error: `Header allowlist cannot exceed ${MAX_HEADER_ALLOWLIST_SIZE} headers. Provided: ${headers.length}`,
};
}
return { success: true };
}
/**
* Parse a CLI --header flag value ("Key: Value" or "Key:Value") into a key-value pair.
* The header name is normalized according to AgentCore Runtime rules.
* Returns null if the format is invalid.
*/
export function parseHeaderFlag(raw: string): { name: string; value: string } | null {
const colonIndex = raw.indexOf(':');
if (colonIndex < 1) return null;
const name = raw.slice(0, colonIndex).trim();
const value = raw.slice(colonIndex + 1).trim();
if (!name) return null;
return { name: normalizeHeaderName(name), value };
}
/**
* Parse multiple --header flag values into a Record<string, string>.
* Normalizes header names and deduplicates (last value wins).
*/
export function parseHeaderFlags(rawHeaders: string[]): Record<string, string> {
const result: Record<string, string> = {};
for (const raw of rawHeaders) {
const parsed = parseHeaderFlag(raw);
if (!parsed) {
throw new Error(`Invalid header format: "${raw}". Expected "Header-Name: value" or "Header-Name:value".`);
}
result[parsed.name] = parsed.value;
}
return result;
}