Repository: vercel/next.js
File: packages/next/src/server/app-render/csrf-protection.test.ts (TypeScript)

Code before:

  it('should handle trailing dots (FQDN notation)', () => {
    // Trailing dot represents the DNS root and is valid FQDN notation
    expect(isCsrfOriginAllowed('sub.vercel.com.', ['*.vercel.com'])).toBe(true)
    expect(isCsrfOriginAllowed('sub.vercel.com', ['*.vercel.com.'])).toBe(true)
    expect(isCsrfOriginAllowed('vercel.com.', ['vercel.com'])).toBe(true)
    expect(isCsrfOriginAllowed('vercel.com', ['vercel.com.'])).toBe(true)
  })

  it('should only lowercase ASCII A-Z characters (RFC 1035)', () => {
    // RFC 1035 specifies DNS is case-insensitive for 7-bit ASCII only
    // Non-ASCII characters should remain unchanged to prevent security issues
    
    // Punycode domains should work correctly (already ASCII)
    expect(isCsrfOriginAllowed('xn--80akhbyknj4f.com', ['xn--80akhbyknj4f.com'])).toBe(true)
    expect(isCsrfOriginAllowed('XN--80AKHBYKNJ4F.COM', ['xn--80akhbyknj4f.com'])).toBe(true)
    
    // Unicode characters should NOT be lowercased (security requirement)
    // If someone passes raw unicode, it should be matched exactly
    expect(isCsrfOriginAllowed('münchen.com', ['münchen.com'])).toBe(true)
    expect(isCsrfOriginAllowed('MÜNCHEN.com', ['münchen.com'])).toBe(false) // Capital Ü stays capital
    expect(isCsrfOriginAllowed('münchen.com', ['MÜNCHEN.com'])).toBe(false) // They must match exactly
    
    // ASCII portion should still be case-insensitive
    expect(isCsrfOriginAllowed('münchen.COM', ['münchen.com'])).toBe(true)
    expect(isCsrfOriginAllowed('MÜNCHEN.COM', ['MÜNCHEN.com'])).toBe(true)
  })
})


Review comment (security):
I think this does a better job of testing what the comment suggests:

```suggestion
    // ASCII portion should still be case-insensitive
    expect(isCsrfOriginAllowed('MüNCHEN.COM', ['münchen.com'])).toBe(true)
    expect(isCsrfOriginAllowed('mÜnchen.COM', ['MÜNCHEN.com'])).toBe(true)
```

Code after:
import { isCsrfOriginAllowed } from './csrf-protection'

describe('isCsrfOriginAllowed', () => {
  it('should return true when allowedOrigins contains originDomain', () => {
    expect(isCsrfOriginAllowed('vercel.com', ['vercel.com'])).toBe(true)
    expect(isCsrfOriginAllowed('www.vercel.com', ['www.vercel.com'])).toBe(true)
  })

  it('should return true when allowedOrigins contains originDomain with matching pattern', () => {
    expect(isCsrfOriginAllowed('asdf.vercel.com', ['*.vercel.com'])).toBe(true)
    expect(isCsrfOriginAllowed('asdf.vercel.com', ['**.vercel.com'])).toBe(true)
    expect(isCsrfOriginAllowed('asdf.jkl.vercel.com', ['**.vercel.com'])).toBe(
      true
    )
  })

  it("should correctly handle origins that don't have a TLD (eg for localhost)", () => {
    // Single level wildcard
    expect(isCsrfOriginAllowed('subdomain.localhost', ['*.localhost'])).toBe(
      true
    )
    expect(isCsrfOriginAllowed('localhost', ['*.localhost'])).toBe(false)

    // Multi-level wildcard
    expect(isCsrfOriginAllowed('subdomain.localhost', ['**.localhost'])).toBe(
      true
    )
    expect(isCsrfOriginAllowed('a.b.localhost', ['**.localhost'])).toBe(true)
    expect(isCsrfOriginAllowed('localhost', ['**.localhost'])).toBe(false)

    // Exact match
    expect(isCsrfOriginAllowed('localhost', ['localhost'])).toBe(true)
    expect(isCsrfOriginAllowed('subdomain.localhost', ['localhost'])).toBe(
      false
    )

    // Multiple patterns
    expect(
      isCsrfOriginAllowed('subdomain.localhost', ['localhost', '*.localhost'])
    ).toBe(true)
    expect(
      isCsrfOriginAllowed('a.b.localhost', [
        'localhost',
        '*.localhost',
        '**.localhost',
      ])
    ).toBe(true)
  })

  it('should return false when allowedOrigins contains originDomain with non-matching pattern', () => {
    expect(isCsrfOriginAllowed('asdf.vercel.com', ['*.vercel.app'])).toBe(false)
    expect(isCsrfOriginAllowed('asdf.jkl.vercel.com', ['*.vercel.com'])).toBe(
      false
    )
    expect(isCsrfOriginAllowed('asdf.jkl.vercel.app', ['**.vercel.com'])).toBe(
      false
    )
  })

  it('should return false when allowedOrigins does not contain originDomain', () => {
    expect(isCsrfOriginAllowed('vercel.com', ['nextjs.org'])).toBe(false)
  })

  it('should return false when allowedOrigins is undefined', () => {
    expect(isCsrfOriginAllowed('vercel.com', undefined)).toBe(false)
  })

  it('should return false when allowedOrigins is empty', () => {
    expect(isCsrfOriginAllowed('vercel.com', [])).toBe(false)
  })

  it('should return false when allowedOrigins is empty string', () => {
    expect(isCsrfOriginAllowed('vercel.com', [''])).toBe(false)
  })

  it('wildcards are only supported below the domain level', () => {
    expect(isCsrfOriginAllowed('vercel.com', ['*'])).toBe(false)
    expect(isCsrfOriginAllowed('vercel.com', ['**'])).toBe(false)
  })

  it('should match case-insensitively (RFC 1035)', () => {
    // DNS names are case-insensitive per RFC 1035
    expect(isCsrfOriginAllowed('sub.VERCEL.com', ['*.vercel.com'])).toBe(true)
    expect(isCsrfOriginAllowed('SUB.vercel.COM', ['*.vercel.com'])).toBe(true)
    expect(isCsrfOriginAllowed('VERCEL.COM', ['vercel.com'])).toBe(true)
    expect(isCsrfOriginAllowed('vercel.com', ['VERCEL.COM'])).toBe(true)
    expect(isCsrfOriginAllowed('Sub.Vercel.Com', ['**.vercel.com'])).toBe(true)
  })
})