From 2c8106c433c4add4b43e2e2b2f5c5c4887b17314 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Wed, 30 Aug 2023 12:17:50 -0700 Subject: [PATCH] Merge pull request from GHSA-j5g3-5c8r-7qfx * failing tests * implement fix + tests passing --- packages/server/src/ApolloServer.ts | 2 +- .../server/src/__tests__/ApolloServer.test.ts | 32 +++++++++++++++++++ packages/server/src/determineApolloConfig.ts | 32 +++++++++++++++++-- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/server/src/ApolloServer.ts b/packages/server/src/ApolloServer.ts index 02f01a676eb..3ecf63b46fb 100644 --- a/packages/server/src/ApolloServer.ts +++ b/packages/server/src/ApolloServer.ts @@ -221,7 +221,7 @@ export class ApolloServer { this.logger = config.logger ?? defaultLogger(); - const apolloConfig = determineApolloConfig(config.apollo); + const apolloConfig = determineApolloConfig(config.apollo, this.logger); const isDev = nodeEnv !== 'production'; diff --git a/packages/server/src/__tests__/ApolloServer.test.ts b/packages/server/src/__tests__/ApolloServer.test.ts index d48a0cf0e6a..224948ec966 100644 --- a/packages/server/src/__tests__/ApolloServer.test.ts +++ b/packages/server/src/__tests__/ApolloServer.test.ts @@ -177,6 +177,38 @@ describe('ApolloServer construction', () => { await server.stop(); }); }); + + it('throws when an API key is not a valid header value', () => { + expect(() => { + new ApolloServer({ + typeDefs, + resolvers, + apollo: { + key: 'bar▒baz▒', + }, + }); + }).toThrowErrorMatchingInlineSnapshot( + `"The API key provided to Apollo Server contains characters which are invalid as HTTP header values. The following characters found in the key are invalid: ▒, ▒. Valid header values may only contain ASCII visible characters. If you think there is an issue with your key, please contact Apollo support."`, + ); + }); + + it('trims whitespace from incoming API keys and logs a warning', () => { + const logger = mockLogger(); + expect(() => { + new ApolloServer({ + typeDefs, + resolvers, + apollo: { + key: 'barbaz\n', + }, + logger, + }); + }).not.toThrow(); + expect(logger.warn).toHaveBeenCalledWith( + 'The provided API key has unexpected leading or trailing whitespace. ' + + 'Apollo Server will trim the key value before use.', + ); + }); }); const failToStartPlugin: ApolloServerPlugin = { diff --git a/packages/server/src/determineApolloConfig.ts b/packages/server/src/determineApolloConfig.ts index 10f67928f60..20a91a53ea2 100644 --- a/packages/server/src/determineApolloConfig.ts +++ b/packages/server/src/determineApolloConfig.ts @@ -1,10 +1,12 @@ import { createHash } from '@apollo/utils.createhash'; import type { ApolloConfig, ApolloConfigInput } from './externalTypes/index.js'; +import type { Logger } from '@apollo/utils.logger'; // This function combines the `apollo` constructor argument and some environment // variables to come up with a full ApolloConfig. export function determineApolloConfig( input: ApolloConfigInput | undefined, + logger: Logger, ): ApolloConfig { const apolloConfig: ApolloConfig = {}; @@ -17,9 +19,21 @@ export function determineApolloConfig( // Determine key. if (input?.key) { - apolloConfig.key = input.key; + apolloConfig.key = input.key.trim(); } else if (APOLLO_KEY) { - apolloConfig.key = APOLLO_KEY; + apolloConfig.key = APOLLO_KEY.trim(); + } + if ((input?.key ?? APOLLO_KEY) !== apolloConfig.key) { + logger.warn( + 'The provided API key has unexpected leading or trailing whitespace. ' + + 'Apollo Server will trim the key value before use.', + ); + } + + // Assert API key is a valid header value, since it's going to be used as one + // throughout. + if (apolloConfig.key) { + assertValidHeaderValue(apolloConfig.key); } // Determine key hash. @@ -65,3 +79,17 @@ export function determineApolloConfig( return apolloConfig; } + +function assertValidHeaderValue(value: string) { + // Ref: node-fetch@2.x `Headers` validation + // https://github.com/node-fetch/node-fetch/blob/9b9d45881e5ca68757077726b3c0ecf8fdca1f29/src/headers.js#L18 + const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/g; + if (invalidHeaderCharRegex.test(value)) { + const invalidChars = value.match(invalidHeaderCharRegex)!; + throw new Error( + `The API key provided to Apollo Server contains characters which are invalid as HTTP header values. The following characters found in the key are invalid: ${invalidChars.join( + ', ', + )}. Valid header values may only contain ASCII visible characters. If you think there is an issue with your key, please contact Apollo support.`, + ); + } +}