UnicodeSecurity
Pinned Unicode identifier security for Elixir: confusable comparison keys, script and restriction checks, and hostname policy. Unicode 18.0.0 data is final. Runtime calls are pure Elixir, offline, and have no dependencies.
A skeleton is a comparison key. It is not a canonical identifier, a display replacement, or an authorization decision. Equal skeletons do not prove ownership or intent.
Installation
Add unicode_security to your dependencies:
def deps do
[
{:unicode_security, "~> 0.1"}
]
end
Comparison keys
UnicodeSecurity.skeleton("paypal")
#=> "paypal"
# The two visually similar letters are Cyrillic U+0430.
UnicodeSecurity.skeleton("p\u0430yp\u0430l")
#=> "paypal"
skeleton/1 implements bidiSkeleton(LTR, input) from UTS #39 revision 34. Inputs are UTF-8 binaries of at most 4,096 bytes. See Hexdocs for the full key algorithm.
Policy checks
check/2 requires type: :username, :tenant_slug, :organization_name, or :domain. It returns the original input plus a verdict and reasons. It does not trim, rewrite, or reserve the name.
UnicodeSecurity.check("alice-smith", type: :username).verdict
#=> :safe
UnicodeSecurity.check("pay\u200Dpal", type: :username).verdict
#=> :dangerous
Hostnames
Pass type: :domain explicitly. Generic skeleton and comparison functions never guess a domain from a dot.
result = UnicodeSecurity.check("BÜCHER.例え.", type: :domain)
result.domain.ascii
#=> "xn--bcher-kva.xn--r8jz45g."
This is hostname analysis. It does not query DNS, check public suffixes, or parse browser URLs.
Scope
The package does not:
- turn a skeleton into an identity, storage key, or authorization decision;
- integrate with Ecto in this version;
- claim CONTEXTO coverage or IDNA2008 registration conformance.
Applications keep the original value and decide uniqueness, ownership, and storage.
License
MIT. See LICENSE. Unicode data attribution is recorded in THIRD_PARTY_NOTICES.md.