Skip to content

Commit 551a105

Browse files
committed
fix(core): normalize external ref host policy checks
1 parent a8b74ea commit 551a105

2 files changed

Lines changed: 22 additions & 2 deletions

File tree

packages/core-internal/src/validators/externalRefResolver.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ function ipv6LiteralFromHost(host: string): string | undefined {
115115
return host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : undefined;
116116
}
117117

118+
function normalizeHostForPolicy(host: string): string {
119+
return ipv6LiteralFromHost(host) === undefined ? host.replace(/\.+$/, '') : host;
120+
}
121+
118122
function isBlockedIPv6Literal(host: string): boolean {
119123
if (host === '::' || host === '::1' || host.startsWith('::ffff:')) {
120124
return true;
@@ -139,10 +143,10 @@ function assertHostAllowed(url: URL, options: ResolvedOptions): void {
139143
);
140144
}
141145

142-
const host = url.hostname.toLowerCase();
146+
const host = normalizeHostForPolicy(url.hostname.toLowerCase());
143147

144148
if (options.allowlist) {
145-
const allowlist = new Set(options.allowlist.map(allowedHost => allowedHost.toLowerCase()));
149+
const allowlist = new Set(options.allowlist.map(allowedHost => normalizeHostForPolicy(allowedHost.toLowerCase())));
146150
if (!allowlist.has(host)) {
147151
throw new Error(`Refusing to dereference "${url.href}": host "${host}" is not in the allowlist.`);
148152
}

packages/core-internal/test/validators/externalRefResolver.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,20 @@ describe('resolveExternalSchemaRefs', () => {
7474
});
7575
});
7676

77+
it('matches allowlist hosts with a trailing DNS root dot', async () => {
78+
const schema: JsonSchemaType = { $ref: 'https://schemas.example.com./forecast.json' };
79+
const fetchImpl = fetchStub({
80+
'https://schemas.example.com./forecast.json': { type: 'string' }
81+
});
82+
83+
const resolved = await resolveExternalSchemaRefs(schema, { allowlist: ['schemas.example.com'], fetch: fetchImpl });
84+
85+
expect(resolved).toEqual({
86+
$ref: '#/$defs/__externalRef_0',
87+
$defs: { __externalRef_0: { type: 'string' } }
88+
});
89+
});
90+
7791
it.each([
7892
['AJV', () => new AjvJsonSchemaValidator()],
7993
['CfWorker', () => new CfWorkerJsonSchemaValidator()]
@@ -191,9 +205,11 @@ describe('resolveExternalSchemaRefs', () => {
191205

192206
it.each([
193207
'https://localhost/x.json',
208+
'https://localhost./x.json',
194209
'https://127.0.0.1/x.json',
195210
'https://10.0.0.5/x.json',
196211
'https://169.254.169.254/x.json',
212+
'https://metadata.google.internal./computeMetadata/v1',
197213
'https://[::1]/x.json',
198214
'https://[fd00::1]/x.json',
199215
'https://[fe80::1]/x.json',

0 commit comments

Comments
 (0)