diff --git a/src/profileManager.ts b/src/profileManager.ts index 89e8ae6..f18cdbb 100644 --- a/src/profileManager.ts +++ b/src/profileManager.ts @@ -53,8 +53,15 @@ export function parseIni(iniData: string): Record export function stringifyIni(data: Record>): string { const sections: string[] = []; for (const [sectionName, sectionData] of Object.entries(data)) { + if (/[\r\n]/.test(sectionName)) { + throw new Error('INI section names must not contain newline characters'); + } const lines: string[] = [`[${sectionName}]`]; for (const [key, value] of Object.entries(sectionData)) { + // A newline in a key or value would inject arbitrary INI lines (e.g. credential_process). + if (/[\r\n]/.test(key) || /[\r\n]/.test(value)) { + throw new Error('INI keys and values must not contain newline characters'); + } lines.push(`${key} = ${value}`); } sections.push(lines.join('\n')); diff --git a/test/profileManager.test.ts b/test/profileManager.test.ts index b545840..2e0cbec 100644 --- a/test/profileManager.test.ts +++ b/test/profileManager.test.ts @@ -114,6 +114,22 @@ describe('Profile Manager', {}, () => { const result = stringifyIni({ dev: {} }); expect(result).toBe('[dev]\n'); }); + + it('rejects values containing newlines', {}, () => { + expect(() => + stringifyIni({ dev: { aws_session_token: 'token\n[injected]\ncredential_process = evil' } }), + ).toThrow('must not contain newline characters'); + }); + + it('rejects keys containing newlines', {}, () => { + expect(() => stringifyIni({ dev: { 'key\ninjected': 'val' } })).toThrow('must not contain newline characters'); + }); + + it('rejects section names containing newlines', {}, () => { + expect(() => stringifyIni({ 'dev\r\n[injected]': { key: 'val' } })).toThrow( + 'must not contain newline characters', + ); + }); }); describe('validateProfileName', {}, () => { @@ -423,6 +439,24 @@ describe('Profile Manager', {}, () => { expect(configParsed['profile dev'].region).toBe('us-east-1'); }); + it('refuses to write credentials containing newlines instead of injecting profiles', {}, () => { + expect(() => + writeProfileFiles( + 'dev', + { + AccessKeyId: 'AKIAIOSFODNN7EXAMPLE', + SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + SessionToken: 'token\n[injected]\ncredential_process = evil-command', + }, + 'us-east-1', + false, + ), + ).toThrow('must not contain newline characters'); + + const credsPath = getProfileFilePaths().credentials; + expect(fs.existsSync(credsPath)).toBe(false); + }); + it('uses correct section naming for default profile', {}, () => { writeProfileFiles( 'default',