shroud-privacy 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/NOTICE +7 -0
- package/README.md +369 -0
- package/dist/audit.d.ts +46 -0
- package/dist/audit.js +127 -0
- package/dist/canary.d.ts +31 -0
- package/dist/canary.js +73 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +123 -0
- package/dist/detectors/base.d.ts +8 -0
- package/dist/detectors/base.js +2 -0
- package/dist/detectors/code.d.ts +25 -0
- package/dist/detectors/code.js +144 -0
- package/dist/detectors/context.d.ts +31 -0
- package/dist/detectors/context.js +357 -0
- package/dist/detectors/patterns.d.ts +15 -0
- package/dist/detectors/patterns.js +58 -0
- package/dist/detectors/regex.d.ts +28 -0
- package/dist/detectors/regex.js +955 -0
- package/dist/generators/base.d.ts +6 -0
- package/dist/generators/base.js +2 -0
- package/dist/generators/codes.d.ts +20 -0
- package/dist/generators/codes.js +231 -0
- package/dist/generators/names.d.ts +29 -0
- package/dist/generators/names.js +194 -0
- package/dist/generators/network.d.ts +86 -0
- package/dist/generators/network.js +477 -0
- package/dist/hooks.d.ts +27 -0
- package/dist/hooks.js +457 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +58 -0
- package/dist/mapping.d.ts +33 -0
- package/dist/mapping.js +72 -0
- package/dist/obfuscator.d.ts +78 -0
- package/dist/obfuscator.js +603 -0
- package/dist/redaction.d.ts +26 -0
- package/dist/redaction.js +76 -0
- package/dist/store.d.ts +40 -0
- package/dist/store.js +79 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +35 -0
- package/ncg_adapter.py +530 -0
- package/openclaw.plugin.json +72 -0
- package/package.json +56 -0
- package/shroud_bridge.mjs +225 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fake generators for API keys, tokens, file paths, credit cards, SSNs.
|
|
3
|
+
*/
|
|
4
|
+
import { Category } from "../types.js";
|
|
5
|
+
import type { BaseGenerator } from "./base.js";
|
|
6
|
+
export declare const FILE_DIRS: string[];
|
|
7
|
+
export declare const FILE_NAMES: string[];
|
|
8
|
+
export declare class CodeGenerator implements BaseGenerator {
|
|
9
|
+
readonly categories: Category[];
|
|
10
|
+
generate(category: Category, seed: number, original?: string): string;
|
|
11
|
+
_fakeApiKey(seed: number, original: string): string;
|
|
12
|
+
_fakeFilePath(seed: number, original: string): string;
|
|
13
|
+
_fakeCreditCard(seed: number, original: string): string;
|
|
14
|
+
_fakeSsn(seed: number): string;
|
|
15
|
+
_fakePhone(seed: number, original: string): string;
|
|
16
|
+
_fakeIban(seed: number, original: string): string;
|
|
17
|
+
_fakeNationalId(seed: number, original: string): string;
|
|
18
|
+
_fakeJwt(seed: number): string;
|
|
19
|
+
_fakeGps(seed: number): string;
|
|
20
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fake generators for API keys, tokens, file paths, credit cards, SSNs.
|
|
3
|
+
*/
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { Category } from "../types.js";
|
|
6
|
+
export const FILE_DIRS = [
|
|
7
|
+
"/opt/app/data", "/var/lib/service", "/home/user/projects",
|
|
8
|
+
"/etc/app", "/tmp/cache", "/srv/web/static",
|
|
9
|
+
"/opt/pipeline/output", "/var/log/app",
|
|
10
|
+
"/home/user/docs", "/etc/ssl/certs",
|
|
11
|
+
];
|
|
12
|
+
export const FILE_NAMES = [
|
|
13
|
+
"store.db", "config.yaml", "main.py", "settings.conf",
|
|
14
|
+
"session.bin", "index.html", "results.csv", "service.log",
|
|
15
|
+
"report.pdf", "app.pem", "weights.bin", "dump.json",
|
|
16
|
+
"app.toml", "file.dat", "runner.sh", "artifact.tar.gz",
|
|
17
|
+
];
|
|
18
|
+
/** Compute Luhn check digit for a string of digits. */
|
|
19
|
+
function luhnCheckDigit(digits) {
|
|
20
|
+
let sum = 0;
|
|
21
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
22
|
+
let d = parseInt(digits[i], 10);
|
|
23
|
+
if ((digits.length - i) % 2 === 1) {
|
|
24
|
+
d *= 2;
|
|
25
|
+
if (d > 9)
|
|
26
|
+
d -= 9;
|
|
27
|
+
}
|
|
28
|
+
sum += d;
|
|
29
|
+
}
|
|
30
|
+
return (10 - (sum % 10)) % 10;
|
|
31
|
+
}
|
|
32
|
+
export class CodeGenerator {
|
|
33
|
+
categories = [
|
|
34
|
+
Category.API_KEY,
|
|
35
|
+
Category.FILE_PATH,
|
|
36
|
+
Category.CREDIT_CARD,
|
|
37
|
+
Category.SSN,
|
|
38
|
+
Category.PHONE,
|
|
39
|
+
Category.IBAN,
|
|
40
|
+
Category.NATIONAL_ID,
|
|
41
|
+
Category.JWT,
|
|
42
|
+
Category.GPS_COORDINATE,
|
|
43
|
+
Category.ICS_IDENTIFIER,
|
|
44
|
+
Category.CERTIFICATE,
|
|
45
|
+
];
|
|
46
|
+
generate(category, seed, original = "") {
|
|
47
|
+
if (category === Category.API_KEY) {
|
|
48
|
+
return this._fakeApiKey(seed, original);
|
|
49
|
+
}
|
|
50
|
+
else if (category === Category.FILE_PATH) {
|
|
51
|
+
return this._fakeFilePath(seed, original);
|
|
52
|
+
}
|
|
53
|
+
else if (category === Category.CREDIT_CARD) {
|
|
54
|
+
return this._fakeCreditCard(seed, original);
|
|
55
|
+
}
|
|
56
|
+
else if (category === Category.SSN) {
|
|
57
|
+
return this._fakeSsn(seed);
|
|
58
|
+
}
|
|
59
|
+
else if (category === Category.PHONE) {
|
|
60
|
+
return this._fakePhone(seed, original);
|
|
61
|
+
}
|
|
62
|
+
else if (category === Category.IBAN) {
|
|
63
|
+
return this._fakeIban(seed, original);
|
|
64
|
+
}
|
|
65
|
+
else if (category === Category.NATIONAL_ID) {
|
|
66
|
+
return this._fakeNationalId(seed, original);
|
|
67
|
+
}
|
|
68
|
+
else if (category === Category.JWT) {
|
|
69
|
+
return this._fakeJwt(seed);
|
|
70
|
+
}
|
|
71
|
+
else if (category === Category.GPS_COORDINATE) {
|
|
72
|
+
return this._fakeGps(seed);
|
|
73
|
+
}
|
|
74
|
+
else if (category === Category.ICS_IDENTIFIER) {
|
|
75
|
+
return `ICS-${String(seed % 100000).padStart(5, "0")}`;
|
|
76
|
+
}
|
|
77
|
+
else if (category === Category.CERTIFICATE) {
|
|
78
|
+
return `[REDACTED-CERT-${String(seed % 10000).padStart(4, "0")}]`;
|
|
79
|
+
}
|
|
80
|
+
return `code-${String(seed % 10000).padStart(4, "0")}`;
|
|
81
|
+
}
|
|
82
|
+
_fakeApiKey(seed, original) {
|
|
83
|
+
const buf = Buffer.alloc(8);
|
|
84
|
+
buf.writeUInt32BE(seed >>> 0, 0);
|
|
85
|
+
buf.writeUInt32BE(((seed >>> 16) ^ 0xcafebabe) >>> 0, 4);
|
|
86
|
+
const h = createHash("sha256").update(buf).digest("hex");
|
|
87
|
+
if (original) {
|
|
88
|
+
// Preserve prefix pattern (e.g., "sk-prod-" -> "sk-test-")
|
|
89
|
+
const prefixMatch = original.match(/^([a-zA-Z]+[-_](?:[a-zA-Z]+[-_])?)/);
|
|
90
|
+
if (prefixMatch) {
|
|
91
|
+
const prefix = prefixMatch[1];
|
|
92
|
+
const remainingLen = Math.max(8, original.length - prefix.length);
|
|
93
|
+
const fakeBody = h.slice(0, remainingLen);
|
|
94
|
+
return `${prefix}${fakeBody}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return `sk-test-${h.slice(0, 32)}`;
|
|
98
|
+
}
|
|
99
|
+
_fakeFilePath(seed, original) {
|
|
100
|
+
if (!original) {
|
|
101
|
+
const d = FILE_DIRS[seed % FILE_DIRS.length];
|
|
102
|
+
const f = FILE_NAMES[Math.floor(seed / FILE_DIRS.length) % FILE_NAMES.length];
|
|
103
|
+
return `${d}/${f}`;
|
|
104
|
+
}
|
|
105
|
+
// Preserve path depth and extension
|
|
106
|
+
const normalised = original.replace(/\\/g, "/");
|
|
107
|
+
const parts = normalised.split("/");
|
|
108
|
+
let ext = "";
|
|
109
|
+
const lastPart = parts[parts.length - 1];
|
|
110
|
+
if (lastPart.includes(".")) {
|
|
111
|
+
ext = "." + lastPart.split(".").slice(1).join(".");
|
|
112
|
+
}
|
|
113
|
+
// Match depth
|
|
114
|
+
const dirNames = [
|
|
115
|
+
"opt", "var", "home", "etc", "srv", "tmp", "app", "lib", "data", "logs",
|
|
116
|
+
];
|
|
117
|
+
const fakeParts = [];
|
|
118
|
+
for (let i = 0; i < parts.length; i++) {
|
|
119
|
+
const part = parts[i];
|
|
120
|
+
if (!part) {
|
|
121
|
+
fakeParts.push("");
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (i === parts.length - 1) {
|
|
125
|
+
// Filename
|
|
126
|
+
let fname = FILE_NAMES[(seed + i) % FILE_NAMES.length];
|
|
127
|
+
if (ext) {
|
|
128
|
+
fname = fname.split(".")[0] + ext;
|
|
129
|
+
}
|
|
130
|
+
fakeParts.push(fname);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
fakeParts.push(dirNames[(seed + i) % dirNames.length]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Preserve separator
|
|
137
|
+
if (original.includes("\\")) {
|
|
138
|
+
return fakeParts.join("\\");
|
|
139
|
+
}
|
|
140
|
+
return fakeParts.join("/");
|
|
141
|
+
}
|
|
142
|
+
_fakeCreditCard(seed, original) {
|
|
143
|
+
// Generate 15 digits, then compute Luhn check digit for digit 16
|
|
144
|
+
const part1 = String((seed & 0xffffffff) >>> 0).padStart(8, "0").slice(0, 8);
|
|
145
|
+
const part2 = String(((seed >>> 4) ^ 0x12345678) >>> 0).padStart(8, "0").slice(0, 7);
|
|
146
|
+
const first15 = (part1 + part2).slice(0, 15).padStart(15, "0");
|
|
147
|
+
const checkDigit = luhnCheckDigit(first15 + "0");
|
|
148
|
+
const digits = first15 + String(checkDigit);
|
|
149
|
+
if (original) {
|
|
150
|
+
// Detect separator
|
|
151
|
+
const sep = original.includes("-")
|
|
152
|
+
? "-"
|
|
153
|
+
: original.includes(" ")
|
|
154
|
+
? " "
|
|
155
|
+
: "";
|
|
156
|
+
if (sep) {
|
|
157
|
+
return `${digits.slice(0, 4)}${sep}${digits.slice(4, 8)}${sep}${digits.slice(8, 12)}${sep}${digits.slice(12, 16)}`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return `${digits.slice(0, 4)}-${digits.slice(4, 8)}-${digits.slice(8, 12)}-${digits.slice(12, 16)}`;
|
|
161
|
+
}
|
|
162
|
+
_fakeSsn(seed) {
|
|
163
|
+
// Avoid invalid area numbers: 000, 666, 900-999
|
|
164
|
+
let area = (seed % 898) + 1; // 1-898
|
|
165
|
+
if (area >= 666)
|
|
166
|
+
area++; // skip 666 -> gives us 1-665, 667-899
|
|
167
|
+
// Avoid invalid group 00 and serial 0000
|
|
168
|
+
const group = (Math.floor(seed / 898) % 99) + 1; // 01-99
|
|
169
|
+
const serial = (Math.floor(seed / (898 * 99)) % 9999) + 1; // 0001-9999
|
|
170
|
+
return `${String(area).padStart(3, "0")}-${String(group).padStart(2, "0")}-${String(serial).padStart(4, "0")}`;
|
|
171
|
+
}
|
|
172
|
+
_fakePhone(seed, original) {
|
|
173
|
+
const area = 200 + (seed % 800);
|
|
174
|
+
const mid = 200 + (Math.floor(seed / 800) % 800);
|
|
175
|
+
const last = seed % 10000;
|
|
176
|
+
const lastStr = String(last).padStart(4, "0");
|
|
177
|
+
if (!original) {
|
|
178
|
+
return `(${area}) ${mid}-${lastStr}`;
|
|
179
|
+
}
|
|
180
|
+
// Detect format: +1, parens, dashes, spaces, dots
|
|
181
|
+
const hasCountry = original.startsWith("+");
|
|
182
|
+
const hasParens = original.includes("(");
|
|
183
|
+
const hasSep = original.includes("-") || original.includes(".") || original.includes(" ");
|
|
184
|
+
const sep = original.includes("-")
|
|
185
|
+
? "-"
|
|
186
|
+
: original.includes(".")
|
|
187
|
+
? "."
|
|
188
|
+
: original.includes(" ")
|
|
189
|
+
? " "
|
|
190
|
+
: "";
|
|
191
|
+
if (hasCountry) {
|
|
192
|
+
// International format — preserve original separator style (or none)
|
|
193
|
+
const countryMatch = original.match(/^\+(\d{1,3})/);
|
|
194
|
+
const cc = countryMatch ? countryMatch[1] : "1";
|
|
195
|
+
if (!hasSep) {
|
|
196
|
+
// Original had no separators (e.g. +15551234567) — keep it compact
|
|
197
|
+
return `+${cc}${area}${mid}${lastStr}`;
|
|
198
|
+
}
|
|
199
|
+
return `+${cc}${sep}${area}${sep}${mid}${sep}${lastStr}`;
|
|
200
|
+
}
|
|
201
|
+
else if (hasParens) {
|
|
202
|
+
return `(${area}) ${mid}-${lastStr}`;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
return `${area}${sep}${mid}${sep}${lastStr}`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
_fakeIban(seed, original) {
|
|
209
|
+
// Preserve country code, generate fake digits
|
|
210
|
+
const cc = original.slice(0, 2).toUpperCase() || "DE";
|
|
211
|
+
const check = String(seed % 98).padStart(2, "0");
|
|
212
|
+
const body = String(seed).padStart(18, "0").slice(0, 18);
|
|
213
|
+
return `${cc}${check}${body}`;
|
|
214
|
+
}
|
|
215
|
+
_fakeNationalId(seed, original) {
|
|
216
|
+
// Preserve length and format (digits/letters)
|
|
217
|
+
const len = original.length || 10;
|
|
218
|
+
const digits = String(seed).padStart(len, "0").slice(0, len);
|
|
219
|
+
return digits;
|
|
220
|
+
}
|
|
221
|
+
_fakeJwt(seed) {
|
|
222
|
+
const h = createHash("sha256").update(String(seed)).digest("base64url");
|
|
223
|
+
return `eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.${h}`;
|
|
224
|
+
}
|
|
225
|
+
_fakeGps(seed) {
|
|
226
|
+
// Generate coordinates near null island (0,0) — clearly fake
|
|
227
|
+
const lat = ((seed % 18000) / 100 - 90).toFixed(6);
|
|
228
|
+
const lon = (((seed >>> 8) % 36000) / 100 - 180).toFixed(6);
|
|
229
|
+
return `${lat}, ${lon}`;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fake name generators for persons, organizations, and locations.
|
|
3
|
+
*
|
|
4
|
+
* Includes semantic/cultural matching -- CJK names get CJK fakes,
|
|
5
|
+
* Hispanic names get Hispanic fakes, etc.
|
|
6
|
+
*/
|
|
7
|
+
import { Category } from "../types.js";
|
|
8
|
+
import type { BaseGenerator } from "./base.js";
|
|
9
|
+
export declare const CJK_FIRST: string[];
|
|
10
|
+
export declare const CJK_LAST: string[];
|
|
11
|
+
export declare const HISPANIC_FIRST: string[];
|
|
12
|
+
export declare const HISPANIC_LAST: string[];
|
|
13
|
+
export declare const ARABIC_FIRST: string[];
|
|
14
|
+
export declare const ARABIC_LAST: string[];
|
|
15
|
+
export declare const SOUTH_ASIAN_FIRST: string[];
|
|
16
|
+
export declare const SOUTH_ASIAN_LAST: string[];
|
|
17
|
+
export declare const FIRST_NAMES: string[];
|
|
18
|
+
export declare const LAST_NAMES: string[];
|
|
19
|
+
export declare const ORG_PREFIXES: string[];
|
|
20
|
+
export declare const ORG_SUFFIXES: string[];
|
|
21
|
+
export declare const LOCATIONS: string[];
|
|
22
|
+
/** Detect the likely cultural origin of a name. */
|
|
23
|
+
export declare function detectCulture(name: string): string;
|
|
24
|
+
export declare class NameGenerator implements BaseGenerator {
|
|
25
|
+
readonly categories: Category[];
|
|
26
|
+
generate(category: Category, seed: number, original?: string): string;
|
|
27
|
+
private _fakePerson;
|
|
28
|
+
private _fakeOrg;
|
|
29
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fake name generators for persons, organizations, and locations.
|
|
3
|
+
*
|
|
4
|
+
* Includes semantic/cultural matching -- CJK names get CJK fakes,
|
|
5
|
+
* Hispanic names get Hispanic fakes, etc.
|
|
6
|
+
*/
|
|
7
|
+
import { Category } from "../types.js";
|
|
8
|
+
// --- Cultural name pools ---
|
|
9
|
+
// CJK names (Chinese/Japanese/Korean)
|
|
10
|
+
export const CJK_FIRST = [
|
|
11
|
+
"Haruki", "Yuki", "Ren", "Sora", "Hana", "Mei", "Kai", "Ryo",
|
|
12
|
+
"Aoi", "Sakura", "Wei", "Min", "Jun", "Lin", "Jing", "Xiao",
|
|
13
|
+
"Soo", "Jin", "Hyun", "Yeon", "Tae", "Haru", "Nao", "Shin",
|
|
14
|
+
];
|
|
15
|
+
export const CJK_LAST = [
|
|
16
|
+
"Tanaka", "Suzuki", "Takahashi", "Watanabe", "Yamamoto", "Nakamura",
|
|
17
|
+
"Chen", "Wang", "Li", "Zhang", "Liu", "Yang",
|
|
18
|
+
"Kim", "Park", "Lee", "Choi", "Jung", "Kang",
|
|
19
|
+
"Sato", "Ito", "Kobayashi", "Mori", "Hayashi", "Ono",
|
|
20
|
+
];
|
|
21
|
+
// Hispanic/Latin names
|
|
22
|
+
export const HISPANIC_FIRST = [
|
|
23
|
+
"Carlos", "Maria", "Diego", "Sofia", "Lucas", "Valentina", "Mateo", "Camila",
|
|
24
|
+
"Santiago", "Isabella", "Miguel", "Lucia", "Andres", "Elena", "Pablo", "Ana",
|
|
25
|
+
"Rafael", "Carmen", "Gabriel", "Rosa", "Fernando", "Luisa", "Javier", "Pilar",
|
|
26
|
+
];
|
|
27
|
+
export const HISPANIC_LAST = [
|
|
28
|
+
"Garcia", "Rodriguez", "Martinez", "Lopez", "Hernandez", "Gonzalez",
|
|
29
|
+
"Perez", "Sanchez", "Ramirez", "Torres", "Flores", "Rivera",
|
|
30
|
+
"Diaz", "Morales", "Ortiz", "Reyes", "Cruz", "Castillo",
|
|
31
|
+
"Mendoza", "Vargas", "Romero", "Ruiz", "Alvarez", "Jimenez",
|
|
32
|
+
];
|
|
33
|
+
// Arabic/Middle Eastern names
|
|
34
|
+
export const ARABIC_FIRST = [
|
|
35
|
+
"Omar", "Fatima", "Hassan", "Layla", "Youssef", "Amira", "Khalid", "Nour",
|
|
36
|
+
"Tariq", "Salma", "Rashid", "Zahra", "Karim", "Leila", "Samir", "Hana",
|
|
37
|
+
"Faris", "Dina", "Nabil", "Rania", "Amir", "Yasmin", "Zaid", "Muna",
|
|
38
|
+
];
|
|
39
|
+
export const ARABIC_LAST = [
|
|
40
|
+
"Al-Rashid", "Hassan", "Ibrahim", "Abbas", "Khalil", "Mahmoud",
|
|
41
|
+
"Nasser", "Rahman", "Salem", "Haddad", "Bishara", "Farouk",
|
|
42
|
+
"Mansour", "Qasim", "Sharif", "Taleb", "Wahab", "Zayed",
|
|
43
|
+
];
|
|
44
|
+
// South Asian names
|
|
45
|
+
export const SOUTH_ASIAN_FIRST = [
|
|
46
|
+
"Arjun", "Priya", "Raj", "Ananya", "Vikram", "Sita", "Rohan", "Meera",
|
|
47
|
+
"Arun", "Kavita", "Sanjay", "Nisha", "Amit", "Pooja", "Sunil", "Rekha",
|
|
48
|
+
"Harsh", "Deepa", "Nikhil", "Asha", "Ravi", "Lata", "Kiran", "Maya",
|
|
49
|
+
];
|
|
50
|
+
export const SOUTH_ASIAN_LAST = [
|
|
51
|
+
"Patel", "Sharma", "Singh", "Kumar", "Gupta", "Mehta",
|
|
52
|
+
"Reddy", "Nair", "Rao", "Verma", "Joshi", "Shah",
|
|
53
|
+
"Das", "Mishra", "Srinivasan", "Krishnamurthy", "Pillai", "Iyer",
|
|
54
|
+
];
|
|
55
|
+
export const FIRST_NAMES = [
|
|
56
|
+
"Alex", "Blake", "Casey", "Dana", "Ellis", "Frankie", "Gray", "Harper",
|
|
57
|
+
"Indigo", "Jordan", "Kai", "Lane", "Morgan", "Noah", "Oakley", "Parker",
|
|
58
|
+
"Quinn", "Reese", "Sage", "Taylor", "Uma", "Val", "Wren", "Xen",
|
|
59
|
+
"Yael", "Zara", "Avery", "Brook", "Cedar", "Drew", "Eden", "Fern",
|
|
60
|
+
"Glen", "Haven", "Iris", "Jules", "Kira", "Lark", "Marlo", "Nico",
|
|
61
|
+
"Onyx", "Phoenix", "Rain", "Skyler", "Tatum", "Unity", "Vesper", "Winter",
|
|
62
|
+
];
|
|
63
|
+
export const LAST_NAMES = [
|
|
64
|
+
"Stone", "Rivers", "Bell", "Cross", "Drake", "Fields", "Grant", "Hayes",
|
|
65
|
+
"Ivy", "James", "Knight", "Lake", "Moon", "North", "Oak", "Price",
|
|
66
|
+
"Reed", "Shaw", "Thorn", "Vale", "Ward", "York", "Ash", "Banks",
|
|
67
|
+
"Cole", "Dawn", "East", "Frost", "Gold", "Hill", "Jade", "King",
|
|
68
|
+
"Lowe", "Marsh", "Noel", "Peak", "Rose", "Snow", "Troy", "Vane",
|
|
69
|
+
"Wells", "Birch", "Clay", "Dove", "Elm", "Fox", "Gale", "Hart",
|
|
70
|
+
];
|
|
71
|
+
export const ORG_PREFIXES = [
|
|
72
|
+
"Nexus", "Vertex", "Prism", "Atlas", "Cipher", "Beacon", "Forge", "Crest",
|
|
73
|
+
"Pulse", "Apex", "Echo", "Nova", "Summit", "Core", "Bridge", "Spark",
|
|
74
|
+
"Tide", "Haven", "Peak", "Drift", "Flux", "Orbit", "Zenith", "Pine",
|
|
75
|
+
];
|
|
76
|
+
export const ORG_SUFFIXES = [
|
|
77
|
+
"Corp", "Labs", "Systems", "Group", "Inc", "Partners", "Technologies",
|
|
78
|
+
"Industries", "Networks", "Solutions", "Dynamics", "Ventures", "Digital",
|
|
79
|
+
"Analytics", "Collective", "Innovations", "Software", "Consulting",
|
|
80
|
+
"Strategies", "Media", "Engineering", "Capital", "Works", "Services",
|
|
81
|
+
];
|
|
82
|
+
export const LOCATIONS = [
|
|
83
|
+
"Maple Ridge", "Cedar Falls", "Pine Valley", "Oak Harbor", "Birch Creek",
|
|
84
|
+
"Silver Lake", "Crystal Bay", "Shadow Glen", "Amber Hills", "Coral Springs",
|
|
85
|
+
"Iron Bridge", "Stone Haven", "River Bend", "Summit View", "Harbor Point",
|
|
86
|
+
"Eagle Pass", "Fox Hollow", "Raven Cliff", "Wolf Creek", "Bear Valley",
|
|
87
|
+
"Falcon Heights", "Elk Grove", "Lark Meadow", "Heron Bay", "Sparrow Hill",
|
|
88
|
+
"Aspen Ridge", "Willow Park", "Jasper Cove", "Slate Crossing", "Flint Mesa",
|
|
89
|
+
];
|
|
90
|
+
// --- Cultural detection helpers ---
|
|
91
|
+
const CJK_RE = /[\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/;
|
|
92
|
+
const HISPANIC_PATTERNS = new RegExp("(?:Garcia|Rodriguez|Martinez|Lopez|Hernandez|Gonzalez|Perez|Sanchez|" +
|
|
93
|
+
"Ramirez|Torres|Flores|Rivera|Diaz|Morales|Cruz|Reyes|Castillo|" +
|
|
94
|
+
"Juan|Jose|Maria|Carlos|Miguel|Diego|Sofia|Camila|Santiago)", "i");
|
|
95
|
+
const ARABIC_PATTERNS = new RegExp("(?:Al-|Abu |Ibn |Mohammed|Muhammad|Ahmed|Hassan|Hussein|Fatima|Omar|" +
|
|
96
|
+
"Khalid|Youssef|Tariq|Rashid|Karim|Nour|Zahra|Layla)", "i");
|
|
97
|
+
const SOUTH_ASIAN_PATTERNS = new RegExp("(?:Kumar|Singh|Patel|Sharma|Gupta|Mehta|Reddy|Nair|Rao|" +
|
|
98
|
+
"Srinivasan|Krishnamurthy|Arjun|Priya|Raj|Vikram|Sanjay|Amit)", "i");
|
|
99
|
+
/** Detect the likely cultural origin of a name. */
|
|
100
|
+
export function detectCulture(name) {
|
|
101
|
+
if (CJK_RE.test(name))
|
|
102
|
+
return "cjk";
|
|
103
|
+
if (ARABIC_PATTERNS.test(name))
|
|
104
|
+
return "arabic";
|
|
105
|
+
if (HISPANIC_PATTERNS.test(name))
|
|
106
|
+
return "hispanic";
|
|
107
|
+
if (SOUTH_ASIAN_PATTERNS.test(name))
|
|
108
|
+
return "south_asian";
|
|
109
|
+
return "western";
|
|
110
|
+
}
|
|
111
|
+
const CULTURE_POOLS = {
|
|
112
|
+
cjk: [CJK_FIRST, CJK_LAST],
|
|
113
|
+
hispanic: [HISPANIC_FIRST, HISPANIC_LAST],
|
|
114
|
+
arabic: [ARABIC_FIRST, ARABIC_LAST],
|
|
115
|
+
south_asian: [SOUTH_ASIAN_FIRST, SOUTH_ASIAN_LAST],
|
|
116
|
+
western: [FIRST_NAMES, LAST_NAMES],
|
|
117
|
+
};
|
|
118
|
+
function selectNamePools(original, _seed) {
|
|
119
|
+
if (!original)
|
|
120
|
+
return [FIRST_NAMES, LAST_NAMES];
|
|
121
|
+
const culture = detectCulture(original);
|
|
122
|
+
return CULTURE_POOLS[culture] ?? [FIRST_NAMES, LAST_NAMES];
|
|
123
|
+
}
|
|
124
|
+
export class NameGenerator {
|
|
125
|
+
categories = [
|
|
126
|
+
Category.PERSON_NAME,
|
|
127
|
+
Category.ORG_NAME,
|
|
128
|
+
Category.LOCATION,
|
|
129
|
+
];
|
|
130
|
+
generate(category, seed, original = "") {
|
|
131
|
+
if (category === Category.PERSON_NAME) {
|
|
132
|
+
return this._fakePerson(seed, original);
|
|
133
|
+
}
|
|
134
|
+
else if (category === Category.ORG_NAME) {
|
|
135
|
+
return this._fakeOrg(seed, original);
|
|
136
|
+
}
|
|
137
|
+
else if (category === Category.LOCATION) {
|
|
138
|
+
return LOCATIONS[seed % LOCATIONS.length];
|
|
139
|
+
}
|
|
140
|
+
return `Entity-${String(seed % 10000).padStart(4, "0")}`;
|
|
141
|
+
}
|
|
142
|
+
_fakePerson(seed, original) {
|
|
143
|
+
const [firstPool, lastPool] = selectNamePools(original, seed);
|
|
144
|
+
const first = firstPool[seed % firstPool.length];
|
|
145
|
+
const last = lastPool[Math.floor(seed / firstPool.length) % lastPool.length];
|
|
146
|
+
if (!original)
|
|
147
|
+
return `${first} ${last}`;
|
|
148
|
+
// Preserve the structure of the original name
|
|
149
|
+
const parts = original.split(" ");
|
|
150
|
+
if (parts.length === 1) {
|
|
151
|
+
return first;
|
|
152
|
+
}
|
|
153
|
+
else if (parts.length === 3) {
|
|
154
|
+
const middle = firstPool[Math.floor(seed / 3) % firstPool.length];
|
|
155
|
+
return `${first} ${middle} ${last}`;
|
|
156
|
+
}
|
|
157
|
+
else if (parts.length > 3) {
|
|
158
|
+
const extra = [];
|
|
159
|
+
for (let i = 0; i < parts.length - 2; i++) {
|
|
160
|
+
extra.push(firstPool[(seed + i) % firstPool.length]);
|
|
161
|
+
}
|
|
162
|
+
return [first, ...extra, last].join(" ");
|
|
163
|
+
}
|
|
164
|
+
// Preserve separator (hyphen, period)
|
|
165
|
+
if (original.includes("-") && !original.includes(" ")) {
|
|
166
|
+
return `${first}-${last}`;
|
|
167
|
+
}
|
|
168
|
+
if (original.includes(".") && !original.includes(" ")) {
|
|
169
|
+
return `${first[0]}.${last}`;
|
|
170
|
+
}
|
|
171
|
+
return `${first} ${last}`;
|
|
172
|
+
}
|
|
173
|
+
_fakeOrg(seed, original) {
|
|
174
|
+
const prefix = ORG_PREFIXES[seed % ORG_PREFIXES.length];
|
|
175
|
+
const suffix = ORG_SUFFIXES[Math.floor(seed / ORG_PREFIXES.length) % ORG_SUFFIXES.length];
|
|
176
|
+
if (!original)
|
|
177
|
+
return `${prefix} ${suffix}`;
|
|
178
|
+
// Match word count of original
|
|
179
|
+
const origWords = original.split(" ");
|
|
180
|
+
if (origWords.length === 1) {
|
|
181
|
+
return prefix;
|
|
182
|
+
}
|
|
183
|
+
else if (origWords.length === 2) {
|
|
184
|
+
return `${prefix} ${suffix}`;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const extras = [];
|
|
188
|
+
for (let i = 1; i < origWords.length - 1; i++) {
|
|
189
|
+
extras.push(ORG_PREFIXES[(seed + i) % ORG_PREFIXES.length]);
|
|
190
|
+
}
|
|
191
|
+
return [prefix, ...extras, suffix].join(" ");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fake network entity generators: IPs, emails, URLs, domains, MACs, BGP ASNs.
|
|
3
|
+
*/
|
|
4
|
+
import { Category } from "../types.js";
|
|
5
|
+
import type { BaseGenerator } from "./base.js";
|
|
6
|
+
export declare const DOMAINS: string[];
|
|
7
|
+
export declare const DOMAINS_BY_TLD: Record<string, string[]>;
|
|
8
|
+
export declare const EMAIL_PREFIXES: string[];
|
|
9
|
+
export declare const EMAIL_PREFIXES_SHORT: string[];
|
|
10
|
+
export declare const EMAIL_PREFIXES_MEDIUM: string[];
|
|
11
|
+
export declare const EMAIL_PREFIXES_LONG: string[];
|
|
12
|
+
export declare const SNMP_COMMUNITIES: string[];
|
|
13
|
+
export declare const HOSTNAME_ROLES: string[];
|
|
14
|
+
export declare const HOSTNAME_SITES: string[];
|
|
15
|
+
export declare const PATH_SEGMENTS: string[];
|
|
16
|
+
/** 100.64.0.0 as unsigned 32-bit int */
|
|
17
|
+
export declare const CGNAT_BASE: number;
|
|
18
|
+
/** Mask for CGNAT /10 range */
|
|
19
|
+
export declare const CGNAT_MASK_10 = 4290772992;
|
|
20
|
+
export declare function ipToInt(ip: string): number;
|
|
21
|
+
export declare function intToIp(n: number): string;
|
|
22
|
+
export declare class SubnetMapper {
|
|
23
|
+
/** Forward mapping: serialized key "netInt,prefixLen" -> fake network int */
|
|
24
|
+
subnetFwd: Map<string, number>;
|
|
25
|
+
/** Reverse mapping: fake network int -> serialized key */
|
|
26
|
+
subnetRev: Map<number, string>;
|
|
27
|
+
/** Next CGNAT slot to allocate */
|
|
28
|
+
subnetNextSlot: number;
|
|
29
|
+
/** Learned subnets from text context */
|
|
30
|
+
knownSubnets: Array<{
|
|
31
|
+
networkInt: number;
|
|
32
|
+
prefixLen: number;
|
|
33
|
+
}>;
|
|
34
|
+
/**
|
|
35
|
+
* Scan text for CIDR notation and subnet masks to learn subnet boundaries.
|
|
36
|
+
* Call this before obfuscating IPs so the generator knows the correct
|
|
37
|
+
* prefix length for each address.
|
|
38
|
+
*/
|
|
39
|
+
learnSubnetsFromText(text: string): void;
|
|
40
|
+
/** Find the best matching prefix length for an IP from learned subnets. */
|
|
41
|
+
findPrefixLen(ipInt: number): number;
|
|
42
|
+
/** Map a real network address to a fake CGNAT network address. */
|
|
43
|
+
mapSubnet(netInt: number, prefixLen: number): number;
|
|
44
|
+
/** Reset all subnet mapping state. */
|
|
45
|
+
reset(): void;
|
|
46
|
+
}
|
|
47
|
+
export declare const VLAN_NAMES: string[];
|
|
48
|
+
export declare const INTERFACE_DESCS: string[];
|
|
49
|
+
export declare const ROUTE_MAP_NAMES: string[];
|
|
50
|
+
export declare const ACL_NAMES: string[];
|
|
51
|
+
export declare class NetworkGenerator implements BaseGenerator {
|
|
52
|
+
readonly categories: Category[];
|
|
53
|
+
private readonly subnetMapper;
|
|
54
|
+
constructor(subnetMapper: SubnetMapper);
|
|
55
|
+
generate(category: Category, seed: number, original?: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Subnet-preserving IP obfuscation for any prefix length.
|
|
58
|
+
*
|
|
59
|
+
* Network bits are mapped to the CGNAT range (100.64.0.0/10),
|
|
60
|
+
* host bits are preserved exactly. Defaults to /24 when no
|
|
61
|
+
* subnet context is available.
|
|
62
|
+
*/
|
|
63
|
+
_fakeIp(seed: number, original: string, subnetMapper: SubnetMapper): string;
|
|
64
|
+
_fakeEmail(seed: number, original: string): string;
|
|
65
|
+
_fakeUrl(seed: number, original: string): string;
|
|
66
|
+
/** Generate a fake MAC address preserving format (colon, dash, or Cisco dot). */
|
|
67
|
+
_fakeMac(seed: number, original: string): string;
|
|
68
|
+
/** Map BGP AS numbers to the private AS range (64512-65534). */
|
|
69
|
+
_fakeAsn(seed: number): string;
|
|
70
|
+
/** Replace SNMP community strings with generic names. */
|
|
71
|
+
_fakeSnmpCommunity(seed: number): string;
|
|
72
|
+
/** Replace network credentials with seed-derived unique fake values. */
|
|
73
|
+
_fakeNetworkCredential(seed: number, original: string): string;
|
|
74
|
+
/** Generate a fake hostname preserving structure. */
|
|
75
|
+
_fakeHostname(seed: number): string;
|
|
76
|
+
/** Fake VLAN ID/name. Preserves the keyword structure. */
|
|
77
|
+
_fakeVlanId(seed: number, original: string): string;
|
|
78
|
+
/** Fake interface description. */
|
|
79
|
+
_fakeInterfaceDesc(seed: number, _original: string): string;
|
|
80
|
+
/** Fake route-map or prefix-list name. */
|
|
81
|
+
_fakeRouteMap(seed: number, _original: string): string;
|
|
82
|
+
/** Fake OSPF identifiers (router-id or area). */
|
|
83
|
+
_fakeOspfId(seed: number, original: string): string;
|
|
84
|
+
/** Fake ACL name. */
|
|
85
|
+
_fakeAclName(seed: number, _original: string): string;
|
|
86
|
+
}
|