Маскування даних

Ядро приховує чутливі значення перед записом у логи, debug-панелі та інші місця, де дані можуть потрапити до сторонніх очей. DataMasker рекурсивно обробляє масиви, об’єкти та рядки за правилами ключів і regex.

Реєстрація: DataMaskerServiceProvider у провайдерах HTTP (перед LogServiceProvider, щоб логер отримав маскер). Правила за замовчуванням — у config/masking.php (див. конфігурація, Конфігурація → masking.php).

Архітектура

КласРоль
DataMaskerInterfaceКонтракт: mask(), clearRules()
DataMaskerОркестратор правил, рекурсія по структурах
RegexDataMaskerRuleRegex для рядків і шаблонів ключів (з config/masking.php)
DataMaskerRuleInterfaceВласне правило (apply, isSensitiveKey)

За замовчуванням DataMaskerServiceProvider створює DataMasker з одним правилом RegexDataMaskerRule з патернами з конфігурації. Сервіс — shared singleton.

config/masking.php

У skeleton файл config/masking.php задає три групи налаштувань. DataMaskerServiceProvider читає їх через ConfigInterface (ConfigKey::MASKING_PATTERNS, MASKING_KEY_PATTERNS, MASKING_RULES) і будує один RegexDataMaskerRule плюс додаткові класи з masking.rules:

<?php declare(strict_types=1);

return [
    'masking' => [
        // Regex для рядків (лог-повідомлення, scalar values)
        'patterns' => [
            '/[a-z0-9_\-\+\.]+@[a-z0-9\-]+\.[a-z]{2,}/i' => '***@***.***',
            '/\d{4}-\d{4}-\d{4}-\d{4}/' => '****-****-****-****',
            '/(password|token|api_key|...)[:=]+([^\s,;]+)/i' => '$1=*****',
        ],
        // Regex для імен ключів у масивах/об'єктах → значення стає ***
        'key_patterns' => [
            '/.*password.*/i',
            '/.*token.*/i',
            '/.*_csrf_token.*/i',
            '/api_key/i',
        ],
        // Класи DataMaskerRuleInterface — резолвяться з DI
        'rules' => [
            // MyCustomMaskerRule::class,
        ],
    ],
];
  • patternsRegexDataMaskerRule::apply() для рядків (email, карти, password=… у тексті)
  • key_patternsisSensitiveKey(): збіг імені ключа → DataMasker::MASK_CHARS (***)
  • rules — додаткові правила; клас має реалізувати DataMaskerRuleInterface

Зміни в masking.php не потребують правок PHP — достатньо перезапуску запиту.

Правила за замовчуванням (skeleton)

Те, що дає stock config/masking.php у Concept Skeleton:

Ключі (key_patterns) — значення поля замінюється на ***:

  • імена з password, token, _csrf_token, secret
  • api_key, authorization

Рядки (patterns) — regex-заміна в тексті:

  • email → ***@***.***
  • номер картки 9999-9999-9999-9999 → зірочки
  • пари на кшталт password=..., token=..., api_key=... → значення замінюється на *****

Інтеграція з логером

LogServiceProvider передає DataMaskerInterface у Concept\Core\Services\Logger\Logger. Monolog-процесор маскує $record->context перед записом у файл або stderr.

$logger->info('User updated', [
    'email' => 'user@example.com',
    'password' => 'secret123',  // у лог потрапить ***
]);

Повідомлення ($message) не маскується автоматично — не вставляйте паролі та токени у текст повідомлення, лише в context.

Ручне використання

use Concept\Core\Services\DataMasker\Contracts\DataMaskerInterface;

/** @var DataMaskerInterface $masker */
$masker = $container->get(DataMaskerInterface::class);

$safe = $masker->mask([
    'login' => 'oleh',
    'password' => 'hunter2',
    'card' => '4111-1111-1111-1111',
]);

Метод mask() для масивів і об’єктів спочатку робить deepClone, щоб не змінювати оригінал. maskRecursive() змінює передану структуру на місці — корисно, коли клонування зайве.

Власні правила

Переважно достатньо розширити config/masking.php (див. вище). Якщо потрібен повний контроль — у власному провайдері (після DataMaskerServiceProvider) можна перевизначити binding:

use Concept\Core\Services\DataMasker\Contracts\DataMaskerInterface;
use Concept\Core\Services\DataMasker\DataMasker;
use Concept\Core\Services\DataMasker\RegexDataMaskerRule;

$masker = new DataMasker();
$masker->addRule(new RegexDataMaskerRule(
    patterns: ['/iban=\d+/i' => 'iban=***'],
    keyPatterns: ['/.*_pin$/i']
));

$container->add(DataMaskerInterface::class, fn () => $masker)->setShared(true);

Або додайте клас у masking.rules у конфігурації — провайдер підключить його через DI. Для складнішої логіки реалізуйте DataMaskerRuleInterface.

DebugBar та інші компоненти

Компонент DebugBar інжектує DataMaskerInterface у колектор запиту — у панелі $_POST, $_SESSION тощо показуються вже замасковані значення. Будь-який власний інструмент діагностики варто будувати так само.

Маскування не замінює шифрування в БД і не прибирає чутливі поля з HTTP-відповідей. Воно знижує ризик витоку через логи та dev-інструменти.