---
title: "GN012 – Do Not Log Request Bodies Containing Sensitive Data"
impact: high
impactDescription: "Logging raw request bodies exposes passwords, tokens, credit card numbers, and PII to anyone with log access, violating GDPR, PCI-DSS, and basic security hygiene."
tags: [go, gin, security, logging]
---

# GN012 – Do Not Log Request Bodies Containing Sensitive Data

## Rule

Never log the raw HTTP request body. Log only request metadata (method, path, status, duration, request ID). If you must log body fields for debugging, explicitly allowlist safe fields and mask or omit sensitive ones.

## Why

Request bodies for auth endpoints contain passwords. Payment endpoints contain card data. Profile endpoints contain PII. Logging bodies stores this data in plaintext log files, log aggregators, and monitoring tools — where it lives far beyond the request lifetime and is often accessible to many people.

## Wrong

```go
// ❌ Logging the full request body
func RequestBodyLogger() gin.HandlerFunc {
    return func(c *gin.Context) {
        body, _ := io.ReadAll(c.Request.Body)
        log.Printf("Request body: %s", body)   // ❌ logs passwords, tokens, PII
        c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
        c.Next()
    }
}

// ❌ Logging bound request struct directly (may contain passwords)
func (h *AuthHandler) Login(c *gin.Context) {
    var req LoginRequest
    c.ShouldBindJSON(&req)
    log.Printf("Login attempt: %+v", req)   // ❌ req.Password logged in plaintext
}
```

## Correct

```go
// ✅ Log only safe metadata — never raw body
func RequestLogger(logger *slog.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        logger.Info("request",
            slog.String("method",     c.Request.Method),
            slog.String("path",       c.Request.URL.Path),
            slog.Int("status",        c.Writer.Status()),
            slog.Duration("duration", time.Since(start)),
            slog.String("ip",         c.ClientIP()),
            slog.String("request_id", c.GetString("request_id")),
            // ✅ no body, no headers that might contain auth tokens
        )
    }
}

// ✅ Log only safe fields from struct - never password/token fields
func (h *AuthHandler) Login(c *gin.Context) {
    var req LoginRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    // ✅ Log only the email, not the password
    h.logger.Info("login attempt", slog.String("email", req.Email))
    // ...
}
```

## Sensitive fields to never log

- `password`, `password_confirmation`, `current_password`
- `token`, `access_token`, `refresh_token`, `api_key`, `secret`
- `card_number`, `cvv`, `expiry`
- `ssn`, `national_id`, `date_of_birth` (PII)
- `Authorization` header value

## Notes

- If you need request body logging for debugging, create a separate debug middleware that is only enabled when `GIN_MODE=debug`.
- Use log scrubbing libraries (e.g., `go-sanitize`) as a last-resort safety net, but don't rely on them — fix the root cause.
- `c.Request.Header` can contain `Authorization: Bearer <token>` — log header names only, never values.
