System Info
llama-stack main @ 2e08be040be2cd15b528d23e39b58b286e02d379
Python 3.12.12
macOS (arm64)
Information
🐛 Describe the bug
X-LlamaStack-Provider-Data currently allows a caller to inject the reserved
__authenticated_user key into the same request-context dictionary that the
server uses for authenticated user state.
That creates two problems on current main:
get_authenticated_user() can return a caller-controlled object instead of a
validated User
- downstream code that expects a real
User can crash with a 500
This is distinct from the already-fixed background-worker context leak in #5221 /
#5227. That issue was about ContextVar propagation across worker tasks. This
one happens earlier, when the request provider-data context is created for a
normal HTTP request.
A minimal reproduction path is:
- start from current
main
- install
ProviderDataMiddleware
- send
X-LlamaStack-Provider-Data: {"__authenticated_user": {"principal": "attacker", "attributes": {"roles": ["admin"]}}}
- call a route that reaches
get_authenticated_user() or AuthorizedSqlStore
Observed behavior:
- the reserved key survives into the request context when no authenticated
User is present
get_authenticated_user() returns a raw dict
- a storage-backed route that calls
AuthorizedSqlStore.insert() can return 500
Why I think this is a real bug and not just an unsupported input shape:
- the project explicitly documents multi-tenant isolation as an intended
production scenario
- caller-controlled provider-data is otherwise treated with a default-deny /
hardening mindset (for example, forwarded-header config rejects __-prefixed
names and security-sensitive headers)
- mixing caller-controlled provider-data with server-owned auth context in the
same namespace breaks that boundary
Error logs
Local reproduction against current main produced:
AttributeError: 'dict' object has no attribute 'principal'
I also reproduced the bug through a real FastAPI app with ProviderDataMiddleware:
- a debug route that returns
get_authenticated_user() sees user_type == "dict"
- a storage-backed POST route succeeds without the malicious header, but returns
HTTP 500 with it
Expected behavior
Caller-controlled provider-data should not be able to set or override reserved
server-owned auth-context keys.
At minimum, __authenticated_user should be stripped or rejected before the
provider-data context is installed. Defense-in-depth would also be to make
get_authenticated_user() ignore non-User values.
System Info
Information
🐛 Describe the bug
X-LlamaStack-Provider-Datacurrently allows a caller to inject the reserved__authenticated_userkey into the same request-context dictionary that theserver uses for authenticated user state.
That creates two problems on current
main:get_authenticated_user()can return a caller-controlled object instead of avalidated
UserUsercan crash with a 500This is distinct from the already-fixed background-worker context leak in #5221 /
#5227. That issue was about
ContextVarpropagation across worker tasks. Thisone happens earlier, when the request provider-data context is created for a
normal HTTP request.
A minimal reproduction path is:
mainProviderDataMiddlewareX-LlamaStack-Provider-Data: {"__authenticated_user": {"principal": "attacker", "attributes": {"roles": ["admin"]}}}get_authenticated_user()orAuthorizedSqlStoreObserved behavior:
Useris presentget_authenticated_user()returns a rawdictAuthorizedSqlStore.insert()can return 500Why I think this is a real bug and not just an unsupported input shape:
production scenario
hardening mindset (for example, forwarded-header config rejects
__-prefixednames and security-sensitive headers)
same namespace breaks that boundary
Error logs
Local reproduction against current
mainproduced:I also reproduced the bug through a real FastAPI app with
ProviderDataMiddleware:get_authenticated_user()seesuser_type == "dict"HTTP 500 with it
Expected behavior
Caller-controlled provider-data should not be able to set or override reserved
server-owned auth-context keys.
At minimum,
__authenticated_usershould be stripped or rejected before theprovider-data context is installed. Defense-in-depth would also be to make
get_authenticated_user()ignore non-Uservalues.