rules:
  - id: auth.py.mcp.token-passthrough
    languages:
      - python
    severity: ERROR
    message: |
      An MCP server forwards the INCOMING caller token to an upstream API
      (token pass-through). The token was issued for THIS server as its
      audience (RFC 8707); replaying it against another resource server is a
      confused-deputy vulnerability (CWE-863). The MCP authorization spec is
      explicit: a resource server MUST NOT accept or transit a token that was
      not issued for it.

      Never send `get_access_token().token` / `access_token.token` / the raw
      `Authorization` header upstream. Do a token exchange (RFC 8693) or use a
      credential minted for the upstream audience, and send THAT token:
        upstream = await exchange_token(access_token.token, audience=UPSTREAM)
        await client.get(UPSTREAM, headers={"Authorization": f"Bearer {upstream}"})
    # Taint mode so `t = access_token.token; httpx.get(url, headers={"Authorization": f"Bearer {t}"})`
    # is caught. A value routed through a token-exchange helper is a fresh upstream
    # token, not the inbound one, so it clears the taint. `access_token` / `auth_info`
    # are the SDK's own variable names for the AccessToken from get_access_token().
    mode: taint
    pattern-sources:
      - pattern: get_access_token().token
      - pattern: access_token.token
      - pattern: auth_info.token
      - pattern: 'request.headers["authorization"]'
      - pattern: 'request.headers.get("authorization")'
    pattern-sanitizers:
      - pattern: exchange_token(...)
      - pattern: $P.exchange_token(...)
    pattern-sinks:
      - patterns:
          - pattern-either:
              - pattern: 'httpx.$M($URL, ..., headers={..., "Authorization": $SINK, ...}, ...)'
              - pattern: 'httpx2.$M($URL, ..., headers={..., "Authorization": $SINK, ...}, ...)'
              - pattern: 'requests.$M($URL, ..., headers={..., "Authorization": $SINK, ...}, ...)'
              - pattern: '$CLIENT.$M($URL, ..., headers={..., "Authorization": $SINK, ...}, ...)'
          - focus-metavariable: $SINK
    metadata:
      oauthlint-rule-id: AUTH-PY-MCP-001
      oauthlint-doc-url: https://oauthlint.dev/rules/py-mcp-token-passthrough
      category: security
      cwe: CWE-863
      owasp: API5:2023
      llm-prevalence: HIGH
      technology:
        - modelcontextprotocol
        - mcp
        - fastmcp
      references:
        - https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization
        - https://datatracker.ietf.org/doc/html/rfc8693
        - https://datatracker.ietf.org/doc/html/rfc8707
