From 82408b69eb3de29d3ce5320ab6ccce6f12262722 Mon Sep 17 00:00:00 2001 From: Tom Keller Date: Mon, 31 Aug 2026 12:00:11 -0700 Subject: [PATCH] fix: reject newlines in names and values when writing profile files If the profile file writing was enabled, we emitted newlines into the file verbatim, permitting injecting arbitrary profiles into the file. Writing now fails instead. --- src/profileManager.ts | 7 +++++++ test/profileManager.test.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) 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',