feat: expose run id in STS client user-agent (#1774)

* feat: expose run id in STS client user-agent

Closes #483.
This commit modifies the user-agent string so that it includes the
GITHUB_RUN_ID and the GITHUB_RUN_ATTEMPT, in the format typically used
by the SDK. User agent strings are logged to CloudTrail, allowing users
to correlate CloudTrail events with GHA runs. We took this approach
instead of logging the ACCESS_KEY_ID as suggested in the issue to avoid
logging sensitive information.

* feat: add github_action to ua string
This commit is contained in:
Tom Keller
2026-05-12 16:18:34 -07:00
committed by GitHub
parent ef734cca81
commit 29d1be3027
5 changed files with 5877 additions and 10943 deletions
+77 -3
View File
@@ -1218,9 +1218,7 @@ describe('Configure AWS Credentials', {}, () => {
}),
);
// biome-ignore lint/suspicious/noExplicitAny: any required to mock private method
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials').mockRejectedValue(
new Error('network glitch'),
);
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials').mockRejectedValue(new Error('network glitch'));
await run();
expect(core.setFailed).toHaveBeenCalled();
expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('Retry'));
@@ -1258,4 +1256,80 @@ describe('Configure AWS Credentials', {}, () => {
expect(core.setFailed).not.toHaveBeenCalled();
});
});
describe('User-Agent enrichment', {}, () => {
async function getCustomUserAgent(): Promise<unknown> {
const { CredentialsClient: FreshClient } = await import('../src/CredentialsClient');
const client = new FreshClient({ region: 'fake-region-1', roleChaining: false });
// biome-ignore lint/suspicious/noExplicitAny: SDK config readout
return (client.stsClient.config as any).customUserAgent;
}
it('includes action, run_id and attempt tokens when env vars are valid', async () => {
vi.resetModules();
process.env.GITHUB_ACTION = '__run_2';
process.env.GITHUB_RUN_ID = '16412345678';
process.env.GITHUB_RUN_ATTEMPT = '1';
const ua = await getCustomUserAgent();
expect(ua).toEqual([
['configure-aws-credentials-for-github-actions'],
['md', 'action#__run_2'],
['md', 'run_id#16412345678'],
['md', 'attempt#1'],
]);
expect(core.warning).not.toHaveBeenCalled();
});
it('omits tokens when env vars are unset, with no warning', async () => {
vi.resetModules();
delete process.env.GITHUB_ACTION;
const ua = await getCustomUserAgent();
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
expect(core.warning).not.toHaveBeenCalled();
});
it('warns and skips when env vars are malformed', async () => {
vi.resetModules();
process.env.GITHUB_ACTION = '$(curl evil)';
process.env.GITHUB_RUN_ID = '$(curl evil)';
process.env.GITHUB_RUN_ATTEMPT = '1; rm -rf /';
const ua = await getCustomUserAgent();
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
expect(core.warning).toHaveBeenCalledWith('GITHUB_ACTION has unexpected format; omitting from User-Agent');
expect(core.warning).toHaveBeenCalledWith('GITHUB_RUN_ID has unexpected format; omitting from User-Agent');
expect(core.warning).toHaveBeenCalledWith('GITHUB_RUN_ATTEMPT has unexpected format; omitting from User-Agent');
expect(core.warning).toHaveBeenCalledTimes(3);
});
it('warns and skips when env vars exceed the length bound', async () => {
vi.resetModules();
process.env.GITHUB_ACTION = 'a'.repeat(200);
process.env.GITHUB_RUN_ID = '1'.repeat(50);
process.env.GITHUB_RUN_ATTEMPT = '1'.repeat(50);
const ua = await getCustomUserAgent();
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
expect(core.warning).toHaveBeenCalledTimes(3);
});
it('rejects GITHUB_ACTION containing whitespace or other characters', async () => {
vi.resetModules();
process.env.GITHUB_ACTION = 'has space';
const ua = await getCustomUserAgent();
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
expect(core.warning).toHaveBeenCalledWith('GITHUB_ACTION has unexpected format; omitting from User-Agent');
});
it('sets AWS_EXECUTION_ENV to GitHubActions when unset', async () => {
vi.resetModules();
await import('../src/CredentialsClient');
expect(process.env.AWS_EXECUTION_ENV).toBe('GitHubActions');
});
it('preserves a pre-existing AWS_EXECUTION_ENV value', async () => {
vi.resetModules();
process.env.AWS_EXECUTION_ENV = 'CustomRunner';
await import('../src/CredentialsClient');
expect(process.env.AWS_EXECUTION_ENV).toBe('CustomRunner');
});
});
});