1
0
Fork 0
mirror of synced 2026-06-05 15:08:19 +00:00

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.
This commit is contained in:
Tom Keller 2026-05-12 12:05:07 -07:00
commit f8d102ad7d
5 changed files with 8552 additions and 13618 deletions

View file

@ -431,12 +431,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----------
The following npm packages may be included in this product:
The following npm package may be included in this product:
- @aws-crypto/crc32@5.2.0
- @aws-crypto/util@5.2.0
These packages each contain the following license:
This package contains the following license:
Apache License
Version 2.0, January 2004
@ -644,7 +643,7 @@ Apache License
The following npm packages may be included in this product:
- @aws-sdk/client-sts@3.1045.0
- @aws-sdk/client-sts@3.1044.0
- @aws-sdk/util-user-agent-browser@3.972.10
- @aws-sdk/util-user-agent-node@3.973.24
- @smithy/middleware-retry@4.5.7
@ -868,7 +867,7 @@ The following npm packages may be included in this product:
- @aws-sdk/middleware-sdk-s3@3.972.37
- @aws-sdk/middleware-user-agent@3.972.38
- @aws-sdk/signature-v4-multi-region@3.996.25
- @smithy/core@3.24.1
- @smithy/core@3.23.17
- @smithy/invalid-dependency@4.2.14
- @smithy/middleware-serde@4.2.20
- @smithy/protocol-http@5.3.14
@ -1710,8 +1709,8 @@ The following npm packages may be included in this product:
- @smithy/middleware-content-length@4.2.14
- @smithy/middleware-endpoint@4.4.32
- @smithy/middleware-stack@4.2.14
- @smithy/node-http-handler@4.7.1
- @smithy/property-provider@4.3.1
- @smithy/node-http-handler@4.6.1
- @smithy/property-provider@4.2.14
- @smithy/shared-ini-file-loader@4.4.9
- @smithy/signature-v4@5.3.14
- @smithy/util-base64@4.3.2

16252
dist/index.js generated vendored

File diff suppressed because it is too large Load diff

View file

@ -3,10 +3,12 @@ import { STSClient } from '@aws-sdk/client-sts';
import type { AwsCredentialIdentity } from '@aws-sdk/types';
import { NodeHttpHandler } from '@smithy/node-http-handler';
import { ProxyAgent } from 'proxy-agent';
import { errorMessage, getCallerIdentity } from './helpers';
import { buildCustomUserAgent, errorMessage, getCallerIdentity } from './helpers';
import { ProxyResolver } from './ProxyResolver';
const USER_AGENT = 'configure-aws-credentials-for-github-actions';
if (!process.env.AWS_EXECUTION_ENV) {
process.env.AWS_EXECUTION_ENV = 'GitHubActions';
}
export interface CredentialsClientProps {
region?: string;
@ -51,16 +53,12 @@ export class CredentialsClient {
public get stsClient(): STSClient {
if (!this._stsClient || this.roleChaining) {
const config = { customUserAgent: USER_AGENT } as {
customUserAgent: string;
region?: string;
endpoint?: string;
requestHandler?: NodeHttpHandler;
};
if (this.region !== undefined) config.region = this.region;
if (this.stsEndpoint !== undefined) config.endpoint = this.stsEndpoint;
if (this.requestHandler !== undefined) config.requestHandler = this.requestHandler;
this._stsClient = new STSClient(config);
this._stsClient = new STSClient({
customUserAgent: buildCustomUserAgent(),
...(this.region !== undefined && { region: this.region }),
...(this.stsEndpoint !== undefined && { endpoint: this.stsEndpoint }),
...(this.requestHandler !== undefined && { requestHandler: this.requestHandler }),
});
}
return this._stsClient;
}

View file

@ -1,11 +1,36 @@
import * as core from '@actions/core';
import type { Credentials, STSClient } from '@aws-sdk/client-sts';
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
import type { UserAgent } from '@smithy/types';
import type { CredentialsClient } from './CredentialsClient';
const MAX_TAG_VALUE_LENGTH = 256;
const SANITIZATION_CHARACTER = '_';
const SPECIAL_CHARS_REGEX = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+/;
const USER_AGENT_PREFIX = 'configure-aws-credentials-for-github-actions';
const RUN_ID_PATTERN = /^[0-9]{1,20}$/;
const ATTEMPT_PATTERN = /^[0-9]{1,10}$/;
export function buildCustomUserAgent(): UserAgent {
const tokens: UserAgent = [[USER_AGENT_PREFIX]];
const runId = process.env.GITHUB_RUN_ID;
const attempt = process.env.GITHUB_RUN_ATTEMPT;
if (runId !== undefined) {
if (RUN_ID_PATTERN.test(runId)) {
tokens.push(['md', `run_id#${runId}`]);
} else {
core.warning('GITHUB_RUN_ID has unexpected format; omitting from User-Agent');
}
}
if (attempt !== undefined) {
if (ATTEMPT_PATTERN.test(attempt)) {
tokens.push(['md', `attempt#${attempt}`]);
} else {
core.warning('GITHUB_RUN_ATTEMPT has unexpected format; omitting from User-Agent');
}
}
return tokens;
}
export function translateEnvVariables() {
const envVars = [

View file

@ -1258,4 +1258,70 @@ 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 run_id and attempt tokens when env vars are valid', async () => {
vi.resetModules();
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', '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();
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_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_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(2);
});
it('warns and skips when env vars exceed the length bound', async () => {
vi.resetModules();
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(2);
});
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');
});
});
});