7f5288726a
Monolog PiiScrubbingProcessor (телефоны/email -> [PHONE]/[EMAIL]) + ScrubPii tap на single/daily в config/logging.php. Pest 6/6 GREEN. Sentry-scrubbing (OPEN-И-16) не реализуем: sentry-laravel не установлен — open-item. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
65 lines
2.3 KiB
PHP
65 lines
2.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Logging;
|
|
|
|
use Monolog\LogRecord;
|
|
use Monolog\Processor\ProcessorInterface;
|
|
|
|
/**
|
|
* Monolog-процессор: маскирует ПДн в логах перед записью.
|
|
*
|
|
* Закрывает Medium go-live: laravel.log (LOG_LEVEL=debug) мог сохранить телефон/email
|
|
* открытым, если они попадут в текст исключения или контекст. Процессор ловит ВСЕ
|
|
* записи каналов, к которым подключён (см. App\Logging\ScrubPii + config/logging.php),
|
|
* централизованно — надёжнее правки отдельных вызовов Log::.
|
|
*/
|
|
final class PiiScrubbingProcessor implements ProcessorInterface
|
|
{
|
|
/**
|
|
* Телефоны РФ: 11 цифр в формате 7XXXXXXXXXX / 8XXXXXXXXXX / +7XXXXXXXXXX.
|
|
* Lookbehind/lookahead не дают маскировать часть более длинной цифровой строки
|
|
* (например 14-значный технический id).
|
|
*/
|
|
private const PHONE_PATTERN = '/(?<!\d)(?:\+?7|8)\d{10}(?!\d)/';
|
|
|
|
private const EMAIL_PATTERN = '/[\p{L}0-9._%+\-]+@[\p{L}0-9.\-]+\.\p{L}{2,}/u';
|
|
|
|
public function __invoke(LogRecord $record): LogRecord
|
|
{
|
|
return $record->with(
|
|
message: $this->scrub($record->message),
|
|
context: $this->scrubArray($record->context),
|
|
extra: $this->scrubArray($record->extra),
|
|
);
|
|
}
|
|
|
|
private function scrub(string $value): string
|
|
{
|
|
$value = preg_replace(self::PHONE_PATTERN, '[PHONE]', $value) ?? $value;
|
|
|
|
return preg_replace(self::EMAIL_PATTERN, '[EMAIL]', $value) ?? $value;
|
|
}
|
|
|
|
/**
|
|
* @param array<array-key, mixed> $data
|
|
* @return array<array-key, mixed>
|
|
*/
|
|
private function scrubArray(array $data): array
|
|
{
|
|
$result = [];
|
|
foreach ($data as $key => $value) {
|
|
if (is_string($value)) {
|
|
$result[$key] = $this->scrub($value);
|
|
} elseif (is_array($value)) {
|
|
$result[$key] = $this->scrubArray($value);
|
|
} else {
|
|
$result[$key] = $value;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|