@link-foundation/sensitive-data-sanitizer 0.0.0-stage → 0.11.31

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 (78) hide show
  1. package/CHANGELOG.md +530 -0
  2. package/LICENSE +24 -0
  3. package/README.md +184 -2
  4. package/bin/legacy-example-package-name.js +95 -0
  5. package/bin/sensitive-data-sanitizer.js +430 -0
  6. package/bin/stream.js +56 -0
  7. package/docs/API.md +336 -0
  8. package/docs/COVERAGE.md +136 -0
  9. package/docs/HISTORY-REMEDIATION.md +41 -0
  10. package/examples/combined-scanners.mjs +23 -0
  11. package/examples/presidio-bridge.py +32 -0
  12. package/examples/realistic-fakes.mjs +18 -0
  13. package/examples/sanitize-session.mjs +27 -0
  14. package/examples/shared-tool-logs.mjs +21 -0
  15. package/examples/stream-jsonl.mjs +17 -0
  16. package/package.json +124 -4
  17. package/src/adapters.js +204 -0
  18. package/src/arithmetic.js +11 -0
  19. package/src/confidence.js +65 -0
  20. package/src/context.js +424 -0
  21. package/src/credentials.js +54 -0
  22. package/src/detection.js +293 -0
  23. package/src/encoded.js +233 -0
  24. package/src/engines.js +348 -0
  25. package/src/entities.js +166 -0
  26. package/src/escape-spans.js +20 -0
  27. package/src/eslint-plugin.d.ts +10 -0
  28. package/src/eslint-plugin.js +193 -0
  29. package/src/external.js +169 -0
  30. package/src/fake-names.js +222 -0
  31. package/src/fake.js +524 -0
  32. package/src/files.js +131 -0
  33. package/src/gitleaks.js +194 -0
  34. package/src/history-rewrite.js +339 -0
  35. package/src/history.js +103 -0
  36. package/src/identity.js +274 -0
  37. package/src/index.d.ts +321 -0
  38. package/src/index.js +40 -0
  39. package/src/jsonl-stream.js +198 -0
  40. package/src/keywords.js +45 -0
  41. package/src/known.js +164 -0
  42. package/src/legacy.d.ts +3 -0
  43. package/src/legacy.js +2 -0
  44. package/src/locales.js +217 -0
  45. package/src/log-bytes.js +85 -0
  46. package/src/names.js +65 -0
  47. package/src/national-checks.js +129 -0
  48. package/src/national.js +128 -0
  49. package/src/outbound.js +28 -0
  50. package/src/personal-context.js +85 -0
  51. package/src/personal.js +248 -0
  52. package/src/projection.js +126 -0
  53. package/src/propagation.js +163 -0
  54. package/src/public-verifier.js +77 -0
  55. package/src/public.js +153 -0
  56. package/src/record-worker.js +20 -0
  57. package/src/rules.js +210 -0
  58. package/src/sanitizer.js +794 -0
  59. package/src/specimens.js +34 -0
  60. package/src/stream-identity.js +129 -0
  61. package/src/stream-worker.js +18 -0
  62. package/src/stream.js +449 -0
  63. package/src/structured.js +278 -0
  64. package/src/transforms.js +192 -0
  65. package/src/transliteration.js +75 -0
  66. package/src/utf8.js +60 -0
  67. package/src/vendor/entities/aws.json +42 -0
  68. package/src/vendor/entities/azure.json +182 -0
  69. package/src/vendor/entities/google.json +219 -0
  70. package/src/vendor/gitleaks/LICENSE +21 -0
  71. package/src/vendor/gitleaks/provenance.json +8 -0
  72. package/src/vendor/gitleaks/rules-0.json +268 -0
  73. package/src/vendor/gitleaks/rules-1.json +1794 -0
  74. package/src/vendor/gitleaks/rules-2.json +292 -0
  75. package/src/vendor/gitleaks/rules-3.json +263 -0
  76. package/src/vendor/gitleaks/rules-4.json +277 -0
  77. package/src/vendor/gitleaks/rules-5.json +154 -0
  78. package/src/worker-options.js +27 -0
@@ -0,0 +1,198 @@
1
+ import { TextDecoder } from 'node:util';
2
+ import { Worker } from 'node:worker_threads';
3
+ import { URL } from 'node:url';
4
+ import { failure } from './detection.js';
5
+ import { encodingFailure } from './utf8.js';
6
+ import { workerExecArgv } from './worker-options.js';
7
+ import { prepareStreamEngine } from './engines.js';
8
+ import { inspect } from './sanitizer.js';
9
+
10
+ async function* records(source, { maxRecordBytes, maxTotalBytes }) {
11
+ const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
12
+ let parts = [],
13
+ length = 0,
14
+ total = 0,
15
+ record = 1,
16
+ offset = 0;
17
+ function decode() {
18
+ try {
19
+ return decoder.decode(Buffer.concat(parts, length));
20
+ } catch {
21
+ throw encodingFailure(Buffer.concat(parts, length), record, offset);
22
+ }
23
+ }
24
+ for await (const chunk of source) {
25
+ const bytes = Buffer.from(chunk);
26
+ total += bytes.length;
27
+ if (total > maxTotalBytes || bytes.includes(0)) {
28
+ throw failure(bytes.includes(0) ? 'ERR_BINARY' : 'ERR_LIMIT');
29
+ }
30
+ let start = 0;
31
+ while (start < bytes.length) {
32
+ const newline = bytes.indexOf(10, start);
33
+ const end = newline < 0 ? bytes.length : newline + 1;
34
+ length += end - start;
35
+ if (length > maxRecordBytes) {
36
+ throw failure('ERR_LIMIT');
37
+ }
38
+ // Copy a fragment so a small retained tail never holds a huge input buffer.
39
+ parts.push(Buffer.from(bytes.subarray(start, end)));
40
+ if (newline >= 0) {
41
+ yield decode();
42
+ record++;
43
+ offset += length;
44
+ parts = [];
45
+ length = 0;
46
+ }
47
+ start = end;
48
+ }
49
+ }
50
+ if (length) {
51
+ yield decode();
52
+ }
53
+ }
54
+ async function* batches(source, config) {
55
+ let parts = [],
56
+ length = 0;
57
+ for await (const record of records(source, config)) {
58
+ const bytes = Buffer.byteLength(record);
59
+ if (length && length + bytes > config.batchBytes) {
60
+ yield parts.join('');
61
+ parts = [];
62
+ length = 0;
63
+ }
64
+ parts.push(record);
65
+ length += bytes;
66
+ if (length >= config.batchBytes) {
67
+ yield parts.join('');
68
+ parts = [];
69
+ length = 0;
70
+ }
71
+ }
72
+ if (length) {
73
+ yield parts.join('');
74
+ }
75
+ }
76
+ function workerSlot(options, heap, timeout) {
77
+ let worker;
78
+ try {
79
+ worker = new Worker(new URL('./record-worker.js', import.meta.url), {
80
+ workerData: options,
81
+ execArgv: workerExecArgv(),
82
+ resourceLimits: { maxOldGenerationSizeMb: heap, stackSizeMb: 4 },
83
+ });
84
+ } catch {
85
+ throw failure('ERR_CONFIG');
86
+ }
87
+ let pending,
88
+ failed = false;
89
+ function settle(result) {
90
+ if (!pending) {
91
+ return;
92
+ }
93
+ clearTimeout(pending.timer);
94
+ pending.resolve(result);
95
+ pending = undefined;
96
+ }
97
+ worker.on('message', settle);
98
+ worker.on('error', () => {
99
+ failed = true;
100
+ settle({ error: 'ERR_WORKER' });
101
+ });
102
+ worker.on('exit', () => {
103
+ failed = true;
104
+ settle({ error: 'ERR_WORKER' });
105
+ });
106
+ return {
107
+ run(text, confirmedPersonal, nativeFindings) {
108
+ if (failed) {
109
+ return Promise.resolve({ error: 'ERR_WORKER' });
110
+ }
111
+ return new Promise((resolve) => {
112
+ pending = {
113
+ resolve,
114
+ timer: setTimeout(() => settle({ error: 'ERR_WORKER' }), timeout),
115
+ };
116
+ worker.postMessage({ text, confirmedPersonal, nativeFindings });
117
+ });
118
+ },
119
+ async close() {
120
+ settle({ error: 'ERR_WORKER' });
121
+ await worker.terminate();
122
+ },
123
+ };
124
+ }
125
+ function workerCount(options, config) {
126
+ const callbacks =
127
+ options.sanitizer ||
128
+ config.sanitizerOptions.debug ||
129
+ config.sanitizerOptions.verifyPublic ||
130
+ config.sanitizerOptions.detectors?.length;
131
+ const count =
132
+ options.workers ?? (callbacks || typeof Deno !== 'undefined' ? 1 : 2);
133
+ if (
134
+ !Number.isSafeInteger(count) ||
135
+ count < 1 ||
136
+ count > 16 ||
137
+ (callbacks && count > 1)
138
+ ) {
139
+ throw failure('ERR_CONFIG');
140
+ }
141
+ // Deno's Node compatibility does not implement resource-limited workers.
142
+ return typeof Deno !== 'undefined' ? 1 : count;
143
+ }
144
+ export async function* sanitizeJsonlStream(source, options, config) {
145
+ const count = config.collecting ? 1 : workerCount(options, config);
146
+ if (count === 1) {
147
+ for await (const batch of batches(source, config)) {
148
+ config.identity.prepare(
149
+ batch,
150
+ await prepareStreamEngine(config.engine, batch)
151
+ );
152
+ if (!config.collecting) {
153
+ yield (await config.engine.sanitize(batch)).text;
154
+ }
155
+ }
156
+ return;
157
+ }
158
+ const heap = options.workerHeapMb ?? 256,
159
+ timeout = options.workerTimeoutMs ?? 60000;
160
+ if (
161
+ !Number.isSafeInteger(heap) ||
162
+ heap < 32 ||
163
+ !Number.isSafeInteger(timeout) ||
164
+ timeout <= 0
165
+ ) {
166
+ throw failure('ERR_CONFIG');
167
+ }
168
+ const slots = [],
169
+ queue = [];
170
+ let sequence = 0;
171
+ async function next() {
172
+ const result = await queue.shift();
173
+ if (result.error) {
174
+ throw failure(result.error, result);
175
+ }
176
+ return result.value;
177
+ }
178
+ try {
179
+ for (let i = 0; i < count; i++) {
180
+ slots.push(workerSlot(config.sanitizerOptions, heap, timeout));
181
+ }
182
+ for await (const batch of batches(source, config)) {
183
+ const nativeFindings = inspect(batch, config.sanitizerOptions);
184
+ const confirmedPersonal = config.identity.prepare(batch, nativeFindings);
185
+ queue.push(
186
+ slots[sequence++ % count].run(batch, confirmedPersonal, nativeFindings)
187
+ );
188
+ if (queue.length === count) {
189
+ yield await next();
190
+ }
191
+ }
192
+ while (queue.length) {
193
+ yield await next();
194
+ }
195
+ } finally {
196
+ await Promise.all(slots.map((slot) => slot.close()));
197
+ }
198
+ }
@@ -0,0 +1,45 @@
1
+ // Aho-Corasick keyword prefilter: equivalent to checking every literal with
2
+ // includes(), including overlapping keywords, but scans a detection view once.
3
+ export function keywordMatcher(words) {
4
+ const nodes = [{ next: new Map(), fail: 0, output: [] }];
5
+ for (const word of new Set(words)) {
6
+ let state = 0;
7
+ for (let i = 0; i < word.length; i++) {
8
+ const char = word[i];
9
+ if (!nodes[state].next.has(char)) {
10
+ nodes[state].next.set(char, nodes.length);
11
+ nodes.push({ next: new Map(), fail: 0, output: [] });
12
+ }
13
+ state = nodes[state].next.get(char);
14
+ }
15
+ nodes[state].output.push(word);
16
+ }
17
+ const queue = [...nodes[0].next.values()];
18
+ for (let index = 0; index < queue.length; index++) {
19
+ const state = queue[index];
20
+ for (const [char, child] of nodes[state].next) {
21
+ let fallback = nodes[state].fail;
22
+ while (fallback && !nodes[fallback].next.has(char)) {
23
+ fallback = nodes[fallback].fail;
24
+ }
25
+ nodes[child].fail = nodes[fallback].next.get(char) ?? 0;
26
+ nodes[child].output.push(...nodes[nodes[child].fail].output);
27
+ queue.push(child);
28
+ }
29
+ }
30
+ return (text) => {
31
+ const found = new Set();
32
+ let state = 0;
33
+ for (let i = 0; i < text.length; i++) {
34
+ const char = text[i];
35
+ while (state && !nodes[state].next.has(char)) {
36
+ state = nodes[state].fail;
37
+ }
38
+ state = nodes[state].next.get(char) ?? 0;
39
+ for (const word of nodes[state].output) {
40
+ found.add(word);
41
+ }
42
+ }
43
+ return found;
44
+ };
45
+ }
package/src/known.js ADDED
@@ -0,0 +1,164 @@
1
+ import { failure } from './detection.js';
2
+ import { readTextFile } from './files.js';
3
+
4
+ /** Explicitly load every host/account token from gh's local YAML config. */
5
+ export async function knownSecretsFromGitHubConfig({ path } = {}) {
6
+ if (path !== undefined && (typeof path !== 'string' || !path)) {
7
+ throw failure('ERR_CONFIG');
8
+ }
9
+ const { lstat } = await import('node:fs/promises');
10
+ path ??= await gitHubConfigPath();
11
+ try {
12
+ await lstat(path);
13
+ } catch (error) {
14
+ if (error.code === 'ENOENT') {
15
+ return [];
16
+ }
17
+ throw failure('ERR_AUTH');
18
+ }
19
+ try {
20
+ const { parseDocument } = await import('yaml');
21
+ const document = parseDocument(await readTextFile(path, 1024 * 1024), {
22
+ logLevel: 'silent',
23
+ prettyErrors: false,
24
+ });
25
+ if (document.errors.length) {
26
+ throw failure('ERR_AUTH');
27
+ }
28
+ return configTokens(document.toJS({ maxAliasCount: 0 }));
29
+ } catch {
30
+ throw failure('ERR_AUTH');
31
+ }
32
+ }
33
+
34
+ async function gitHubConfigPath() {
35
+ const { join } = await import('node:path');
36
+ const { homedir } = await import('node:os');
37
+ const directory =
38
+ process.env.GH_CONFIG_DIR ||
39
+ (process.env.XDG_CONFIG_HOME
40
+ ? join(process.env.XDG_CONFIG_HOME, 'gh')
41
+ : process.platform === 'win32' && process.env.AppData
42
+ ? join(process.env.AppData, 'GitHub CLI')
43
+ : join(homedir(), '.config', 'gh'));
44
+ return join(directory, 'hosts.yml');
45
+ }
46
+
47
+ function configTokens(config) {
48
+ const values = new Set(),
49
+ pending = [config];
50
+ let count = 0;
51
+ while (pending.length) {
52
+ const entry = pending.pop();
53
+ if (!entry || typeof entry !== 'object') {
54
+ continue;
55
+ }
56
+ if (++count > 10000) {
57
+ throw failure('ERR_LIMIT');
58
+ }
59
+ for (const [key, value] of Object.entries(entry)) {
60
+ if (
61
+ ['oauth_token', 'api_token'].includes(key) &&
62
+ typeof value === 'string' &&
63
+ value.trim()
64
+ ) {
65
+ values.add(value);
66
+ } else if (value && typeof value === 'object') {
67
+ pending.push(value);
68
+ }
69
+ }
70
+ }
71
+ return [...values];
72
+ }
73
+
74
+ const secretEnvName =
75
+ /(?:^|_)(?:TOKEN|(?:API|ACCESS|PRIVATE)?KEY|SECRET|PASSWORD|PASSWD|CREDENTIALS?|AUTH|PAT)(?:_|$)/i;
76
+ /** Opt-in literal values from credential-named keys, or an explicit selection. */
77
+ export function knownSecretsFromEnv(env = process.env, names) {
78
+ if (names === undefined) {
79
+ names =
80
+ env && typeof env === 'object'
81
+ ? Object.keys(env).filter((name) => secretEnvName.test(name))
82
+ : [];
83
+ }
84
+ if (
85
+ !env ||
86
+ typeof env !== 'object' ||
87
+ !Array.isArray(names) ||
88
+ names.some((name) => typeof name !== 'string')
89
+ ) {
90
+ throw failure('ERR_CONFIG');
91
+ }
92
+ return [
93
+ ...new Set(
94
+ names
95
+ .map((name) => env[name])
96
+ .filter((value) => typeof value === 'string' && value.length)
97
+ ),
98
+ ];
99
+ }
100
+
101
+ /** Date and numeric spellings from documents-processing, with no inferred transliteration. */
102
+ export function personalVariants(entries) {
103
+ if (!Array.isArray(entries)) {
104
+ throw failure('ERR_CONFIG');
105
+ }
106
+ return entries.flatMap((entry) => {
107
+ if (
108
+ !entry ||
109
+ typeof entry.value !== 'string' ||
110
+ !entry.value ||
111
+ !/^[A-Z][A-Z0-9_]{0,63}$/.test(entry.type)
112
+ ) {
113
+ throw failure('ERR_CONFIG');
114
+ }
115
+ const values = new Set([entry.value]);
116
+ const numeric = entry.value.replace(/\D/g, '');
117
+ if (numeric.length >= 6 && /^[+\d ()/.-]+$/.test(entry.value)) {
118
+ values.add(numeric);
119
+ }
120
+ const iso = entry.value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
121
+ if (iso) {
122
+ for (const separator of ['/', '.', '-']) {
123
+ values.add(`${iso[3]}${separator}${iso[2]}${separator}${iso[1]}`);
124
+ }
125
+ }
126
+ const local = entry.value.match(/^(\d{2})[./-](\d{2})[./-](\d{4})$/);
127
+ if (local) {
128
+ values.add(`${local[3]}-${local[2]}-${local[1]}`);
129
+ }
130
+ return [...values].map((value) => ({ type: entry.type, value }));
131
+ });
132
+ }
133
+
134
+ /** Explicitly load the current local gh credential without logging the value. */
135
+ export async function knownSecretsFromGitHubAuth({
136
+ command = 'gh',
137
+ hostname,
138
+ timeoutMs = 5000,
139
+ } = {}) {
140
+ const { execFile } = await import('node:child_process');
141
+ if (
142
+ typeof command !== 'string' ||
143
+ !command ||
144
+ (hostname !== undefined && !/^[a-z0-9.-]+$/i.test(hostname)) ||
145
+ !Number.isSafeInteger(timeoutMs) ||
146
+ timeoutMs <= 0
147
+ ) {
148
+ throw failure('ERR_CONFIG');
149
+ }
150
+ return new Promise((accept, reject) => {
151
+ execFile(
152
+ command,
153
+ ['auth', 'token', ...(hostname ? ['--hostname', hostname] : [])],
154
+ { timeout: timeoutMs, maxBuffer: 65536, encoding: 'utf8' },
155
+ (error, stdout) => {
156
+ if (error || !stdout.trim()) {
157
+ reject(failure('ERR_AUTH'));
158
+ return;
159
+ }
160
+ accept([stdout.trim()]);
161
+ }
162
+ );
163
+ });
164
+ }
@@ -0,0 +1,3 @@
1
+ export declare const add: (a: number, b: number) => number;
2
+ export declare const multiply: (a: number, b: number) => number;
3
+ export declare const delay: (ms: number) => Promise<void>;
package/src/legacy.js ADDED
@@ -0,0 +1,2 @@
1
+ /** Arithmetic helpers for the retained template examples. */
2
+ export { add, multiply, delay } from './arithmetic.js';
package/src/locales.js ADDED
@@ -0,0 +1,217 @@
1
+ /** Authored vocabulary for contextual personal fields and credential prose. */
2
+ export const localeVocabulary = [
3
+ [
4
+ 'en',
5
+ 'password',
6
+ 'name',
7
+ 'address',
8
+ 'date of birth',
9
+ 'passport',
10
+ 'phone',
11
+ 'email',
12
+ 'organization',
13
+ 'username',
14
+ ],
15
+ [
16
+ 'es',
17
+ 'contraseña',
18
+ 'nombre',
19
+ 'dirección',
20
+ 'fecha de nacimiento',
21
+ 'pasaporte',
22
+ 'teléfono',
23
+ 'correo electrónico',
24
+ 'organización',
25
+ 'usuario',
26
+ ],
27
+ [
28
+ 'fr',
29
+ 'mot de passe',
30
+ 'nom',
31
+ 'adresse',
32
+ 'date de naissance',
33
+ 'passeport',
34
+ 'téléphone',
35
+ 'courriel',
36
+ 'organisation',
37
+ 'identifiant',
38
+ ],
39
+ [
40
+ 'de',
41
+ 'passwort',
42
+ 'name',
43
+ 'adresse',
44
+ 'geburtsdatum',
45
+ 'reisepass',
46
+ 'telefon',
47
+ 'e-mail',
48
+ 'organisation',
49
+ 'benutzername',
50
+ ],
51
+ [
52
+ 'pt',
53
+ 'senha',
54
+ 'nome',
55
+ 'endereço',
56
+ 'data de nascimento',
57
+ 'passaporte',
58
+ 'telefone',
59
+ 'correio eletrônico',
60
+ 'organização',
61
+ 'usuário',
62
+ ],
63
+ [
64
+ 'ru',
65
+ 'пароля',
66
+ 'имя',
67
+ 'адрес',
68
+ 'дата рождения',
69
+ 'паспорт',
70
+ 'телефон',
71
+ 'электронная почта',
72
+ 'организация',
73
+ 'имя пользователя',
74
+ ],
75
+ [
76
+ 'ja',
77
+ 'パスワード',
78
+ '氏名',
79
+ '住所',
80
+ '生年月日',
81
+ '旅券',
82
+ '電話番号',
83
+ 'メール',
84
+ '組織',
85
+ 'ユーザー名',
86
+ ],
87
+ [
88
+ 'zh',
89
+ '密码',
90
+ '姓名',
91
+ '地址',
92
+ '出生日期',
93
+ '身份证',
94
+ '电话号码',
95
+ '邮箱',
96
+ '组织',
97
+ '用户名',
98
+ ],
99
+ [
100
+ 'zh-Hant',
101
+ '密碼',
102
+ '姓名',
103
+ '地址',
104
+ '出生日期',
105
+ '身分證',
106
+ '電話號碼',
107
+ '郵箱',
108
+ '組織',
109
+ '用戶名',
110
+ ],
111
+ [
112
+ 'ko',
113
+ '비밀번호',
114
+ '이름',
115
+ '주소',
116
+ '생년월일',
117
+ '여권',
118
+ '전화번호',
119
+ '이메일',
120
+ '조직',
121
+ '사용자 이름',
122
+ ],
123
+ [
124
+ 'ar',
125
+ 'كلمة المرور',
126
+ 'الاسم',
127
+ 'العنوان',
128
+ 'تاريخ الميلاد',
129
+ 'جواز السفر',
130
+ 'الهاتف',
131
+ 'البريد الإلكتروني',
132
+ 'المؤسسة',
133
+ 'اسم المستخدم',
134
+ ],
135
+ [
136
+ 'he',
137
+ 'סיסמה',
138
+ 'שם',
139
+ 'כתובת',
140
+ 'תאריך לידה',
141
+ 'דרכון',
142
+ 'טלפון',
143
+ 'דואר אלקטרוני',
144
+ 'ארגון',
145
+ 'שם משתמש',
146
+ ],
147
+ [
148
+ 'hi',
149
+ 'पासवर्ड',
150
+ 'नाम',
151
+ 'पता',
152
+ 'जन्म तिथि',
153
+ 'पासपोर्ट',
154
+ 'फ़ोन',
155
+ 'ईमेल',
156
+ 'संगठन',
157
+ 'उपयोगकर्ता नाम',
158
+ ],
159
+ [
160
+ 'tr',
161
+ 'şifre',
162
+ 'ad soyad',
163
+ 'adres',
164
+ 'doğum tarihi',
165
+ 'pasaport',
166
+ 'telefon',
167
+ 'e-posta',
168
+ 'kuruluş',
169
+ 'kullanıcı adı',
170
+ ],
171
+ [
172
+ 'id',
173
+ 'kata sandi',
174
+ 'nama',
175
+ 'alamat',
176
+ 'tanggal lahir',
177
+ 'paspor',
178
+ 'telepon',
179
+ 'surel',
180
+ 'organisasi',
181
+ 'nama pengguna',
182
+ ],
183
+ [
184
+ 'vi',
185
+ 'mật khẩu',
186
+ 'họ tên',
187
+ 'địa chỉ',
188
+ 'ngày sinh',
189
+ 'hộ chiếu',
190
+ 'điện thoại',
191
+ 'thư điện tử',
192
+ 'tổ chức',
193
+ 'tên người dùng',
194
+ ],
195
+ [
196
+ 'th',
197
+ 'รหัสผ่าน',
198
+ 'ชื่อ',
199
+ 'ที่อยู่',
200
+ 'วันเกิด',
201
+ 'หนังสือเดินทาง',
202
+ 'โทรศัพท์',
203
+ 'อีเมล',
204
+ 'องค์กร',
205
+ 'ชื่อผู้ใช้',
206
+ ],
207
+ ];
208
+ export const personalFieldTypes = [
209
+ 'PERSON',
210
+ 'ADDRESS',
211
+ 'DATE_OF_BIRTH',
212
+ 'ID',
213
+ 'PHONE',
214
+ 'EMAIL',
215
+ 'ORGANIZATION',
216
+ 'USERNAME',
217
+ ];