rules:
  - id: auth.php.cors.wildcard-with-credentials
    languages:
      - php
    severity: WARNING
    message: |
      This endpoint sends `Access-Control-Allow-Credentials: true` together with
      an `Access-Control-Allow-Origin` that is either the wildcard `*` or the
      request's own `Origin` reflected back unchecked. That combination lets any
      website make credentialed cross-origin requests and read the response,
      leaking the victim's session / OAuth tokens cross-origin (CWE-942), an
      account-takeover primitive. AI-generated code pairs these two headers to
      "make the browser call work" without an origin allowlist.

      Echo the origin only after checking it against a trusted allowlist, e.g.
        if (in_array($origin, $allowed, true)) {
            header('Access-Control-Allow-Origin: ' . $origin);
            header('Access-Control-Allow-Credentials: true');
        }
    # Both headers must be co-located (the credentials header and a wildcard or
    # reflected-origin header in the same statement sequence). A reflected origin
    # wrapped in an `in_array(...)` allowlist guard is excluded via
    # pattern-not-inside; Allow-Origin alone (no credentials) never fires. The
    # header strings contain ": " so each pattern is a block scalar (no quoting
    # traps).
    patterns:
      - pattern-either:
          - pattern: |
              header('Access-Control-Allow-Origin: *');
              ...
              header('Access-Control-Allow-Credentials: true');
          - pattern: |
              header('Access-Control-Allow-Credentials: true');
              ...
              header('Access-Control-Allow-Origin: *');
          - pattern: |
              header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
              ...
              header('Access-Control-Allow-Credentials: true');
          - pattern: |
              header('Access-Control-Allow-Credentials: true');
              ...
              header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
      - pattern-not-inside: |
          if (in_array(...)) { ... }
    paths:
      exclude:
        - "**/test/**"
        - "**/*Test.php"
        - "**/vendor/**"
        - "**/examples/**"
        - "**/samples/**"
        - "**/demo/**"
    metadata:
      oauthlint-rule-id: AUTH-PHP-CORS-001
      oauthlint-doc-url: https://oauthlint.dev/rules/php-cors-wildcard-with-credentials
      category: security
      cwe: CWE-942
      owasp: API8:2023
      llm-prevalence: MEDIUM
      technology:
        - php
      references:
        - https://developer.mozilla.org/docs/Web/HTTP/CORS
        - https://cwe.mitre.org/data/definitions/942.html
