ReviewOS

stacks/ts-cloud

S3Client ignores AWS_PROFILE — always defaults to 'default'

#99
Closed glennmichael123 opened this 2 months ago ·
2 months ago

Bug

S3Client constructor accepts an optional profile parameter but no caller ever passes it. Every instantiation in the deploy code only passes region:

const s3 = new S3Client(region)  // profile defaults to 'default'

The constructor defaults to 'default' when no profile is provided:

constructor(region: string = 'us-east-1', profile?: string) {
  this.profile = profile || 'default'

This means resolveProfileCredentialsSync() skips env vars when AWS_PROFILE is set to anything other than 'default':

if (envAccessKey && envSecretKey && (!envProfile || envProfile === this.profile)) {
  // this.profile is 'default', envProfile is 'eliinova' — mismatch, skipped

And then tries to read the [default] profile from ~/.aws/credentials instead of the one specified by AWS_PROFILE.

Expected Behavior

S3Client should respect AWS_PROFILE env var when no explicit profile is passed.

Fix

In the constructor, default to process.env.AWS_PROFILE before falling back to 'default':

this.profile = profile || process.env.AWS_PROFILE || 'default'

Affected Files

  • packages/ts-cloud/src/aws/s3.ts (constructor line 75)
  • All callers in packages/ts-cloud/src/deploy/static-site.ts and static-site-external-dns.ts

Impact

Any project using a non-default AWS profile for deployment gets AccessDenied on S3 operations.

9 days ago

Fixed in earlier credential-resolution refactor (packages/ts-cloud/src/aws/credentials.ts + the S3Client constructor in packages/ts-cloud/src/aws/s3.ts).

The current behavior matches what this issue asked for, plus stricter handling of the explicit-profile case:

  • S3Client constructor delegates to resolveCredentials(profile), which honors AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY env vars first, then AWS_PROFILE, then default.
  • When a caller passes an explicit profile string (e.g. cost:analyze --profile stacks), resolveCredentials is strict: missing profile → throw, no silent env fallback. This matches aws --profile semantics and prevents the old "wrong account, no error" failure mode.
  • S3Client.getCredentials() (the path used by presigned URLs / bucket policies) goes through the same helper.

Verified end-to-end via cloud cost:analyze --profile stacks returning data scoped to that profile's account.

Closing — if a regression sneaks back in, please reopen with the failing invocation.

Sign in to comment on this issue.