Skip to content

Fall back to GetBucketLocation when HeadBucket on the deployment bucket is forbidden - #463

Open
danielnc wants to merge 2 commits into
oss-serverless:4.xfrom
danielnc:fix/head-bucket-forbidden-fallback
Open

danielnc wants to merge 2 commits into
oss-serverless:4.xfrom
danielnc:fix/head-bucket-forbidden-fallback

Conversation

@danielnc

@danielnc danielnc commented Oct 6, 2026 •

Copy link
Copy Markdown

Problem

Since #113, ensureValidBucketExists checks a custom provider.deploymentBucket with HeadBucket. S3 authorizes HeadBucket as s3:ListBucket without an s3:prefix, so a deploy role whose s3:ListBucket is limited to its own prefix of a shared deployment bucket gets a 403 for a bucket it can deploy to. That's a common least-privilege setup: one shared bucket, each service/stage role scoped to serverless/<service>/<stage>/.

The deploy then fails before uploading anything:

Could not locate deployment bucket: "my-shared-bucket". Error: UnknownError

(UnknownError because a HEAD 403 has no body.) The same roles worked with Serverless v3 and osls before #113, which called GetBucketLocation; that needs only s3:GetBucketLocation.

A policy that hits this:

{ "Effect": "Allow", "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::my-shared-bucket" },
{ "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::my-shared-bucket",
  "Condition": { "StringLike": { "s3:prefix": ["serverless/my-service/prod", "serverless/my-service/prod/*"] } } },
{ "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
  "Resource": "arn:aws:s3:::my-shared-bucket/serverless/my-service/prod/*" }

Change

Only when HeadBucket fails with a 403 (isS3HeadBucketForbiddenError), ask GetBucketLocation and feed the result through the existing region check. An empty LocationConstraint means us-east-1, which was the reason for #113, and EU is normalized as before via getS3BucketRegion. If the fallback fails too, the original error is reported exactly as before. A successful HeadBucket and any non-403 failure follow the same code paths and messages as today.

Tests

Four unit tests in ensure-valid-bucket-exists.test.js:

  • a forbidden HeadBucket falls back to GetBucketLocation and accepts a us-east-1 bucket;
  • the fallback still rejects a bucket in another region (DEPLOYMENT_BUCKET_INVALID_REGION);
  • a failing fallback reports the original HeadBucket error (DEPLOYMENT_BUCKET_NOT_FOUND);
  • a non-403 failure (404) does not fall back.

We're carrying this exact change as a pnpm patch of osls@4.4.0 while we move our services from Serverless v3 to osls, and would like to drop it. I haven't checked whether 3.x needs the same change (#113 landed there too); happy to backport if you want it.

…et is forbidden

HeadBucket is authorized as s3:ListBucket with no s3:prefix. A deploy role that may list only its own prefix of a shared deploymentBucket (a common least-privilege setup) gets a 403 for a bucket it can deploy to, and the deploy fails with "Could not locate deployment bucket ... Error: UnknownError" (the 403 has no body). Serverless v3 and osls before oss-serverless#113 called GetBucketLocation, which needs only s3:GetBucketLocation.

On a 403 only, ask GetBucketLocation and run the result through the existing region check; an empty constraint is us-east-1. Every other outcome is unchanged.
@GrahamCampbell
GrahamCampbell self-requested a review October 6, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant