The credential that went to the wrong place
- Symptom
A tool failed with an empty-token error while the logs cheerfully reported that the token had been supplied.
- Diagnosis
The backend decided which tool parameter should receive the OAuth token by scanning each parameter’s description for substrings like
token,key,auth. One tool took a person’s display name, and its description read “if omitted, returns the authenticated user”. Authenticated contains auth. So the matcher flaggednameas a credential field and the backend wrote an access token into it. The token wasn’t missing. It had been posted into a search box.- Decision
The obvious fix was a better pattern list. Looking for where the injection happened, I found something better: the backend was already attaching the token as an HTTP
Authorizationheader on the connection to each tool server. That path existed and worked. The parameter injection was a second, redundant mechanism, and the only one that could ever put a credential somewhere the model could see it. So I deleted it. Each server now reads the token from its incoming request context, via one shared helper that replaced four near-identical copies.- Result
No tool declares a token parameter, so the model is never offered one, and no heuristic has to guess correctly.
tool get_user_profile scan params[name].description ~ "returns the authenticated user" found auth params: [auth_token, name] mapping token → name ← wrong. this field is user-visible mapping token → auth_token call get_user_profile(name="<token, 1.4kB>", auth_token="<token, 1.4kB>") error empty token
tool get_user_profile found auth params: [] ← no tool declares one header Authorization: Bearer <token, 1.4kB> call get_user_profile() ok 200
A guessing mechanism whose failure mode is credential misplacement shouldn’t be made more accurate. It should stop existing.