namespace-guard
The world's first library that detects confusable characters across non-Latin scripts. Slug claimability, Unicode anti-spoofing, and LLM Denial of Spend defence in one zero-dependency package.
- Live demo: https://paultendo.github.io/namespace-guard/
- Blog post: https://paultendo.github.io/posts/namespace-guard-launch/
Cross-script confusable detection
Existing confusable standards (TR39, IDNA) map non-Latin characters to Latin equivalents. They have zero coverage for confusable pairs between two non-Latin scripts.
namespace-guard ships 3,525 cross-script pairs from confusable-vision (measured across 245 system fonts using vector-outline raycasting — RaySpace). This catches attacks that no other library detects:
import { areConfusable, detectCrossScriptRisk } from "namespace-guard";
import { CONFUSABLE_WEIGHTS } from "namespace-guard/confusable-weights";
// Hangul ᅵ and Han 丨 are visually identical (ray distance 0.004, Arial Unicode MS)
areConfusable("\u1175", "\u4E28", { weights: CONFUSABLE_WEIGHTS }); // true
// Greek Τ and Han 丅 are near-identical (multiple fonts)
areConfusable("\u03A4", "\u4E05", { weights: CONFUSABLE_WEIGHTS }); // true
// Cyrillic І and Greek Ι are identical outlines (62 fonts)
areConfusable("\u0406", "\u0399", { weights: CONFUSABLE_WEIGHTS }); // true
areConfusable("\u1175", "\u4E28"); // false
// Analyze an identifier for cross-script risk const risk = detectCrossScriptRisk("\u1175\u4E28", { weights: CONFUSABLEWEIGHTS }); // { riskLevel: "high", scripts: ["han", "hangul"], crossScriptPairs: [...] }
4,174 total confusable pairs scored by visual measurement (3,111 TR39-confirmed, 1,063 novel). Each pair carries a `danger` score (0–1) representing geometric similarity across fonts; the shipped dataset uses a 0.5 floor. For higher precision, filter at `danger > 0.7` (574 pairs). Cross-script data licensed CC-BY-4.0.
## Installation
npm install namespace-guard
## Quick Start (60 seconds)
import { createNamespaceGuardWithProfile } from "namespace-guard"; import { createPrismaAdapter } from "namespace-guard/adapters/prisma"; import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const guard = createNamespaceGuardWithProfile( "consumer-handle", { reserved: ["admin", "api", "settings", "dashboard", "login", "signup"], sources: [ { name: "user", column: "handleCanonical", scopeKey: "id" }, { name: "organization", column: "slugCanonical", scopeKey: "id" }, ], }, createPrismaAdapter(prisma) );
await guard.assertClaimable("acme-corp");
For race-safe writes, use `claim()`:
const result = await guard.claim(input.handle, async (canonical) = { return prisma.user.create({ data: { handle: input.handle, handleCanonical: canonical, }, }); });