maisecrets / What it detects
What it detects, and how
Detection uses fixed rules, not a model. maisecrets checks each prompt and each tool result that passes the hooks of Claude Code or Codex, whatever the text comes from, and gives the model a placeholder of the right type.
How well each kind is found
Measured with maisecrets 0.8.2 on 26 October 2026, on our sealed holdout set: 872 synthetic texts with 828 hidden values. We made the set ourselves, in a separate agent session that had no access to the code, the results or the misses of maisecrets. The set was sealed before its first run, and no rule was written from it. This is our own measurement, not an evaluation by a third party. A value counts as found when maisecrets masks it whole. In all 872 texts, maisecrets raised 3 false alarms.
Connection strings and URL credentials are masked in part, by design. maisecrets is designed to mask the password of postgres://user:password@host/db and keep the scheme, the user, the host and the database, so that the agent can still work with the connection. The count above takes only a whole masked value as found. Whether the password itself was masked in each of these holdout cases is not measured yet: the set is sealed and does not mark which part of a string is the secret.
Names are the gap: a name has no shape, so a rule finds it only after a label. Put the names you care about on your term list, or import what your own NER found. The set is synthetic and private, so that no rule can be written from it; its totals per release are in the holdout log, and the result file of this run is in the repository.
Secrets: tokens, keys, passwords
Token shapes anywhere in the text: the rules of betterleaks (the successor of gitleaks), prefixes such as glpat- or whsec_, AWS keys, Bearer headers. A password is found after a label in any spelling, also on the next line, such as password: or DB_PASSWORD=. It needs 8 characters or more and must not be a dictionary word.
The labels come in 19 languages, Chinese, Japanese and Russian among them. All of them are on from 0.8.2, whatever your system language. Your regions select the country formats of ID numbers. A key or a password in a typed sentence counts too, such as “my stripe key is …”. Releases 0.7.9 to 0.7.20 added about 350 provider token formats that need no label, from the providers' own code and documentation.
E-mail addresses
An e-mail address has a fixed shape, so maisecrets finds it anywhere, also written with %40 where an address is expected. In our sealed holdout set, maisecrets 0.8.2 masked all 42 addresses whole. The model gets the type and a short display, such as ⟦EMAIL_c2:ma•••@•••.de⟧, so it still knows that it is an address. Addresses at example.com and the test domains stay text on purpose.
Other personal data
In every text: a phone number with a country code, an IBAN and a card number with their checksums, a public IP address, and German identifiers by default (more countries by setting).
In a tool result or a file, maisecrets also reads the context of a value:
- A name after its label, also a first name or a last name alone, such as
Vorname: Jana, also a double surname, a name after “first name is” in a sentence, and a name in a self-introduction such as “I, …, hereby”, and a full name after a title or a salutation, such as “Mr …” or “Dear …,”. In a tool result, from 0.8.16, also a surname alone after Herr, Herrn or Frau (German-speaking regions only). - A phone number in its national form after a phone word, such as
Tel. 030 …, with its extension (x204,ext. 31). Also a person’s own number in a sentence, such as “my number is …”, while a company’s line stays text. - A passport or driving licence number after its label in English and in eight EU languages: German, French, Spanish, Italian, Dutch, Portuguese, Swedish and Polish.
- An ID number after a health, tax or national label, such as a social security number, a tax ID or a medical record number.
- An address: with its postcode, or a street line after an address label (also
Anschrift,Adres,Adress,Indirizzo) or after “resides at”, with its direction, unit, postcode, city and country, also a US address on two lines or a US home address on one line. A birth date after its label or after “born on”; from 0.8.2 also after its label in 16 more languages, Dutch, Polish, Turkish, Arabic and Japanese among them. - A full name in a self-introduction (from 0.8.2) in 13 more languages: French, Spanish, Italian, Dutch, Swedish, Danish, Norwegian, Polish, Portuguese, Czech, Finnish, Romanian and German.
- A card number written as one run of digits, when it has the prefix and the length of a card network, and an account number after its label or in a column named account number.
- Tables and records: the person columns of a CSV file, a Markdown or HTML table and an SQL
INSERT, and the personal fields of a JSON object and an XML record, with one policy for all formats, and from 0.8.2 also YAML,.envand INI files, log lines withkey=value, SQLUPDATEandWHERE, PHPprint_r, a printed Python dict and JSON inside a JSON string. Column and field names count in English, German, French, Spanish, Italian, Dutch, Swedish, Portuguese and Polish, as short forms such asfname,dobortel, and with a prefix such ascustomer__surname. A password under a password column is masked. A Markdown table of fields and values, one field per row, counts as one record. - vCards: the name, address and birth date of a vCard, also in a single line that grep prints and in a line that the card folds over two lines.
- A label in Markdown bold, such as
**Name:**or**Password:**, counts as a label.
A company’s address after its legal form, such as a GmbH, stays text with its postcode and VAT ID. If a person’s name stands near the company, these values stay masked. A typed prompt takes none of the rules in this list: a person who types a name means to send it, and a false hit blocks the prompt. The opt-in NER service is different: from 0.8.7, with the service on your own computer, a name in a typed prompt becomes a placeholder in Claude Code, and nothing is blocked.
What 0.6.35 changed, measured
Share of the labelled values that maisecrets masked in two public datasets, before and after the seven rules of 0.6.35, in a tool result. The rules were built on one half of each dataset and measured on the other half (17 October 2026).
| Kind | Nemotron-PII | Gretel |
|---|---|---|
| Phone numbers | 26 % → 76 % | 60 % → 69 % |
| ID numbers | 31 % → 76 % | 49 % → 58 % |
| Addresses | 6 % → 36 % | 20 % → 22 % |
| Birth dates | 30 % → 44 % | — |
| Names | 3 % → 20 % | 8 % → 9 % |
The new rules gave 0 new hits on 2,464 developer texts and on 771 typed prompts. Names stay the weak point: maisecrets masks one name in five in Nemotron-PII and fewer than one in ten in Gretel, because most names there have no label. The full tables, the false hits and the commands are in BENCHMARKS.md.
Not detected
- A password without a label in a sentence, such as “use Sommer2026 for the login”, and a password of plain words. Store such a value once with
/maisecrets:put, or check it withms:(…). - A name without a label, a title or a salutation, unless it is on your term list or the opt-in NER service is on. A US phone number without a phone word.
- Personal data in a typed prompt that has no fixed shape, such as a name or a street after its label. Exception: a name, when the local NER service is on, in Claude Code.
- A value that a command transforms, or one split across lines.