Перейти к содержанию

24. Data и Privacy

Зачем это нужно?

Агент работает с персональными данными пользователей (email, телефон, адрес). Эти данные попадают в логи и отправляются в LLM API. Без защиты данных вы нарушаете GDPR и рискуете утечкой данных.

Реальный кейс

Ситуация: Агент обрабатывает запрос пользователя: "Мой email john@example.com, телефон +7-999-123-4567. Создай тикет".

Проблема: PII попадает в логи и отправляется в LLM API без маскирования. При утечке логов персональные данные попадают в чужие руки.

Решение: Обнаружение и маскирование PII перед логированием и отправкой в LLM, защита секретов, redaction логов, TTL для хранения.

Теория простыми словами

Что такое PII?

PII (Personally Identifiable Information) — это данные, которые позволяют идентифицировать человека: email, телефон, адрес, паспорт.

Что такое redaction?

Redaction — это удаление чувствительных данных из логов перед сохранением.

Как это работает (пошагово)

Шаг 1: Обнаружение и маскирование PII

Маскируйте PII перед отправкой в LLM:

import "regexp"

func sanitizePII(text string) string {
    // Маскируем email
    emailRegex := regexp.MustCompile(`\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b`)
    text = emailRegex.ReplaceAllString(text, "[EMAIL_REDACTED]")

    // Маскируем телефон (российский и международный формат)
    phoneRegex := regexp.MustCompile(`[\+]?[78]\s?[\(-]?\d{3}[\)-]?\s?\d{3}[-]?\d{2}[-]?\d{2}`)
    text = phoneRegex.ReplaceAllString(text, "[PHONE_REDACTED]")

    return text
}

Примечание: Регулярки выше — упрощённый пример для обучения. В production используйте специализированные библиотеки: Microsoft Presidio для PII detection, truffleHog или detect-secrets для поиска секретов в коде. Они покрывают десятки форматов данных и регулярно обновляются.

Шаг 2: Защита секретов

Никогда не логируйте секреты:

func sanitizeSecrets(text string) string {
    // Удаляем паттерны типа "password: ..."
    secretRegex := regexp.MustCompile(`(?i)(password|api_key|token|secret)\s*[:=]\s*[\w-]+`)
    text = secretRegex.ReplaceAllString(text, "[SECRET_REDACTED]")

    return text
}

Шаг 3: Redaction логов

Удаляйте чувствительные данные из логов:

func logWithRedaction(runID string, data map[string]any) {
    sanitized := make(map[string]any)
    for k, v := range data {
        if str, ok := v.(string); ok {
            sanitized[k] = sanitizePII(sanitizeSecrets(str))
        } else {
            sanitized[k] = v
        }
    }

    logJSON, _ := json.Marshal(sanitized)
    log.Printf("AGENT_RUN: %s", string(logJSON))
}

Где это встраивать в нашем коде

Точка интеграции: User Input

В labs/lab05-human-interaction/main.go санитизируйте входные данные:

userInput := sanitizePII(sanitizeSecrets(rawInput))
messages = append(messages, openai.ChatCompletionMessage{
    Role: "user",
    Content: userInput,
})

Типовые ошибки

Ошибка 1: PII попадает в логи

Симптом: Email и телефоны пользователей видны в логах.

Решение: Маскируйте PII перед логированием.

Ошибка 2: Секреты логируются

Симптом: API ключи и пароли попадают в логи.

Решение: Удаляйте секреты из логов через redaction.

Ошибка 3: Нет Data Retention Policy

Симптом: Логи растут бесконечно, дисковое пространство заканчивается. Старые логи содержат PII, но никто их не чистит.

Причина: Нет политики хранения данных. Логи и трассировки записываются без TTL и ротации.

Решение:

// ПЛОХО: логи без ограничения хранения
func writeLog(entry LogEntry) {
    file.Write(entry) // Файл растёт бесконечно
}

// ХОРОШО: TTL и ротация
type RetentionPolicy struct {
    MaxAge    time.Duration // Максимальный срок хранения
    MaxSizeMB int          // Максимальный размер в MB
}

func (p *RetentionPolicy) Cleanup(logDir string) error {
    entries, _ := os.ReadDir(logDir)
    for _, entry := range entries {
        info, _ := entry.Info()
        if time.Since(info.ModTime()) > p.MaxAge {
            os.Remove(filepath.Join(logDir, entry.Name()))
        }
    }
    return nil
}

Ошибка 4: Отсутствие шифрования в Transit

Симптом: Данные между агентом и LLM API передаются по незащищённому каналу. Man-in-the-middle атака перехватывает запросы с PII.

Причина: HTTP вместо HTTPS, отсутствие TLS-верификации, самоподписанные сертификаты без проверки.

Решение:

// ПЛОХО: HTTP без шифрования
client := &http.Client{}
resp, _ := client.Post("http://api.llm.example.com/v1/chat", ...)

// ХОРОШО: HTTPS + проверка сертификатов
client := &http.Client{
    Transport: &http.Transport{
        TLSClientConfig: &tls.Config{
            MinVersion: tls.VersionTLS12,
        },
    },
}
resp, _ := client.Post("https://api.llm.example.com/v1/chat", ...)

Ошибка 5: PII в трассировках

Симптом: OpenTelemetry трассировки содержат пользовательские данные. Dashboards и алерты показывают email и телефоны.

Причина: Span-атрибуты и логи добавляются без фильтрации.

Решение:

// ПЛОХО: PII попадает в span-атрибуты
span.SetAttributes(
    attribute.String("user.input", userMessage), // Содержит PII!
)

// ХОРОШО: санитизация перед добавлением в трассировку
span.SetAttributes(
    attribute.String("user.input", sanitizePII(userMessage)),
    attribute.String("user.input_hash", hashForCorrelation(userMessage)),
)

Мини-упражнения

Упражнение 1: Детектор PII

Реализуйте детектор PII, который находит email и телефоны в тексте и возвращает список найденных совпадений:

type PIIMatch struct {
    Type  string // "email", "phone"
    Value string // Найденное значение
    Start int    // Позиция начала
    End   int    // Позиция конца
}

func detectPII(text string) []PIIMatch {
    // Реализуйте поиск email и телефонов
    // Поддержите форматы: user@example.com, +7-999-123-4567, 8 (999) 123-45-67
}

Ожидаемый результат:

  • Находит email-адреса в произвольном тексте
  • Находит телефоны в разных форматах (с +7, 8, скобками, дефисами)
  • Возвращает позиции совпадений для точечной замены

Упражнение 2: Middleware для Redaction логов

Создайте middleware, который автоматически маскирует PII во всех логах агента:

type RedactionMiddleware struct {
    next     slog.Handler
    patterns []RedactionPattern
}

type RedactionPattern struct {
    Name    string
    Regex   *regexp.Regexp
    Replace string
}

func NewRedactionMiddleware(next slog.Handler) *RedactionMiddleware {
    return &RedactionMiddleware{
        next: next,
        patterns: []RedactionPattern{
            {Name: "email", Regex: regexp.MustCompile(`\b[\w.+-]+@[\w.-]+\.\w{2,}\b`), Replace: "[EMAIL]"},
            {Name: "phone", Regex: regexp.MustCompile(`[\+]?[78]\s?[\(-]?\d{3}[\)-]?\s?\d{3}[-]?\d{2}[-]?\d{2}`), Replace: "[PHONE]"},
        },
    }
}

func (m *RedactionMiddleware) Handle(ctx context.Context, r slog.Record) error {
    // Реализуйте фильтрацию всех атрибутов записи
}

Ожидаемый результат:

  • Все строковые атрибуты проходят через redaction
  • Паттерны легко расширяются (добавить ИНН, паспорт, карту)
  • Middleware прозрачен для остального кода

Критерии сдачи / Чек-лист

Сдано:

  • PII маскируется перед отправкой в LLM
  • Секреты не логируются
  • Логи проходят redaction

Не сдано:

  • PII не маскируется
  • Секреты логируются

Связь с другими главами

Что дальше?

После понимания Data и Privacy переходите к: