| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
🌐 English · 简体中文
A PHP library that masks sensitive data before it reaches an LLM, and restores it afterward — so the model, the network, and the vendor's logs never see the real values.
Phone numbers, national ID numbers/SSNs, bank cards, emails, cloud credentials, private keys, JWTs, and your own custom keywords are detected and replaced with placeholders ([PHONE_1]) before a request leaves your process. When the LLM's reply references that placeholder, llmasking swaps it back — including in streamed (SSE) responses, where a placeholder can be split across chunk boundaries.
$engine = Engine::new();
$session = $engine->newSession();
$result = $session->anonymize("I'm John, my phone is 13800138000");
// $result->text → "I'm John, my phone is [PHONE_1]"
// ... send $result->text to the LLM ...
$restored = $session->restore("Sure, I'll contact [PHONE_1]");
// $restored->text → "Sure, I'll contact 13800138000"Already using a PSR-18 HTTP client (Guzzle, Symfony, ...)? The MaskingClient decorator auto-detects the request format and anonymizes every supported free-text field — your SDK and framework code stay untouched:
$client = new MaskingClient(
$innerClient, // any PSR-18 ClientInterface
$streamFactory, // any PSR-17 StreamFactoryInterface
$engine,
);
// Every outgoing request is anonymized; every response (including SSE streams) is restored.llmasking follows the same two-stage Analyzer/Anonymizer split as microsoft/presidio, compiled into a single PHP call:
Restoring is the mirror image: Session::restore() scans for placeholder-shaped tokens and looks each up in the mapping table.
composer require yolorouter/llmasking-phpPHP 8.1+. Dependencies: PSR-18 / PSR-17 / PSR-7 interfaces and wikimedia/aho-corasick (keyword matching).
| Entity | Placeholder | What it matches | Region | Default strategy |
|---|---|---|---|---|
| PHONE | [PHONE_1] | China mobile, US phone, international +-prefixed | CN / US / Universal | Placeholder |
| IDCARD | [IDCARD_1] | 18-char Chinese resident ID (ISO 7064 checksum) | CN | Placeholder |
| LANDLINE | [LANDLINE_1] | China landline (area code + number) | CN | Placeholder |
| SSN | [SSN_1] | US Social Security Numbers | US | Placeholder |
| [EMAIL_1] | Email addresses | Universal | Placeholder | |
| BANKCARD | [BANKCARD_1] | Luhn-valid 13–19 digit PAN | Universal | Placeholder |
| IP | [IP_1] | IPv4 addresses | Universal | Placeholder |
| URL | [URL_1] | http(s) URLs | Universal | Placeholder |
| KEYWORD | [KEYWORD_1] | Your own terms via WithKeywords | Universal | Placeholder |
| CLOUDKEY | [CLOUDKEY_1] | AWS AKIA..., Alibaba LTAI.../AKID... | Universal | Redact |
| PRIVATEKEY | [PRIVATEKEY_1] | PEM private key blocks | Universal | Redact |
| JWT | [JWT_1] | JSON Web Tokens | Universal | Redact |
| GITTOKEN | [GITTOKEN_1] | GitHub ghp_/gho_, GitLab glpat- | Universal | Redact |
| SECRET | [SECRET_1] | Generic high-entropy strings | Universal | Redact |
CLOUDKEY/PRIVATEKEY/JWT/GITTOKEN/SECRET form the SECRET family: they always win conflict resolution, and WithStrategy refuses to assign them a reversible strategy.
Engine::new() takes zero or more EngineOptions; all validation happens at construction time.
| Option | Purpose | Default |
|---|---|---|
| EngineOption::withRecognizers(...) | Replace the default recognizer set | All 11 built-ins |
| EngineOption::withKeywords(...) | Add custom keyword recognizer | none |
| EngineOption::withRegions(...) | Trim geographic rule packs | All regions |
| EngineOption::withStrategy($entity, $strategy) | Override strategy for one entity | Redact for SECRET, Placeholder otherwise |
| EngineOption::withEntityType($name) | Register a custom entity name | — |
| EngineOption::withMaxEntities($n) | Max findings per session | 10,000 |
| EngineOption::withMaxSessionBytes($n) | Max mapping-table bytes | 10 MB |
| EngineOption::withMaxInputBytes($n) | Max input per call | 1 MB |
| EngineOption::withMaxOutputBytes($n) | Max output per call | 16 MB |
$engine = Engine::new(
EngineOption::withRegions(Region::US),
EngineOption::withKeywords('Project Chimera', 'internal codename X'),
EngineOption::withStrategy('PHONE', Strategies::maskMiddle()),
EngineOption::withMaxEntities(50000),
);| Strategy | Output | Reversible | Typical use |
|---|---|---|---|
| Placeholder | [PHONE_1] | Yes | Default — round-trips through the LLM |
| Redact | [SECRET_1] | No | Default for SECRET family |
| MaskMiddle | 138****8000 | No | Keep value shape visible |
| Hash | First 8 hex of SHA-256 | No | Deterministic correlation |
$engine = Engine::new(
EngineOption::withStrategy('PHONE', Strategies::maskMiddle()),
);
$session = $engine->newSession();
$result = $session->anonymize('13800138000');
// $result->text → "138****8000"Implement the Strategy interface (apply(Finding $f, int $seq): string) for custom strategies — always non-reversible.
use Yolorouter\Llmasking\Engine;
use Yolorouter\Llmasking\EngineOption;
$engine = Engine::new();
$session = $engine->newSession();
// Mask
$result = $session->anonymize('email a@example.com, phone 13800138000');
echo $result->text;
// "email [EMAIL_1], phone [PHONE_1]"
// ... send $result->text to the LLM ...
// Restore
$restored = $session->restore($llmReply);
echo $restored->text;
// Original values swapped backThe MaskingClient PSR-18 decorator wraps any HTTP client. It detects application/json POST requests, anonymizes all known free-text fields (messages content, tool descriptions, schema annotations), and restores placeholders in responses — including text/event-stream SSE.
use Yolorouter\Llmasking\Transport\MaskingClient;
use Yolorouter\Llmasking\Transport\TransportOptions;
$client = new MaskingClient(
$guzzle, // PSR-18 ClientInterface
$streamFactory, // PSR-17 StreamFactoryInterface
Engine::new(),
TransportOptions::withPassthrough(), // optional: forward unparseable bodies
);
// Use $client as your PSR-18 client — masking and restoration happen automatically.
$response = $client->sendRequest($request);$client = new MaskingClient(
$inner, $factory, $engine,
TransportOptions::withMaskReport(function ($request, $events) {
foreach ($events as $e) {
error_log("masked {$e->entity} → {$e->replacement}");
}
}),
TransportOptions::withRestoreReport(function ($request, $events, $complete, $error) {
foreach ($events as $e) {
if (!$e->restored) {
error_log("unresolved placeholder: {$e->placeholder}");
}
}
}),
);For logs, data export, or anything write-only — no Session, no mapping, safe for concurrent use:
$engine = Engine::new();
$masked = $engine->mask('user 13800138000 login failed');
// → "user [PHONE_1] login failed"A local web UI for end-to-end testing against any LLM endpoint — see playground/README.md:
./playground/playground
# → 🚀 llmasking-php playground → http://127.0.0.1:8787Open the URL in a browser, configure your LLM endpoint, send a message, and watch the anonymize → LLM → restore pipeline side-by-side — for both plain and streamed responses.
All errors implement LlmaskingException (which extends \Throwable). Typed subclasses:
| Exception | When |
|---|---|
| LimitExceededException | A resource limit would be exceeded |
| InvalidUTF8Exception | Input is not valid UTF-8 |
| InvalidConfigException | An EngineOption is invalid |
| InvalidFindingException | A recognizer produced an invalid Finding |
| StreamClosedException | StreamRestorer used after terminal state |
| InvalidRequestException | (transport) Request body cannot be safely processed |
| StreamRestoreException | (transport) Response restore failed |
This project is a PHP port of llmasking-go, whose design and rule sets draw on:
MIT, see LICENSE.
| Back | FazBrowse Home | New Git URL |