Frontend Labs
⋯

Day 79 — Sensitive Data Protection

Learning Objectives

  • Mask sensitive fields in logs (PII protection, GDPR compliance)
  • Scrub PII from response payloads before returning to clients
  • Build log filtering for SIEM-safe audit trails

Exercise: PII Scrubber Middleware

package pii

import (
    "bytes"
    "encoding/json"
    "io"
    "net/http"
    "regexp"
    "strings"
)

// sensitiveFields — mask these JSON field values in logs
var sensitiveFields = map[string]bool{
    "password": true, "passwd": true, "secret": true,
    "token":    true, "api_key": true, "apikey": true,
    "ssn":      true, "social_security_number": true,
    "card_number": true, "cvv": true, "pin": true,
    "credit_card": true, "bank_account": true,
}

// PII patterns — redact in response bodies
var (
    emailRe    = regexp.MustCompile(`[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}`)
    ssrRe      = regexp.MustCompile(`\b\d{3}-\d{2}-\d{4}\b`) // SSN: 123-45-6789
    cardRe     = regexp.MustCompile(`\b\d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{4}\b`)
    phoneRe    = regexp.MustCompile(`\b\+?1?\s*\(?\d{3}\)?[\s.\-]?\d{3}[\s.\-]?\d{4}\b`)
)

// MaskJSONBody replaces sensitive field values with "***"
func MaskJSONBody(data []byte) []byte {
    var parsed interface{}
    if err := json.Unmarshal(data, &parsed); err != nil {
        return data // not JSON, return as-is
    }
    masked := maskValue(parsed)
    result, err := json.Marshal(masked)
    if err != nil { return data }
    return result
}

func maskValue(v interface{}) interface{} {
    switch val := v.(type) {
    case map[string]interface{}:
        result := make(map[string]interface{}, len(val))
        for k, v := range val {
            if sensitiveFields[strings.ToLower(k)] {
                result[k] = "***"
            } else {
                result[k] = maskValue(v)
            }
        }
        return result
    case []interface{}:
        result := make([]interface{}, len(val))
        for i, v := range val {
            result[i] = maskValue(v)
        }
        return result
    default:
        return v
    }
}

// ScrubPIIFromBody replaces PII patterns with redacted markers
func ScrubPIIFromBody(data []byte) []byte {
    result := data
    result = emailRe.ReplaceAll(result, []byte("[EMAIL REDACTED]"))
    result = ssrRe.ReplaceAll(result, []byte("[SSN REDACTED]"))
    result = cardRe.ReplaceAll(result, []byte("[CARD REDACTED]"))
    result = phoneRe.ReplaceAll(result, []byte("[PHONE REDACTED]"))
    return result
}

// PIIProtectedLogger wraps a handler, masks request body in logs, scrubs response
type piiCapture struct {
    http.ResponseWriter
    buf bytes.Buffer
}

func (p *piiCapture) Write(b []byte) (int, error) {
    p.buf.Write(b)
    return p.ResponseWriter.Write(b)
}

func PIIProtectionMiddleware(scrubResponses bool) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Read and mask request body for logging
            body, _ := io.ReadAll(io.LimitReader(r.Body, 1<<20))
            r.Body.Close()

            maskedBody := MaskJSONBody(body)
            // Log maskedBody (not original body) — password="***"
            fmt.Printf("[REQ] path=%s body=%s\n", r.URL.Path, maskedBody)

            // Restore body for handler
            r.Body = io.NopCloser(bytes.NewReader(body))

            if scrubResponses {
                // Capture and scrub response
                capture := &piiCapture{ResponseWriter: w}
                next.ServeHTTP(capture, r)
                // Note: response already written; scrubbing would require buffering first
                // In production: use a buffering ResponseWriter (more complex)
            } else {
                next.ServeHTTP(w, r)
            }
        })
    }
}

Real-World Context

GDPR Article 32 requires "appropriate technical measures" to protect personal data. PCI DSS Requirement 3 says cardholder data must never be logged. A common compliance audit finding is "passwords appear in application logs" — this middleware prevents it. The sensitiveFields map should be reviewed quarterly and aligned with your data classification policy.


Generated from week11.md · 8 Oct 2026 16:36