Skip to content

ip_address field in auth.audit_log_entries is always empty, even with Sb-Forwarded-For configured #2370

@welch

Description

@welch

Bug report

  • [ x] I confirm this is a bug with Supabase, not with my own application.
  • [ x] I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

When self-hosting Supabase with a reverse proxy (nginx), the ip_address field in auth.audit_log_entries is always empty, even when:

  1. GOTRUE_SECURITY_SB_FORWARDED_FOR_ENABLED=true is set
  2. The reverse proxy sends the Sb-Forwarded-For header with the client IP
  3. GoTrue v2.186.0+ is used (which added Sb-Forwarded-For support)

The user_agent field is also not persisted to the database, though it does appear in GoTrue's stdout logs.

Steps to Reproduce

  1. Deploy self-hosted Supabase with Docker
  2. Configure nginx reverse proxy with:
    proxy_set_header Sb-Forwarded-For $remote_addr;
  3. Set GOTRUE_SECURITY_SB_FORWARDED_FOR_ENABLED=true in GoTrue environment
  4. Perform a signup/login through the reverse proxy
  5. Query the audit log:
    SELECT ip_address, payload->>'action', payload->>'actor_username'
    FROM auth.audit_log_entries
    ORDER BY created_at DESC LIMIT 5;

Expected Behavior

  • ip_address should contain the client IP from the Sb-Forwarded-For header
  • user_agent should be persisted in the payload

Actual Behavior

  • ip_address is always empty string ''
  • user_agent appears in GoTrue stdout logs but is not in the database payload:

GoTrue stdout (has user_agent):

{"auth_audit_event":{"action":"login","ip_address":"","user_agent":"Mozilla/5.0..."},...}

Database payload (missing user_agent):

{"action":"login","actor_id":"...","traits":{"provider":"email"}}

Root Cause Analysis

After tracing through the code:

  1. sbff.Middleware correctly extracts IP from Sb-Forwarded-For header and stores it in request context

  2. sbff.GetIPAddress(req) can retrieve the IP for rate limiting (this works)

  3. However, callers of NewAuditLogEntry() pass "" for the IP address parameter:

    // From internal/api/token.go, signup.go, etc.
    models.NewAuditLogEntry(config.AuditLog, r, tx, user, models.LoginAction, "", map[string]interface{}{...})
    //                                                                        ^^
    //                                                            Empty string passed for IP
  4. NewAuditLogEntry() does not extract IP from the request context - it only uses the value passed to it

Proposed Fix

Callers of NewAuditLogEntry() should use sbff.GetIPAddress(req) to extract the IP:

ipAddress := ""
if addr, ok := sbff.GetIPAddress(r); ok {
    ipAddress = addr
}
models.NewAuditLogEntry(config.AuditLog, r, tx, user, models.LoginAction, ipAddress, traits)

Or alternatively, NewAuditLogEntry() could extract the IP from the request context when an empty string is passed.

Environment

  • GoTrue version: v2.186.0 (also tested v2.184.0)
  • Deployment: Self-hosted Docker
  • Reverse proxy: nginx
  • OS: Linux/macOS

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions