Skip to content

x-sensitive-data

Automatically mask sensitive data in structured logs.

Overview

The x-sensitive-data extension allows you to mark fields as containing sensitive information that should be automatically masked when logging. This is useful for preventing accidental exposure of sensitive data like passwords, API keys, credit card numbers, etc. in logs.

The extension generates two methods:

  • Masked() - Returns a copy of the struct with sensitive fields masked
  • LogValue() - Implements Go's slog.LogValuer interface (calls Masked() internally)

This means:

  • JSON serialization stays raw - json.Marshal(user) returns the actual values (needed for API calls)
  • Masked JSON - json.Marshal(user.Masked()) returns masked values
  • Structured logging is masked - slog.Info("user", "user", user) automatically masks sensitive fields

Masking Strategies

The extension supports several masking strategies:

  • full: Replace the entire value with a fixed-length mask ("********") to hide both content and length
  • regex: Mask only parts of the value matching a regex pattern (keeps context visible)
  • hash: Replace the value with a SHA256 hash (one-way, useful for verification)
  • partial: Mask the middle part while keeping prefix/suffix visible (e.g., show last 4 digits of credit card)

Example

openapi: 3.0.0
info:
  title: Sensitive Data Example
  version: 1.0.0
paths:
  /users:
    get:
      operationId: getUsers
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
components:
  schemas:
    User:
      type: object
      required:
        - id
        - username
      properties:
        id:
          type: integer
          format: int64
        username:
          type: string
        email:
          type: string
          x-sensitive-data:
            mask: full
        ssn:
          type: string
          x-sensitive-data:
            mask: regex
            pattern: '\d{3}-\d{2}-\d{4}'
        creditCard:
          type: string
          x-sensitive-data:
            mask: partial
            keepSuffix: 4
        apiKey:
          type: string
          x-sensitive-data:
            mask: hash
            algorithm: sha256

Generated Code

This generates a struct with Masked() and LogValue() methods:

type User struct {
    ID         int64   `json:"id" validate:"required"`
    Username   string  `json:"username" validate:"required"`
    Email      *string `json:"email,omitempty" sensitive:""`
    Ssn        *string `json:"ssn,omitempty" sensitive:""`
    CreditCard *string `json:"creditCard,omitempty" sensitive:""`
    APIKey     *string `json:"apiKey,omitempty" sensitive:""`
}

func (u User) Validate() error {
    return runtime.ConvertValidatorError(typesValidator.Struct(u))
}

// Masked returns a copy of the struct with sensitive fields masked.
func (u User) Masked() User {
    masked := u
    if masked.Email != nil {
        v := runtime.MaskSensitiveString(*masked.Email, runtime.SensitiveDataConfig{
            Type:       runtime.MaskTypeFull,
            Pattern:    "",
            Algorithm:  "",
            KeepPrefix: 0,
            KeepSuffix: 0,
        })
        masked.Email = &v
    }
    if masked.Ssn != nil {
        v := runtime.MaskSensitiveString(*masked.Ssn, runtime.SensitiveDataConfig{
            Type:       runtime.MaskTypeRegex,
            Pattern:    "\\d{3}-\\d{2}-\\d{4}",
            Algorithm:  "",
            KeepPrefix: 0,
            KeepSuffix: 0,
        })
        masked.Ssn = &v
    }
    if masked.CreditCard != nil {
        v := runtime.MaskSensitiveString(*masked.CreditCard, runtime.SensitiveDataConfig{
            Type:       runtime.MaskTypePartial,
            Pattern:    "",
            Algorithm:  "",
            KeepPrefix: 0,
            KeepSuffix: 4,
        })
        masked.CreditCard = &v
    }
    if masked.APIKey != nil {
        v := runtime.MaskSensitiveString(*masked.APIKey, runtime.SensitiveDataConfig{
            Type:       runtime.MaskTypeHash,
            Pattern:    "",
            Algorithm:  "sha256",
            KeepPrefix: 0,
            KeepSuffix: 0,
        })
        masked.APIKey = &v
    }
    return masked
}

// LogValue implements slog.LogValuer interface for structured logging.
func (u User) LogValue() slog.Value {
    type plain User
    return slog.AnyValue(plain(u.Masked()))
}

Behavior

When logging with slog:

user := User{
    ID:         1,
    Username:   "johndoe",
    Email:      Ptr("user@example.com"),
    Ssn:        Ptr("123-45-6789"),
    CreditCard: Ptr("1234-5678-9012-3456"),
    APIKey:     Ptr("my-secret-key"),
}

// Masked in logs
slog.Info("user created", "user", user)
// Output: user={id=1 username=johndoe email=******** ssn=***-**-**** creditCard=********3456 apiKey=325ededd...}

// Raw in JSON (for API calls)
json.Marshal(user)
// Output: {"id":1,"username":"johndoe","email":"user@example.com","ssn":"123-45-6789",...}

Masked JSON Output

If you need masked JSON (e.g., for API responses that should hide sensitive data), use the Masked() method:

// Get masked JSON
maskedJSON, _ := json.Marshal(user.Masked())
// Output: {"id":1,"username":"johndoe","email":"********","ssn":"***-**-****",...}

Partial Masking Options

  • keepPrefix: Number of characters to keep at the start
  • keepSuffix: Number of characters to keep at the end

Full Example

You can see this in more detail in the example code.