rules:
  - id: auth.cors.wildcard-with-credentials
    languages:
      - javascript
      - typescript
    severity: ERROR
    message: |
      CORS is configured with `Access-Control-Allow-Origin: *` and
      `Access-Control-Allow-Credentials: true` at the same time. The
      spec forbids this combination (browsers will block it), but
      developers regularly try to "fix" the resulting error by switching
      to `origin: true` (which echoes the requesting origin back),
      effectively turning the policy into "allow credentials from
      ANYWHERE". That's a CSRF-on-steroids primitive.

      Decide which one you actually need:
       - Public API, no cookies/auth headers needed cross-site →
         `origin: '*'`, `credentials: false` (default).
       - Authenticated API for a known frontend → enumerate the exact
         origins, `credentials: true`.

      Never combine wildcard origins with credentials enabled.
    pattern-either:
      # express cors() middleware: a wildcard ("*"), origin-echoing (true),
      # request-reflected, or always-allow callback origin combined with
      # credentials: true.
      - patterns:
          - pattern-either:
              - pattern: 'cors({ ..., origin: "*", ..., credentials: true, ... })'
              - pattern: 'cors({ ..., credentials: true, ..., origin: "*", ... })'
              - pattern: 'cors({ ..., origin: true, ..., credentials: true, ... })'
              - pattern: 'cors({ ..., credentials: true, ..., origin: true, ... })'
              - pattern: 'cors({ ..., origin: $REQ.headers.origin, ..., credentials: true, ... })'
              - pattern: 'cors({ ..., credentials: true, ..., origin: $REQ.headers.origin, ... })'
              - pattern: 'cors({ ..., origin: ($O, $CB) => $CB(null, true), ..., credentials: true, ... })'
              - pattern: 'cors({ ..., credentials: true, ..., origin: ($O, $CB) => $CB(null, true), ... })'
      # Manual header writes: Access-Control-Allow-Origin: * together with
      # Access-Control-Allow-Credentials: true (the two setHeader calls are
      # always adjacent, so a bounded span regex catches both orders).
      - pattern-regex: |-
          Access-Control-Allow-Origin['"]\s*,\s*['"]\*['"][\s\S]{0,200}Access-Control-Allow-Credentials['"]\s*,\s*['"]true['"]
      - pattern-regex: |-
          Access-Control-Allow-Credentials['"]\s*,\s*['"]true['"][\s\S]{0,200}Access-Control-Allow-Origin['"]\s*,\s*['"]\*['"]
    metadata:
      oauthlint-rule-id: AUTH-CORS-001
      oauthlint-doc-url: https://oauthlint.dev/rules/cors-wildcard-with-credentials
      category: security
      cwe: CWE-942
      owasp: API8:2023
      llm-prevalence: HIGH
      technology:
        - express
        - cors
      references:
        - https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials
        - https://portswigger.net/web-security/cors
