The model understands the message. Never the person.
fahmek replaces Moroccan identifiers with labels before a prompt leaves your network, in Darija, Arabic and French, then puts them back in the answer. It runs on your own servers.
One round trip, four steps
The model never sees the RIB, and your employee never sees a label.
- Your employee writes
bghit nbdl RIB dyali 999001123456789012345691
- fahmek masks it, on your server
bghit nbdl RIB dyali «MA_RIB_1»
- The model answers with the label
D'accord, le RIB «MA_RIB_1» sera mis à jour.
- fahmek puts the value back
D'accord, le RIB 999001123456789012345691 sera mis à jour.
The label map lives in memory only, for two minutes under the bank-pilot policy, then it is gone.
What it recognizes
Nine kinds of identifiers, each with its own rule. A checksum where one exists; shape and context words where none is public.
| Label | Identifier | How it is checked |
|---|---|---|
| MA_RIB | Bank account number (RIB), 24 digits | RIB key verified (mod 97) |
| MA_IBAN | Moroccan IBAN, MA and 26 digits | IBAN check digits verified |
| PAN | Payment card number, 13 to 19 digits | Luhn checksum |
| MA_PHONE | Moroccan phone number, 0 or +212 | National numbering structure |
| Email address | Mailbox syntax | |
| MA_CIN | National identity card (CIN) | Shape and nearby words; no public checksum exists |
| MA_CNSS | CNSS social security number | Shape and nearby words |
| MA_CNSS_AFFILIATION | CNSS employer affiliation number | Shape and nearby words |
| MA_ICE | Company identifier (ICE) | Reported, not masked by default |
Every rule is tested in five registers: Modern Standard Arabic, Darija in Arabic script, Darija in Latin letters, French, and French and Arabic mixed in one sentence. Arabic-Indic digits, tatweel and hidden direction characters are normalized first.
Built to stay inside the bank
Every rule below is checked by the service itself or by its tests, not left to a deployment note.
- No network calls while inspecting
- No telemetry, no license server, no model download. It runs on an air-gapped network.
- Nothing detected is written down
- Values are never logged or saved to disk. Redis keeps the label map in memory only, and fahmek refuses to start under a strict policy if Redis can save to disk.
- Fails closed
- Under the bank-pilot policy, a message fahmek cannot inspect is refused. It is never sent unmasked.
- Under 15 ms at the 99th percentile
- Normalization, detection and masking of a message up to 8,192 characters, measured in CI for each register. The network hop comes on top.
- An offline license
- A signed file checked against a key built into the image. Nothing phones home.
- A shared secret on every call
- Logs hold a route, a status and a duration: never message text, never the key.
Fits the gateway you already run
fahmek sits beside LiteLLM, between your applications and every model provider.
- LiteLLM web UIAdd fahmek as a Generic Guardrail API from the admin screen. No file on the proxy.
- LiteLLM integration kitTwo Python files next to your config.yaml. Masks every message and tool call, restores streamed answers.
- One machine, offlineThe whole stack with Docker Compose, from a package that installs without internet.
- KubernetesA Helm chart: in-memory Redis, network rules, secrets kept out of the chart.
Optional: auto-gad corrects French and English spelling before the model reads the message. It never changes Darija, Arabic script, names or the labels.
What it does not do
- It finds identifiers that have a known shape. Names, addresses and dates of birth are not detected.
- Accuracy has been measured on a synthetic corpus of 65 messages. On real messages, it is measured during a pilot, on your premises, on your own data.
- It does not inspect images, audio or files. Requests that carry them are refused.
- It does not cover passports, and it does not make you PCI DSS compliant.
Try it on your own messages
A pilot runs on one of your machines, offline, with your gateway and your policy.
fahmek (فاهمك) is Darija for “I understand you”.