rules:
  - id: auth.oauth.open-redirect-callback
    languages:
      - javascript
      - typescript
    severity: ERROR
    message: |
      The OAuth callback handler redirects to a URL taken straight from
      the request without validating it. An attacker can craft a phishing
      link to your real callback that forwards the victim to a malicious
      site under your domain's trust.

      Maintain an explicit allow-list of post-login redirect destinations
      (route names or full URLs you control). Never forward to an
      arbitrary `req.query.redirect_to`, `req.query.next`, or
      `req.query.return_url` value.
    # Taint mode so indirection (const next = req.query.next; res.redirect(next))
    # and defaults/casts are caught, not just the direct form. Validating the
    # value against an allow-list (Set.has / Array.includes) clears the taint.
    mode: taint
    pattern-sources:
      - pattern: '$REQ.query.$A'
      - pattern: '$REQ.body.$A'
      - pattern: '$REQ.params.$A'
      - pattern: '$REQ.query[$K]'
      - pattern: '$REQ.body[$K]'
    pattern-sanitizers:
      # An inline allow-list membership guard vets the value: a value used inside
      # `if (set.has(x)) { ... }` / `if (arr.includes(x)) { ... }` /
      # `if (arr.indexOf(x) ...) { ... }` is treated as validated. The
      # boolean-returning membership call itself does NOT sanitize its argument
      # (that would only clear the boolean, not the value), so we clear taint by
      # the if-guard, mirroring the Python rule's `if is_safe_url(v): ...`.
      - patterns:
          - pattern: $X
          - pattern-inside: |
              if (<... $SET.has($X) ...>) { ... }
      - patterns:
          - pattern: $X
          - pattern-inside: |
              if (<... $ARR.includes($X) ...>) { ... }
      - patterns:
          - pattern: $X
          - pattern-inside: |
              if (<... $ARR.indexOf($X) ...>) { ... }
    pattern-sinks:
      - patterns:
          - pattern-either:
              - pattern: '$RES.redirect($SINK)'
              - pattern: '$RES.redirect($CODE, $SINK)'
          - focus-metavariable: $SINK
    metadata:
      oauthlint-rule-id: AUTH-OAUTH-008
      oauthlint-doc-url: https://oauthlint.dev/rules/oauth-open-redirect-callback
      category: security
      cwe: CWE-601
      owasp: API1:2023
      llm-prevalence: HIGH
      technology:
        - express
      references:
        - https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
