Description
Any access_token whose lenient base64 decode yields zero bytes reaches the guest-macaroon fallback in get_user_by_access_token(). Macaroon.deserialize on empty bytes then raises IndexError inside pymacaroons, which is not in the caught exception tuple — so the request fails with HTTP 500 M_UNKNOWN instead of a clean 401, and a full traceback (~39 log lines vs 3 for a normal 401) is written per request.
Affected versions
matrixdotorg/synapse:latest = 1.160.0 (reproduced 2026-09-05, Docker, fresh generated config)
- Also reproduced on 1.159.0
develop as of 2026-09-05: synapse/api/auth/internal.py still catches only MacaroonException, TypeError, ValueError — unfixed
Reproduction
Start a stock Synapse (docker guide default) and send a request with a token that decodes to empty bytes:
curl -sk -w '\nHTTP %{http_code}\n' \
'http://localhost:8008/_matrix/client/v3/account/whoami?access_token=*'
Buggy result: HTTP 500, body {"errcode":"M_UNKNOWN","error":"Internal server error"}
Controls — zzz and empty are handled correctly, empty-decoding payloads are not:
- token
* → HTTP 500 M_UNKNOWN
- token
! → HTTP 500 M_UNKNOWN
- token
~~~ → HTTP 500 M_UNKNOWN
- token
zzz → HTTP 401 M_UNKNOWN_TOKEN (correct)
- token empty → HTTP 401
M_UNKNOWN_TOKEN (correct)
Any payload whose lenient base64 decode results in zero bytes triggers it (*, !, ~~~, spaces). Payloads containing - or _ (valid urlsafe-alphabet chars) decode to non-empty bytes and correctly return 401.
The bug is not limited to the query parameter — the Authorization header path and other authenticated endpoints behave identically:
curl -sk -o /dev/null -w '%{http_code}\n' \
-H 'Authorization: Bearer ***' \
'http://localhost:8008/_matrix/client/v3/account/whoami'
curl -sk -o /dev/null -w '%{http_code}\n' \
'http://localhost:8008/_matrix/client/v3/sync?access_token=*'
curl -sk -o /dev/null -w '%{http_code}\n' -X POST \
'http://localhost:8008/_matrix/client/v3/rooms/!test:example.com/kick?access_token=*'
All three return HTTP 500.
Traceback (from server log)
Traceback (most recent call last):
File ".../synapse/http/server.py", line 336, in _async_render_wrapper
callback_return = await self._async_render(request)
File ".../synapse/rest/client/account.py", line 869, in on_GET
requester = await self.auth.get_user_by_req(request, allow_guest=True)
File ".../synapse/api/auth/internal.py", line 250, in get_user_by_access_token
user_id = self._macaroon_generator.verify_guest_token(token)
...
File ".../pymacaroons/macaroon.py", line 39, in deserialize
return serializer.deserialize(serialized)
File ".../pymacaroons/serializers/binary_serializer.py", line 96, in deserialize_raw
first = six.byte2int(serialized[:1])
IndexError: index out of range
Root cause
base64.b64decode in non-strict mode strips characters outside the alphabet, so * decodes to empty bytes. binary_serializer.py:96 then indexes into empty bytes (serialized[:1] is empty, six.byte2int raises IndexError). The except clause in synapse/api/auth/internal.py only covers:
except (
pymacaroons.exceptions.MacaroonException,
TypeError,
ValueError,
) as e:
IndexError slips through to the generic 500 handler.
Suggested fix
Add IndexError to the caught exceptions in get_user_by_access_token() (or validate that the decoded payload is non-empty before calling deserialize()). Longer term, a guard in pymacaroons' deserialize_raw would fix the class of issue for all consumers.
Impact
No security impact demonstrated: the server does not crash (verified healthy after 50 consecutive payloads — /login still returns 200), no data is disclosed to the client (traceback is server-log only), and authentication still denies access. The practical effects are the wrong status code and per-request traceback log amplification (39 lines vs 3).
Originally reported to security@element.io, closed as "unable to identify the security implications" — filing here as a code defect / error-handling bug.
Reported-by: KITLAB13
Description
Any
access_tokenwhose lenient base64 decode yields zero bytes reaches the guest-macaroon fallback inget_user_by_access_token().Macaroon.deserializeon empty bytes then raisesIndexErrorinside pymacaroons, which is not in the caught exception tuple — so the request fails with HTTP 500M_UNKNOWNinstead of a clean 401, and a full traceback (~39 log lines vs 3 for a normal 401) is written per request.Affected versions
matrixdotorg/synapse:latest= 1.160.0 (reproduced 2026-09-05, Docker, fresh generated config)developas of 2026-09-05:synapse/api/auth/internal.pystill catches onlyMacaroonException, TypeError, ValueError— unfixedReproduction
Start a stock Synapse (docker guide default) and send a request with a token that decodes to empty bytes:
Buggy result: HTTP 500, body
{"errcode":"M_UNKNOWN","error":"Internal server error"}Controls —
zzzand empty are handled correctly, empty-decoding payloads are not:*→ HTTP 500M_UNKNOWN!→ HTTP 500M_UNKNOWN~~~→ HTTP 500M_UNKNOWNzzz→ HTTP 401M_UNKNOWN_TOKEN(correct)M_UNKNOWN_TOKEN(correct)Any payload whose lenient base64 decode results in zero bytes triggers it (
*,!,~~~, spaces). Payloads containing-or_(valid urlsafe-alphabet chars) decode to non-empty bytes and correctly return 401.The bug is not limited to the query parameter — the
Authorizationheader path and other authenticated endpoints behave identically:All three return HTTP 500.
Traceback (from server log)
Root cause
base64.b64decodein non-strict mode strips characters outside the alphabet, so*decodes to empty bytes.binary_serializer.py:96then indexes into empty bytes (serialized[:1]is empty,six.byte2intraisesIndexError). The except clause insynapse/api/auth/internal.pyonly covers:IndexErrorslips through to the generic 500 handler.Suggested fix
Add
IndexErrorto the caught exceptions inget_user_by_access_token()(or validate that the decoded payload is non-empty before callingdeserialize()). Longer term, a guard in pymacaroons'deserialize_rawwould fix the class of issue for all consumers.Impact
No security impact demonstrated: the server does not crash (verified healthy after 50 consecutive payloads —
/loginstill returns 200), no data is disclosed to the client (traceback is server-log only), and authentication still denies access. The practical effects are the wrong status code and per-request traceback log amplification (39 lines vs 3).Originally reported to security@element.io, closed as "unable to identify the security implications" — filing here as a code defect / error-handling bug.
Reported-by: KITLAB13