Skip to content

Commit f1f3a2b

Browse files
committed
feat: add random confusable replacement functions
- Add getRandomConfusable() for single character random replacement - Add randomizeConfusables() for string random replacement - Add RandomConfusableOptions interface for flexible configuration - Support type filtering, character exclusion, and probability control - Update README with usage examples and feature documentation
1 parent a1ad819 commit f1f3a2b

4 files changed

Lines changed: 97 additions & 3 deletions

File tree

packages/unconfusables/README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ UTS #39 defines security mechanisms for handling Unicode characters, including c
1313

1414
- 🔍 **Confusable Detection**: Identify characters that can be visually confused with others
1515
- 🔧 **String Normalization**: Convert strings to their canonical forms
16+
- 🎲 **Random Replacement**: Replace characters with random confusable alternatives
1617
- 🛡️ **Security Applications**: Detect potential phishing attacks and homograph attacks
1718
- 🌐 **Unicode Support**: Comprehensive support for Unicode confusable mappings
1819
- ⚡️ **High Performance**: Efficient lookup algorithms with 6296+ confusable mappings
@@ -42,6 +43,8 @@ import {
4243
normalizeString,
4344
areConfusable,
4445
getConfusableVariations,
46+
getRandomConfusable,
47+
randomizeConfusables,
4548
} from "unconfusables";
4649

4750
// Character confusable lookup
@@ -64,6 +67,19 @@ console.log(areConfusable("google", "goog1e")); // true
6467
// Generate confusable variations
6568
const variations = getConfusableVariations("admin");
6669
console.log(variations); // ["admin", "adrin", "adnin"]
70+
71+
// Random character replacement
72+
console.log(getRandomConfusable("a")); // Random confusable of "a"
73+
console.log(getRandomConfusable("0")); // "O" or other confusable
74+
75+
// With options: only MA type, exclude certain characters
76+
console.log(
77+
getRandomConfusable("a", { type: "MA", exclude: new Set(["a", "A"]) }),
78+
);
79+
80+
// Random string replacement (30% probability)
81+
const randomized = randomizeConfusables("paypal", { probability: 0.3 });
82+
console.log(randomized); // "paypal" with some characters randomly replaced
6783
```
6884

6985
## 🔧 Advanced Usage
@@ -100,14 +116,40 @@ console.log(detectPhishingUrl("g00gle.com"));
100116
### 🎭 String Variation Generation
101117

102118
```typescript
103-
import { getConfusableVariations, getConfusableSources } from "unconfusables";
119+
import {
120+
getConfusableVariations,
121+
getConfusableSources,
122+
getRandomConfusable,
123+
randomizeConfusables,
124+
} from "unconfusables";
104125

105126
// Generate all possible confusable variations
106127
const text = "admin";
107128
const variations = getConfusableVariations(text);
108129
console.log(`"${text}" has ${variations.length} confusable variations:`);
109130
variations.forEach((v) => console.log(` - ${v}`));
110131

132+
// Random character replacement
133+
const randomChar = getRandomConfusable("a");
134+
console.log(`Random confusable for "a": ${randomChar}`);
135+
136+
// With options: only MA type
137+
const randomMA = getRandomConfusable("a", { type: "MA" });
138+
console.log(`Random MA confusable for "a": ${randomMA}`);
139+
140+
// Excluding specific characters
141+
const randomExcluded = getRandomConfusable("a", {
142+
exclude: new Set(["a", "A"]),
143+
});
144+
console.log(`Random confusable for "a" excluding "a"/"A": ${randomExcluded}`);
145+
146+
// Randomize entire string with options
147+
const randomString = randomizeConfusables("paypal", {
148+
type: "MA",
149+
probability: 0.4,
150+
});
151+
console.log(`Randomized "paypal" (40% probability, MA only): ${randomString}`);
152+
111153
// Check what characters can be confused with a target character
112154
const sources = getConfusableSources("O");
113155
console.log(

packages/unconfusables/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "unconfusables",
3-
"version": "0.0.0",
3+
"version": "0.0.1",
44
"description": "Unicode confusable characters detection and string normalization library based on UTS #39",
55
"main": "dist/index.mjs",
66
"types": "dist/index.d.ts",

packages/unconfusables/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,9 @@ export interface ConfusableMetadata {
2525
X: number;
2626
};
2727
}
28+
29+
export interface RandomConfusableOptions {
30+
type?: ConfusableType;
31+
exclude?: Set<string>;
32+
probability?: number;
33+
}

packages/unconfusables/src/utils.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
* Unicode Confusables Utility Functions
33
*/
44

5-
import type { ConfusableRecord, ConfusableType } from "./types";
5+
import type {
6+
ConfusableRecord,
7+
ConfusableType,
8+
RandomConfusableOptions,
9+
} from "./types";
610
import { confusables } from "./data";
711

812
// Get confusable mapping for a character
@@ -95,6 +99,48 @@ export function getConfusableVariations(
9599
return Array.from(variations);
96100
}
97101

102+
// Replace character with random confusable
103+
export function getRandomConfusable(
104+
char: string,
105+
options: RandomConfusableOptions = {},
106+
): string {
107+
if (char.length === 0) return char;
108+
109+
const confusable = confusables.confusables[char];
110+
if (!confusable || confusable.target.length === 0) return char;
111+
112+
// Filter by type if specified
113+
if (options.type && confusable.type !== options.type) return char;
114+
115+
// Filter out excluded characters
116+
let targets = confusable.target;
117+
if (options.exclude) {
118+
targets = targets.filter((target) => !options.exclude!.has(target));
119+
if (targets.length === 0) return char;
120+
}
121+
122+
const randomIndex = Math.floor(Math.random() * targets.length);
123+
return targets[randomIndex];
124+
}
125+
126+
// Randomly replace characters in string
127+
export function randomizeConfusables(
128+
text: string,
129+
options: RandomConfusableOptions & { probability?: number } = {},
130+
): string {
131+
const probability = options.probability ?? 0.5;
132+
133+
return text
134+
.split("")
135+
.map((char) => {
136+
if (Math.random() < probability) {
137+
return getRandomConfusable(char, options);
138+
}
139+
return char;
140+
})
141+
.join("");
142+
}
143+
98144
// Get metadata about the confusables dataset
99145
export function getMetadata() {
100146
const typeStats = { MA: 0, MI: 0, X: 0 };

0 commit comments

Comments
 (0)