Merge pull request #34285 from backstage/rugvip/cimd-metadata-size-cap
auth-backend: cap CIMD metadata response size
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Limit the size of fetched client ID metadata documents to prevent oversized responses from being accepted.
|
||||
@@ -328,6 +328,29 @@ describe('CimdClient', () => {
|
||||
).rejects.toThrow('Invalid client metadata document');
|
||||
});
|
||||
|
||||
it('should throw for oversized JSON without content-length', async () => {
|
||||
const oversizedMetadata = {
|
||||
client_id: 'https://example.com/oauth-metadata.json',
|
||||
client_name: 'x'.repeat(64 * 1024),
|
||||
redirect_uris: ['http://localhost:8080/callback'],
|
||||
};
|
||||
const fetchMock = jest.spyOn(global, 'fetch').mockResolvedValue(
|
||||
new Response(JSON.stringify(oversizedMetadata), {
|
||||
headers: { 'content-type': 'application/json' },
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
await expect(
|
||||
fetchCimdMetadata({
|
||||
clientId: 'https://example.com/oauth-metadata.json',
|
||||
}),
|
||||
).rejects.toThrow('Client metadata document too large');
|
||||
} finally {
|
||||
fetchMock.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw for client_id mismatch', async () => {
|
||||
const mismatchedMetadata = {
|
||||
client_id: 'https://different.com/metadata',
|
||||
|
||||
@@ -187,6 +187,24 @@ function validateMetadata(
|
||||
}
|
||||
}
|
||||
|
||||
async function readCappedResponseBody(response: Response): Promise<string> {
|
||||
if (!response.body) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const chunks: Buffer[] = [];
|
||||
let received = 0;
|
||||
for await (const chunk of response.body) {
|
||||
received += chunk.byteLength;
|
||||
if (received > MAX_RESPONSE_BYTES) {
|
||||
throw new InputError('Client metadata document too large');
|
||||
}
|
||||
chunks.push(Buffer.from(chunk));
|
||||
}
|
||||
|
||||
return Buffer.concat(chunks).toString('utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and validates a CIMD metadata document.
|
||||
* @throws InputError if fetching or validation fails
|
||||
@@ -228,8 +246,12 @@ export async function fetchCimdMetadata(opts: {
|
||||
|
||||
let metadata: CimdMetadata;
|
||||
try {
|
||||
metadata = await response.json();
|
||||
} catch {
|
||||
const responseBody = await readCappedResponseBody(response);
|
||||
metadata = JSON.parse(responseBody) as CimdMetadata;
|
||||
} catch (error) {
|
||||
if (isError(error) && error.name === 'InputError') {
|
||||
throw error;
|
||||
}
|
||||
throw new InputError('Invalid client metadata document');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user