Disclosure note: I initially reported this through Cloudflare's HackerOne program (report #3596700) as I didn't want to draw public attention before Cloudflare had a chance to assess it. It was classified as informational. I'm opening this issue because the behavior affects anyone building an authenticated MCP server following the official examples and docs.
Summary
The library's examples and documentation establish a pattern of storing tokens in props and documents that they're encrypted at rest. When used with the Agents SDK (which is the primary use case), those same tokens end up serialized as a plain-text HTTP header and captured in full by Workers observability logs.
Security expectations
The README makes strong security claims about props:
The props associated with a grant [...] are stored encrypted with the secret token as key material. Hence, the contents of props are impossible to derive from storage unless a valid token is provided.
The official blog post reinforces this next to a code sample that puts the access token in props:
props: {
accessToken, // Stored encrypted, never sent to MCP client
},
The library clearly treats props as a container for sensitive data.
Information disclosure path
When an authenticated request reaches an MCP server built with the Agents SDK, the SDK calls getAgentByName(namespace, sessionId, { props: ctx.props }).
This delegates to partyserver's getServerByName, which serializes the entire props object as JSON in the x-partykit-props HTTP header on an internal request to the Durable Object:
// TODO: fix this to use RPC
if (options?.props) {
req.headers.set("x-partykit-props", JSON.stringify(options?.props));
}
Workers observability automatically captures the full request headers of Durable Object invocations. There is no per-header redaction. The only opt-out is invocation_logs: false, which disables all invocation logging.
End-user OAuth access tokens and refresh tokens are persisted in clear text in Workers Logs (default 7-day retention), visible in the Cloudflare dashboard, wrangler tail, and any Logpush destination.
Affected examples and docs
Every Cloudflare example that stores tokens in props is affected. This includes:
remote-mcp-github-oauth (the main recommended template for building authenticated MCP servers)
props: {
accessToken, // end user's GitHub OAuth token
email,
login,
name,
} as Props,
Stytch integration (referenced from Cloudflare's authorization docs)
c.executionCtx.props = {
claims: verifyResult.payload,
accessToken,
};
This library's own README, in the tokenExchangeCallback example:
return {
accessTokenProps: { ...options.props, upstreamAccessToken: upstreamTokens.access_token },
newProps: { ...options.props, upstreamRefreshToken: upstreamTokens.refresh_token },
};
Log entry example
{
"level": "info",
"message": "GET http://dummy-example.cloudflare.com/cdn-cgi/partyserver/set-name/",
"$workers": {
"truncated": false,
"event": {
"request": {
"headers": {
"x-partykit-props": "<full props serialised as json>"
}
}
},
"executionModel": "durableObject"
}
}
Who is affected
Anyone who:
- Built an authenticated MCP server following Cloudflare's docs or templates
- Has Workers observability enabled, which is the default for all newly created Workers
The tokens in the logs belong to the authenticating end users. Anyone with access to Workers logs (which in many organizations means the entire engineering team) can see fully functional upstream OAuth tokens.
Suggested fixes
- Coordinate with partyserver to migrate props delivery to RPC. There's already a
// TODO: fix this to use RPC comment in the code acknowledging this.
- Update the examples and docs to not store tokens in
props, or at minimum document that props will appear in plain text in observability logs when used with the Agents SDK.
Environment
@cloudflare/workers-oauth-provider: 0.3.0
partyserver: 0.3.3
agents: 0.7.9 (also checked latest 0.8.1, same underlying partyserver behaviour)
- Cloudflare Workers with
observability: { enabled: true }
Summary
The library's examples and documentation establish a pattern of storing tokens in
propsand documents that they're encrypted at rest. When used with the Agents SDK (which is the primary use case), those same tokens end up serialized as a plain-text HTTP header and captured in full by Workers observability logs.Security expectations
The README makes strong security claims about
props:The official blog post reinforces this next to a code sample that puts the access token in props:
The library clearly treats
propsas a container for sensitive data.Information disclosure path
When an authenticated request reaches an MCP server built with the Agents SDK, the SDK calls
getAgentByName(namespace, sessionId, { props: ctx.props }).This delegates to partyserver's
getServerByName, which serializes the entirepropsobject as JSON in thex-partykit-propsHTTP header on an internal request to the Durable Object:Workers observability automatically captures the full request headers of Durable Object invocations. There is no per-header redaction. The only opt-out is
invocation_logs: false, which disables all invocation logging.End-user OAuth access tokens and refresh tokens are persisted in clear text in Workers Logs (default 7-day retention), visible in the Cloudflare dashboard,
wrangler tail, and any Logpush destination.Affected examples and docs
Every Cloudflare example that stores tokens in
propsis affected. This includes:remote-mcp-github-oauth (the main recommended template for building authenticated MCP servers)
Stytch integration (referenced from Cloudflare's authorization docs)
This library's own README, in the
tokenExchangeCallbackexample:Log entry example
{ "level": "info", "message": "GET http://dummy-example.cloudflare.com/cdn-cgi/partyserver/set-name/", "$workers": { "truncated": false, "event": { "request": { "headers": { "x-partykit-props": "<full props serialised as json>" } } }, "executionModel": "durableObject" } }Who is affected
Anyone who:
The tokens in the logs belong to the authenticating end users. Anyone with access to Workers logs (which in many organizations means the entire engineering team) can see fully functional upstream OAuth tokens.
Suggested fixes
// TODO: fix this to use RPCcomment in the code acknowledging this.props, or at minimum document that props will appear in plain text in observability logs when used with the Agents SDK.Environment
@cloudflare/workers-oauth-provider: 0.3.0partyserver: 0.3.3agents: 0.7.9 (also checked latest 0.8.1, same underlying partyserver behaviour)observability: { enabled: true }