maisecrets menu

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.

WHAT GOES IN You type a promptfewer checks The agent reads a file.env, a config, a log A command printsgit diff, psql, kubectl An MCP tool returnsa ticket, a CRM record A PDF, with convertits text only Your own NERan export, a script JSON lines maisecrets checks the text Values you storedexact match, in every later text Secret rulestoken shapes, a value after a label Personal data rulese-mail, phone, IBAN, card, IP, checksums Tool results onlylabels, Markdown tables, vCards, base64 Your termsnames, case numbers, customers you list Your imported valueswhat your NER found your allow list lets exact values through WHAT THE MODEL GETS ⟦SECRET_c1⟧ ⟦EMAIL_c2:ma•••@•••.de⟧ ⟦PHONE_c3⟧ ⟦IBAN_c4⟧ ⟦NAME_c5⟧ The modelplaceholders only
No model decides what a secret is. A tool result gets more checks than a typed prompt. The values you stored, your terms and your imports turn the gaps of pattern matching into placeholders too.

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, .env and INI files, log lines with key=value, SQL UPDATE and WHERE, PHP print_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 as fname, dob or tel, and with a prefix such as customer__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).

KindNemotron-PIIGretel
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 with ms:(…).
  • 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.