Download the PHP package edulazaro/laranon without Composer
On this page you can find all versions of the php package edulazaro/laranon. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download edulazaro/laranon
More information about edulazaro/laranon
Files in edulazaro/laranon
Package laranon
Short Description Reversible PII anonymization for Laravel: detect, pseudonymize and restore personal data before it reaches LLMs, logs or third parties. Spanish and English recognizer packs included. Zero dependencies beyond Laravel core.
License MIT
Informations about the package laranon
Laranon
Reversible PII anonymization for Laravel. Detect personal data (Spanish and English packs included), replace it with stable tokens, realistic surrogates or redactions, and restore it afterwards, so LLMs, logs and third parties never see the real thing. Zero dependencies beyond Laravel core.
Why it is different
- Checksum validation, not just regexes. DNI (mod-23 letter), NIE, CIF, IBAN (mod-97), credit cards (Luhn plus IIN), NSS, CCC. A
12345678Awith a wrong control letter is not flagged, which crushes false positives. - Per-word name tokens. Person names are tokenized per word, never per person: "John" always yields the same token wherever it appears, with zero identity guessing. See below.
- Stable scoped tokens. Within a scope (a conversation, a case), the same value always becomes the same token, across every call, so multi-turn chats stay coherent and can be restored at any point.
- Replacements never repeat. The token map guarantees that two different values never share a replacement, whatever the strategy generates. A shared surrogate would merge two people and garble restoration.
- Known entities. Feed the values you already have (client emails, phones, company names) for exact, total-recall matching where it matters most.
- Streaming-safe restore. Tokens split across SSE chunks (even inside the multibyte
«) are buffered and restored correctly. - Exact restoration. Every token maps back to the literal original text, byte for byte.
Install
Optionally publish the config, and the vault migration if you use the database store:
Usage
Basic
Helpers: anon($text, $scope = null) and deanon($text, $map = null, $scope = null).
Sessions: the recommended way for LLM chat
For a chat turn you want one in-memory map shared across every piece of the prompt (user message, retrieved context, tool results), and you want it to vanish when the request ends. That is exactly a session: a stateful, throwaway object that owns its own map, persists nothing, and dies with the request. No scope, no vault, no cache.
anonymize() and restore() accept three shapes, all sharing the session's map:
Only the string leaves at the given paths are touched; role, ids, tool names, keys and non-string values are left untouched. Missing keys are skipped. $anon->stream() gives an SSE-safe restorer bound to the same map (see Streaming).
Because the chat history is stored in the clear (real values), each turn just builds a new session and re-anonymizes the whole prompt from scratch: tokens come out identical (deterministic in reading order), so consistency holds with nothing persisted between turns.
Per-word name tokens
Person names are tokenized per WORD, never per person. "John Smith" becomes «PER_1» «AP_1»: the given name and the surname each get their own independent, stable token. Consistency is automatic and no identity is ever guessed:
- A later bare "John" (same text or a later call in the same scope) gets
«PER_1»again. The token belongs to the word, not to a person, so it is correct by construction. - "John Baker" becomes
«PER_1» «AP_2»: same given-name token, different surname token, which is exactly the information a human reader has. - "Mr. Baker" becomes "Mr.
«AP_2»", sharing the token of the "Baker" in "John Baker". Honorifics themselves stay in cleartext. - Grammatical particles stay in cleartext too: "John de la Cruz" reads
«PER_1» de la «AP_3». - Restoration is exact: each token maps back to the literal word.
Honorific conventions drive the typing of lone words: Mr./Mrs./Dr./Sr./Sra. introduce a surname ("Mr. Smith", "Sr. García"), while D./Dña./Don/Doña/Sir/Lady introduce a given name ("Sir Ian", "Doña Luz"). With several words, the first is the given name and the rest are surnames ("Mr. James Miller", "D. Juan Pérez García").
Scopes: stable tokens across turns
Scoped maps persist encrypted (app key) in the configured vault: cache (default, TTL-expiring), database (survives days, for queued jobs) or array (single request). Each anonymize() call scans only the new text; name words already tokenized in the scope are swept in that new text as exact, word-bounded literals, so a bare "John" in turn 5 reuses the token established in turn 1.
Strategies
token(default):«PER_1»,«DNI_1»... reversible, ideal for LLM round-trips.faker: realistic surrogates with the same format (a valid fake DNI for a DNI, a bare given name for a name word), reversible, ideal for natural-reading document generation. Uniqueness is enforced by the map.redact:[DNI],[PER]... one-way, nothing can be reversed, ideal for logs and outbound scrubbing.
Known entities
Or from models, with the Anonymizable trait:
Use entities for values with no ambiguity (emails, phones, company names). Person names are already covered by the per-word detector; feeding them as whole-string entities would fight it.
Stable tokens for known entities
Values you own that have a database id (a company, an email) can pin their placeholder to it instead of the ad-hoc first-seen counter, so the same record gets the same token in every scope and every call:
Do NOT use this for person names: a full-name pinned token would conflict with per-word tokenization and silently assert an identity that a bare later mention cannot confirm. Person names are best left to the detector.
Filtering
Streaming (SSE)
Log scrubbing
Every log message and context string is redacted before being written.
Outbound HTTP scrubbing
Detection audit
Prints every span the anonymizer would replace (type, value, offset, confidence) plus a per-type summary. Useful to measure recall against your own corpus before trusting a pipeline.
What it detects
| Pack | Types |
|---|---|
| Universal | email, IBAN (mod-97), credit cards (Luhn plus IIN), international phones (+cc / 00cc, any grouping), IPv4/v6, URLs, BIC/SWIFT, known entities |
Spanish (es) |
DNI, NIE, CIF, NSS, CCC, bare phones (6xx/7xx mobiles, 9xx landlines, with or without separators), postal codes (context-gated), plates, court refs (NIG, "autos 123/2023", "expediente número 456/2023"), person names |
English (en) |
SSN, UK NINO, passports (context-gated), US/UK phones, ZIP codes, person names |
Ambiguous formats (postal codes, plain 5-digit ZIPs, passports) are context-gated on purpose: precision first.
Person names combine honorific triggers (D., Dña., Sr., Mr., Dr...) with gazetteer-gated capitalized sequences: a run of 2+ capitalized words only counts once its first token is a known given name, and a last-token hit in the surname gazetteer bumps confidence. Every match is split into per-word spans (person for the given name, surname for each surname word). A curated list of names that are also plain nouns ("Luz", "Rose", "Faith"...) requires a confirmed surname before matching at all, so bare "Luz" is left alone but "Luz Martínez" is not (honorific patterns skip the gazetteer, so "Dña. Luz" still matches on its own). Grammatical particles and honorific titles never gate a match or become name spans, even when census artifacts list them as given names.
The bundled dictionaries (data/) are built from official sources, not toy lists:
| File | Entries | Source |
|---|---|---|
data/es/names.php |
~16,600 | INE, national census, frequency >= 20 (via marcboquet/spanish-names) |
data/es/surnames.php |
~24,300 | INE, national census, frequency >= 20 |
data/es/names_ambiguous.php |
34 | hand-curated (Marian and virtue names) |
data/en/names.php |
~7,300 | US SSA baby name applications, aggregate occurrences >= 100 (via hackerb9/ssa-baby-names) |
data/en/surnames.php |
~162,000 | US Census Bureau, 2010 release, count >= 100 |
data/en/names_ambiguous.php |
31 | hand-curated (flower, gem and virtue names) |
Extend them or point data_path in the config to your own directory with the same filenames. Static PHP arrays are cached by opcache in shared memory, so they load once per server, not per request.
Configuration
config/laranon.php controls:
localesandpacks: which locale packs run (es,enbundled; add your own).universal: recognizers that run regardless of locale.strategyandstrategies: the default replacement strategy and the registered ones.token_formatandlabels:«%s_%d»with labels likeperson => PER,surname => AP,dni => DNI.vault: store (cache,database,array), cache prefix, TTL, table name.data_path: override the bundled gazetteers.
Extending
- Recognizer: implement
Contracts\Recognizer, or extendRecognizers\RegexRecognizer(patterns with a confidence each, plus an optional checksum validation) and add it to a pack or to theuniversalconfig list. - Locale pack: implement
Contracts\LocalePackand register it inconfig/laranon.php. - Strategy: implement
Contracts\Strategyand add it tostrategies. - Vault: implement
Contracts\VaultStorefor custom persistence (for example, a column on your own chat session model).
Testing
Sponsors
Laranon is supported by the following sponsors. Thank you for keeping it growing:
Author
Created by Edu Lazaro
License
Laranon is open-sourced software licensed under the MIT license.