Data Masking
The core hides sensitive values before they are written to logs, debug panels, and other places
where data may be exposed to third parties. DataMasker recursively processes arrays,
objects, and strings according to key and regex rules.
Registration: DataMaskerServiceProvider in
HTTP providers (before
LogServiceProvider, so the logger receives the masker).
Default rules live in config/masking.php
(see configuration, Configuration → masking.php).
Architecture
| Class | Role |
|---|---|
DataMaskerInterface | Contract: mask(), clearRules() |
DataMasker | Rule orchestrator, recursion over structures |
RegexDataMaskerRule | Regex for strings and key patterns (from config/masking.php) |
DataMaskerRuleInterface | Custom rule (apply, isSensitiveKey) |
By default DataMaskerServiceProvider creates a DataMasker with one
RegexDataMaskerRule using patterns from configuration. The service is a shared singleton.
config/masking.php
In the skeleton, config/masking.php defines three groups of settings.
DataMaskerServiceProvider reads them via ConfigInterface
(ConfigKey::MASKING_PATTERNS, MASKING_KEY_PATTERNS, MASKING_RULES)
and builds one RegexDataMaskerRule plus additional classes from masking.rules:
<?php declare(strict_types=1);
return [
'masking' => [
// Regex for strings (log messages, 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 for key names in arrays/objects → value becomes ***
'key_patterns' => [
'/.*password.*/i',
'/.*token.*/i',
'/.*_csrf_token.*/i',
'/api_key/i',
],
// DataMaskerRuleInterface classes — resolved from DI
'rules' => [
// MyCustomMaskerRule::class,
],
],
];
patterns—RegexDataMaskerRule::apply()for strings (email, cards,password=…in text)key_patterns—isSensitiveKey(): matching key name →DataMasker::MASK_CHARS(***)rules— additional rules; class must implementDataMaskerRuleInterface
Changes to masking.php do not require PHP edits — a request restart is enough.
Default rules (skeleton)
What stock config/masking.php provides in Concept Skeleton:
Keys (key_patterns) — field value is replaced with ***:
- names containing
password,token,_csrf_token,secret api_key,authorization
Strings (patterns) — regex replacement in text:
- email →
***@***.*** - card number
9999-9999-9999-9999→ asterisks - pairs like
password=...,token=...,api_key=...→ value replaced with*****
Logger integration
LogServiceProvider passes DataMaskerInterface to
Concept\Core\Services\Logger\Logger. The Monolog processor masks
$record->context before writing to file or stderr.
$logger->info('User updated', [
'email' => 'user@example.com',
'password' => 'secret123', // logged as ***
]);
The message ($message) is not masked automatically — do not embed passwords or tokens
in the message text; put them only in context.
Manual usage
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',
]);
The mask() method for arrays and objects first performs a deepClone so
the original is not modified. maskRecursive() modifies the passed structure in place — useful
when cloning is unnecessary.
Custom rules
Usually extending config/masking.php is enough (see above).
If you need full control — in your own provider (after DataMaskerServiceProvider)
you can override the 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);
Or add a class to masking.rules in configuration — the provider will wire it through DI.
For more complex logic, implement DataMaskerRuleInterface.
DebugBar and other components
The DebugBar component injects DataMaskerInterface into the request collector — the panel shows
already masked values for $_POST, $_SESSION, etc.
Any custom diagnostic tool should be built the same way.
Masking does not replace database encryption and does not remove sensitive fields from HTTP responses. It reduces the risk of leakage through logs and dev tools.