TRMNL is an awesome open-source ePaper display that lets users show almost anything they want. Launched on Kickstarter, it’s now got thousands of plugins and users. I’ve published a few plugins, including a realtime TfL bus stop arrival time board.
A few weeks ago, TRMNL added an MCP server. This lets you use a local coding agent to do most of the grunt work. I wrote about how to set that up in another post.
Secret disclosure
As soon as i’d set up the MCP server, i asked Claude to test it against a plugin i’d already built and deployed. The model called IntegrationsShowTool with no arguments. The response included:
"custom_fields_values" => {
"crs" => "NSG",
"api_key" => "<my Rail Data Marketplace consumer key, in full>",
"filter_crs" => "FPK"
}The api_key field is defined as field_type: password. The polling_url field, which the placeholder text suggests often carries an API key in the query string, is marked encrypted: true and is also returned in full.
To their credit, when digging a little deeper i saw that the built in oauth_client_secret field is absent from the settings output, so the server already masks at least one secret. The password-type custom fields and encrypted settings appear to have been missed.
Where is my secret now?
MCP tool output goes straight into the agent's context. Claude Code writes every tool result to a transcript on disk, and the content is sent to the model provider with each turn. Other MCP clients log tool output too. A user who follows TRMNL’s AI Agent setup guide will therefore inadvertently copy any secret credentials into agent logs and third-party services without any warning, and like i said, this happened automatically on the first call to the default discovery tool exposed by the server i.e. the one which an agent is likely to run unprompted.
Fix: return [set] for configured secret values
I suggested to TRMNL that they update the server to redact any custom field with field_type: password in IntegrationsShowTool output, instead returning an indicator (e.g. "api_key" => "[set]") to help agents understand whether the configuration has been applied.
TRMNL’s reply thanked me for my report and explained that the deny list was built from the plugin's built-in form schema, so user-defined custom fields never passed through the redaction code - a key lesson for MCP server authors is to ensure that redaction is applied consistently across all code paths. They also:
- Ran a scan over the entire MCP surface identified which found one other tool that returned polling headers in full, a field that commonly carries a bearer token".
- Added additional redaction to error logging.
- Added help text to the
polling_urlfield to warn authors to put keys in a custompasswordfield and reference them by variable. - Stated that they’ll notify all users who might have been affected to rotate affected secrets.
- Pushed out a fix within 19 hours. Impressive.
What to do now?
I retested after they’d deployed their fix and as far as i can tell, it’s correct and complete. Anyone who connected their agent to the TRMNL MCP server before 5th September 2026 should rotate any secrets they had stored in their plugins, as the secrets are already exposed in your transcripts. Deleting the transcript is not sufficient as the key will have been sent to your model provider.
Timeline
- Friday, September 4, 2026 16:56: initial report to TRMNL’s security team.
- Friday, September 4, 2026 23:40: report acknowledged
- Saturday, September 5, 2026 11:23: TRMNL reply to confirm the validity of the finding and that they’ve published a fix.
No comments yet