rules:
  - id: auth.mcp.token-passthrough
    languages:
      - javascript
      - typescript
    severity: ERROR
    message: |
      An MCP server forwards the INCOMING caller token to an upstream API
      (token pass-through). The token the MCP client presented was issued for
      THIS server as its audience (RFC 8707); replaying it against a different
      resource server is a confused-deputy vulnerability (CWE-863), the exact
      class behind CVE-2026-13341. The MCP authorization spec forbids it: a
      resource server MUST NOT accept or transit a token that was not issued
      for it.

      Never send `req.auth.token` / `ctx.http.authInfo.token` / the raw
      `Authorization` header upstream. Perform a token exchange (RFC 8693) or
      use a separately-obtained credential minted for the upstream audience,
      and send THAT token:
        const up = await exchangeToken(authInfo.token, { audience: UPSTREAM });
        fetch(UPSTREAM, { headers: { Authorization: `Bearer ${up}` } });
    # Taint mode so indirection (const t = req.auth.token; fetch(url,{headers:{Authorization:`Bearer ${t}`}}))
    # is caught, not only the inline form. A value routed through a token-exchange
    # helper is a fresh upstream token, not the inbound one, so it clears the taint.
    mode: taint
    pattern-sources:
      - pattern: $REQ.auth.token
      - pattern: $X.authInfo.token
      - pattern: authInfo.token
      - pattern: $REQ.headers.authorization
      - pattern: "$REQ.headers['authorization']"
      - pattern: $REQ.headers.Authorization
    pattern-sanitizers:
      # A token exchange returns a NEW token minted for the upstream audience.
      # The inbound token no longer flows to the sink.
      - pattern: exchangeToken(...)
      - pattern: exchangeAuthorizationCode(...)
      - pattern: exchangeRefreshToken(...)
      - pattern: $P.exchangeToken(...)
    pattern-sinks:
      - patterns:
          - pattern-either:
              - pattern: 'fetch($URL, {..., headers: {..., Authorization: $SINK, ...}, ...})'
              - pattern: 'fetch($URL, {..., headers: {..., "Authorization": $SINK, ...}, ...})'
              - pattern: 'axios($URL, {..., headers: {..., Authorization: $SINK, ...}, ...})'
              - pattern: 'axios.$M($URL, {..., headers: {..., Authorization: $SINK, ...}, ...})'
              - pattern: 'axios($URL, {..., headers: {..., "Authorization": $SINK, ...}, ...})'
          - focus-metavariable: $SINK
    metadata:
      oauthlint-rule-id: AUTH-MCP-001
      oauthlint-doc-url: https://oauthlint.dev/rules/mcp-token-passthrough
      category: security
      cwe: CWE-863
      owasp: API5:2023
      llm-prevalence: HIGH
      technology:
        - modelcontextprotocol
        - mcp
      references:
        - https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization
        - https://datatracker.ietf.org/doc/html/rfc8693
        - https://datatracker.ietf.org/doc/html/rfc8707
