Опирается на правила:
AUTH-16…AUTH-18из Auth Patterns Style Guide → раздел 7. PII и секреты.
Важно знать
- PII не в логах (даже DEBUG), не в
Exception.getMessage, не вProblemDetails.detail, не в Kafka-events широкого scope.- Передавать только id, payload подгружается через специальный сервис по запросу.
- Секреты никогда в git — только через
application-${profile}.ymlв Vault / SealedSecrets / env vars.RestControllerAdviceне выводитcause.getMessage()вdetail— только заранее заданное сообщение по коду.- PII leak в одном слое — пробивает всю безопасность системы.
@Slf4jlog.info("User: {}", user) — потенциальная утечка если вtoString()есть PII.
PII (Personally Identifiable Information) — это данные, которые регулирующие органы и compliance считают защищаемыми: email, телефон, ФИО, паспорт, адрес, IP, биометрия. Утечка через лог, через response, через Kafka — это инцидент compliance + штраф + потеря доверия. UCP формулирует правила «PII никогда не покидает domain layer».
PII не в логах
AUTH-16: запрет на все уровни логирования.
// КАТАСТРОФА
log.info("User registered: email={} phone={}", user.email(), user.phone());
// ХОРОШО — только internal id
log.info("User registered: userId={}", user.id());
// ЕСЛИ нужен PII для диагностики — masked
log.info("Email verification sent: userId={} emailMask={}",
user.id(), maskEmail(user.email())); // u***@example.com
Маскирование:
public final class PiiMasking {
public static String maskEmail(String email) {
if (email == null || !email.contains("@")) return "***";
var parts = email.split("@");
return parts[0].charAt(0) + "***@" + parts[1];
}
public static String maskPhone(String phone) {
if (phone == null || phone.length() < 4) return "***";
return "***" + phone.substring(phone.length() - 4);
}
}
Дополнительно — toString() агрегата не возвращает PII:
public record Customer(Long id, String email, String phone, String fullName) {
@Override
public String toString() {
return "Customer[id=" + id + "]";
}
}
Подробнее — Logging → PII-гигиена.
PII не в Exception.getMessage()
AUTH-16 + AUTH-18: exception message может попасть в logs И в response.
// КАТАСТРОФА
throw new InvalidEmailException("Email " + email + " is invalid format");
Что происходит:
log.error(...)пишет message в лог → PII leak.RestControllerAdvicemapping →ProblemDetails.detail = "Email user@example.com is invalid format"→ клиент (включая attacker, который пытается enumerate emails) видит подтверждение.
Корректно:
public class InvalidEmailException extends DomainException {
public InvalidEmailException() {
super("INVALID_EMAIL_FORMAT", "Provided email is in invalid format");
}
}
В message и detail — общее сообщение без значения. Если нужно для диагностики — лог с masked email, не exception.
RestControllerAdvice без cause.getMessage()
AUTH-18: handler-mapping явный, не «прокинуть exception дальше».
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OrderDomainException.class)
public ProblemDetail handleOrderDomain(OrderDomainException ex) {
var problem = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
problem.setType(URI.create("urn:order:domain"));
problem.setTitle("Order operation failed");
problem.setDetail(switch (ex.errorCode()) {
case "ORDER_NOT_FOUND" -> "Order with given id not found";
case "ORDER_NOT_CANCELLABLE" -> "Order in current status cannot be cancelled";
default -> "Order operation failed";
});
problem.setProperty("errorCode", ex.errorCode());
return problem;
}
@ExceptionHandler(Exception.class)
public ProblemDetail handleGeneric(Exception ex) {
var problem = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
problem.setTitle("Internal server error");
problem.setDetail("An unexpected error occurred. Reference: " + MDC.get("requestId"));
return problem;
}
}
Что не делаем:
// ПЛОХО
problem.setDetail(ex.getMessage()); // может содержать PII
problem.setDetail(ex.getCause().getMessage()); // тем более внутренние details
problem.setProperty("stackTrace", ex.getStackTrace()); // утечка структуры
Подробнее — Error handling → RFC 9457.
PII не в Kafka широкого scope
AUTH-16: Kafka — broadcast canal.
// ПЛОХО — все consumers видят PII
public record OrderConfirmedEvent(
Long orderId,
String customerEmail, // ← leak
String customerPhone // ← leak
) {}
// ХОРОШО — id, PII подгружается через customer-service по необходимости
public record OrderConfirmedEvent(
Long orderId,
Long customerId,
Money totalAmount
) {}
Notification-service, которому нужен email — делает GET /customers/{id}/email к customer-service. Это даёт точечный access + audit log на customer-service-side.
Подробнее — Kafka → event design и Kafka → security.
Секреты не в git
AUTH-17: правило для всех уровней.
# КАТАСТРОФА — в git
spring:
datasource:
password: super-secret-password-prod
# ХОРОШО — env
spring:
datasource:
password: ${DB_PASSWORD}
Откуда брать значения env vars:
- Vault (HashiCorp) —
kubectlчерезvault-secrets-operatorили Spring Cloud Vault. - SealedSecrets (Kubernetes) — шифрованный secret в git, расшифровывается оператором в кластере.
- Cloud Secret Manager (AWS SM, GCP SM) — IAM-роль pod-а получает secret напрямую.
- Kubernetes Secret (с
imagePullPolicy-style рестрикциями) — минимальный baseline.
.gitignore со списком sensitive файлов:
application-prod.yml
application-secrets.yml
*.pem
*.key
.env
Pre-commit hook через git-secrets или trufflehog — отдельная защита от случайного commit.
При обнаружении secret в git history — rotate секрет (даже если коммит удалён, он может быть в forks, CI cache, attacker уже клонировал).
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
| PII в логах (email, phone) | AUTH-16 | masked или только id |
PII в Exception.getMessage() | AUTH-16 + AUTH-18 | error code, общее сообщение |
PII в ProblemDetails.detail | AUTH-18 | mapping по error code |
cause.getMessage() в detail | AUTH-18 | заранее заданное сообщение |
| PII в Kafka-events широкого scope | AUTH-16 | только id + lazy fetch |
password: super-secret в application.yml | AUTH-17 | env ${DB_PASSWORD} |
| Secrets в git history | AUTH-17 | rotate immediately |
stackTrace в response | AUTH-18 | только traceId для cross-ref |
@Slf4j log.info("User: {}", user) с PII в toString | AUTH-16 | toString без PII |
Куда дальше
- Auth → раздел 7. PII и секреты — нормативные формулировки.
- Observability → logging —
R-OBS-LOG-X1PII-гигиена. - Error handling → RFC 9457 —
ProblemDetails.detailправила. - Kafka → event design — PII не в payload.
- Kafka → security — restricted PII topics.
- Audit admin-команд — audit без PII в plain.
- Security style guide — общая дисциплина.