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.
Files changed (45) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +7 -0
  3. package/README.md +369 -0
  4. package/dist/audit.d.ts +46 -0
  5. package/dist/audit.js +127 -0
  6. package/dist/canary.d.ts +31 -0
  7. package/dist/canary.js +73 -0
  8. package/dist/config.d.ts +27 -0
  9. package/dist/config.js +123 -0
  10. package/dist/detectors/base.d.ts +8 -0
  11. package/dist/detectors/base.js +2 -0
  12. package/dist/detectors/code.d.ts +25 -0
  13. package/dist/detectors/code.js +144 -0
  14. package/dist/detectors/context.d.ts +31 -0
  15. package/dist/detectors/context.js +357 -0
  16. package/dist/detectors/patterns.d.ts +15 -0
  17. package/dist/detectors/patterns.js +58 -0
  18. package/dist/detectors/regex.d.ts +28 -0
  19. package/dist/detectors/regex.js +955 -0
  20. package/dist/generators/base.d.ts +6 -0
  21. package/dist/generators/base.js +2 -0
  22. package/dist/generators/codes.d.ts +20 -0
  23. package/dist/generators/codes.js +231 -0
  24. package/dist/generators/names.d.ts +29 -0
  25. package/dist/generators/names.js +194 -0
  26. package/dist/generators/network.d.ts +86 -0
  27. package/dist/generators/network.js +477 -0
  28. package/dist/hooks.d.ts +27 -0
  29. package/dist/hooks.js +457 -0
  30. package/dist/index.d.ts +12 -0
  31. package/dist/index.js +58 -0
  32. package/dist/mapping.d.ts +33 -0
  33. package/dist/mapping.js +72 -0
  34. package/dist/obfuscator.d.ts +78 -0
  35. package/dist/obfuscator.js +603 -0
  36. package/dist/redaction.d.ts +26 -0
  37. package/dist/redaction.js +76 -0
  38. package/dist/store.d.ts +40 -0
  39. package/dist/store.js +79 -0
  40. package/dist/types.d.ts +101 -0
  41. package/dist/types.js +35 -0
  42. package/ncg_adapter.py +530 -0
  43. package/openclaw.plugin.json +72 -0
  44. package/package.json +56 -0
  45. package/shroud_bridge.mjs +225 -0
@@ -0,0 +1,6 @@
1
+ /** Base generator interface. */
2
+ import { Category } from "../types.js";
3
+ export interface BaseGenerator {
4
+ readonly categories: Category[];
5
+ generate(category: Category, seed: number, original?: string): string;
6
+ }
@@ -0,0 +1,2 @@
1
+ /** Base generator interface. */
2
+ export {};
@@ -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
+ }