Skip to content

fix(local-apigw): treat authorizer resource ARNs as literals, not regex - #9155

Open
devteamaegis wants to merge 1 commit into
aws:developfrom
devteamaegis:fix/local-authorizer-arn-regex-escape
Open

fix(local-apigw): treat authorizer resource ARNs as literals, not regex#9155
devteamaegis wants to merge 1 commit into
aws:developfrom
devteamaegis:fix/local-authorizer-arn-regex-escape

Conversation

@devteamaegis

Copy link
Copy Markdown

What's broken

sam local start-api returns 403 for requests a Lambda authorizer explicitly allows, whenever the resource ARN contains a regex metacharacter. The everyday case is the HTTP API $default stage:

# authorizer handler
return {"principalId": "user", "policyDocument": {"Statement": [{
    "Action": "execute-api:Invoke", "Effect": "Allow",
    "Resource": "arn:aws:execute-api:us-east-1:123456789012:abc123/$default/*",
}]}}
$ sam local start-api
$ curl localhost:3000/hello
{"message":"User is not authorized to access this resource"}

It fails even when the authorizer echoes back the exact methodArn it was handed — the most common example in AWS's own docs. Deployed API Gateway allows the same request, so this is a pure local/deployed divergence.

A [ or ( in the path is worse — the request dies with re.PatternError: unterminated character set.

Why it happens

_is_resource_authorized builds a regex directly from the ARN, escaping nothing but the wildcards:

regex_method_arn = resource_arn.replace("*", ".*").replace("?", ".")
regex_method_arn += "$"

So $ becomes an end-of-string anchor, . matches any character, and [ / ( are unbalanced syntax.

The fix

re.escape() the ARN first, then translate the two IAM wildcards:

regex_method_arn = re.escape(resource_arn).replace(r"\*", ".*").replace(r"\?", ".")

* and ? keep working; everything else in the ARN is matched literally. This also stops . from over-matching — .../GET/a.c no longer authorizes .../GET/abc.

The test

test_is_resource_authorized_treats_arn_as_literal in tests/unit/local/apigw/test_lambda_authorizer.py — six cases covering $default, an echoed method ARN, [, (, literal ., and a stage that should still be denied. Five of six fail on develop:

$ pytest tests/unit/local/apigw/test_lambda_authorizer.py -k treats_arn_as_literal   # before
5 failed, 1 passed

$ pytest tests/unit/local/apigw                                                      # after
286 passed

black --check clean on both files.

_is_resource_authorized built a regular expression straight from the
Lambda authorizer's Resource ARN, escaping nothing but the wildcards.
Any regex metacharacter in the ARN was therefore interpreted as syntax.

The common case is the HTTP API '$default' stage: '$' is an end-of-string
anchor, so an Allow statement for
'arn:aws:execute-api:...:api/$default/*' never matched the method ARN and
sam local start-api returned 403 for a request that succeeds when
deployed - even when the authorizer echoed back the exact methodArn it
was handed.  A '[' or '(' in the path raised re.PatternError outright.

Escape the ARN first, then translate the '*' and '?' wildcards.

Signed-off-by: devteamaegis <devteam.aegis@gmail.com>
@github-actions github-actions Bot added area/local/start-api sam local start-api command area/local/invoke sam local invoke command area/local/start-invoke pr/external stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at. labels Aug 2, 2026
@devteamaegis
devteamaegis marked this pull request as ready for review August 3, 2026 16:45
@devteamaegis
devteamaegis requested a review from a team as a code owner August 3, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/local/invoke sam local invoke command area/local/start-api sam local start-api command area/local/start-invoke pr/external stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant