diff --git a/packages/config-loader/src/lib/reader.test.ts b/packages/config-loader/src/lib/reader.test.ts index 3e6269b8d9..6ccceef60d 100644 --- a/packages/config-loader/src/lib/reader.test.ts +++ b/packages/config-loader/src/lib/reader.test.ts @@ -106,6 +106,24 @@ describe('readConfigFile', () => { }); }); + it('should not allow keys adjacent to secrets', async () => { + const readFile = memoryFiles({ + './app-config.yaml': 'app: { extraKey: 3, $file: "./my-secret" }', + }); + const readSecret = jest.fn().mockResolvedValue('secret'); + + const config = readConfigFile('./app-config.yaml', { + ...mockContext, + readFile, + readSecret: readSecret as ReadSecretFunc, + }); + + await expect(config).rejects.toThrow( + "Secret key '$file' has adjacent keys at .app", + ); + expect(readSecret).not.toHaveBeenCalled(); + }); + it('should read deprecated secrets', async () => { const readFile = memoryFiles({ './app-config.yaml': 'app: { $secret: { file: "./my-secret" } }', diff --git a/packages/config-loader/src/lib/reader.ts b/packages/config-loader/src/lib/reader.ts index af4c63c83d..8eadae0fe0 100644 --- a/packages/config-loader/src/lib/reader.ts +++ b/packages/config-loader/src/lib/reader.ts @@ -74,8 +74,15 @@ export async function readConfigFile( } } + // Check if there's any key that starts with a '$', in that case we treat + // this entire object as a secret. const [secretKey] = Object.keys(obj).filter(key => key.startsWith('$')); if (secretKey) { + if (Object.keys(obj).length !== 1) { + throw new Error( + `Secret key '${secretKey}' has adjacent keys at ${path}`, + ); + } try { return await ctx.readSecret(path, { [secretKey.slice(1)]: obj[secretKey],