rules:
  - id: auth.py.mcp.tool-handler-ssrf
    languages:
      - python
    severity: ERROR
    message: |
      An argument of an MCP tool handler (`@mcp.tool()`) flows into an outbound
      HTTP request without validation, a server-side request forgery (SSRF,
      CWE-918). Tool arguments are attacker-influenced: an LLM (or a malicious
      caller) can point the URL at internal services, cloud metadata
      (169.254.169.254), or localhost. BlueRock found ~36.7% of MCP servers
      SSRF-prone.

      Validate the target before fetching: resolve and allow-list the host,
      reject private/link-local ranges, and disable redirects. Never pass a raw
      tool argument straight into `httpx`/`requests`.
    # Taint from the tool handler's own parameter to an outbound request. This is
    # distinct from generic SSRF (source = HTTP request data). Here the source is
    # the MCP tool argument itself.
    mode: taint
    pattern-sources:
      - patterns:
          - pattern-either:
              - pattern: |
                  @$DEC.tool(...)
                  def $F(..., $ARG, ...):
                      ...
              - pattern: |
                  @$DEC.tool(...)
                  async def $F(..., $ARG, ...):
                      ...
          - focus-metavariable: $ARG
    pattern-sinks:
      - patterns:
          - pattern-either:
              - pattern: httpx.$M($SINK, ...)
              - pattern: httpx2.$M($SINK, ...)
              - pattern: requests.$M($SINK, ...)
              # A client method call, scoped to HTTP-client variable names AND HTTP
              # verbs so a plain `dict.get(key)` / `cache.get(k)` is NOT a sink.
              - patterns:
                  - pattern: $CLIENT.$M($SINK, ...)
                  - metavariable-regex:
                      metavariable: $CLIENT
                      regex: ^(client|http_client|httpx_client|http|session|_client|c)$
                  - metavariable-regex:
                      metavariable: $M
                      regex: ^(get|post|put|delete|patch|head|request|stream|send)$
          - focus-metavariable: $SINK
    metadata:
      oauthlint-rule-id: AUTH-PY-MCP-004
      oauthlint-doc-url: https://oauthlint.dev/rules/py-mcp-tool-handler-ssrf
      category: security
      cwe: CWE-918
      owasp: API7:2023
      llm-prevalence: HIGH
      technology:
        - mcp
        - fastmcp
        - modelcontextprotocol
      references:
        - https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization
        - https://cwe.mitre.org/data/definitions/918.html
