disarm

A set-shaped question

Which of these names are the same name?

Every other tool here asks something about one string. This one cannot, because a collision is not a property of a string. gross.txt is an ordinary filename. admin spelled with a Cyrillic а is only a problem when the real admin is sitting beside it in the same table. Paste the list and see which entries your system will treat as different and a reader will treat as the same.

The tool

5 names
83 profiles, reaching search_key and catalog_key

These 5 names are really 1 name.

Under search_key, 5 names fall into 1 group below, which accounts for all of them. Your system stores every one as a distinct value; a reader reads some of them as the same. Which of the two is the bug depends on what the list is for, and this page does not decide that.

The collisions

Each row is a set of entries that reduce to one key under search_key. Only groups holding two or more names are listed: a name that collides with nothing is a group of one, and a list of those is the list you already have. The line numbers are every position in the group, which is not the same list as the values — a name repeated verbatim is one value and two lines. A registry wants the names; an extractor wants the lines to refuse.

Shared keyEntries that reduce to itLines
admin admin аdmin Admin ADMIN admın 1, 2, 3, 4, 5

The same list under all six keys

There is no default reducer and this is why. A weaker key misses impersonations; a stronger one collides names nobody attacked, and each of those is a registration you refuse or a batch you reject. Both errors are real and they point opposite ways, so the library declines to guess. The gap between the top row and the bottom, measured on your list rather than described in the abstract, is the decision.

KeyWhat it foldsGroupsNames caught
fold_case Case only. Folds no lookalikes, so a Cyrillic а stays Cyrillic. 1 3
normalize_confusables Characters drawn alike, folded onto Latin. Case is left alone. 1 3
canonicalize Lookalikes folded and the invisible classes stripped. Leaves ß alone. 1 3
canonicalize_strict The same, plus the eclipsing-mark rule. Destructive for IPA. 1 3
search_key chosen The identity key: transliterate, fold lookalikes, strip accents, fold case. 1 5
catalog_key The bibliographic key. Same reach, different romanisation choices. 1 5

Running disarm 0.14.1, compiled to WebAssembly. Your list is never uploaded — the engine is loaded into this page and runs on your machine.

The key you pick is the policy you get

There is no default reducer, and that is not an omission. A weaker key misses impersonations. A stronger key collides names that were never an attack, and every one of those is a registration you refuse or a batch you reject. Both errors are real and they point in opposite directions, so the library declines to guess and so does this page.

The clearest case is the language hint, which reaches the two romanizing keys and is ignored by the rest. Take three spellings of one German surname:

Under no language
Müller
Muller

The accent is stripped and the two become one key. Mueller is a third name.

Under lang="de"
Müller
Mueller

German romanizes ü as ue, so these two are one key and Muller is the third name.

Same list, same reducer, opposite answer. Neither is wrong. If you are de-duplicating a German customer table, the second one is what you want and the first will quietly keep two records for one person.

The report decides nothing. The two cases the library cites want opposite things from the same finding. An archive extractor that sees two entries collide must refuse the batch — that is what node-tar's path reservations failed to ask before extracting in parallel, CVE-2026-23950. A registry that sees a new name collide with an existing one must refuse the registration and keep what it already has. One answer, two policies.

What each key folds

Narrowest first. The table in the tool above runs all six over your own list, which is more useful than any description here: the gap between the narrowest and the broadest, measured on your data, is the decision.

sort_key is not offered, and its absence is a design decision rather than a gap. A sort key exists in order to collide — that is what sorting is — so reporting its collisions would be noise rather than a finding.

The same thing in your own code

Each code block has been compiled and verified in CI. Provided under the MIT license to illustrate disarm. disarm on GitHub →

# Which of these names are the same name?
#   pip install disarm
from disarm import find_key_collisions

# Not a per-string check. No entry in this list is wrong on its own: "gross.txt"
# is an ordinary filename, and the Cyrillic а in "аdmin" is only a problem
# because the real "admin" is sitting next to it.
NAMES = ["admin", "аdmin", "Admin", "groß.txt", "gross.txt", "other.txt"]

# Choosing the key is choosing the policy, and there is no default. fold_case is
# the narrowest: it collides case variants and the German sharp s, and folds no
# lookalikes, so the Cyrillic а stays Cyrillic.
narrow = find_key_collisions(NAMES, key="fold_case")
assert [c.values for c in narrow] == [
    ["admin", "Admin"],
    ["groß.txt", "gross.txt"],
]

# search_key is the broadest: transliterate, fold lookalikes, strip accents,
# fold case. It reaches the impersonation the narrow key let through.
broad = find_key_collisions(NAMES, key="search_key")
assert broad[0].values == ["admin", "аdmin", "Admin"]

# indices is every position in the group, not a parallel list to values: a
# registry wants the names, an extractor wants the entries to refuse.
assert broad[0].indices == [0, 1, 2]

# The language hint reaches the two romanizing keys and is ignored by the rest.
# German romanizes ü as ue, so the same list gives a different answer.
DE = ["Müller", "Mueller", "Muller"]
assert find_key_collisions(DE, key="search_key")[0].values == ["Müller", "Muller"]
assert find_key_collisions(DE, key="search_key", lang="de")[0].values == ["Müller", "Mueller"]

print("2 groups under fold_case; search_key also folds the Cyrillic admin")

Related tools