Skip to content

Props containing OAuth tokens are exposed in plain text in Workers observability logs when used with the Agents SDK #170

@lomigmegard

Description

@lomigmegard

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:

  1. Built an authenticated MCP server following Cloudflare's docs or templates
  2. 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

  1. 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.
  2. 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 }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions