Skip to content

Bug: AppSync Events swallows UnauthorizedException in non-aggregate handlers, class-based async middleware leaks a coroutine, HttpResolverLocal is not usable as a Lambda handler #8582

Description

@leandrodamascena

Expected Behaviour

  • AppSync Events: raising UnauthorizedException in a publish handler fails the invocation so the publisher gets 401/403, as the docs say, regardless of aggregate.
  • A class-based middleware with async def handler works in resolve_async(), as the docs say.
  • HttpResolverLocal works as a Lambda handler (handler = app) and accepts binary request bodies.

Current Behaviour

  1. AppSync Events swallows UnauthorizedException in non-aggregate handlers (events_appsync/appsync_events.py:257 sync, :316 async with gather(return_exceptions=True)). The documented example working_with_authorization_control.py uses the default aggregate=False. Against a real AppSync Events API:
    aggregate=False: HTTP 200 {"failed":[{"code":"CustomError","message":"UnauthorizedException - not allowed"}, ...],"successful":[]}
    aggregate=True:  HTTP 401 {"errors":[{"errorType":"UnauthorizedException","message":"Not authorized to access channel agg"}]}
    
    Events are not delivered in either case, so it is not a bypass, but the status is wrong and the internal exception message reaches the publisher.
  2. Class-based async middleware leaks a coroutine (middlewares/async_utils.py:158). The __call__ wrapper is sync, so iscoroutinefunction is False, it runs in a thread and the returned coroutine is treated as the body: TypeError: Object of type coroutine is not JSON serializable. Also wrap_middleware_async (line 101) raises IndexError when a sync middleware short-circuits.
  3. HttpResolverLocal as Lambda handler (http_resolver.py:348, 270, 72). handler = app from the docs example raises TypeError (__call__ is ASGI only); app.resolve() with an HTTP API or Function URL event raises KeyError: 'httpMethod'; a binary body through ASGI raises UnicodeDecodeError.

Code snippet

import asyncio
from aws_lambda_powertools.event_handler import APIGatewayRestResolver, AppSyncEventsResolver, HttpResolver
from aws_lambda_powertools.event_handler.events_appsync.exceptions import UnauthorizedException
from aws_lambda_powertools.event_handler.middlewares import BaseMiddlewareHandler
from tests.functional.utils import load_event

events = AppSyncEventsResolver()
@events.on_publish("/default/*")
def deny(payload): raise UnauthorizedException("nope")
print(events.resolve(load_event("appSyncEventsEvent.json"), None))
# {"events": [{"id": "1", "error": "UnauthorizedException - nope"}, ...]}  (expected: exception propagates)

class Mw(BaseMiddlewareHandler):
    async def handler(self, app, nxt): return await nxt(app)
app = APIGatewayRestResolver()
@app.get("/my/path", middlewares=[Mw()])
async def h(): return {"ok": True}
asyncio.run(app.resolve_async(load_event("apiGatewayProxyEvent.json"), None))
# TypeError: Object of type coroutine is not JSON serializable

local = HttpResolver()
@local.get("/my/path")
def p(): return {"ok": True}
local(load_event("apiGatewayProxyEvent.json"), None)      # TypeError: missing 'send'
local.resolve(load_event("apiGatewayProxyV2Event.json"), None)  # KeyError: 'httpMethod'

Possible Solution

Re-raise UnauthorizedException before the generic except in the per-event loop and after gather in the async path. In AsyncMiddlewareFrame.__call__, detect iscoroutinefunction(getattr(mw, "handler", None)) and await coroutine results; return the held result in wrap_middleware_async. Make HttpResolverLocal.__call__ dispatch on scope["type"] vs (event, context), fall back to the parent event mapping, and base64-encode non-UTF-8 bodies.

Steps to Reproduce

Run the snippet above with the tests/events fixtures. Full context in #8576.

Powertools for AWS Lambda (Python) version

latest

AWS Lambda function runtime

3.13

Packaging format used

PyPi

Debugging logs

Warning

This issue is ready to be picked up, by humans or by agents. If you want it, comment here first so we can assign it to you, and please take only one or two issues at a time so others can join in too. Thanks!

Activity

  1. added this to the 3.39.0 milestone on Oct 18, 2026
  2. jasperdingg commented on Oct 18, 2026

    @jasperdingg

    I'd love to give this a try! Will open a PR soon.

  3. SuellenQwQ commented on Oct 19, 2026

    @SuellenQwQ

    Items 1 and 2 are covered in #8633:

    • UnauthorizedException raised by a per-event (aggregate=False) resolver now fails the whole request, in both the sync and the async path.
    • Class-based middlewares (BaseMiddlewareHandler with async def handler) are now awaited by AsyncMiddlewareFrame, and wrap_middleware_async no longer raises IndexError when a middleware short-circuits.

    Item 3 (HttpResolverLocal as a Lambda handler) is untouched: it needs a dispatch change in __call__ for the ASGI/Lambda mix, so I left it for a follow-up.

  4. jasperdingg commented on Oct 19, 2026

    @jasperdingg

    #8633 covers items 1 and 2, so I opened #8634 for item 3! Happy to adjust the approach if needed.

  5. leandrodamascena commented on Oct 19, 2026

    @leandrodamascena
    ContributorAuthor

    Hey @SuellenQwQ and @jasperdingg, thanks for sorting the split between you. @SuellenQwQ items 1 and 2 in #8633, @jasperdingg item 3 in #8634. I'll review both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions